Skip to main content

ARC developer documentation

Credentials without a browser

Use the device grant when the client has no browser to redirect to: request, have someone approve, poll, and receive a blocklet- access key.

The authorization code grant needs a browser to redirect to and a local port to redirect back to. A client with neither, such as a CLI on a server, a CI job, or an agent on a headless machine, uses the device grant instead.

It produces the same credential as the browser grant: a blocklet- prefixed access key, sent as a bearer token. Replace <host> with the blocklet's host.

1. Start a device authorization

bash
curl -s -X POST https://<host>/.well-known/service/oauth/device_authorization \
  -H 'Content-Type: application/x-www-form-urlencoded' \
  -d 'client_id=<client_id>&scope=mcp'
json
{
  "device_code": "bc0cedc2-899f-49da-b413-213f31c4aae8",
  "user_code": "BZVR-ZQZQ",
  "verification_uri": "https://<host>/.well-known/service/gen-access-key",
  "verification_uri_complete": "https://<host>/.well-known/service/gen-access-key?__token__=bc0cedc2-…",
  "expires_in": 300,
  "interval": 5
}

Obtain the client_id by registering first. See Authorize a client.

FieldUse
user_codeShow it to the person. The alphabet excludes vowels and ambiguous characters, so it can be read aloud
verification_uriWhere the person goes to approve
verification_uri_completeThe same page with the request pre-filled; use it when you can render a link or a QR code
expires_inSeconds until this request expires. 300 above. It does not describe the credential
intervalMinimum seconds between polls. 5 above

2. The person approves

Opening verification_uri_complete shows a consent screen naming the requesting application and the account, with an access level selector. It offers owner (the default), admin, member, and guest; the credential carries whichever is chosen.

The role is scoped to this blocklet instance and is an upper bound, not a grant. See Access tiers.

3. Poll for the credential

Poll the token endpoint with the device code grant until the request is approved or expires. Respect interval; polling faster is what the field exists to prevent.

bash
curl -s -X POST https://<host>/.well-known/service/oauth/token \
  -H 'Content-Type: application/x-www-form-urlencoded' \
  --data-urlencode 'grant_type=urn:ietf:params:oauth:grant-type:device_code' \
  --data-urlencode 'device_code=bc0cedc2-899f-49da-b413-213f31c4aae8' \
  --data-urlencode 'client_id=<client_id>'

Once approved:

json
{
  "access_token": "blocklet-zEJYCdC9awCqxEPLhqreFbwXjqU6Y2BHvKVQAx6kMZfRh",
  "token_type": "Bearer",
  "scope": "mcp"
}

Before approval, and after the five-minute window closes, the same request answers 400:

json
{"error":"invalid_grant","error_description":"Invalid or expired device_code"}

invalid_grant most often means the window expired rather than that the code was wrong. Restart from step 1.

No expires_in, no refresh_token. The authorization server advertises refresh_token as a supported grant, but this flow does not return one. Treat the access key as the credential you keep.

4. Use it

bash
curl -s -X POST https://<host>/mcp \
  -H 'Content-Type: application/json' \
  -H 'Accept: application/json, text/event-stream' \
  -H 'Authorization: Bearer blocklet-…' \
  -d '{"jsonrpc":"2.0","id":1,"method":"tools/list"}'

The credential clears the credential gate. It does not clear the path policy: an authenticated write can still return AFS_FORBIDDEN. See Authorize a client.

Which grant to use

SituationGrant
Client can open a browser and listen on a local portauthorization_code with PKCE. See Authorize a client
Client has no browser, or the person approving is on a different deviceDevice grant, this page

MCP clients generally implement the authorization code grant, not the device grant. Use the device grant for your own tooling rather than expecting a third-party MCP client to discover and drive it.