---
title: "Refresh Token"
method: POST
path: "/v1/feedback/token/refresh"
tags: ["feedback"]
---

# Refresh Token

`POST /v1/feedback/token/refresh`

## Request

- Base URL: `https://api.sonenta.dev`
- URL: `https://api.sonenta.dev/v1/feedback/token/refresh`
- Auth: none declared

Rotate an end-user token pair, under the `feedback` usage gate.

ORDERING IS LOAD-BEARING. `feedback.refresh()` rotates the stored
`refresh_token_hash`, which KILLS the caller's current refresh token. A
gate placed after that call would answer 429 to a client that no longer
holds a usable token and has not received its replacement — a permanent
widget lockout, not a temporary limit. So the org is resolved and the
gate runs HERE, before the rotation.

The org is not otherwise reachable from this handler: it lives on the
stored end-user row, which only `feedback.refresh()` reads. Hence the
extra projected lookup below. It is indexed (`feedback_end_users`
carries a unique sparse index on `refresh_token_hash`, app/db.py) and a
token rotation is not a hot path.

An unknown or already-rotated token yields no row, so NO GATE RUNS and
`feedback.refresh()` owns the 401 exactly as before — this handler does
not duplicate that decision. The cost is that invalid-token refreshes
are neither counted nor capped; see the PR for that open hole.

## Request body

- AppRoutesFeedbackRefreshIn
  - `refresh_token` string, required

## Response `200`

Successful Response

- AppRoutesFeedbackTokenOut
  - `access_token` string, required
  - `token_type` string, required
  - `expires_in` integer, required
  - `refresh_token` string, required
  - `refresh_expires_in` integer, required
  - `end_user_id` string, required
  - `tos_version` string, required
  - `grouping_key` string, required

## Other responses

- `422`: Validation Error

---

[API](https://skmtc.dev/sonenta/apis/sonenta-api.md) · [All operations](https://skmtc.dev/sonenta/apis/sonenta-api/llms.txt) · [OpenAPI document](https://skmtc.dev/sonenta/apis/sonenta-api/revisions/f853ea49fb05?raw)
