/token

Exchange an authorization code, device code, refresh bundle, or client credentials for tokens.

Endpoint

  • Method: POST
  • URL: https://sso.user.m7.org/token
  • Content type: application/x-www-form-urlencoded
  • Response: JSON

Supported grant types currently advertised by discovery:

  • authorization_code
  • refresh_token
  • client_credentials
  • urn:ietf:params:oauth:grant-type:device_code

The current public examples below use device_code in the request body for device-code pickup.

Common response fields

Token responses vary by grant type, but current successful responses may include:

  • token_type
  • access_token
  • expires_in
  • refresh_token
  • refresh_expires_in
  • binding_chain
  • binding_link
  • scope
  • id_token

When openid is in scope on supported end-user flows, the response may include id_token.

authorization_code

Use this after the browser returns code to your redirect_uri.

Required form fields:

  • grant_type=authorization_code
  • code
  • redirect_uri
  • code_verifier

Client authentication:

  • public clients typically send client_id
  • confidential clients use their configured token endpoint auth method

Optional:

  • fingerprint
  • DPoP header when your client uses DPoP

Example:

curl -sS https://sso.user.m7.org/token \
  -d grant_type='authorization_code' \
  -d code='AUTHORIZATION_CODE' \
  -d redirect_uri='https://app.example.com/callback' \
  -d code_verifier='PKCE_CODE_VERIFIER' \
  -d client_id='CLIENT_ID'

refresh_token

Use this to rotate a refresh bundle and obtain a new access token.

Current M7 request shape:

  • grant_type=refresh_token
  • token=<REFRESH_TOKEN>
  • binding_chain
  • binding_link

Client authentication:

  • required or omitted according to the client's configured token endpoint auth method

Optional:

  • fingerprint
  • new_fingerprint

Example:

curl -sS https://sso.user.m7.org/token \
  -d grant_type='refresh_token' \
  -d token='REFRESH_TOKEN' \
  -d binding_chain='BINDING_CHAIN' \
  -d binding_link='BINDING_LINK' \
  -d client_id='CLIENT_ID' \
  -d client_secret='CLIENT_SECRET'

Important note:

  • The current refresh request uses token, not refresh_token, in the form body.

client_credentials

Use this for machine-to-machine access.

Required form fields:

  • grant_type=client_credentials
  • aud

Optional:

  • scope
  • access_expires
  • claims
  • fingerprint
  • label

Client authentication:

  • confidential clients use client_secret_basic or client_secret_post
  • public clients are not the normal shape for this grant

Example:

curl -sS https://sso.user.m7.org/token \
  -u 'CLIENT_ID:CLIENT_SECRET' \
  -d grant_type='client_credentials' \
  -d aud='https://user.m7.org/api/v2/' \
  -d scope='file.read file.write'

Expected response shape:

{
  "token_type": "bearer",
  "access_token": "ACCESS_TOKEN",
  "expires_in": 900,
  "scope": "file.read file.write"
}

device_code

Use this after the user approves a device authorization on /device_login.

Required form fields:

  • grant_type=device_code
  • device_code

Client authentication:

  • confidential clients use their configured token auth method
  • public clients send client_id

Example:

curl -sS https://sso.user.m7.org/token \
  -d grant_type='device_code' \
  -d device_code='DEVICE_CODE' \
  -d client_id='CLIENT_ID'

Typical success response:

{
  "token_type": "bearer",
  "access_token": "ACCESS_TOKEN",
  "expires_in": 900,
  "refresh_token": "REFRESH_TOKEN",
  "refresh_expires_in": 2592000,
  "binding_chain": "BINDING_CHAIN",
  "binding_link": "BINDING_LINK",
  "scope": "openid profile offline_access"
}

Common errors

  • invalid_request
  • invalid_client
  • unauthorized_client
  • invalid_grant
  • unsupported_grant_type
  • authorization_pending

Notes

  • client_credentials returns access-token material only.
  • Device-code pickup is one-time. After a successful pickup, the same device_code should not be reused.
  • Refresh-token rotation should replace the previously stored refresh bundle with the newly returned bundle.
  • When binding_chain and binding_link are returned, store them with the refresh token. They are part of the current refresh request contract.