Cursor Origin
The Cursor Origin API: Authentication, Grants and Acting as Users
The Origin API is a REST API at https://api.cursor.com/v1/origin, currently in Early Beta. An Origin App signs a short-lived JWT with its own Ed25519 key, exchanges it for an installation access token, and uses that token for repositories, pull requests and checks. Grants say which principal holds which permission on a repository or namespace, and an installation user token lets an app act as one namespace member within the limits of both.

On this page
How does an Origin App authenticate to the API?
An Origin App proves who it is with an Ed25519 key pair that you generate yourself. You register only the public half at cursor.com/codebase/settings/apps, an app can hold up to 10 active signing keys, and Cursor stores only the public key. The sequence below is the chain from that key to a request that touches a repository.
- 1Generate the pair locally with OpenSSL (
openssl genpkey -algorithm ED25519for the private key, thenopenssl pkey -puboutfor the public PEM) and register the public PEM. - 2Sign an app JWT with the private key. The header is
alg: EdDSAwithkidset to the app ID, the claims areiss(the app ID),audset toorigin-apps,iatandexp, and the lifetime should be approximately five minutes. - 3Send the JWT as
Authorization: Bearer APP_JWTfor app-level operations: reading app metadata, managing installations, minting installation tokens and recovering webhook deliveries. - 4Call
POST /app/installations/{installationId}/access_tokenswith the app JWT. The installation token that comes back begins withoit_. - 5Use that installation token for repository-scoped operations, including pull requests, check-run writes and Git over HTTPS.
The app JWT identifies the app and manages the app itself, while the installation token is what actually reaches repositories, and the private key's only job is to sign JWTs. An installation token cannot exceed the installation's approved scopes or repository access, and you can narrow it further with fewer scopes or repositoryIds. Empty or omitted arrays inherit the complete installation grant.
A token expires at most 15 minutes after creation and never later than the app JWT that requested it, so the five-minute JWT the docs recommend produces a token of at most five minutes. The docs tell you to read expiresAt and mint a new token when it passes instead of assuming a duration, and they point out that Origin tokens are shorter-lived than GitHub App installation tokens. A job that needs the full 15 minutes has to sign its JWT with a later exp.
Git over HTTPS takes the same installation token but not in the same header. The Git endpoint uses HTTP Basic authentication with the username x-access-token and the token as the password, and it rejects Bearer credentials, which belong on the REST API. Clone, fetch and pull need repository:contents:read, push needs repository:contents:write, and the token's grant must include the target repository. Putting the token in the clone URL writes it to .git/config, so the docs either rewrite the remote afterwards or pass the token through Git's credential helper instead.
Do not put a Cursor API key directly in the Authorization header. For user-authenticated requests, the docs point to the Origin CLI: origin auth login for an interactive session, or CURSOR_API_KEY set to a personal user API key for a non-interactive one. The CLI exchanges the key for the short-lived user access token Origin accepts. App integrations use app JWTs and installation tokens instead.
This is covered hands-on in Cursor Compile 2026 — 1 short Unit, free to read.
Rather do it than read about it? Run 11 interactive Cursor walkthroughs in a simulated editor. Free, no account needed.
What do Origin grants control?
A grant binds one principal to one repository or namespace with one permission. The grants API lists the grants held directly on a resource, upserts the grant a principal holds and deletes it, so access changes can be scripted and reviewed as code. Writes reuse the access checks behind the Codebase permissions UI and record the same repository.access_changed and namespace.access_changed audit events.
Each grant names exactly one principal, and the kinds differ in what they identify. A user must belong to the owner's organization, and a group must be one the owner's team owns or an active group in that team's organization.
- Principal
user- Field
user.id- Identifies
- A Cursor user, by the encoded
user_…id the organization API uses
- Principal
group- Field
group.id- Identifies
- A Cursor group, by its public
grp_…id (thepublicIdthe Organization API group routes return, not theg_…id those routes take)
- Principal
teamGroup- Field
teamGroup.kind- Identifies
- One of the owning team's built-in groups:
membersoradmins
| Principal | Field | Identifies |
|---|---|---|
user | user.id | A Cursor user, by the encoded user_… id the organization API uses |
group | group.id | A Cursor group, by its public grp_… id (the publicId the Organization API group routes return, not the g_… id those routes take) |
teamGroup | teamGroup.kind | One of the owning team's built-in groups: members or admins |
The three principal kinds a grant can name.
A team-group grant is the floor every member of that built-in group holds on the resource. On a namespace it is the team's namespace floor, and on a repository it is the team's per-repository override, so deleting it returns the repository to the namespace floor. The write endpoints also answer a user or group that does not exist exactly like one outside the organization, which means a response never confirms that a principal exists.
Repositories and namespaces use different permission ladders, and both map to the presets the Codebase permissions UI offers. The values are not interchangeable between the two.
- Resource
- Repository
- `permission` values
read,write,admin
- Resource
- Namespace
- `permission` values
PERMISSION_READ,PERMISSION_CONTRIBUTOR,PERMISSION_WRITE,PERMISSION_ADMIN
| Resource | `permission` values |
|---|---|
| Repository | read, write, admin |
| Namespace | PERMISSION_READ, PERMISSION_CONTRIBUTOR, PERMISSION_WRITE, PERMISSION_ADMIN |
On a namespace, the first three grant that level on its internal repositories and `PERMISSION_ADMIN` administers the namespace itself.
Upsert is a POST that creates or replaces the grant a principal holds. Each principal holds one grant per resource, so repeating a request leaves the same grant in place and a different permission replaces the earlier one. Delete names the principal in the request body and returns 204 No Content. Two guardrails apply: list responses report custom or PERMISSION_CUSTOM for a grant with a custom policy and the upsert endpoints reject those values with InvalidArgument (HTTP 400), and an upsert or delete that would leave a namespace without an admin returns FailedPrecondition (HTTP 400).
Four scopes cover the grants API: repository:settings:read and repository:settings:write for repository grants, namespace:settings:read and namespace:settings:write for namespace grants. App installations can hold all four, so a robot calls it with an installation access token, and user access tokens work too. Requesting a :write scope also grants the matching :read scope. List endpoints cost 1 point against the rate-limit budget and writes cost 5.
How does an app act on behalf of a user?
It mints an installation user token. Origin Apps use these to act on behalf of members of the namespace they are installed in, and each request is limited to permissions held by both the installation and the user. The four steps below are the whole flow, with the confirmation step optional.
- 1A workspace admin approves the
namespace:user_tokens:writescope for your app's installation. - 2Optionally, confirm the user's Cursor identity. Origin returns a signed receipt with their
user_…ID and the installation they confirmed for. - 3Sign an app JWT and mint an installation user token for the user's ID or email.
- 4Use the token with the REST API or Git over HTTPS until
expiresAt, then mint another.
The scope goes in the scope parameter of the installation URL, and for an existing installation you also send include_granted_scopes=true so the admin approves only the addition. The installation can then mint tokens for any active namespace member. The scope cannot be included in a token's own scopes, and the other admin-approved scopes still cap each token's permissions.
Confirmation is optional. The admin's approval already covers every namespace member, so a receipt is not needed to mint. You 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, grants no permissions, and Create Installation User Token neither accepts nor requires one.
To start, open https://cursor.com/codebase/apps/user-confirmation in the user's browser with redirect_uri, a random state value (strongly recommended) and either installation_id or client_id. The redirect_uri must exactly match an entry in the app's installationRedirectUris, though a loopback URI is accepted on any port. Origin shows the app's name, icon and description, lists the details the app will receive (user ID, email, and namespace slug and ID) and says plainly that confirmation shares identity and membership without granting repository access. Canceling sends no callback at all, so a missing callback should be treated as not confirmed.
On confirmation, Origin redirects to your callback with a confirmation_receipt JWT. It expires five minutes after issuance, carries a unique jti, and is signed with the same keys as installation receipts, so verification is a short fixed list.
- 1Resolve the signing key from the JWKS by the
kidheader. - 2Require
alg: EdDSAandtyp: origin-user-confirmation-receipt+jwt, which separates confirmation receipts from installation receipts and access tokens. - 3Validate the signature,
iss,audandexp. - 4Match the signed
stateto the value you sent. When the link named an installation, also matchinstallation_id; when it named your app, readinstallation_idfrom the receipt, because that is the installation to mint for.
Trust the signed state claim, not the query parameter, and reject the callback when any check fails. The JWKS lives at https://api.cursor.com/v1/origin/keys, advertised through the discovery document at /.well-known/openid-configuration. It is sent with Cache-Control: public, max-age=600, stale-if-error=600, and keys rotate weekly, so cache it for 10 minutes and refresh on a signature that no key verifies.
Minting is POST /v1/origin/app/installations/{installationId}/user_access_tokens with an app JWT, setting exactly one of userId or userEmail. userId is the receipt's sub; userEmail must match exactly one active member of the installation's namespace. Optional scopes and up to 50 repositoryIds narrow the token, but namespace:user_tokens:write and any scope prefixed app: or installation: cannot be delegated. With both limits set, minting succeeds only if the installation and the user each hold every requested scope on every listed repository. With fewer limits, permissions are checked on each request instead.
The token itself behaves like an installation token with a user attached. expiresAt is at most 15 minutes after minting and never later than the app JWT's exp, there is no refresh token, and each mint costs 1 point against the app JWT's rate limit. Send it as a Bearer credential, or as the Git password with the username x-access-token, and treat its contents as opaque. A request that does not fit the installation's approved scopes and repositories, the user's Origin grants and the token's limits returns 403, or 404 if the resource is not visible. Actor fields name the user and can include performedVia.app, which identifies the app that created, say, a comment, though not an app that later edited or deleted it.
What happens when minting fails or access ends?
Most mint failures come from a short list of causes, and the status code is usually enough to tell which. The table below covers the mint endpoint.
- HTTP status
400- Cause
- Not exactly one of
userIdoruserEmail, an invalid ID or email, duplicate, malformed or non-delegable scopes, or duplicate repository IDs or more than 50
- HTTP status
401- Cause
- The app JWT is invalid or expired, or the installation does not exist, belongs to another app, or is suspended
- HTTP status
403- Cause
- The installation lacks
namespace:user_tokens:write, the requested limits exceed its grant, the user is not eligible, or a request with both limits includes a permission the installation or user lacks
- HTTP status
429- Cause
- The app JWT's rate limit is exhausted
- HTTP status
503- Cause
- Origin could not look up the user or sign the token; retry with backoff
| HTTP status | Cause |
|---|---|
400 | Not exactly one of userId or userEmail, an invalid ID or email, duplicate, malformed or non-delegable scopes, or duplicate repository IDs or more than 50 |
401 | The app JWT is invalid or expired, or the installation does not exist, belongs to another app, or is suspended |
403 | The installation lacks namespace:user_tokens:write, the requested limits exceed its grant, the user is not eligible, or a request with both limits includes a permission the installation or user lacks |
429 | The app JWT's rate limit is exhausted |
503 | Origin could not look up the user or sign the token; retry with backoff |
Unknown users, non-members and emails matching several members all return the same `403`.
Individual tokens cannot be revoked. Access ends through changes around the token instead. Uninstalling or deleting the app invalidates its user tokens before expiresAt, and requests return 401. Closing the user's Cursor account does the same. Removing namespace:user_tokens:write or suspending the installation stops new mints, but tokens already issued run until expiresAt. Changes to the installation's or the user's grants apply no later than expiresAt.
The docs' security notes say to mint just in time with the smallest scopes and repositoryIds the job needs, treat tokens like passwords, and neither store nor log them. Key linked accounts on sub, not email, since the user_… ID is stable and an email can change, and verify an email address before minting with userEmail because a typed address can name a different member.
Receipts need their own care. Use each one once by recording its jti and rejecting repeats within its five-minute lifetime. A receipt is not a credential, so do not send it as a Bearer token or log it, since it contains the user's email. It also describes membership only at confirmation time, but every mint rechecks membership, so a stored receipt does not let you mint for someone who left. The docs state that the whole API is in Early Beta and subject to change.
Frequently asked questions
What is the base URL of the Origin API?
https://api.cursor.com/v1/origin. The API is in Early Beta and subject to change.
How long does an Origin installation token last?
At most 15 minutes after creation, and never later than the app JWT that requested it. The docs recommend an app JWT lifetime of approximately five minutes, so read expiresAt and mint a new token when it passes.
Does an app need a confirmation receipt to act as a user?
No. The admin's approval of namespace:user_tokens:write covers every namespace member, and Create Installation User Token neither accepts nor requires a receipt. Confirmation is for linking accounts or verifying identity instead of trusting a typed email.
Sources & last verified
- Cursor Docs: Origin API
- Cursor Docs: Acting on Behalf of Users
- Cursor Docs: Origin Grants API
- Cursor Docs: Origin API Authentication
- Cursor Docs: App JWT
- Cursor Docs: Create Installation User Token
Cursor ships frequently. Facts verified against primary sources on October 17, 2026.