Skip to content
AtheronDEVELOPERS
← API reference

Token: swap a code

Swaps the code from the redirect for an ID token, an access token and, with offline_access, a refresh token.

Sign in to see your own test client ID in the samples.

POST https://accounts.aton-network.org/oauth/token

When to use it

On your server, in the route your redirect address points at, straight after checking state.

Authentication

client_secret_basic (preferred), client_secret_post, private_key_jwt, or none for a public app. See Secrets and certificates.

Parameters

NameInTypeRequiredLimitsWhat it is
grant_typeformstringyesauthorization_codeThis grant.
codeformstringyesThe code from the redirect. 60 seconds, once.
redirect_uriformstringyesThe same redirect address the authorize request used.
code_verifierformstringyes43 to 128 charactersThe verifier whose challenge you sent to authorize.
client_idformstringnoRequired for a public app, and with client_secret_post.
client_secretformstringnoOnly with client_secret_post. Prefer HTTP Basic.
client_assertion_typeformstringnourn:ietf:params:oauth:client-assertion-type:jwt-bearerWith private_key_jwt.
client_assertionformstringnoA JWT signed with your certificate's private key: iss and sub your client ID, aud the token endpoint, exp at most ten minutes ahead, a jti used once.

Request

# Swap the code from the redirect for tokens, on your server. HTTP Basic carries your client ID and
# secret; the verifier proves this is the browser that started the sign-in.
curl https://accounts.aton-network.org/oauth/token \
  -u "$ATHERON_CLIENT_ID:$ATHERON_CLIENT_SECRET" \
  -d grant_type=authorization_code \
  -d code="$CODE" \
  -d redirect_uri=https://app.example.com/callback \
  -d code_verifier="$VERIFIER"

Response

http
HTTP 200
content-type: application/json
cache-control: no-store

{
  "access_token": "eyJhbGciOiJFUzI1NiIsInR5cCI6ImF0K2p3dCJ9...",
  "token_type": "Bearer",
  "expires_in": 900,
  "scope": "openid profile email offline_access",
  "id_token": "eyJhbGciOiJFUzI1NiIsImtpZCI6IjIwMjYtMTAtMDEiLCJ0eXAiOiJKV1QifQ.eyJpc3MiOiJodHRwczovL2FjY291bnRzLmF0b24tbmV0d29yay5vcmciLCJzdWIiOiI5YTZk...",
  "refresh_token": "ath_rt_6Qm9..."
}

Errors

HTTPerrorCauseFix
400invalid_requestA parameter is missing or malformed: no PKCE, PKCE with plain, a bad max_age, prompt=none combined with another prompt, or a token request that is not form encoded.Send every required parameter once, PKCE with S256, and form-encode the token request.
401invalid_clientYour app could not be authenticated: a wrong or expired secret, a revoked or expired certificate, a bad client_assertion (aud must be the token endpoint, exp at most ten minutes ahead, jti new), a public app sending a secret, or the app is suspended.Check the credential on the app's Credentials page and its expiry. Rotate without downtime: add a new one, deploy, then revoke the old.
400invalid_grantThe code or refresh token cannot be used: the code expired (60 seconds) or was used, it was issued to another client, the redirect_uri or code_verifier does not match, a refresh token was used twice, access was withdrawn, or the tester was removed.Start the sign-in again. Swap each code once, straight away, with the same redirect_uri and the verifier you kept.
400unsupported_grant_typegrant_type is not authorization_code or refresh_token.Use one of the two.
429rate_limitedOver your quota for this bucket, this minute or this day.Wait the Retry-After seconds. Requests over the limit still count, so a retry loop keeps you over it.

Good to know

  • The body must be application/x-www-form-urlencoded. JSON is refused with invalid_request.
  • Check the ID token before you trust it: signature (ES256, from the keys), iss, aud is your client ID, exp, and nonce.
  • A refresh token comes back only when you asked for offline_access and the person agreed.

Security

  • Never call this from a browser with a secret. A public app sends client_id and its PKCE verifier only.
  • Send an assertion or a secret, never both.

Ask Atlas

AI assistant

Ask anything about Sign in with Atheron, the API or the portal. You get an answer from our docs, with links to the pages it used.