---
title: Performance & limits
description: Service limits at a glance — query timeout, page size, concurrency, caching — and how to scope queries so they stay fast.
---

## Service limits at a glance

| Limit                    | Value                                                                 |
| ------------------------ | --------------------------------------------------------------------- |
| Authentication           | Required on every request ([bearer token](/guides/authentication))     |
| API keys per account     | 5                                                                      |
| Per-query time limit     | 30 seconds — longer queries fail with HTTP 500                        |
| Max page size            | `limit` clamped at 10,000 features                                    |
| Concurrent requests      | Shared cap across all callers — excess gets `503` + `Retry-After`      |
| Response cache           | 60 seconds, private to your client                                     |
| Total counts             | No `numberMatched` in responses — page until a short page ([paging](/guides/paging-and-bulk-export#paging)) |
| Server-side sorting      | None — `sortby` is accepted but ignored; sort client-side                |
| Writes                   | None — the API is read-only                                            |
| CORS                     | Open (`Access-Control-Allow-Origin: *`)                                |

## Scope your queries

The service is backed by a large database (full detection history since January 2025).
Well-scoped queries are fast — sub-second to a few seconds; broad unscoped ones can be
slow or time out.

- **Combine** a reasonable `bbox`, an `observed_at` time window, and/or `active = true`.
  A broad-area `bbox` with a large `limit` and no other filter is the main query shape
  that can exceed the 30-second limit and return **HTTP 500**.
- **`active = true` and `observed_at` ranges are fast** (indexed). Filtering by
  non-indexed attributes over large sets is slower.
- **Prefer time windows over deep paging** for bulk pulls — see
  [Paging & bulk export](/guides/paging-and-bulk-export).

## Concurrency

The API admits a fixed number of requests at a time, shared across all callers, so one heavy
consumer cannot starve everyone else. Past that ceiling you get an immediate **`503`** with
`Retry-After` and a `{"code":"ogc-busy"}` body — the request never reached the database, so
retrying after the suggested delay is safe and cheap.

Practically: run a handful of parallel requests, not dozens. A serial loop over narrow time
windows finishes a bulk export faster than a wide fan-out that spends its time being refused.

## Retries

Treat an HTTP 500 with a timeout message as **retryable**: narrow the query (smaller
bbox, shorter time window, add `active = true`) and retry with backoff.

A `503` is retryable too — see Concurrency above, and honor `Retry-After`. A `401` is not:
re-run the token exchange rather than retrying the same request.

## Caching & freshness

Responses carry `Cache-Control: private, max-age=60`. Because they are authenticated they are
never held in a shared cache, but your own HTTP client may reuse one for up to a minute. Freshly
ingested detections can therefore lag by up to a minute on top of ingestion itself.
