Skip to content

Authentication ​

Programmatic access uses an API key in the x-api-key header.

bash
curl -H "x-api-key: drse_your_api_key" \
  https://api.utskick.dr.se/api/v1/campaigns

Keys are prefixed drse_. No OAuth flow, no bearer token, no Authorization header.

Creating a key ​

Keys are created in the web app — no endpoint mints them, by design. Key-management routes reject API keys with 403 Forbidden: API keys cannot access this resource, so a leaked key can never create more keys or widen its own permissions.

The API is opened per account: an integration is something somebody builds, and we would rather talk to you first. If API in the sidebar shows a lock, click it and ask for access — we answer, usually the same working day.

  1. Open the web app → API in the sidebar (under Avancerat).
  2. Create key, give it a name.
  3. Tick only the permissions the integration needs — see Permissions.
  4. Optionally set an expiry (1–365 days).
  5. Copy the key. It is displayed once and cannot be retrieved again.

Treat a key like a password

A key acts as your account for every permission it holds — including creating campaigns, which spends money and mails paper. Keep keys in a secret manager or environment variables; never in source control, client-side code or a browser. If one leaks, revoke it in the web app immediately.

Managing keys ​

All from the web app's API page: rename, change permissions, disable temporarily, revoke permanently. Changes take effect on the next request.

No credentials, no fallback ​

A request carrying an x-api-key header is never re-interpreted as a browser session. A bad key returns its specific failure, not a generic 401 — so you can tell "fix the key" from "back off and retry".

Failure reference ​

ConditionStatuscodemessage
No credentials at all401unauthorizedUnauthorized
Unknown or malformed key401unauthorizedInvalid API key
Key past its expiry401unauthorizedAPI key expired
Key disabled in the web app401unauthorizedAPI key disabled
Key's usage quota exhausted401unauthorizedAPI key usage exceeded
Over the key's rate limit429rate_limitedAPI key rate limit exceeded
Over the account's rate limit429rate_limitedAccount rate limit exceeded
Too much traffic from your network429rate_limitedToo many requests from this IP address
Key lacks the endpoint's permission403forbiddenForbidden: insufficient API key permissions
Endpoint refuses API keys entirely403forbiddenForbidden: API keys cannot access this resource
json
{ "error": { "code": "unauthorized", "message": "API key expired" } }

How to respond:

  • 401 — a configuration problem. Retrying will not help. Check the header name, then whether the key was revoked, disabled or expired.
  • 403 insufficient API key permissions — the key is valid; it lacks a permission. Grant it in the web app. Retrying will not help.
  • 403 API keys cannot access this resource — this endpoint is web-app-only and no permission will unlock it.
  • 429 — transient. Back off and retry; see Rate limits.

Rate limit ​

100 requests per 60 seconds, per key — the limit you will actually meet.

Above it sits a per-account ceiling of 300 requests / 60 seconds covering all your keys together, so issuing more keys no longer multiplies total throughput. Separate keys still isolate one workload's budget from another's.

Every /api/v1 response carries your position in RateLimit-Remaining, and every 429 carries Retry-After. Details and retry strategy: Rate limits.

Auth is checked before routing ​

On a guarded path, authentication runs before the method and sub-path are matched. An unauthenticated request to a mistyped /api/v1/... URL returns 401, not 404.

bash
curl -X DELETE https://api.utskick.dr.se/api/v1/lists
# → 401 {"error":{"code":"unauthorized","message":"Unauthorized"}}

So when an unexpected 401 appears, verify the key before you start doubting the URL. Once authenticated, a genuinely unknown route returns 404 Route not found.

Checking connectivity without a key ​

GET /api needs no credentials:

bash
curl https://api.utskick.dr.se/api
# → {"data":{"status":"ok"}}

200 here plus 401 elsewhere isolates the problem to the key.

Storing keys ​

bash
export DRSE_API_KEY="drse_your_api_key"
export DRSE_BASE_URL="https://your-deployment.example"
ts
const KEY = process.env.DRSE_API_KEY;
if (!KEY) throw new Error("DRSE_API_KEY is not set");

export const drse = (path: string, init: RequestInit = {}) =>
  fetch(`${process.env.DRSE_BASE_URL}/api/v1${path}`, {
    ...init,
    headers: { ...init.headers, "x-api-key": KEY },
  });

Next ​

Permissions — exactly which endpoints each permission unlocks.