---
title: Authentication
description: API keys, rate limits and what happens when you exceed them.
---

## Anonymous access

Every data endpoint works without a key, at **60 requests per minute per IP**.
That is enough for a form on a website that looks up a bank as the user types,
because the response is cacheable and the component only calls out once per
four-digit prefix.

## API keys

Create an account and a key through the auth endpoints under `/v1/auth`, then
send the key on every request:

```bash
curl https://api.kontosjekker.asamsig.com/v1/banks/lookup?clearingCode=8601 \
  -H "x-api-key: kntsj_xxxxxxxxxxxxxxxxxxxx"
```

`Authorization: Bearer kntsj_xxx` works identically.

Keys carry:

- **Permissions** — `banks:read` and `accounts:validate` by default; `logos:write`
  and `dataset:refresh` are reserved for operators.
- **A quota** — a daily request budget enforced when the key is verified.
- **An optional expiry** — up to 365 days.

Keys are stored hashed. A lost key cannot be recovered, only rotated.

## Rate limits

Every response carries the limit state:

```http
ratelimit-limit: 600
ratelimit-remaining: 597
ratelimit-reset: 41
```

Exceeding the limit returns `429` with `retry-after` and an RFC 9457 problem
document:

```json
{
  "type": "https://api.kontosjekker.asamsig.com/problems/rate-limit-exceeded",
  "title": "Rate limit exceeded",
  "status": 429,
  "code": "rate-limit-exceeded",
  "detail": "Rate limit of 60 requests per 1 minute exceeded"
}
```

## Errors

All errors follow [RFC 9457](https://www.rfc-editor.org/rfc/rfc9457):
`application/problem+json`, with a stable `type` URI and `code`. Branch on
`code`, never on the prose in `detail`.

| Status | Code | Cause |
| --- | --- | --- |
| 400 | `invalid-request` | Failed validation; see `errors[]` |
| 401 | `unauthorized` | Key required but missing or invalid |
| 403 | `forbidden` | Key lacks a required permission |
| 404 | `not-found` | Unknown bank, or no logo on file |
| 429 | `rate-limit-exceeded` | Slow down; honour `retry-after` |
| 500 | `internal-error` | Our fault — retry |

## Caching

Responses carry `ETag` and `Cache-Control`. Send `If-None-Match` and you will
get `304 Not Modified` while the register is unchanged. Logo URLs that include
the `v=` hash from the bank object are immutable and can be cached forever.
