Skip to main content
Headless agents — CI runners, containerised workers, server-side pipelines — cannot open a browser to complete the authorization-code + PKCE flow. The OAuth 2.0 device-authorization grant (RFC 8628) is the correct mechanism for these callers: the agent requests a short user_code, the human operator approves it in a browser, and the agent polls the token endpoint until it receives an access token. For a full comparison of all supported authentication methods, see Authentication.

Flow overview

1

Request device and user codes

The agent sends POST /v1/oauth/device_authorization and receives a device_code, user_code, verification_uri, expires_in, and interval.
2

Display the code to the operator

The agent displays the user_code and verification_uri to the human operator — log them to a console, embed in a CLI prompt, or generate a QR code from verification_uri_complete.
3

Operator approves in a browser

The operator opens verification_uri, enters the user_code, and approves access. No action is required from the agent during this step.
4

Agent polls for the access token

The agent polls POST /v1/oauth/token with grant_type=urn:ietf:params:oauth:grant-type:device_code at the prescribed interval until it receives an access token or a terminal error.

Device-authorization request

POST /v1/oauth/device_authorization HTTP/1.1
Host: api.mnemom.ai
Content-Type: application/x-www-form-urlencoded

client_id=<your_client_id>&scope=api:read%20api:write
Successful response (HTTP 200):
{
  "device_code": "GmRh...opaque",
  "user_code": "WDJB-MJHT",
  "verification_uri": "https://api.mnemom.ai/v1/oauth/device",
  "verification_uri_complete": "https://api.mnemom.ai/v1/oauth/device?user_code=WDJB-MJHT",
  "expires_in": 900,
  "interval": 5
}
FieldTypeDescription
device_codestringOpaque code the agent presents when polling the token endpoint. Keep this secret.
user_codestringShort, human-typeable code the operator enters at verification_uri. Display this to your operator.
verification_uristringURL the operator must visit to approve the request.
verification_uri_completestringverification_uri with user_code pre-filled as a query parameter — use this for QR codes or one-click links where possible.
expires_inintegerSeconds until both codes expire. The agent must complete the poll loop before this window closes.
intervalintegerMinimum seconds the agent must wait between poll attempts. Do not poll faster than this; the server enforces the limit.

Poll loop

After the device-authorization request succeeds, poll POST /v1/oauth/token at interval-second intervals. On each slow_down response, permanently increase the interval by 5 seconds for the remainder of the session. Stop when you receive an access token or a terminal error.
import time
import httpx

CLIENT_ID = "<your_client_id>"

def poll_for_token(device_code: str, interval: int, expires_in: int) -> dict:
    deadline = time.time() + expires_in
    while time.time() < deadline:
        time.sleep(interval)
        resp = httpx.post(
            "https://api.mnemom.ai/v1/oauth/token",
            data={
                "grant_type": "urn:ietf:params:oauth:grant-type:device_code",
                "device_code": device_code,
                "client_id": CLIENT_ID,
            },
        )
        body = resp.json()
        if resp.status_code == 200:
            return body  # access_token, token_type, expires_in, [refresh_token]
        error = body.get("error")
        if error == "authorization_pending":
            continue
        elif error == "slow_down":
            interval += 5  # back off permanently for this session
            continue
        elif error in ("expired_token", "access_denied"):
            raise RuntimeError(f"Device authorization failed: {error}")
        else:
            raise RuntimeError(f"Unexpected error: {error}")
    raise TimeoutError("Device code expired before authorization was granted")

Error codes

The following error codes may be returned by POST /v1/oauth/token during the poll loop:
error valueHTTP statusMeaningAction
authorization_pending400The operator has not yet approved the request.Continue polling after interval seconds.
slow_down400The agent is polling too fast.Increase interval by 5 seconds permanently and continue polling.
expired_token400The device_code has expired (expires_in elapsed).Start over: request a new device_code and prompt the operator again.
access_denied400The operator denied the request or cancelled it.Abort. Do not retry with the same device_code.
invalid_client401The client_id is unknown or invalid.Check your client registration.
Never poll faster than the current interval value. Each slow_down error permanently increases the required interval for that session. Repeated fast polling may result in the session being terminated with access_denied.

Token response

A successful poll returns HTTP 200 with a token response:
{
  "access_token": "mnm_act_…",
  "token_type": "Bearer",
  "expires_in": 1800,
  "refresh_token": "mnm_rft_…",
  "scope": "api:read api:write"
}
Use the access token as a Bearer token on subsequent API requests:
Authorization: Bearer <access_token>
The refresh token can be exchanged for a new access token via the standard refresh_token grant at POST /v1/oauth/token before the access token expires.

TTLs at a glance

ItemTTL
device_code / user_codeexpires_in seconds from the device-authorization response (typically 900 s / 15 min)
Access token1800 s (30 minutes)
Refresh token30 days; single-use — each exchange rotates it.

Client registration

If you do not yet have a client_id, register your OAuth client with POST /v1/oauth/register (RFC 7591 dynamic client registration) before starting the device flow. Public clients (no client secret) are supported. See the OAuth register endpoint.

See also

  • Authentication — full auth method comparison (passkeys, MFA, SSO, API keys)
  • API Keys — server-to-server auth without OAuth; simpler for non-delegated agent calls
  • API reference overview — auth header formats and rate limits