Revocation and one consent

APIBreaking

Servers that act for many people get the standard OAuth pieces they expect: revocation, one consent for both resources, discovery that names the right resource, and a challenge header on a refused person credential.

What changed

  • Token revocation. POST /v1/oauth/revoke takes a refresh token or an access token with your client_id and ends that person's connection to your client, on the API and on MCP. An expired access token still works for this. It is advertised as revocation_endpoint in the authorization server metadata.
  • One authorization for both resources. Send resource=https://api.layout.link and resource=https://mcp.layout.link on one authorize request. The person approves once, one refresh token covers both, and each token request names the one resource its access token is for. Keep one refresh token for the person, not one per resource, and refresh one at a time.
  • Discovery names the API. https://api.layout.link/.well-known/oauth-protected-resource now describes the REST API, with "resource": "https://api.layout.link". MCP clients keep reading https://mcp.layout.link/.well-known/oauth-protected-resource, which has not changed.
  • A real WWW-Authenticate on API refusals. A 401 from an endpoint that takes a person’s credential (places, menus, images, carts, orders, profile and whoami) now carries WWW-Authenticate itself, with resource_metadata pointing at the API's metadata. Endpoints that take only your application secret answer 401 without it.
  • MCP refusals use the API's error envelope. A 401, 403 or 503 from mcp.layout.link, before the request reaches the protocol, is now { "error": { "code", "message" } }. The WWW-Authenticate challenge is unchanged.
  • A retried refresh keeps the connection. Retrying a refresh within 20 seconds, for instance after its response was lost, now returns a new pair instead of ending the connection.
  • A disconnect stops MCP tokens at once. When a person disconnects your app, you revoke their connection, or you delete the client, an MCP access token already issued is refused on its next request, as on the API, instead of working until it expires.
curl https://api.layout.link/v1/oauth/revoke \
  -d token=$REFRESH_TOKEN \
  -d token_type_hint=refresh_token \
  -d client_id=$CLIENT_ID

200 OK

Why it matters

When someone signs out of your product, you can end their Layout connection from your server instead of asking them to disconnect in Layout. A server that uses both the API and MCP no longer needs two consent screens. And a standard OAuth client library can now discover the API and read its challenges without special cases.

What to do

Nothing is required. To revoke on sign-out, or to ask for both resources at once, see Revoking a connection and One authorization for both. Refresh lifetimes and rotation are spelled out in Tokens and refresh.

Breaking changes

  • The body of an MCP 401 was { "error": "unauthenticated" } and is now { "error": { "code": "unauthorized", "message": "…" } }. If your code reads that body, switch on error.code. MCP clients that follow the WWW-Authenticate header are unaffected.
  • The API's protected resource metadata no longer names https://mcp.layout.link. If you read MCP's metadata from the API's origin, read it from https://mcp.layout.link instead.
  • An MCP access token for a disconnected person or a deleted client is now refused on its next request rather than up to ten minutes later.

All changes