Skip to content

OAuth Endpoints

Base URL for every endpoint on this page: https://civitai.com.

GET/POST /api/auth/oauth/authorize

Start the Authorization Code + PKCE flow. The caller is the end user's browser — your app sends the user here, Civitai handles sign-in and consent, then redirects them back to your redirect_uri.

Request parameters

All parameters are URL-query on GET and form-body on the consent POST.

ParamRequiredNotes
response_typeMust be code.
client_idFrom app registration.
redirect_uriMust exact-match one of the URIs you registered.
scopeDecimal integer bitmask. See Scopes.
stateOpaque value echoed back on the redirect. Use it to bind the response to a session and defeat CSRF.
code_challengeURL-safe base64 SHA-256 of your code verifier.
code_challenge_methodMust be S256plain is rejected.
approvedconsent POSTtrue when the user clicks Allow on the consent screen.
rememberconsent POSTtrue to persist consent so subsequent flows skip the screen.
buzz_limitconsent POSTJSON-encoded buzz-spend budget. See Buzz limits.

Behavior

  • If the user has no session, Civitai redirects them to /login with a return URL pointing back at /authorize. Your app's request continues once they sign in.
  • If the user has already consented with the same scope, Civitai skips the consent page and issues a code immediately.
  • If the requested scope is wider than the prior consent (or there's no prior consent), the user sees the consent page.

Successful response

302 Found redirect to:

<redirect_uri>?code=<authorization_code>&state=<your_state>

The code is single-use, valid for 10 minutes.

Error responses

StatuserrorCause
400invalid_requestMissing required param, bad redirect_uri, missing or non-S256 PKCE, missing state.
400invalid_clientclient_id doesn't exist.
400invalid_scopeScope is not a non-negative integer ≤ Full.
429rate_limitMore than 10 requests / minute / user.

CORS

Permissive with credentials (Access-Control-Allow-Credentials: true) on the preflight, but this endpoint is meant for top-level browser navigation — don't call it from fetch().


POST /api/auth/oauth/token

Exchange an authorization code for tokens, or refresh an existing pair, or mint a client-owned token via client_credentials.

Common request headers

Content-Type: application/x-www-form-urlencoded

Grant: authorization_code

ParamRequiredNotes
grant_typeauthorization_code
codeThe code from the /authorize redirect.
code_verifierThe PKCE verifier paired with the code_challenge.
client_id
client_secretconfidential onlyRequired for confidential clients; rejected for public clients.
redirect_uriMust match the value sent to /authorize.

Grant: refresh_token

ParamRequiredNotes
grant_typerefresh_token
refresh_tokenThe refresh token you currently hold.
client_id
client_secretconfidential only
scopeOptional — narrow the granted scope; cannot widen.

The old refresh token is invalidated when this call succeeds. Persist the new one before discarding the old one.

Grant: client_credentials

Issues a token bound to the client's owner account, with no end user involved. Confidential clients only.

ParamRequiredNotes
grant_typeclient_credentials
client_id
client_secret
scopeDefaults to the client's allowedScopes.

Successful response

json
{
  "access_token": "civitai_…",
  "token_type": "Bearer",
  "expires_in": 3600,
  "refresh_token": "civitai_…",
  "scope": "114689"
}

refresh_token is omitted for client_credentials.

Error responses

RFC 6749 §5.2 error envelope:

json
{ "error": "invalid_grant", "error_description": "…" }
errorMeaning
invalid_grantCode is unknown, already used, expired, or PKCE verifier didn't match. Refresh token unknown or expired.
invalid_clientclient_id unknown, or confidential client supplied wrong client_secret.
invalid_requestMissing required param.
unsupported_grant_typegrant_type not in the list above.
invalid_scopeRequested scope exceeds the client's allowedScopes or the original consent.

Rate limit: 20 requests / minute / client_id429.

CORS: permissive — this endpoint is designed to be called from third-party origins.


POST /api/auth/oauth/revoke

Invalidate an access or refresh token (RFC 7009).

Request

Content-Type: application/x-www-form-urlencoded
ParamRequiredNotes
tokenThe token string to revoke.
token_type_hintaccess_token or refresh_token — Civitai tries both regardless, the hint is an optimization.
client_idpublic via sessionRequired for the client-credentials authentication path.
client_secretconfidential clientsRequired if you're authenticating as a confidential client.

Authentication

The caller must prove authority to revoke the token via one of:

  • A Civitai session cookie (browser context — user revoking their own token).
  • client_id + client_secret on a confidential client.

Public clients with no session can't call /revoke — that's fine, just drop the tokens locally. Revoking a token whose owner doesn't match the caller is silently ignored, per RFC 7009.

Response

Always 200 {} regardless of whether the token existed or matched the caller. Don't treat the response as confirmation that the token was real or that you owned it.

Revoking a refresh token also invalidates every access token it minted for the same client_id / user pair.

Rate limit: 20 requests / minute / IP.


GET /api/auth/oauth/userinfo

Identify the user behind an access token.

Request

Authorization: Bearer civitai_…

No body. Token must include the UserRead scope.

Response

json
{
  "sub": "12345",
  "id": 12345,
  "username": "ada",
  "image": "https://image.civitai.com/…"
}

sub is the string form of id for compatibility with OIDC consumers.

Error responses

StatuserrorCause
401invalid_tokenMissing, malformed, or expired bearer token.
403insufficient_scopeToken doesn't include UserRead.

CORS: permissive — call from any origin.

For richer user info (email, links, stats, etc.) use the GET /api/v1/me endpoint with the same bearer token.

Civitai Developer Documentation