# Gränser & retry

API:t öppnar fysiska dörrar. Retry-policyn är därför inte en optimering utan en säkerhetsfråga.

## Är unlock säkert att försöka om?

> **Nej. Retry:a aldrig unlock automatiskt.** — `PUT /device/{deviceId}/unlock` är inte idempotent i praktiken: varje accepterat anrop skickar ett nytt öppningskommando till enheten. Ett timeout betyder inte att kommandot uteblev — det kan ha nått fram. En automatisk retry kan därför öppna dörren en andra gång, vid en tidpunkt då ingen står där.

- Vid fel: visa resultatet för en människa och låt personen trycka igen. Det är den enda säkra "retryn".
- Logga varje försök i er egen audit-logg med tidsstämpel, aktör och `{deviceId}`.
- Hur länge dörren står olåst efter en upplåsning är konfigurerat per enhet. Behöver ni veta det exakta värdet för era enheter, fråga KiiOn.

## Retry-policy per operation

| Operation | Får försökas om? | Policy |
| --- | --- | --- |
| `GET /user/device` | Ja | Idempotent. Exponentiell backoff med jitter på `429`/`5xx` |
| `GET /user/location` | Ja | Samma som ovan |
| `GET /device/{deviceId}` | Ja | Samma som ovan |
| `POST /auth/login-by-phone` | Begränsat | Högst ett omförsök vid `5xx`. Aldrig vid `400`/`401` — uppgifterna är fel |
| `POST /auth/refresh` | Nej (samma token) | Vid `5xx`: ett omförsök. Vid `400`/`401`: ominloggning, aldrig samma token igen |
| `PUT /device/{deviceId}/unlock` | **Nej** | Aldrig automatiskt. Visa felet |
| `POST /auth/logout` | Ja | Ofarlig att upprepa |

```typescript
const RETRYABLE = new Set([429, 500, 502, 503, 504]);

async function withBackoff<T>(fn: () => Promise<Response>, attempts = 4): Promise<Response> {
  for (let attempt = 0; ; attempt++) {
    const res = await fn();
    if (!RETRYABLE.has(res.status) || attempt >= attempts - 1) return res;

    const retryAfter = Number(res.headers.get("Retry-After")) * 1000;
    const backoffMs = Number.isFinite(retryAfter) && retryAfter > 0
      ? retryAfter
      : 2 ** attempt * 500 + Math.random() * 250; // jitter: never a synchronised thundering herd
    await sleep(backoffMs);
  }
}
// NOTE: never wrap PUT /device/{deviceId}/unlock in this. See "Is unlock safe to retry?".
```

## Hur ofta ni bör anropa

| Operation | Rekommenderad frekvens | Varför |
| --- | --- | --- |
| `POST /auth/login-by-phone` | En gång vid uppstart, samt som fallback | Varje inloggning avlivar den föregående sessionen |
| `POST /auth/refresh` | Var 36:e timme | Proaktivt, inte som reaktion på ett fel |
| `GET /user/device` | Var 24:e timme, samt vid `404` | Listan ändras sällan; cachelagra mappningen |
| `PUT /device/{deviceId}/unlock` | En gång per faktisk öppning | Ingen polling, ingen spekulativ föröppning |

> **Polla inte enhetslistan** — En hämtning per öppning ger onödig latens och riskerar throttling. Uppdatera mappningen på schema (24 h är en rimlig utgångspunkt) — se [Golden path](/docs/golden-path#device-mapping).

## Kvoter och throttling

KiiOn publicerar i dagsläget inga numeriska anropstak per konto. Bygg därför klienten så att den hanterar throttling korrekt oavsett: respektera `429` och `Retry-After`, backa av exponentiellt och låt aldrig en retry-loop springa fritt. Behöver ni ett garanterat tak för ett högvolymsfall — hör av er till developers@kiion.io innan driftsättning.

> **En retry-storm slår ut er egen session** — Upprepade inloggningar kolliderar med regeln om en aktiv session per konto. Ett skript som loopar på `login` kan låsa ute er produktionsintegration — se [Autentisering & tokens](/docs/authentication#one-session-per-account).

**Vidare**
- [Statuskoder & felsvar](/docs/errors)
- [Samma regler som direktiv för AI-agenter](/docs/ai-instructions#failure-signals)
