# Credits, rate limits & errors

> How credits are charged, per-minute burst limits, rate-limit headers, and every error shape the API returns.

Prices are on [the pricing page](/pricing); this page is the full fine
print. Limits are enforced at the API gateway, per account: all your keys
and your Explorer session share one credit balance.

## Credits

Elevation calls and downloads are paid for in credits:

| What | Credits |
|---|---|
| One elevation point (`point`, each coordinate of `points`, each `samples` point of `sample`) | 1 |
| 1 MB of data out (raster extract responses, [COG downloads](/docs/downloads)) | 100 |
| A 1 m elevation raster extract, per km² (measured) | ≈ 240 |
| STAC catalog browsing and search | free |

- **Every account gets 10,000 free credits a month.** They reset at the
  start of each calendar month (UTC), don't roll over, and are spent first.
- **Purchased credits never expire.** Packs start at $10 for 100,000
  credits; bigger packs carry bonus credits. Buy them from
  [your dashboard](/dashboard/billing).
- **Your first purchase** also unlocks full-size raster extracts, 512-point
  batches, Darly-hosted COG downloads, and higher burst limits.
- Data out is rounded up per request: a raster response always costs at
  least 1 credit. Downloads are charged at full file size.
- Credits are charged only on successful (`2xx`) responses. Errors and
  rate-limited calls are never charged.
- When you run out, the API returns `402` until you buy more or your free
  credits reset. There are no overage bills.
- A batch is all or nothing: if its points cost more than you have left,
  the whole request returns `402`, so split it or buy credits.
- A raster extract's size is only known once it is built, so an extract
  starts as long as you have any credits left and is charged in full; your
  balance can end slightly below zero. That shortfall is forgiven when the
  free credits reset, or comes out of your next purchase if you buy credits
  before then. While an extract runs, its maximum size is held against your
  balance, so parallel extracts need credits for each.
- **Refunds:** unused purchased credits can be refunded within 14 days of
  purchase; write to [support@darly.io](mailto:support@darly.io). See the
  [terms](/terms) for details.
- Need volume pricing or invoicing? [contact@darly.io](mailto:contact@darly.io).

## Per-minute burst limits

| | Anonymous | Free | After a purchase |
|---|---|---|---|
| STAC requests / min | 30 (per IP) | 300 | 1,000 |
| Elevation points / min | — | 100 | 12,000 |
| Raster calls / min | — | 2 | 60 |
| Max points per request | — | 100 | 512 |
| Max raster size (px per side) | — | 512 | 2,500 |
| STAC page size (default / max) | 10 / 100 | 10 / 100 | 10 / 100 |

Elevation points/min is point-weighted like credits: one 512-point batch
consumes 512 units of the 12,000/min budget. Burst capacity is reserved
before a request is processed, so requests that finish with a `4xx` or `5xx`
still consume per-minute capacity (they never cost credits). A request
rejected by the burst limiter does not consume additional capacity; wait
until `X-RateLimit-Reset` before retrying.

STAC requests/min is page-weighted: `/search` and `/collections/{id}/items`
calls consume one unit per 10 items requested (a `limit=100` page costs 10
units; everything else, including metadata routes, costs 1). Small pages and
narrow filters stretch your budget further than crawling at `limit=100`.

## Rate-limit headers

Every response carries the state of the limiter that applied to it:

```text
X-RateLimit-Limit: 300
X-RateLimit-Remaining: 297
X-RateLimit-Reset: 1783041000
```

`X-RateLimit-Reset` is a Unix timestamp (seconds). On `429`, wait until the
reset before retrying; a simple sleep-until-reset loop is more effective
than exponential backoff here, because windows are fixed, not sliding.

## Error reference

Key off the **HTTP status code** (and `X-RateLimit-*` headers), not the body
text.

| Status | When | Body |
|---|---|---|
| `400` | Invalid elevation parameters | `{"status": "INVALID_REQUEST", "error": "…"}` |
| `401` | Elevation or download call without authentication | `{"detail": "Authentication is required for the elevation API"}` / `…to download assets"}` |
| `402` | Out of credits | `{"detail": "Out of credits", "buy_credits_url": "https://darly.io/dashboard/billing"}` |
| `403` | Your account doesn't include the endpoint (e.g. downloads before your first purchase) | `{"detail": "The current plan does not include this endpoint"}` / `…downloads"}` |
| `404` | Unknown route, collection, item, or asset, including collections outside your account | `{"detail": "Not found"}` |
| `405` | `POST` to a STAC route other than `/search` | `{"detail": "Method not allowed"}` |
| `429` | Per-minute limit hit | `{"detail": "Rate limit exceeded"}` + `X-RateLimit-*` headers |
| `500` | Elevation backend failure | `{"status": "SERVER_ERROR", "error": "…"}` |
| `502` | Upstream service unavailable | `{"detail": "…"}` |

An invalid or revoked key is **not** an error on STAC routes; it falls back
to anonymous access silently. See
[authentication](/docs/authentication#auth-errors).

## Playing nice

- Batch point lookups into `points` calls instead of many `point` calls:
  same credit cost, far fewer requests against the per-minute window.
- Cache responses on your side. A value for a fixed location only changes
  when new data lands for it (a newer acquisition or a finer collection wins
  the [source ranking](/docs/elevation#how-the-source-is-chosen)), so cache
  freely and record the `collection` from the response to know what a cached
  value came from.
- Use `bbox`/`datetime` filters and reasonable `limit`s on STAC search
  instead of paginating the world.
