norra

Utvecklare

Skicka ett bolags egna siffror till norra från systemen det redan kör.

Kontraktet, version v1

Send what your systems already know. A delivery is stored before anything reads it, so a number in a report can always be traced back to the bytes it came from.

Authenticate with a service connection, made on the company's developer page. A connection belongs to one company and is bound to one of its sources, so a call names neither. It expires after a year and can be rotated with an overlap so you deploy without a gap.

Use client credentials where you can. The connection has a client id and a secret that starts `ncs_`. POST `grant_type=client_credentials` to `https://app.norra-co.com/oauth/token` with the id and the secret in HTTP Basic, and send the `access_token` it answers with as `Authorization: Bearer <token>`. A token lasts an hour; ask for a new one a minute before `expires_in` runs out, and once more on a 401. The secret never goes to the endpoints below.

A device or a script that cannot exchange a token uses a static credential instead. It starts `nsk_` and is sent as the bearer itself. Both kinds are shown once, when the connection is made.

A connection holds permissions, chosen when it is made: `sources.ingest` pushes deliveries and `metrics.read` reads the source's samples. The permissions cannot change after it is made; for different ones, make a new connection. Each operation names the permission it needs. A connection without it gets 403 with the code `permission_denied`, and a `WWW-Authenticate` header with `error="insufficient_scope"` naming the permission.

A connection that is not bound to a source names one with `source_id`, and a connection that acts on more than one company names it with `orgnr`.

Every delivery carries an idempotency key. The same key with the same body returns the same receipt and stores nothing twice. The same key with a different body is refused with 409 and the code `key_reused`, and nothing in it is kept.

A delivery keeps only the fields its source declares. Any other key is dropped before the delivery is stored, and the receipt lists it in `dropped_keys`.

A source delivers events or statements, chosen when the company declares it, and the receipt says which in `delivers`. An events source adds every delivery's rows to what came before. To correct a number, send a row that cancels the wrong one, such as a negative amount or a refund. A statements source takes each delivery as the whole of every period it has a row in, a day, a week or a month as each metric counts it. The delivery received last for a period replaces what earlier ones said about it, and the period is marked restated. To correct a number, send the whole period again. A period sent with only some of its rows reads as only those rows. Send a statement once a week, and on demand in between when a number has to change sooner. Each statement holds every day of each period it touches, so a monthly metric reads the month so far, not one day of it.

Rate limit: 60 requests a minute per connection, reads and pushes together, bursting to 20. A refusal is 429 with `Retry-After` in seconds.

A delivery is at most 8388608 bytes.

A sandbox connection stores and parses like any other and marks what it stores as test, so nothing it sends reaches a report.

The documentation site can send a request from its reference. A request from that site takes a sandbox connection or the demo connection. A production connection is refused with 403 and the code `docs_origin_needs_sandbox` before anything is read. The demo connection reads fixture samples on the demo deployment, https://demo.norra-co.com, and cannot push. It is limited to 10 requests a minute per client network, bursting to 5.

Versions live in the path. Additions happen inside a version; a remove or a rename is a new one, and both run for 90 days before the old one stops (see the changelog).

A webhook source is not pushed to over a connection: its sender signs each delivery with the source's signing secret. The Webhooks section says how.

Adressen att skicka till

POST https://app.norra-co.com/v1/ingest

The body is JSON with an idempotency key, the shape version it is written against, and the payload. A document source sends raw bytes with its own media type instead. Needs a connection that holds sources.ingest.

Det som kommer tillbaka

artifact_id
What was stored. Cite this when you ask why a number is what it is.
delivers
How the source counts this delivery. `events` adds its rows to what came before. `statements` makes it the whole of every period it has a row in, replacing what earlier deliveries said about those periods.
dropped_keys
The keys the delivery carried that the source does not declare, sorted, at most 50. They were not stored. Empty when nothing was dropped.
sandbox
The connection is a sandbox connection, so what this becomes is test.
shape_version
The version it will be parsed under.
status
Where the delivery is.

När något är fel

code
A short machine-readable reason.
field
The field it is about, when it is about one.
message
One plain sentence.

Webhooks

POST https://app.norra-co.com/v1/webhooks/{source_id}

A webhook source is fed by your system calling Norra when something happens. Each webhook source has its own address, `POST /v1/webhooks/{source_id}`, and its own signing secret. The secret is shown once, when the source becomes a webhook source, and can be rotated on the company's sources page.

Sign every attempt. Send `Norra-Signature: t=<unix seconds>,v1=<signature>`. The signature is the lowercase hex HMAC-SHA256 of `<t>.<delivery id>.<raw body>`, keyed with the signing secret exactly as it was shown, prefix included. Norra refuses a time more than 300 seconds from its own clock, either way, so sign each attempt when you send it.

Name every delivery. `Norra-Delivery-Id` is your id for the event: letters, digits, dash, underscore or colon, at most 200 characters. It is signed, and it is the idempotency key: a redelivery with the same id and the same body stores nothing twice and answers with the first receipt, however long after the first it comes. The same id with a different body is refused with 409 and the code `key_reused`, and nothing in it is kept. A delivery is also stored by the hash of its body, so two deliveries with byte-identical bodies are one delivery whatever their ids. Put the event's own id or time in the body.

The body is the delivery itself: JSON, one object or an array of objects, or CSV with a header row, carrying the fields the source declares. It is stored as it arrived, less any key or column the source does not declare, and parsed afterwards. The receipt lists what was dropped. `X-Norra-Shape-Version` names the shape version it is written against; without it, the source's current version.

Signature only. A connection does not open a webhook, and a connection cannot push to a webhook source. A bearer in a sender's configuration travels with every call and works for as long as it lives. A signature covers one body at one moment, so a copied request cannot be changed and stops working after 300 seconds.

Rotation. A new signing secret works at once, and the old one keeps working for 24 hours, so you can switch without a gap. A second rotation inside that window ends the older secret at once.

2xx: accepted and stored. The receipt names the artifact. Do not send it again.

4xx: do not retry the same request. 401 is a request with no `Norra-Signature` header at all. 400 is a signature header or delivery id that is not well formed, or a time outside the window: fix it and sign again. 404 is an address and signature that open no webhook source. The source does not exist, is not a webhook source, or the secret is wrong. All three get the same answer, so the address tells somebody without the secret nothing. 409 is a source the company has paused (`source_paused`): nothing is kept, so send it again once the source is resumed. It is also a delivery id that already stored a different body (`key_reused`): nothing is kept. 413 is a body over 8388608 bytes. 415 is a body that is not JSON or CSV.

429 and 5xx: retry. On 429, wait the seconds in Retry-After. Otherwise back off exponentially from 30 seconds, doubling each time up to one hour between attempts, with jitter. Give up after 3 days. Sign each attempt anew and keep the same delivery id.

Rate limit: 60 deliveries a minute per source, bursting to 20.

Ändringslogg

  • v1 · 2026-10-02

    A source delivers events or statements, chosen when it is declared. Events add up, and a correction is a row that cancels the wrong one. A statement is the whole of every period it has a row in, so the delivery received last for a period replaces the earlier ones and the period is marked restated. The receipt says which in `delivers`.

  • v1 · 2026-10-02

    A timestamp field takes a date alone, such as `2026-09-01`, and reads it as midnight on that day in the company's timezone. A time with no offset is still refused. The developer page says how many rows of the last delivery were read that way.

  • v1 · 2026-09-30

    Client credentials. A service connection can hold a client id and secret instead of a static credential, and the developer page makes that kind first. Exchange them at `/oauth/token` for an access token and push with the token. The static credential stays for devices. Webhook sources still sign with HMAC.

  • v1 · 2026-09-30

    Access keys are service connections. A company makes one on its developer page, bound to a source, with a static credential that starts `nsk_`. A connection holds `sources.ingest` to push and `metrics.read` to read; a missing one is 403 `permission_denied`. `nk_` keys no longer open anything. There was no production traffic, so there is no deprecation window.

  • v1 · 2026-10-02

    A delivery keeps only the fields its source declares. Any other key is dropped before the delivery is stored, and the receipt lists it in `dropped_keys`.

  • v1 · 2026-09-25

    A company can pause a source. A push or a webhook to a paused source is refused with 409 and the code `source_paused`, and nothing it carried is kept.

  • v1 · 2026-09-24

    Webhooks: a signed address per webhook source, with a signing secret, rotation and a retry contract. A write key no longer pushes to a webhook source.

  • v1 · 2026-09-24

    Access keys carry a scope: read, write or read_write. A read endpoint for a source's samples. Keys made before this are write.

  • v1 · 2026-09-22

    The first version. The push endpoint, the receipt and the error shape.

OpenAPI-filen

Ladda ner openapi.json