Skip to main content

Overview

The token endpoint is used to exchange an authorization code for access tokens, or to refresh an expired access token using a refresh token.
POST https://api.auth-agent.com/token

Request Body

Content-Type: application/json

Authorization Code Grant

grant_type
string
required
Must be authorization_code for code exchange
code
string
required
The authorization code received from the /authorize endpoint
code_verifier
string
required
The PKCE code verifier that was used to generate the code challenge
redirect_uri
string
required
The same redirect URI used in the authorization request
client_id
string
required
Your OAuth client ID
client_secret
string
required
Your OAuth client secret

Refresh Token Grant

grant_type
string
required
Must be refresh_token for token refresh
refresh_token
string
required
The refresh token received in a previous token response
client_id
string
required
Your OAuth client ID
client_secret
string
required
Your OAuth client secret

Response

Success (200 OK)

{
  "access_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
  "token_type": "Bearer",
  "expires_in": 3600,
  "refresh_token": "rt_abc123xyz...",
  "scope": "openid email profile"
}
access_token
string
JWT access token used to authenticate API requests. Valid for 1 hour.
token_type
string
Always Bearer
expires_in
number
Time in seconds until the access token expires (3600 = 1 hour)
refresh_token
string
Refresh token used to obtain new access tokens. Valid for 30 days.
Refresh tokens are automatically rotated. The old refresh token is revoked when you use it, and a new one is issued.
scope
string
Space-separated list of granted scopes

Error (400 / 401)

{
  "error": "invalid_grant",
  "error_description": "Invalid or expired authorization code"
}

Error Codes

Error CodeHTTP StatusDescription
unsupported_grant_type400Grant type must be authorization_code or refresh_token
invalid_request400Missing required parameters
invalid_client401Invalid client credentials
invalid_grant400Invalid, expired, or revoked authorization code/refresh token
server_error500Internal server error

Example Requests

// After receiving authorization code in callback
const tokenResponse = await fetch('https://api.auth-agent.com/token', {
  method: 'POST',
  headers: {
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({
    grant_type: 'authorization_code',
    code: authorizationCode,
    code_verifier: sessionStorage.getItem('code_verifier'),
    redirect_uri: 'https://yourwebsite.com/api/auth/callback',
    client_id: process.env.AUTH_AGENT_CLIENT_ID,
    client_secret: process.env.AUTH_AGENT_CLIENT_SECRET,
  }),
});

const tokens = await tokenResponse.json();

// Store tokens securely
// tokens.access_token - Use for API requests
// tokens.refresh_token - Use to get new access tokens

Token Lifecycle

1

Authorization Code Exchange

Exchange the authorization code for initial access and refresh tokens. Authorization codes expire after 10 minutes.
2

Use Access Token

Use the access token to make authenticated API requests. Access tokens are valid for 1 hour.
3

Refresh When Expired

When the access token expires, use the refresh token to get new tokens. Refresh tokens are valid for 30 days.
4

Automatic Rotation

Each refresh generates new access and refresh tokens. The old refresh token is automatically revoked.

Access Token Structure

Access tokens are JWTs (JSON Web Tokens) with the following structure:
{
  "alg": "HS256",
  "typ": "JWT"
}

Payload

{
  "sub": "agent_abc123",
  "iss": "https://api.auth-agent.com",
  "iat": 1234567890,
  "exp": 1234571490,
  "client_id": "client_xyz789",
  "model": "gpt-4",
  "scope": "openid email profile"
}
ClaimDescription
subAgent ID (subject)
issIssuer (Auth Agent API URL)
iatIssued at timestamp
expExpiration timestamp
client_idOAuth client ID
modelAI model used by the agent
scopeGranted scopes

Token Storage Best Practices

Store tokens in secure, HTTP-only cookies or server-side sessions. Never expose client secrets in client-side code.
// Set secure HTTP-only cookie
res.cookie('auth_tokens', JSON.stringify(tokens), {
  httpOnly: true,
  secure: true,
  sameSite: 'lax',
  maxAge: 30 * 24 * 60 * 60 * 1000, // 30 days
});
Implement proactive token refresh before expiration:
async function getValidAccessToken() {
  const tokens = getStoredTokens();
  const decoded = jwt.decode(tokens.access_token);

  // Refresh if expires in less than 5 minutes
  const expiresIn = decoded.exp - Date.now() / 1000;
  if (expiresIn < 300) {
    return await refreshAccessToken(tokens.refresh_token);
  }

  return tokens.access_token;
}
Always update stored tokens after refresh:
async function refreshAccessToken(refreshToken: string) {
  const response = await fetch('https://api.auth-agent.com/token', {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({
      grant_type: 'refresh_token',
      refresh_token: refreshToken,
      client_id: process.env.AUTH_AGENT_CLIENT_ID,
      client_secret: process.env.AUTH_AGENT_CLIENT_SECRET,
    }),
  });

  const newTokens = await response.json();

  // IMPORTANT: Update stored tokens with new refresh token
  updateStoredTokens(newTokens);

  return newTokens.access_token;
}

Security Considerations

Never expose client secrets in client-side code. Token exchange should always happen server-side.
Refresh token rotation - Old refresh tokens are revoked immediately upon use. Always store the new refresh token from the response.
PKCE validation - The code_verifier must match the code_challenge used in the authorization request, or the token exchange will fail.

Next Steps