Skip to main content

Command Palette

Search for a command to run...

API

Acting on behalf of users

Origin Apps use installation user tokens to act on behalf of members of the namespace they're installed in. Each request is limited to permissions held by both the installation and the user.

Use the Origin API base URL and error model. Mint tokens with an app JWT.

How it works

  1. A workspace admin approves the namespace:user_tokens:write scope for your app's installation.
  2. Optionally, confirm the user's Cursor identity. Origin returns a signed receipt with their user_… ID and the installation they confirmed for.
  3. Sign an app JWT and mint an installation user token for the user's ID or email.
  4. Use the installation user token with the REST API or Git over HTTPS until expiresAt, then mint another.

Request the scope

Add namespace:user_tokens:write to the scope parameter of the installation URL. For an existing installation, also send include_granted_scopes=true so the admin approves only the addition.

https://cursor.com/codebase/apps/install  ?client_id=APP_ID  &scope=namespace:user_tokens:write%20repository:pull_requests:write  &redirect_uri=REGISTERED_CALLBACK  &state=RANDOM_ANTI_FORGERY_VALUE  &include_granted_scopes=true

This scope lets the installation mint tokens for any active namespace member. It can't be included in a token's scopes. Other admin-approved scopes still cap each token's permissions.

When to confirm a user

User confirmation is optional: the admin's approval of namespace:user_tokens:write covers every namespace member.

Confirm a user to link an account in your product to their Cursor account, or to verify an identity instead of trusting a typed email address. The receipt proves the signed-in user's user_… ID, email, and namespace membership at confirmation time.

Receipts prove identity and grant no permissions. Create Installation User Token neither accepts nor requires one.

User confirmation

Send the user to Origin

Open this URL in the user's browser. Name the installation when you know which of the user's namespaces your app is installed in:

https://cursor.com/codebase/apps/user-confirmation  ?installation_id=INSTALLATION_ID  &redirect_uri=REGISTERED_CALLBACK  &state=RANDOM_ANTI_FORGERY_VALUE

Name your app instead when you don't. Origin finds the installations the user can confirm for:

https://cursor.com/codebase/apps/user-confirmation  ?client_id=APP_ID  &redirect_uri=REGISTERED_CALLBACK  &state=RANDOM_ANTI_FORGERY_VALUE
ParameterRequiredDescription
installation_idOne of installation_id or client_idThe i_… ID of your app's active installation in the user's namespace.
client_idOne of installation_id or client_idYour app's app_… ID. On its own, it resolves to the app's active installations in the namespaces the user belongs to. Alongside installation_id, the installation is used and client_id must be the ID of the app it belongs to; any other value makes the link invalid.
redirect_uriYesCallback URI. Must exactly match an entry in the app's installationRedirectUris, the installation flow's allowlist. A loopback URI, such as http://127.0.0.1:PORT/callback or http://localhost:PORT/callback, is accepted on any port without being registered.
stateStrongly recommendedRandom anti-forgery value, echoed as the state claim of the confirmation receipt.

Send each parameter at most once. A link that names neither an installation nor an app, omits redirect_uri, or repeats a parameter produces an error after sign-in.

What the user sees

Signed-out users sign in to Cursor, then return to the same link with its parameters. Origin validates parameters after sign-in, so even malformed links go through sign-in before showing an error.

A link that names your app resolves to one installation first. Origin lists the app's active installations in the namespaces the user belongs to: their personal namespace, then their teams by slug. With one installation, the page continues as if the link had named it. With several, the user picks the namespace whose membership they want to confirm. With none, the user sees an error and nothing is confirmed; see Confirmation errors.

Origin shows your app's name, icon, and description alongside the installation's namespace. It also lists the details your app will receive:

  • Cursor user ID (user_…)
  • Email
  • Origin namespace slug and ID

The page explains that confirmation shares identity and current namespace membership without granting repository access. The user chooses Confirm or Cancel.

Only active members of the owning team, or the owner of a personal namespace, can confirm.

Callback

On confirmation, Origin redirects to your callback:

https://app.example.com/origin/confirm?confirmation_receipt=RECEIPT_JWT&state=RANDOM_ANTI_FORGERY_VALUE

Verify the confirmation receipt before reading its user claims. The callback includes state only if the confirmation URL supplied a non-empty value. Trust the signed state claim rather than the query parameter.

Canceling or failing to confirm sends no callback or redirect. Treat a missing callback as "not confirmed" and let the user restart.

Confirmation receipt

confirmation_receipt is a compact JWT signed by Origin with the same keys as installation receipts.

JOSE header:

{  "alg": "EdDSA",  "kid": "origin-key-id",  "typ": "origin-user-confirmation-receipt+jwt"}

Claims:

{  "iss": "https://api.cursor.com/v1/origin",  "aud": "app_01...",  "sub": "user_01...",  "installation_id": "i_01...",  "namespace_id": "ns_01...",  "email": "user@example.com",  "iat": 1786465200,  "exp": 1786465500,  "jti": "RECEIPT_UUID",  "state": "ORIGINAL_VALUE"}
  • aud is your app ID; sub is the confirmed user's user_… ID, used as userId when minting.
  • installation_id and namespace_id identify the installation and namespace where membership was confirmed. For a link that named your app, they tell you which installation the user chose.
  • email is the user's account email at confirmation time.
  • Receipts expire five minutes after issuance. jti is unique per receipt.
  • state is included only if the confirmation URL supplied a non-empty value.

Verify the receipt before trusting the callback:

  1. Resolve the signing key from the JWKS by the kid header.
  2. Require alg: EdDSA and typ: origin-user-confirmation-receipt+jwt to distinguish confirmation receipts from installation receipts and access tokens.
  3. Validate the signature, iss, aud, and exp.
  4. Match the signed state to the value you sent. When the link named an installation, also match installation_id. When it named your app, read installation_id from the receipt; it's the installation to mint for.

Reject the callback when any check fails.

Confirmation errors

Failures stay on the Cursor page without calling your callback.

ErrorCause
Invalid linkThe link names neither an installation nor an app, omits redirect_uri, repeats a parameter, or pairs installation_id with a client_id that isn't the app the installation belongs to.
Not authorizedThe installation doesn't exist or isn't active, redirect_uri isn't registered for the app, or the user isn't a member of the installation's namespace. For a link that names an app, the app doesn't exist, or only its owner can install it and the user has no installation to confirm for. The page doesn't say which.
Not installedThe link names an app anyone can install, and the app has no active installation in a namespace the user belongs to. The page names the app.
Temporarily unavailableOrigin couldn't sign the receipt after confirmation. The user can retry.

Mint an installation user token

Call Create Installation User Token, POST /v1/origin/app/installations/{installationId}/user_access_tokens, with an app JWT. Set exactly one of userId or userEmail.

curl --request POST \  --url 'https://api.cursor.com/v1/origin/app/installations/INSTALLATION_ID/user_access_tokens' \  --header 'Authorization: Bearer APP_JWT' \  --header 'Content-Type: application/json' \  --data '{  "userId": "user_01...",  "scopes": [    "repository:pull_requests:write"  ],  "repositoryIds": [    "repo_01..."  ]}'
{  "token": "YOUR_INSTALLATION_USER_TOKEN",  "expiresAt": "2026-01-01T00:15:00Z"}
FieldDescription
userIdThe user's user_… ID from a confirmation receipt's sub or an actor payload.
userEmailThe user's account email. It must match exactly one active member of the installation's namespace.
scopesOptional limit. Values must be unique and approved for the installation. namespace:user_tokens:write and scopes prefixed with app: or installation: can't be delegated. Empty or omitted uses current permissions.
repositoryIdsOptional limit of up to 50 unique repository IDs accessible to the installation. Empty or omitted uses current permissions.

The user must have an active Cursor account and meet the namespace membership rules. Unknown users, non-members, and emails matching several members return the same 403.

With both scopes and repositoryIds set, minting succeeds only if the installation and user each hold every requested scope on every listed repository. With fewer limits, permissions are checked on each request instead.

Lifetime

expiresAt is at most 15 minutes after minting and never later than the app JWT's exp, as with installation access tokens. There's no refresh token: mint another with a fresh app JWT after expiry. Each mint costs 1 point against the app JWT's rate limit.

Use the token

Treat installation user tokens as opaque: don't inspect or parse their contents. Send the token as a Bearer credential to the REST API, or as the password for Git over HTTPS with the username x-access-token:

Authorization: Bearer YOUR_INSTALLATION_USER_TOKEN

Each request must fit the installation's approved scopes and repository selection, the user's Origin grants, and the token's limits. Otherwise it returns 403, or 404 if the resource isn't visible.

Actor fields name the user and can include performedVia.app with your app's id and optional displayName. Attribution applies to the action that field describes: on a comment's author, it identifies the app that created the comment. It doesn't identify an app that later edited or deleted it. performedVia is absent for direct user actions and can be absent when delegation data is unavailable.

Mint errors

HTTP statusCause
400The request doesn't set exactly one of userId or userEmail, uses an invalid user_… ID or email address, includes duplicate, malformed, or non-delegable scopes, or includes duplicate repository IDs or more than 50.
401The app JWT is invalid or expired, or the installation doesn't exist, belongs to another app, or is suspended.
403The installation lacks namespace:user_tokens:write, the requested limits exceed the installation's grant, the user isn't eligible, or a request with both limits includes a permission the installation or user lacks.
429The app JWT's rate limit is exhausted. See Exceeding the limit.
503Origin couldn't look up the user or sign the token. Retry with backoff.

Revocation

Individual tokens can't be revoked. These changes affect token access:

  • Uninstalling or deleting the app invalidates its user tokens before expiresAt. Requests return 401.
  • Closing the user's Cursor account invalidates their tokens. Requests return 401.
  • Removing namespace:user_tokens:write or suspending the installation stops new mints. Issued tokens expire at expiresAt.
  • Changes to the installation's or the user's grants apply no later than expiresAt.

Security notes

  • Mint just in time with the smallest scopes and repositoryIds needed. Treat tokens like passwords; don't store or log them.
  • Key linked accounts on sub, not email: the user's user_… ID is stable; their email can change.
  • Verify email addresses before minting with userEmail. A typed address can name a different member.
  • Use each receipt once. Record its jti and reject repeats within its five-minute lifetime.
  • A receipt isn't a credential: don't send it as a Bearer token or log it, since it contains the user's email.
  • Receipts describe membership at confirmation time. Each mint rechecks membership, so a stored receipt doesn't let you mint for someone who left.