Token endpoint

POST https://sso.user.m7.org/token
Content-Type: application/x-www-form-urlencoded

Use the token endpoint to exchange authorization codes and device codes, refresh an existing package, or obtain a service token with client_credentials. It accepts only POST.

Client authentication is always tied to the OAuth client's configured token_endpoint_auth_method. A client must also be enabled for the requested grant type.

Client authentication

Method Request contract
none Send client_id in the form body. Suitable for public clients. It cannot use client_credentials.
client_secret_basic Send HTTP Authorization: Basic base64(client_id:client_secret).
client_secret_post Send client_id and client_secret in the form body.
client_secret_jwt Send client_id, client_assertion_type=urn:ietf:params:oauth:client-assertion-type:jwt-bearer, and a signed client_assertion.
private_key_jwt Send the same assertion fields. The client must have an inline jwks or HTTPS jwks_uri configured.

For JWT client assertions, iss and sub must equal the client_id, aud must equal the exact token endpoint URL, and the assertion must contain a current iat, unexpired exp, and unique jti. M7 rejects replayed and stale assertions. Supported private_key_jwt signing algorithms are RSA (RS256, RS384, RS512) and ES256; configure the intended algorithm on the client. client_secret_jwt uses a configured HMAC algorithm: HS256, HS384, or HS512.

Authorization-code exchange

Send these form fields after receiving a code at the registered callback:

Field Required Notes
grant_type Yes authorization_code
code Yes The authorization code from /authorize. It is single-use.
redirect_uri Yes Exactly the same registered URI used in the authorization request.
code_verifier Yes The original PKCE verifier. M7 verifies its SHA-256 challenge.
fingerprint No A 64-character hexadecimal M7 credential binding, when the client integration uses one.

Authenticate the client as described above. See the complete browser flow in Quickstart.

Refresh-token exchange

The normal refresh request sends the complete refresh package, not just the refresh token:

Field Required Notes
grant_type Yes refresh_token
refresh_token Yes The refresh credential. M7 also accepts the legacy alias token.
binding_chain Required for an M7 refresh package Save and send the value returned with the refresh token.
binding_link Required for an M7 refresh package Save and send the value returned with the refresh token.
fingerprint Conditional Required when the existing lineage is fingerprint-bound. Exactly 64 hexadecimal characters.
new_fingerprint No A replacement binding fingerprint. When fingerprint is supplied and new_fingerprint is omitted, M7 keeps the same value.
access_expires No Requests the next access-token lifetime when allowed by policy.
access_claims No Requests allowed access-token claims when allowed by policy.
refresh_mode No M7 lifecycle extension; normally omit it. See below.

The refresh token must belong to the authenticated client_id. Persist the full replacement package before removing the old package. If the requested refresh cannot proceed, keep the prior package unless the service has returned a confirmed replacement or terminal error that makes it unusable.

Refresh lifecycle extension

Without refresh_mode, M7 currently uses strict_rotation. Supported modes are static, strict_rotation, grace_rotation, ack, and ack_supersede_pending.

Most applications should omit this parameter. ACK modes require durable, atomic persistence and a recovery procedure because the returned package is initially pending. See Token acknowledgement.

Client credentials

Use this grant for a machine application acting as itself, never as an end user. The client must be confidential and be enabled for client_credentials.

Field Required Notes
grant_type Yes client_credentials
aud Yes One requested audience or resource identifier.
scope No Requested space-delimited scope string, subject to client policy.
access_expires No Requested access lifetime. expires_in and expires are accepted aliases.
claims No Requested JSON claims object. payload is an accepted alias.
fingerprint No 64-character hexadecimal M7 binding.
label No A management label for the issued record.

Example:

curl --fail-with-body --silent --show-error \
  --request POST 'https://sso.user.m7.org/token' \
  --user 'CLIENT_ID:CLIENT_SECRET' \
  --data-urlencode 'grant_type=client_credentials' \
  --data-urlencode 'aud=https://api.example.com' \
  --data-urlencode 'scope=file.read file.write' \
  --data-urlencode 'access_expires=900'

This flow returns an access token only; it does not issue a refresh token. A client using token_endpoint_auth_method=none is rejected for this grant.

Device-code exchange

Use this only after obtaining device_code from /device_authorization:

Field Required Notes
grant_type Yes device_code. The standard urn:ietf:params:oauth:grant-type:device_code identifier is also accepted.
device_code Yes Secret device code returned by /device_authorization.

Authenticate the same client that initiated the device flow. If the user has not approved yet, the response is authorization_pending. Observe the response's interval; do not poll more frequently. A device code is consumed after successful pickup.

Token responses

The response varies by grant and client policy. A user-authorized success typically contains:

{
  "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",
  "id_token": "ID_TOKEN"
}

id_token is returned when openid was granted. An access token from client_credentials does not have a user refresh package. Treat every token and every binding or activation field as a secret.

The response above is the OAuth token-package envelope, not the decoded access token payload. See Access-token formats for the consumer, tenant-member, and OAuth-application JWT claim profiles.

Current examples use token_type: "bearer", which is the supported public authorization profile. Do not opt a new integration into DPoP based on accepted headers or token confirmation data. An already provisioned DPoP-bound lineage must follow its compatibility profile; see DPoP status.

Errors

Errors use OAuth JSON fields such as invalid_request, invalid_client, invalid_grant, unauthorized_client, invalid_scope, and unsupported_grant_type. Do not branch on prose in error_description.