Skip to main content
NextLevel MCA runs its own OAuth 2.1 authorization server at https://api.nextlevelmca.com. MCP clients discover it, register themselves, and send users through a magic-link sign-in before any token is issued. This page is the reference for what the server does; you do not need to implement any of it to use Claude or ChatGPT.

Discovery

An unauthenticated request to /mcp returns 401 with WWW-Authenticate: Bearer resource_metadata="https://api.nextlevelmca.com/mcp/.well-known/oauth-protected-resource", which is how clients find the authorization server without configuration.
GET /.well-known/oauth-protected-resource
GET /.well-known/oauth-authorization-server

Dynamic client registration

POST /oauth/register (RFC 7591) is open, as MCP expects. Send client_name, redirect_uris and optionally logo_uri, client_uri, grant_types and token_endpoint_auth_method. Redirect URIs must be absolute https URLs (http://localhost and http://127.0.0.1 on any port are allowed for local clients), with no wildcards or fragments. The response is 201 with a client_id; clients are public (token_endpoint_auth_method: "none"), there is no client secret. Registration is limited to 10 per hour per IP address, and clients unused for 90 days are removed.

Authorization flow

  1. Authorize. GET /oauth/authorize with response_type=code, client_id, redirect_uri, scope, state, code_challenge, code_challenge_method=S256 and optionally resource. client_id and redirect_uri are validated first; on failure the server renders an error page and never redirects to an unvalidated URI. Only PKCE S256 is accepted; plain is rejected. Request parameters are stored server-side under an opaque request id for 30 minutes, not carried in URLs.
  2. Sign in. The user enters their email. If it belongs to an active user, a magic link is sent; the page says “check your email” either way, so addresses cannot be enumerated. Links are single use, expire after 10 minutes, and are limited to 3 per email per 15 minutes. Opening one sets a signed, HttpOnly, Secure, SameSite=Lax session cookie that lives 30 minutes.
  3. Location. Users who belong to several locations pick one; users with one location skip this. The grant is pinned to that location.
  4. Consent. The user sees the client, the location and each scope in plain words, then approves or denies. An existing, unrevoked consent for the same client, location and scopes skips this step.
  5. Code. The server issues an authorization code bound to the client, redirect URI, scopes, location and PKCE challenge. Codes expire after 60 seconds and are single use; presenting a consumed code revokes everything issued from it.
  6. Token. POST /oauth/token (JSON or form-encoded) with grant_type=authorization_code, code, redirect_uri, client_id and code_verifier returns an access token, a refresh token, expires_in: 3600 and the granted scope. Limited to 60 requests per minute per IP address.
If the client asks for no scopes (Claude and ChatGPT connectors don’t send any), the consent screen offers every scope, each with a checkbox ticked by default. Untick what the connection should not be able to do; the token only ever carries the scopes you left ticked, and a client that later asks for more shows the consent screen again.

Tokens

Access tokens are RS256-signed JWTs, valid for 1 hour, verified against the JWKS by kid. Claims: iss (https://api.nextlevelmca.com), sub (the user id), aud (the resource requested, by default https://api.nextlevelmca.com/mcp), location_id, scope, client_id, iat, exp and jti. The MCP server only accepts tokens whose aud is its own URL, so a token cannot be replayed against another service; /v1 accepts tokens minted for either the MCP server or the API. Refresh tokens are opaque, stored hashed, and rotate on every use: each refresh returns a new refresh token in the same family and revokes the old one. Presenting an already-rotated token is treated as theft and revokes the whole family. Lifetime is 30 days, sliding on each rotation, with an absolute cap of 90 days from the first grant. Every token, code and magic link is stored as a SHA-256 hash; raw values are never persisted.

Scopes and roles

Two independent checks apply to every tool call and API request:
  • Scopes limit breadth. They are the resources the connection may touch, chosen at consent. A tool without its scope returns a clear error naming the scope.
  • Role limits depth. The connection acts as the signed-in user, with that user’s role from the app: which deals they can see, whether they may withdraw a submission or accept an offer, whether dob is returned. Scopes can never grant more than the user’s role allows.
The full scope list with descriptions is under Authentication.

Tenant isolation

A token carries exactly one location_id, chosen at the location step. Every read and write is filtered by it; a record from another location does not exist as far as that token is concerned (it returns 404). On every request the server also checks that the user is still active in that location; a removed or deactivated user’s tokens stop working at once, regardless of their expiry.

Revoking access

  • Settings → Developers → Connected apps in the app lists each consent for the location with the user, client, scopes, when it was granted and last used. Revoke deletes the consent and every refresh-token family for that user, client and location. Outstanding access tokens expire within the hour and cannot be refreshed.
  • Clients can call POST /oauth/revoke with a refresh token to revoke its family.
  • Removing the connector inside Claude or ChatGPT discards the client’s tokens but leaves the consent in place; revoke in the app as well when offboarding.
  • Deactivating the user in the app invalidates all of their tokens immediately.