Skip to main content

Command Palette

Search for a command to run...

Cloud Agents

OIDC tokens

Cloud Agents can mint short-lived OIDC JWTs from inside the VM. Use those tokens to assume cloud roles or call internal services without storing long-lived credentials in Secrets.

This is a local token API on the agent VM plus public discovery and JWKS endpoints for verifiers. It has nothing to do with the Cloud Agents API, which authenticates with long-lived Cursor API keys and manages agents from outside the VM.

Cursor-managed Cloud Agent VMs serve the token socket, and every token they mint carries agent_runtime: managed.

How it works

  1. Code in the VM calls the local identity socket and asks for a token with an audience you choose.
  2. Cursor signs an RS256 JWT bound to that agent and owner.
  3. The agent sends the JWT to your cloud or verifier (AWS STS, GCP, Azure, Vault, or a service you run).
  4. The verifier checks the signature against Cursor's published JWKS and authorizes on claims such as sub, team_id, or cloud_agent_id.

Nothing in the guest request chooses the agent identity. The host attests the pod, and Cursor fills claims from the linked agent.

Mint a token

The socket path is in CURSOR_AGENT_SOCKET. On Cursor-managed VMs the default is /run/cursor/api.sock.

curl --unix-socket "${CURSOR_AGENT_SOCKET:-/run/cursor/api.sock}" \  -H 'Content-Type: application/json' \  -d '{"aud":"sts.amazonaws.com"}' \  http://localhost/v1/tokens/oidc

Include an optional nonce when your verifier expects replay binding:

curl --unix-socket "${CURSOR_AGENT_SOCKET:-/run/cursor/api.sock}" \  -H 'Content-Type: application/json' \  -d '{"aud":"https://oidc.example.com","nonce":"unpredictable-value"}' \  http://localhost/v1/tokens/oidc

Request

POST /v1/tokens/oidc over the Unix socket. Content-Type: application/json is required. Maximum body size is 4 KB.

FieldRequiredDescription
audYesAudience string your verifier checks. Printable ASCII, no whitespace, up to 512 characters. Examples: sts.amazonaws.com, https://oidc.example.com.
nonceNoOpaque string echoed into the JWT nonce claim. Up to 512 characters.
sub_claimNoPublished claim name to project into sub as <name>:<value>, for verifiers that only match on sub and aud. Up to 64 characters. Supported names are listed in the discovery document as x_cursor_sub_claims_supported; currently team_id. Unsupported names are rejected. An allowed name with no value for the agent, such as team_id on a personal account, refuses the mint instead of falling back to the default subject.

Cursor does not allowlist audiences. Your verifier must reject unexpected aud values.

Response

{  "token": "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCIsImtpZCI6Ii4uLiJ9...",  "expires_at": 1785500000}
FieldDescription
tokenSigned JWT.
expires_atExpiration as Unix seconds. Matches the JWT exp claim.

Tokens are valid for 5 minutes. There is no refresh endpoint. Mint again when you need a new token.

Environment setup

Install scripts can mint on the same socket after the pod is linked to the agent. A token only asserts facts that exist when it is minted: turn_id and turn_start are absent until a coding turn starts, and branch_name is absent until the run records a branch. Owner, team, and repository claims are set from agent creation onward.

If the socket is missing right after boot, retry the connection. If it stays missing, recreate the agent so it lands on a host that supports identity.

Verify a token

Publish these URLs to your identity provider or resource server:

EndpointURL
Issuerhttps://api.cursor.com
Discoveryhttps://api.cursor.com/.well-known/openid-configuration
JWKShttps://api.cursor.com/keys
curl -sS https://api.cursor.com/.well-known/openid-configurationcurl -sS https://api.cursor.com/keys

Discovery follows OpenID Connect Discovery 1.0. Cursor issues tokens directly from the VM, so the document has no authorization_endpoint or token_endpoint.

Check at least:

  • Signature with RS256 and the JWKS kid
  • iss is https://api.cursor.com
  • aud is the audience your service expects
  • nbf / exp with a small clock-skew allowance (nbf is 5 seconds before iat)
  • sub or other identity claims your policy uses

Discovery includes x_cursor_audience_bound: true. Every token is minted for the caller-supplied aud. Do not accept a token issued for a different audience. Discovery also publishes x_cursor_sub_claims_supported, the claim names a mint request can project into sub with sub_claim.

JWT claims

Header: alg=RS256, typ=JWT, plus kid.

ClaimAlways presentDescription
issYeshttps://api.cursor.com
subYesStable owner subject: user:<id> or service_account:<id> by default, or <claim>:<value> (for example team_id:123) when the mint request set sub_claim. Not an email.
audYesAudience from the mint request.
iatYesIssued-at, Unix seconds.
nbfYesNot-before (iat - 5).
expYesExpiration (iat + 300).
jtiYesUnique id per mint.
cloud_agent_idYesCloud Agent id (bcId).
nonceNoPresent only when the mint request included one.
agent_runtimeYesRuntime that attested the mint. managed on Cursor-managed Cloud Agent VMs.
owner_emailWhen knownLowercased user email. Prefer sub or owner_user_id for allowlists; email can change.
owner_user_idWhen knownCursor user id, issued as a decimal string so string-only claim matchers work without coercion.
owner_service_account_idWhen knownService account id when a service account owns the agent.
team_idWhen knownOwning team id, issued as a decimal string like owner_user_id.
turn_idWhen a turn is activeCurrent run id.
turn_startWhen a turn is activeRun start, Unix seconds.
repo_urlWhen knownPrimary repository in canonical host/path form, such as github.com/acme/widgets: lowercased hostname with no scheme, credentials, port, query, or .git suffix. Multi-repo agents have additional writable repositories this claim does not name. A policy meaning "this agent only touches repo X" must pin the complete set with repo_urls (or repo_urls plus repo_count == 1), never repo_url alone.
repo_urlsWhen knownComplete repository set of the agent's workspace, in the same canonical form as repo_url. Primary repository first; additional repositories sorted. Present only when the set is known complete, so absent means unknown, not single-repo.
repo_countWhen knownNumber of entries in repo_urls. Present exactly when repo_urls is. Lets verifiers limited to scalar comparisons express single-repo confinement (repo_count == 1 together with repo_url).
branch_nameWhen knownCurrent branch.
environment_idWhen knownPublic id (UUID) of the persisted Cursor environment the run consumed.
sourceWhen knownHow the agent was started, such as API, SLACK, or AUTOMATIONS.
automation_idFor automationsAutomation id when source is automations.

Cursor never puts internal auth ids in these tokens.

Trust model

The token attests workload identity for the Cloud Agent run, not a specific process inside the VM. Any process that can reach the local socket can mint a token: the agent, code the agent runs, and hooks. Scope relying-party permissions to what you would grant that run as a whole.

Cursor binds each mint to the host-attested pod that owns the agent. Nothing in the guest request chooses the identity, so a guest cannot mint a token for a different agent or forge host attestation.

Rate limits and errors

Each pod can mint 30 tokens per minute, in bursts of up to 10. The socket also accepts at most 8 connections at once per pod. Cache a token until it expires instead of minting per call.

Retry 429, 503, 500, 502, and 504 with backoff. Treat 403 as fatal: the pod is not allowed to mint.

Error bodies carry a machine-readable code. Invalid-request errors (400, 404, 405, 413, and 415) also include a usage string that restates the full request contract; rate-limit and saturation errors stay code-only:

{ "error": "invalid_aud", "usage": "POST /v1/tokens/oidc ..." }
{ "error": "rate_limited" }
HTTPerrorWhen
400invalid_json, invalid_aud, invalid_nonce, or invalid_sub_claimBad request body
404not_foundWrong path
405method_not_allowedNot POST
413body_too_largeBody over 4 KB
415invalid_content_typeMissing or non-JSON Content-Type
429rate_limitedPer-pod mint budget; honor Retry-After
503saturatedLocal admission budget; honor Retry-After
500host_errorHost-internal mint failure; retry
502 / 504backend_unreachableCursor backend unreachable or timed out; retry
Otherbackend_errorThe backend rejected or failed the mint; the HTTP status carries the verdict: 400 means fix the request (for example an unsupported sub_claim, or one with no value for this agent), 403 is fatal, 503 is retryable

AWS IAM example

Use OIDC when you want AWS to trust Cursor-signed JWTs with AssumeRoleWithWebIdentity. For the simpler Cursor-managed assume-role flow (External ID + CURSOR_AWS_ASSUME_IAM_ROLE_ARN), see Using AWS IAM Roles.

  1. Create an IAM OIDC identity provider whose URL is https://api.cursor.com.
  2. Set the audience to sts.amazonaws.com (or another audience your role expects).
  3. Trust the role only for subjects and teams you intend to allow.

Example trust policy:

{  "Version": "2012-10-17",  "Statement": [    {      "Effect": "Allow",      "Principal": {        "Federated": "arn:aws:iam::123456789012:oidc-provider/api.cursor.com"      },      "Action": "sts:AssumeRoleWithWebIdentity",      "Condition": {        "StringEquals": {          "api.cursor.com:aud": "sts.amazonaws.com"        },        "StringLike": {          "api.cursor.com:sub": "user:*"        }      }    }  ]}

Tighten this with an exact sub, such as user:42 for one user or service_account:<id> for an agent that runs as a service account. AWS trust policies only match aud and sub, so scope trust to a team by minting with "sub_claim":"team_id" and matching the projected subject:

"StringEquals": {  "api.cursor.com:aud": "sts.amazonaws.com",  "api.cursor.com:sub": "team_id:123"}

Follow current AWS IAM OIDC instructions for provider creation and thumbprints.

In the VM, mint with "aud":"sts.amazonaws.com" (plus "sub_claim":"team_id" when your trust policy matches the team subject) and pass the JWT to STS. If you use network allowlists, allow sts.amazonaws.com (and any regional STS host you call).

Other verifiers

The same tokens work with any OIDC-compliant verifier:

  • GCP Workload Identity Federation
  • Azure federated credentials / Entra ID
  • Vault JWT/OIDC auth
  • Internal APIs that validate RS256 JWTs

Point the provider at the discovery URL, require your audience, and authorize on claims such as sub, team_id, or cloud_agent_id. To confine an agent to specific repositories, pin the complete set with repo_urls; repo_url names only the primary repository.

Minting uses the local socket only. Exchanging the JWT with AWS, GCP, Azure, or your service still needs outbound network access to those hosts.