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:

json
{
  "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:

StatusReturned byWhat it means
200GET /v1/samplesThe samples, and the window they were read over.
202POST /v1/ingestStored. 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.
400POST /v1/ingestThe body is not well formed, or a field is missing.
GET /v1/samplesfrom 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.
401POST /v1/ingestThe key is missing, unknown, revoked or expired.
GET /v1/samplesThe key is missing, unknown, revoked or expired.
POST /v1/webhooks/{source_id}There is no Norra-Signature header. Do not retry.
403POST /v1/ingestThe 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/samplesThe 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`).
404POST /v1/webhooks/{source_id}Nothing at this address accepts that signature. Do not retry.
413POST /v1/ingestThe delivery is larger than this endpoint accepts.
POST /v1/webhooks/{source_id}The body is larger than this endpoint accepts. Do not retry.
415POST /v1/webhooks/{source_id}The body is not JSON or CSV. Do not retry.
429POST /v1/ingestToo many deliveries on this key. Wait the number of seconds in Retry-After.
GET /v1/samplesToo 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.
5XXPOST /v1/webhooks/{source_id}Norra could not take the delivery. Retry with backoff.

What to do with each

  • 200: the read worked. If truncated is true, ask again for a narrower window. If window.narrowed is true, the window was moved forward to the limit.
  • 202: the delivery is stored. Keep the artifact_id.
  • 400: the request is wrong, and sending it again gets the same answer. field names 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 in Retry-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, a 5xx and a timeout. Do not retry another 4xx unchanged.
  • 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 least Retry-After seconds.

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.