Skip to main content
Use the OAuth 2.1 Authorization Code flow with PKCE when a browser-based MCP client (Cursor, Claude, and the like) connects to a paid Nevermined agent and the user approves in a browser. It’s the browser-based counterpart to the no-browser device flow — the two grants differ only in how the human approves.
Which credential you receive is chosen by the requested resource, not by the grant (details). To obtain an x402 payment permission — the credential you attach to x402 requests against the agent — request that agent’s resource. If you omit resource and send only a plan_id (a plan-only binding with no delegation), the exchange returns an NVM API key instead. A resource matching the Nevermined API host also returns an API key.

Prerequisites

  • An authenticated user. POST /oauth/authorize runs in the signed-in user’s context — the browser client calls it with the user’s Nevermined API key. Without an authenticated user it fails with BCK.OAUTH.0007.
  • A pre-registered client_id. Same as the device flow — connectors are onboarded out of band (no dynamic client registration). An unregistered client_id is rejected with BCK.OAUTH.0016.
  • PKCE is mandatory. Generate a code_verifier and its code_challenge (S256). There is no non-PKCE path.
  • The right tier’s authorize URL. The browser step starts at the authorization_endpoint of the tier you are integrating with — https://nevermined.app/oauth/authorize?network=sandbox or …?network=live — as advertised by that tier’s RFC 8414 document. This is the web app’s consent page, the URL a connector sends the user’s browser to with client_id, redirect_uri, code_challenge and the rest in its query string — keep the network parameter when you add them; it is what makes the consent screen open on the tier your client_id is registered on. It is not the API’s POST /oauth/authorize in Step 1 below, which the signed-in browser client calls on the API host with those same fields in a JSON body (and no network — by then the tier is fixed). The consent URL is the only one of the two an integrator ever configures by hand; if you do, paste it with its query string.

The flow

1

Request an authorization code

From the signed-in browser session, POST to the authorization endpoint with your PKCE challenge. The request is authenticated as the user (their Nevermined API key):
client_id, redirect_uri, code_challenge, and code_challenge_method are required; agent_id, plan_id, state, and resource are optional. resource is optional but load-bearing: the agent’s resource here is what makes the exchange mint an x402 payment permission rather than an API key. Once the user approves the connection, the endpoint returns the code in the response body:
Your client then completes the OAuth hand-off by redirecting the browser to redirect_uri with the code (and your state) appended.
2

Exchange the code for the credential

Prove possession of the PKCE verifier and exchange the code:
Response:
3

Pay the agent

Because you requested the agent’s resource, the access_token is an x402 payment permission for the agent+plan you were authorized against (unlike an NVM API key, it is a JWT and is not environment-prefixed). Use it on your x402 requests to that agent. See the MCP integration for how a paywalled MCP server consumes it.
The connection the user approves is recorded as an AgentBinding — the consent receipt. The client_id is the connector fronting the connection (Cursor, Claude, LangSmith Fleet), which is distinct from the agent the consent is for. Users can review and revoke these at any time from the Connected agents surface.
Both the authorization_endpoint and the authorization_code grant are advertised in the RFC 8414 metadata — per tier, with the tier on the endpoint (?network=sandbox|live). As with the device flow, discovery doesn’t substitute for a pre-registered client_id; and a client_id is registered per tier, so a connector known to Sandbox is “not authorized” on Live and vice versa.
Two grants ask for authority over the user’s whole account rather than one agent, and both omit agent_id: A connector is authorized for each of these separately when it is registered. Being enabled for commerce does not enable account_access, and vice versa — an assistant that pays external services from the user’s wallet never gains the ability to request a card mandate by asking for the other consent type. Registration carries an accountLevelConsentTypes list (["commerce"], ["account_access"], or both); a connector registered before the list existed keeps both. Requesting a consent type your connector is not authorized for is refused with BCK.OAUTH.0035 (403), whose params.consentType names the refused type — distinct from BCK.OAUTH.0018 (your connector cannot request account-level authority at all, so agent_id is required) and BCK.OAUTH.0025 (it can, but is not yet verified). The consent page checks this before it renders the spend form and tells the user which consent type was refused, so the human is never walked through a mandate the API would reject at approval. Commerce-only connector. The typical shape for an AI assistant that only needs to pay external services: registered with accountLevelConsentTypes: ["commerce"], it starts the consent at the tier’s authorization_endpoint with consent_type=commerce (and the commerce_* spend-cap fields — no card, no agent_id). A request from that same connector with neither consent_type=commerce nor an agent_id is an account_access request, and is refused with BCK.OAUTH.0035; its agent-specific grants (an agent_id plus plan_id) are unaffected.