Refresh session tokens

PUBLIC

Exchanges a valid refresh token for new access, ID, and refresh tokens.
The submitted refresh token is immediately invalidated and replaced with a new one.

Recent Requests
Log in to see full request history
TimeStatusUser Agent
Retrieving recent requests…
LoadingLoading…

Why this endpoint exists

Access tokens are deliberately short-lived (typically 60 minutes), so a leaked one has a small window. That is only workable if the session can be extended without dragging the customer through login again — which is what the refresh token is for.

Call this before the access token expires and the customer's session continues uninterrupted. The submitted refresh token is invalidated and replaced, so a stolen refresh token stops working the moment the legitimate client refreshes.

Request

Two ways to supply the token. The cookie wins when both are present.

# Browser — the HttpOnly cookie carries it, no body needed
curl -X POST https://api.acc.funtrips.io/v2/session/token/refresh \
  --cookie "refresh_token=eyJjdHki…"

# Native app — explicit body
curl -X POST https://api.acc.funtrips.io/v2/session/token/refresh \
  -H "Content-Type: application/json" \
  -d '{ "refresh_token": "eyJjdHki…" }'
FieldTypeRequiredNotes
refresh_tokenstringConditionalRequired unless the refresh_token cookie is present

The request body is optional as a whole. Supplying neither cookie nor body field is a 400.

For a browser client with credentials: 'include', the correct call is a bodyless POST — the cookie set by code exchange or magic-link verify does the work.

Response

200 OK:

{
  "data": {
    "access_token": "eyJraWQ…",
    "id_token": "eyJraWQ…",
    "refresh_token": "eyJjdHki…",
    "token_type": "Bearer",
    "expires_in": 3600,
    "expires_at": "2026-09-03T16:35:00Z"
  }
}
📘

Note this response has no meta envelope field — unlike almost every other endpoint. Read data and do not depend on meta.correlation_id here; the X-Correlation-Id response header still carries it.

Cookie rotation

The access_token cookie is always rewritten. A new refresh_token cookie is written only when the provider rotates the refresh token; otherwise the existing refresh cookie stays valid.

So do not assume every refresh yields a new refresh token. For native clients: if refresh_token is present in the body, replace what you stored; if absent, keep the one you have.

When to refresh

Proactively, before expiry. Track expires_at and refresh when roughly a minute remains. This avoids the alternative — refreshing reactively on a 401 — which is worse than it looks on the checkout routes.

Those routes treat an invalid token as guest rather than returning 401. A silently expired session therefore does not fail loudly; it prices the basket without the customer's loyalty discount and returns 200. There is no 401 to react to.

async function withFreshSession(fn) {
  if (Date.now() > expiresAt - 60_000) {
    const res = await fetch(`${BASE}/session/token/refresh`, {
      method: 'POST', credentials: 'include',
    });
    if (!res.ok) return restartHandoff();
    ({ expires_at: expiresAt } = (await res.json()).data);
  }
  return fn();
}

Refresh once and serialise concurrent callers behind that single attempt. Firing several refreshes in parallel means each invalidates the previous one's result, and the loser gets a 401 on a token that was valid a moment ago.

Errors

StatustypeMeaningWhat to do
400Neither cookie nor body field suppliedSend one
401refresh-token-invalidExpired, revoked, or already usedThe session is over — restart the hand-off
500Unexpected fault. Retryable

refresh-token-invalid is terminal for that session. Do not retry: send the customer back through session exchange or magic link. It is 401 rather than 500 specifically so your front-end can branch on it.

Notes

  • A logged-out session cannot be refreshed. POST /auth/logout revokes the refresh token at the identity provider.
  • Refresh tokens are single-use where rotation is enabled. Never refresh from two places concurrently.
Body Params

Either supply the refresh token in this body, OR send the
refresh_token HttpOnly cookie that every session-mint
endpoint now writes. The handler prefers the cookie when both
are present. At least one source must carry the token —
omitting both returns 400.

string

Refresh token issued during a previous authentication.
Optional when the caller presents the refresh_token
cookie set by POST /auth/link/verify,
POST /session/token, or a prior call to this endpoint.

Headers
string
enum
Defaults to application/json

Generated from available response content types

Allowed:
Responses

Language
URL
LoadingLoading…
Response
Click Try It! to start a request and see the response here! Or choose an example:
application/json
application/problem+json