---
title: "Refresh"
method: POST
path: "/v1/auth/refresh"
tags: ["auth"]
---

# Refresh

`POST /v1/auth/refresh`

## Request

- Base URL: `https://app.tokumalabs.com/api`
- URL: `https://app.tokumalabs.com/api/v1/auth/refresh`
- Auth: none declared

Refresh access token using refresh token.

Implements rotating refresh tokens with a supersession grace window:

- Normal rotation: the presented token is the current tail of its
  chain. Mint a successor, link the rows, return the new pair.
- Replay-within-grace: the presented token has already been
  superseded but the grace window hasn't elapsed. This is the
  "client never received the previous response" case (network
  drop, laptop sleep mid-flight). Mint a fresh tail extending the
  chain and return it — the client gets a working token without
  being kicked to /login.
- Replay-after-grace: the same presentation past the grace window
  is presumed reuse (legitimate clients couldn't still be holding
  the old token this long after rotation). Revoke **every** token
  the user holds and return ``REFRESH_TOKEN_REUSE_DETECTED``.

Token plaintext is never stored — only the SHA-256 hash. Replays
therefore always mint a fresh successor rather than re-handing
the exact original successor; the sweep prunes intermediate
superseded rows once they pass the grace window.

**The successor's ``device_id`` comes from the presented row, never
from ``X-Device-Id``.** Both revocation paths — reuse detection and
logout — key on ``(user_id, device_id)``, so a caller who could
choose the successor's device could refresh once under a device
name the victim's client never touches and move their chain out of
reach of the victim's own logout *and* of the reuse detection the
victim's replay triggers. The descriptive fields (name, platform,
app version) still follow the headers; they are labels, not
revocation keys. A header that disagrees with the stored device is
logged (``event=auth.refresh_token.device_id_mismatch``) — that
disagreement is the signature of the attack it now cannot perform.

## Headers

- `x-device-id` string, nullable
- `x-device-name` string, nullable
- `x-platform` string, nullable
- `x-app-version` string, nullable

## Cookies

- `tokuma_rt` string, nullable

## Request body

- RefreshRequest
  - `refresh_token` string, nullable: Refresh token (optional for browser clients)

## Response `200`

Successful Response

- TokenResponse
  - `access_token` string, required: JWT access token
  - `token_type` string: Token type
  - `expires_in_seconds` integer, required: Token expiration time in seconds
  - `scope` string: OAuth2 scopes

## Other responses

- `400`: Validation error or domain rule violation.
- `401`: Missing, expired, or invalid authentication.
- `403`: Authenticated but not permitted.
- `404`: Resource not found.
- `409`: Resource state conflict (duplicate, illegal transition).
- `422`: Request body failed schema validation.
- `500`: Server-side failure.

---

[API](https://skmtc.dev/tokumalabs/apis/tokuma-user-api.md) - [All operations](https://skmtc.dev/tokumalabs/apis/tokuma-user-api/llms.txt) - [OpenAPI document](https://skmtc.dev/tokumalabs/apis/tokuma-user-api/revisions/1f8201af459b?raw)
