> For the complete documentation index, see [llms.txt](https://docs.openiam.com/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.openiam.com/developer-guide/tokens.md).

# Tokens

Obtain, inspect, validate, refresh and revoke an OAuth token, and end a session — the token endpoints, their parameters and their responses.

Every call except the token endpoints themselves carries a bearer token. This page is the reference for obtaining one and for everything you can do to it afterwards.

**The token endpoints do not follow the rest of the API's conventions**, and that is deliberate: they follow OAuth's. Their responses use OAuth field names and OAuth's error shape, not the envelopes described in [*API fundamentals*](/developer-guide/fundamentals.md).

## What this covers

|               |                                                                                           |
| ------------- | ----------------------------------------------------------------------------------------- |
| **You get**   | The token lifecycle: obtain, inspect, validate, refresh, revoke, sign out                 |
| **You need**  | A registered OAuth client — [*API fundamentals*](/developer-guide/fundamentals.md) step 1 |
| **Base path** | `/idp/oauth2` for the token endpoints, `/idp/rest/api/auth` for session operations        |

## Before you start

* **A registered OAuth client**, and its id and secret in a credential store.
* **The identity provider's address.** These endpoints are served by the identity provider, not the console.

## Set these first

```bash
export OPENIAM_URL=<the address your deployment is published on>
export OPENIAM_CLIENT_ID=<your client id>
```

Read the secret from your credential store at the point of use.

***

## The token endpoints

### Obtain a token

```
POST /idp/oauth2/token
Content-Type: application/x-www-form-urlencoded
Authorization: Basic <base64 of clientId:clientSecret>

grant_type=client_credentials
```

`grant_type` is the only required parameter. The endpoint also accepts `client_id` and `client_secret` in the body for clients not using Basic authentication, `client_assertion` and `client_assertion_type` for assertion-based clients, and `redirect_uri` and `code` for the flows that use them.

**The content type matters.** This endpoint declares `Content-Type: application/x-www-form-urlencoded` and will not accept a JSON body.

**Response** — `OAuthTokenResponse`:

| Field                        | Is                                                                                |
| ---------------------------- | --------------------------------------------------------------------------------- |
| `access_token`               | The token to send as `Authorization: Bearer <token>`                              |
| `token_type`                 | The token's type                                                                  |
| `expires_in`                 | Seconds until it expires                                                          |
| `refresh_token`              | Present when the client is configured to issue one                                |
| `id_token`                   | Present for flows that produce one                                                |
| `error`, `error_description` | The OAuth-standard failure pair. **Present instead of a token, not alongside it** |

**Check for `access_token`, and treat its absence as a hard failure.** A response carrying `error` is still an HTTP `200` in some cases, so a client that assumes a token is present will pass a null onward and fail somewhere unrelated.

### Inspect a token

```
GET  /idp/oauth2/token/info?token=<token>
POST /idp/oauth2/token/info
```

`token` is required. **Response** — `OAuthTokenInfoResponse`:

| Field                        | Is                                                                                |
| ---------------------------- | --------------------------------------------------------------------------------- |
| `client_id`                  | The client the token was issued to                                                |
| `user_id`                    | The user it acts for, where it acts for one                                       |
| `expires_in`, `expires_on`   | Seconds remaining, and the absolute expiry                                        |
| `expired`                    | Whether the token itself has expired                                              |
| `session_expired`            | Whether the **session behind it** has expired. **These are two different things** |
| `scopes`                     | The scopes the token carries                                                      |
| `error`, `error_description` | On failure                                                                        |

**`expired` and `session_expired` are separate.** A token can be within its lifetime while the session it belongs to has ended. If your integration caches tokens, check both.

### Validate a token against a pattern

```
GET  /idp/oauth2/token/validate?token=<token>&patternId=<pattern>
POST /idp/oauth2/token/validate
```

`token` is required; `patternId` is optional and narrows the check to a specific URI pattern.

**Response** — `OAuthTokenValidateResponse`, carrying `code`, `scope`, `patternId`, `clientId`, `requesterId`, `expireIn`, and `error` with `error_description` on failure.

**Use `info` to find out about a token and `validate` to ask whether it is good for a particular path.**

### Refresh a token

```
GET  /idp/oauth2/token/refresh?refresh_token=<refresh token>
POST /idp/oauth2/token/refresh
```

`refresh_token` is required. **Response** — `OAuthTokenResponse`, the same shape as the token endpoint.

**An unknown or expired refresh token comes back as `error: invalid_request`** with an `error_description` saying the refresh token is not valid — not as an HTTP failure. Branch on the absence of `access_token`.

**Refreshing needs the client to have been configured to issue refresh tokens.** If `refresh_token` was absent from the original response, this endpoint has nothing to work with.

**A refreshed token carries the scopes the user consented to, not everything the client is configured with.** From 2026.10.2 a client on the authorization-code or Proof Key for Code Exchange (PKCE) flow gets back the scopes the user accepted on the authorization page and is still entitled to; a consented scope the user has since lost the entitlement to is dropped, and the consent to it is removed. Resource-owner, client-credentials and Health Level Seven (HL7) clients collect no consent, so their refreshed tokens keep receiving the scopes configured on the client — every one of them for client-credentials and HL7, and the ones the user is entitled to for resource-owner. Before 2026.10.2 a refreshed token received the configured client scopes the user was entitled to whatever the flow, so a scope the user had declined on the consent page came back on the first refresh.

### Revoke a token

**Two endpoints, and they are not interchangeable.**

```
POST /idp/oauth2/token/revoke
     token=<token>&token_type_hint=<hint>
```

**The token revocation endpoint.** One token per call, with an optional type hint. This is the one an integration should use to invalidate a token it holds.

```
GET  /idp/oauth2/revoke?token=<token>&token=<another>
POST /idp/oauth2/revoke
```

**The sign-out endpoint.** It accepts **several tokens in one call**, and additionally `id_token_hint`, `state` and `post_logout_redirect_uri` — the parameters of a provider-initiated sign-out rather than of a token revocation. Use it when ending a user's session across clients; use `/token/revoke` when discarding a single token.

**Revoke tokens your integration no longer needs.** A short expiry is not a substitute — a leaked token is valid until it expires or is revoked.

### End a session

```
GET /idp/rest/api/auth/logout
Cookie: OPENIAM_AUTH_TOKEN=<token>
```

Deletes the session associated with the token. **The token goes in the cookie, not in the `Authorization` header** — the cookie name is `OPENIAM_AUTH_TOKEN` unless your deployment has been configured to use another.

Two further session operations exist on the same base path — `renewToken` and `renewTokenWithString` — which extend an existing session rather than issuing a new token. **They are used by the interfaces themselves**; a server-to-server integration should obtain and refresh tokens through the OAuth endpoints above.

***

## Verify it

Obtain, inspect, then revoke:

```bash
TOKEN=$(curl -s -X POST "https://$OPENIAM_URL/idp/oauth2/token" \
  -u "$OPENIAM_CLIENT_ID:$OPENIAM_CLIENT_SECRET" \
  -d 'grant_type=client_credentials' | jq -r '.access_token // empty')
[ -n "$TOKEN" ] || { echo "TOKEN REQUEST FAILED"; exit 1; }

curl -s "https://$OPENIAM_URL/idp/oauth2/token/info?token=$TOKEN" \
  | jq '{client_id, expires_in, expired, session_expired, scopes: (.scopes | length)}'

curl -s -X POST "https://$OPENIAM_URL/idp/oauth2/token/revoke" \
  -d "token=$TOKEN" -o /dev/null -w 'revoke: %{http_code}\n'

curl -s "https://$OPENIAM_URL/idp/oauth2/token/info?token=$TOKEN" | jq '{expired, error}'
```

**Expected:** the client id and a positive `expires_in` first, then a successful revoke, then a response showing the token is no longer usable.

**Do not run this against a token another process is using.** Revocation is immediate.

## If it goes wrong

| What you see                                 | What it means                                                                      | What to do                                                                                  |
| -------------------------------------------- | ---------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------- |
| `error: invalid_client`                      | The client id or secret is wrong, or Basic authentication is not encoded correctly | Confirm the pair and the header encoding                                                    |
| `error: unsupported_grant_type`              | The provider is not configured for the grant you asked for                         | Set the grant on the provider to `Client Credentials` for a server-to-server client         |
| The token endpoint rejects your request      | A JSON body was sent                                                               | This endpoint takes `application/x-www-form-urlencoded`                                     |
| `access_token` is null and there is no error | The response was not the one you expected                                          | Print the whole body; the token endpoints do not use the envelopes the rest of the API uses |
| `refresh_token` is absent from the response  | The client is not configured to issue one                                          | Enable refresh tokens on the provider                                                       |
| `error: invalid_request` from refresh        | The refresh token is unknown or expired                                            | Obtain a new token from the token endpoint                                                  |
| `expired` is false but calls return `401`    | `session_expired` is true — the session ended although the token has not           | Obtain a new token                                                                          |
| Calls return `403` with a valid token        | The token's scopes do not cover the path                                           | Widen the provider's scopes to what is needed                                               |

## What this procedure does not cover

The interactive OAuth and OpenID Connect flows — authorization code, implicit and their variants — which involve a browser and are the identity provider's own subject rather than an integration's. Federation, and the FIDO, one-time-password and challenge-response endpoints.

## Related

* [*API fundamentals*](/developer-guide/fundamentals.md) — registering a client, and the response envelopes
* [*Your first calls*](/developer-guide/your-first-calls.md)
* [*Managing users*](/developer-guide/user-lifecycle.md)
* [*Connect AI assistants through the MCP server*](/administration-guide/federation/mcp-server.md) — tokens issued to an MCP client carry only the `openiam:mcp:*` scopes, so they are refused on the REST API, and API tokens are refused by the MCP server

*Applies to OpenIAM 2026.8.1.*


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## Querying This Documentation
If you need additional information that is not directly available in this page, you can query the documentation by asking a question.

Perform an HTTP GET request on the following URL with the `ask` and `goal` query parameters:

```
GET https://docs.openiam.com/developer-guide/tokens.md?ask=<question>&goal=<user_goal>
```

`ask` is the immediate question: it should be specific, self-contained, and written in natural language.
`goal` is what the user is ultimately trying to achieve, the reason they need the answer. Sharing it helps GitBook give you a better, more relevant answer. A goal is most helpful when it describes the outcome the user wants rather than restating the question. For example, with `ask=how do I create an API token`, a goal like `automate deployments from our CI pipeline` lets GitBook tailor the answer to that use case.

The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
