---
title: "Callback"
method: POST
path: "/v1/auth/oauth/{provider}/callback"
tags: ["auth"]
---

# Callback

`POST /v1/auth/oauth/{provider}/callback`

Exchange an authorization code and set the HttpOnly session cookie.

The session is bound to the identity the provider's account resolves to,
exactly as a password sign-in binds one to the identity that authenticated,
so every request it later authenticates resolves the same caller.

A refusal is counted like the other sign-in failures
(``record_auth_failure``) and rendered by the tenancy error handler. Like
the passkey route there is no separate post-failure throttle: this route is
throttled unconditionally on the way in, because there is no legitimate
caller here whose correct credential must never be blocked. An authorization
code is single-use and minted by a redirect, not something a person retries
by hand.

**Maintenance mode freezes this the way it freezes the other two sign-ins.**
The freeze is on starting a session, not on a credential, so an OAuth sign-in
has to answer to it or the switch is bypassable by anybody holding a Google
account. Refused before the exchange, so a frozen deployment makes no
outbound call, spends nobody's authorization code, and counts no auth
failure: nobody failed to authenticate, the gateway declined to try.

## Path parameters

- `provider` string, required — Which OAuth provider to sign in with.

## Request body

- OAuthCallbackRequest — The authorization code a provider handed the browser. No ``redirect_uri``: this deployment derives its own from ``public_base_url`` so the URI used to build the authorization request and the one sent with the exchange are the same string by construction, and a browser cannot choose what this server sends to a provider. No ``state`` either, and that is not an omission. The state is checked in the browser, against the value that browser stored when it started the flow; sending it here would let this deployment compare a value to itself, which proves nothing without somewhere to have kept the original.
  - `code` string, required — The authorization code from the provider's redirect.

## Response `200`

Successful Response

- OAuthSessionResponse — A dashboard session minted by an OAuth sign-in (the token travels only in the cookie). The same three fields ``POST /v1/auth/session`` answers, deliberately: the dashboard's sign-in path does not care which credential got it here.
  - `active_organization_id` string, uuid, required — The organization that identity is acting in, which scopes every tenancy surface.
  - `expires_at` string, date-time, required — When the session cookie stops being accepted.
  - `user_id` string, uuid, required — The identity this session speaks for.

## Other responses

- `422` — Validation Error

## Changes

- **2026-08-25** `0e52dff6b641` — 1 info
  - endpoint added

[Change history](https://skmtc.dev/mozilla-ai/apis/otari/changes/v1/auth/oauth/:provider/callback/post.md)

---

[API](https://skmtc.dev/mozilla-ai/apis/otari.md) · [All operations](https://skmtc.dev/mozilla-ai/apis/otari/llms.txt) · [OpenAPI document](https://skmtc-service-production.skmtc.workers.dev/v1/apis/mozilla-ai/otari/revisions/7f178e92b56c/schema)
