Errors and retries
What each status code means, and what a client does with it.
The error shape
An error is JSON with a short code, one plain sentence, and the field it is about when it is about one:
{
"code": "insufficient_scope",
"message": "this key lacks the write scope"
}Branch on the status and on code. Log message or show it to a person, and do not parse it.
Status codes
Every status the contract defines, read from the contract when these pages were built:
| Status | Returned by | What it means |
|---|---|---|
| 200 | GET /v1/samples | The samples, and the window they were read over. |
| 202 | POST /v1/ingest | Stored. The receipt names the artifact and the version it will be parsed under. |
| POST /v1/webhooks/{source_id} | Stored. The receipt names the artifact. A redelivery with the same delivery id answers with the first receipt and stores nothing. | |
| 400 | POST /v1/ingest | The body is not well formed, or a field is missing. |
| GET /v1/samples | from or to is not a date, or from is not before to. | |
| POST /v1/webhooks/{source_id} | The signature header or the delivery id is not well formed, or the signature's time is outside the window. Do not retry as is. | |
| 401 | POST /v1/ingest | The key is missing, unknown, revoked or expired. |
| GET /v1/samples | The key is missing, unknown, revoked or expired. | |
| POST /v1/webhooks/{source_id} | There is no Norra-Signature header. Do not retry. | |
| 403 | POST /v1/ingest | The key works and its scope does not allow this call (`insufficient_scope`), or a production key was sent from the documentation site (`docs_origin_needs_sandbox`). |
| GET /v1/samples | The key works and its scope does not allow this call (`insufficient_scope`), or a production key was sent from the documentation site (`docs_origin_needs_sandbox`). | |
| 404 | POST /v1/webhooks/{source_id} | Nothing at this address accepts that signature. Do not retry. |
| 413 | POST /v1/ingest | The delivery is larger than this endpoint accepts. |
| POST /v1/webhooks/{source_id} | The body is larger than this endpoint accepts. Do not retry. | |
| 415 | POST /v1/webhooks/{source_id} | The body is not JSON or CSV. Do not retry. |
| 429 | POST /v1/ingest | Too many deliveries on this key. Wait the number of seconds in Retry-After. |
| GET /v1/samples | Too many requests on this key. Wait the number of seconds in Retry-After. | |
| POST /v1/webhooks/{source_id} | Too many deliveries on this source. Retry after the number of seconds in Retry-After. | |
| 5XX | POST /v1/webhooks/{source_id} | Norra could not take the delivery. Retry with backoff. |
What to do with each
200: the read worked. Iftruncatedistrue, ask again for a narrower window. Ifwindow.narrowedistrue, the window was moved forward to the limit.202: the delivery is stored. Keep theartifact_id.400: the request is wrong, and sending it again gets the same answer.fieldnames what to fix.401: the key is missing, unknown, revoked or expired. Do not retry. Check the header, then the key's state on the Developer page.403: the key's scope does not allow the call. Do not retry. Make a key with a scope the operation accepts.413: the delivery is over 8388608 bytes (8 MiB). Split it into smaller deliveries, each with its own idempotency key.429: too many requests on this key. Wait the number of seconds inRetry-After, then send the same request again.- A
5xx, a timeout or a dropped connection: send the same request again, with the same idempotency key.
Retrying safely
A push carries an idempotency key, so sending it again stores nothing twice and returns the same receipt. Every push is safe to retry as long as the retry reuses the key.
- Retry a
429, a5xxand a timeout. Do not retry another4xxunchanged. - Wait longer before each attempt, for example 1, 2, 4 and 8 seconds with some randomness added, up to a limit you choose.
- After a
429, wait at leastRetry-Afterseconds.
Rate limits
A key may make 60 requests a minute, reads and pushes together, bursting to 20. The 200, 202 and 429 answers carry three headers:
RateLimit-Limit: requests allowed in the window.RateLimit-Remaining: requests left in the window.RateLimit-Reset: seconds until the window refills.
A client that reads RateLimit-Remaining can slow down before it gets a 429.