# How does a call flow through the platform?

Every call passes through the same three stations: the platform recognizes the credential, selects the operation under `/v1` and checks its permission, and then answers with JSON and the headers that apply everywhere. Anyone who knows these stations can read every error response at a glance.

> **Type:** concept
> **For:** Developers before the first call against the Szenera API
> **Prerequisite:** none. The page explains what an API key sets in motion later

## Station 1: The credential in the bearer header

The platform recognizes from the prefix of the value in `Authorization: Bearer …` which kind of credential is present, with no further input from you:

| Prefix | Credential | For whom |
|---|---|---|
| `ak_` | API key of an organization | Machines: scripts, services, your integration |
| `eyJ` | Session token from `POST /v1/auth/login` | People in the portal. Valid for 15 minutes, then a refresh |
| `oat_` | Access token of a delegated access | a third-party application on behalf of a person |
| `opk_` | Key of platform operations | only the operator of the platform |
| `rdr_` | Renderer token | the playback process of a paired device |

At the edges you meet `sct_` (a directory service under `/scim/v2`, nowhere else), `inv_` (an invitation before the first sign-in), and `dev_` (the first registration of a device). Devices in operation identify themselves with their certificate. For your integration, `ak_…` is the standard way: no expiry after a quarter of an hour, no refresh.

A value with an unknown prefix explicitly does **not** count as anonymous. It is rejected as an unusable credential with `401 Auth.Required`, so a typo in the prefix becomes a 401 and not a misleading 404. A call without a header gets the same response, and a key with the right prefix but a truncated value gets `401 Auth.TokenMalformed`.

## Station 2: Route and permission check

Every route that Szenera itself controls lives under `/v1`, and the path is part of the contract. Only where a third-party standard dictates its address is the prefix absent (`/oauth/…`, `/.well-known/…`, `/scim/v2/…`). Then the platform checks, per operation, the permission that the reference names for it (for example `playlist.list` for `GET /v1/playlists`). An API key carries the fixed list of permissions from its creation. Devices, pairing and deleting displays, and key management stay reserved for user accounts. If the permission is missing, the answer is `403 Permission.Denied`. Routes of platform operations answer a key of an organization with `403 Permission.OperatorOnly`.

![A call runs from the client through credential recognition, route, and permission check to the response. An unknown credential and a missing permission branch off into the error body](/getting-started/bilder/c0b517a1-request-flow.svg)

_The error branch carries the same body as every error: `code`, `message`, `traceId`._

## Station 3: The response and its headers

The platform speaks JSON. Four headers shape the exchange, three in the response and one in the request, wherever their condition is met:

| Header | When |
|---|---|
| `X-Trace-Id` | always, the trace ID of this call |
| `If-Match` (request) | when changing versioned resources (scenes, playlists, programs, roles, displays): the `version` you read goes along as `If-Match`, and the same number is in the response as `ETag` |
| `Idempotent-Replay: true` | when a retry with the same `Idempotency-Key` received the stored response |
| `X-RateLimit-Limit`, `X-RateLimit-Remaining`, `X-RateLimit-Reset` | only on throttled routes, above all sign-in, registration, and password reset |

An error always comes in the same body: `code` in the form `Area.Cause`, `message` as a short text, and `traceId` with the prefix `trc_`. The HTTP status follows the area of the code: `Auth.*` → 401, `Permission.*` → 403, `*.NotFound` → 404, `Conflict.*` → 409, `Validation.*` → 422, `RateLimit.*` → 429.

## What follows

- **Keys instead of sessions for machines.** A session token expires after 15 minutes. A key `ak_…` does not.
- **Branch on `code`, never on `message`.** The code is stable and part of the contract. The text may change.
- **Log the trace ID.** `X-Trace-Id` and `traceId` are the same value, the one you give when you ask for help.
- **Give the key only the permissions your integration needs.** A key that is too narrow shows up as a `403`. A key that is too wide shows up as nothing.

## Related

- [Create an API key](/getting-started/create-api-key)
- [Authentication](/api/authentication)
- [The error format](/api/error-format)
