Token endpoint
AIGW uses the core token endpoint for multiple grants:
https://core.api.aigw.loginid.ai/oauth2/token
Authorization code exchange
POST /oauth2/token
Content-Type: application/x-www-form-urlencoded
grant_type=authorization_code
&code=<AUTHORIZATION_CODE>
&redirect_uri=<REGISTERED_REDIRECT_URI>
&client_id=<AGENT_CLIENT_ID>
&client_secret=<AGENT_CLIENT_SECRET>
&code_verifier=<PKCE_VERIFIER>
&resource=https%3A%2F%2Fmcp.example.com%2Fmcp
This example authenticates a client_secret_post agent. Use Basic for a client_secret_basic client, omit the secret for public none plus PKCE, or use the registered asymmetric client assertion for private_key_jwt. Validate state before exchanging the code and validate the ID token (including nonce) afterward.
Successful response
HTTP 200 OK with Content-Type: application/json:
{
"access_token": "<AIGW_ACCESS_TOKEN>",
"token_type": "bearer",
"expires_in": 3600,
"scope": "openid ext_token:read search:read",
"id_token": "<ID_TOKEN>"
}
expires_in is the number of seconds until the access token expires; the default is 3600. Use the returned value. id_token is issued for an OpenID Connect authorization. A refresh_token can also be returned when offline access is granted. Treat token strings as credentials; their format is not an API contract.
Refresh an agent access token
Request offline_access during authorization if your agent needs to refresh access without repeating the user authorization flow. Use the returned refresh token with the same client's registered authentication method:
POST /oauth2/token
Content-Type: application/x-www-form-urlencoded
grant_type=refresh_token
&refresh_token=<REFRESH_TOKEN>
&client_id=<AGENT_CLIENT_ID>
&client_secret=<AGENT_CLIENT_SECRET>
HTTP 200 OK:
{
"access_token": "<AIGW_ACCESS_TOKEN>",
"token_type": "bearer",
"expires_in": 3600,
"scope": "openid offline_access ext_token:read search:read",
"refresh_token": "<REFRESH_TOKEN>"
}
Store any replacement refresh token atomically with the new access token. If the response omits refresh_token, retain the existing one. If refresh returns invalid_grant, start a new user authorization. A Proxy token cannot refresh a user's access token.
Proxy client credentials
POST /oauth2/token
Authorization: Basic base64(<PROXY_CLIENT_ID>:<PROXY_CLIENT_SECRET>)
Content-Type: application/x-www-form-urlencoded
grant_type=client_credentials
HTTP 200 OK:
{
"access_token": "<PROXY_TOKEN>",
"token_type": "bearer",
"expires_in": 3600,
"scope": ""
}
Cache the Proxy token until shortly before the returned expiry, then obtain another with the same grant. This grant does not issue a user ID token or a refresh token.
These are different OAuth clients with different purposes: the agent client is dynamically registered; the Proxy client is created by the Project operator for the MCP/UCP server.
Response fields
| Field | Availability | Meaning |
|---|---|---|
access_token | Successful grants | Credential for the authorized operation |
token_type | Successful grants | bearer; use Authorization: Bearer when sending the token |
expires_in | Token lifetime, in seconds | Time from issuance until access-token expiry |
scope | Granted scope information | Space-separated scopes; may be empty for a Proxy token |
id_token | OpenID Connect grants | User authentication result; validate it separately from the access token |
refresh_token | When refresh is granted | Credential for obtaining another agent access token; keep it confidential |
Token, code, and assertion lifetimes
Default lifetimes are listed below. Use the response's expires_in or the validated JWT's exp when handling an issued token.
| Credential | Default lifetime | Expiration and renewal |
|---|---|---|
| AIGW access token / Proxy token | 1 hour (3,600 seconds) | Read expires_in; refresh an agent token or obtain another Proxy token before expiry |
| ID token | 1 hour | Validate JWT exp, issuer, audience, signature, and nonce |
| Refresh token | 24 hours | Store replacement tokens; start authorization again on invalid_grant. Access-token expires_in does not describe refresh-token expiry |
| Authorization code | 10 minutes; single-use | Exchange immediately after validating the callback; restart authorization if expired or already used |
| Merchant-facing user or client assertion | 5 minutes | Validate iat, exp, and the signature at the merchant authorization server; do not reuse an expired assertion |
| Merchant access token | Determined by the merchant authorization server | Read redemption response expires_at when supplied; omission does not mean the token never expires |
Errors
A rejected code or refresh token returns HTTP 400 Bad Request:
{
"error": "invalid_grant",
"error_description": "The provided authorization grant or refresh token is invalid, expired, revoked, does not match the redirection URI used in the authorization request, or was issued to another client."
}
| HTTP status | error | Action |
|---|---|---|
400 | invalid_request | Check required form fields and encoding |
400 or 401 | invalid_client | Check the client ID, credential, and registered authentication method |
400 | invalid_grant | Check redirect URI and PKCE verifier; obtain a new code or repeat user authorization for an expired/revoked refresh token |
400 | unauthorized_client | Use a grant permitted for this client |
400 | unsupported_grant_type | Check the grant_type value |
400 | invalid_scope | Request only scopes allowed for the client and original grant |
Branch on error, not the wording of error_description. Error responses may include additional diagnostic fields. Authentication failures using HTTP Basic can return 401 with a WWW-Authenticate header.