---
title: "Token endpoint (pre-authorized_code grant)"
method: POST
path: "/v1/oauth/token"
tags: ["OpenID4VCI Protocol"]
---

# Token endpoint (pre-authorized_code grant)

`POST /v1/oauth/token`

OpenID4VCI token endpoint. Implements the pre-authorized_code grant only: the wallet exchanges the `pre-authorized_code` from a credential offer (and the transaction code if the offer required one) for a short-lived Bearer access token used at the credential endpoint. Public and unauthenticated (the pre-authorized_code itself is the capability). The token is single-use and expires in 300 seconds. tx_code is brute-force protected — after 5 failed attempts the offer locks out. The grant fails (HTTP 400 `invalid_grant`) if the code is unknown, already redeemed, expired, or the tx_code is wrong; an unsupported `grant_type` returns 400 `unsupported_grant_type`.

## Request body

- object
  - `grant_type` string, required — OAuth grant type. Must be exactly `urn:ietf:params:oauth:grant-type:pre-authorized_code` — the only grant the token endpoint supports. Any other value returns 400 `unsupported_grant_type`.
  - `pre-authorized_code` string, required — The pre-authorized code issued with the credential offer (the `pre_authorized_code` field of POST /v1/credential-offers, also present in the offer's `grants` object). The endpoint also accepts the snake_case alias `pre_authorized_code`. Identifies the offer being redeemed.
  - `tx_code` string — The transaction code (PIN) for offers created with `tx_code: true`. A 6-digit numeric string communicated to the holder out-of-band. Omit (or send empty) for offers that have no tx_code. After 5 wrong attempts the offer is locked.

## Response `200`

Access token issued.

- object
  - `access_token` string — Opaque Bearer access token. Send it as `Authorization: Bearer <access_token>` to POST /v1/credential. Single-use and valid for `expires_in` seconds.
  - `token_type` string — Token type. Always the literal `bearer`.
  - `expires_in` integer — Access token lifetime in seconds. Always 300 (5 minutes).

## Other responses

- `400` — Grant rejected — unsupported grant_type, or an invalid/expired/already-redeemed pre-authorized_code, or a wrong/exceeded tx_code.

## Changes

- **2026-06-30** `1ee4908e9af4` — 1 breaking, 1 info
  - request body became required
  - added the non-success response with the status `400`

[Change history](https://skmtc.dev/didit-protocol/apis/didit-credentials-api/changes/v1/oauth/token/post.md)

---

[API](https://skmtc.dev/didit-protocol/apis/didit-credentials-api.md) · [All operations](https://skmtc.dev/didit-protocol/apis/didit-credentials-api/llms.txt) · [OpenAPI document](https://skmtc-service-production.skmtc.workers.dev/v1/apis/didit-protocol/didit-credentials-api/revisions/2eeb13158ffa/schema)
