---
title: "Exchange a device code, authorization code or refresh token for tokens"
method: POST
path: "/oauth/token"
tags: ["OAuth"]
---

# Exchange a device code, authorization code or refresh token for tokens

`POST /oauth/token`

## Request

- Base URL: `https://api.groundmail.dev`
- URL: `https://api.groundmail.dev/oauth/token`
- Auth: none required

`grant_type=urn:ietf:params:oauth:grant-type:device_code` polls a device sign-in: `authorization_pending` until approved, `slow_down` when polled faster than `interval` (add 5 seconds), `access_denied` when canceled, `expired_token` after ten minutes. `grant_type=refresh_token` rotates the refresh token: each one works once, and presenting a used one revokes the whole sign-in. Pass `scope` to narrow the new access token. Access tokens last 3600 seconds; refresh tokens expire after 90 days without use. `authorization_code` (PKCE S256 required) serves the hosted MCP server; the tokens carry the scopes approved on the consent page, and a code presented twice revokes what it was exchanged for. Clients registered with a secret authenticate with `client_secret` (body or HTTP Basic); public clients send only `client_id`. A `resource` (RFC 8707) must be the one that was authorized.

## Request body

- OAuthTokenRequest
  - `grant_type` 'urn:ietf:params:oauth:grant-type:device_code' | 'refresh_token' | 'authorization_code', required
  - `client_id` string
  - `device_code` string
  - `refresh_token` string
  - `client_secret` string: Confidential clients only (`client_secret_post`); or send HTTP Basic
  - `scope` string: With refresh_token, narrows the new access token (never widens it)
  - `resource` string: RFC 8707 resource indicator. Optional; when sent it must be the resource that was authorized (`https://mcp.groundmail.dev/mcp`, or its origin), else `invalid_target`
  - `code` string
  - `redirect_uri` string
  - `code_verifier` string

## Response `200`

Tokens

- OAuthTokenResponse
  - `access_token` string, required: Bearer token for the API (`gm_oat_…`)
  - `token_type` 'Bearer', required
  - `expires_in` integer, required
  - `refresh_token` string, required: Single-use; store the new one each time
  - `scope` string, required
  - `workspace` object: Device grant only
    - `id` string
    - `name` string
  - `user` object: Device grant only
    - `id` string
    - `email` string

## Other responses

- `400`: `authorization_pending`, `slow_down`, `access_denied`, `expired_token`, `invalid_grant`, `invalid_scope`, `invalid_target`, `invalid_request` or `unsupported_grant_type`
- `401`: `invalid_client`: unknown client, or a confidential client without a valid `client_secret`
- `429`: Rate limited (`Retry-After`)

---

[API](https://skmtc.dev/groundmail/apis/groundmail-api.md) · [All operations](https://skmtc.dev/groundmail/apis/groundmail-api/llms.txt) · [OpenAPI document](https://skmtc.dev/groundmail/apis/groundmail-api/revisions/479821c9fecf?raw)
