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/authorizeruns 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 withBCK.OAUTH.0007. - A pre-registered
client_id. Same as the device flow — connectors are onboarded out of band (no dynamic client registration). An unregisteredclient_idis rejected withBCK.OAUTH.0016. - PKCE is mandatory. Generate a
code_verifierand itscode_challenge(S256). There is no non-PKCE path. - The right tier’s authorize URL. The browser step starts at the
authorization_endpointof the tier you are integrating with —https://nevermined.app/oauth/authorize?network=sandboxor…?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 withclient_id,redirect_uri,code_challengeand the rest in its query string — keep thenetworkparameter when you add them; it is what makes the consent screen open on the tier yourclient_idis registered on. It is not the API’sPOST /oauth/authorizein Step 1 below, which the signed-in browser client calls on the API host with those same fields in a JSON body (and nonetwork— 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):Your client then completes the OAuth hand-off by redirecting the browser to
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: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.Connectors and consent
The connection the user approves is recorded as an AgentBinding — the consent receipt. Theclient_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.Account-level grants are authorized per consent type
Two grants ask for authority over the user’s whole account rather than one agent, and both omitagent_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.