Connect Origin
Connect Origin to Minion by signing app JWTs with secrets.jwtSign and minting short-lived installation tokens per run, or try the API with a single token stored as a secret.
Origin is Cursor's code forge. Its API lets code read repositories, pull requests and checks. Minion has no OAuth integration for Origin, but it can hold the signing key for an Origin App and mint a fresh short-lived token whenever code needs one. This page covers that setup, and the quicker way to try the API with a single token.
Origin is in early beta and its API can change. Read the Origin API docs before you start. This page doesn't repeat the JWT header and claims; use the ones those docs specify.
How Origin authenticates
Origin doesn't issue OAuth tokens that Minion can refresh. Its API accepts three kinds of Bearer credential:
- An app JWT, signed with an Origin App's Ed25519 private key, valid for about five minutes.
- An installation access token (
oit_...), minted from an app JWT, valid for at most 15 minutes. - A user access token, which the Origin CLI gets by exchanging a Cursor API key. Origin doesn't document that exchange for other clients.
A lasting connection means signing a fresh app JWT every few minutes. In Minion that works like this:
- Minion's integrations only run the OAuth 2 authorization code flow, so Origin isn't an integration.
- Code running in Minion never sees secret values, so it can't sign with a stored private key itself. Instead
minion.secrets.jwtSignsigns on the host with a stored PKCS#8 PEM key (EdDSA is supported) and returns only the JWT. - The sandbox's
crypto.subtlesupports digests and HMAC only, so there is no signing in your own code.
Don't use a Cursor dashboard API key as a Bearer token. Origin rejects it, and Cursor says not to send it to Origin endpoints.
Before you start
- You need to be an owner or admin of your Minion organization to save a secret and allow it for JWT signing. Members can use it once it exists.
- You need access to an Origin codebase at cursor.com/codebase .
Create the app on Origin
- Open cursor.com/codebase/settings/apps and create an Origin App. Copy the app id (
app_01...). - Generate an Ed25519 key pair on your own machine, and register the public key on the app:
openssl genpkey -algorithm ED25519 -out origin-app-private.pem
openssl pkey -in origin-app-private.pem -pubout -out origin-app-public.pem - Install the app into your codebase. Ask only for the scopes you need.
repository:metadata:readis added for you. For read-only work,repository:contents:readandrepository:pull_requests:readare usually enough. Addrepository:checks:readto read check runs. - Note the installation id (
i_01...). It's thesubclaim of the installation receipt Origin sends to your callback.
Add the signing key in Minion
- Open app.minion.codes/app/secrets .
- Set Name to
originAppKey. Secret names use letters, digits and underscores only. - Paste the full contents of
origin-app-private.peminto Value. - Leave Approved hosts empty. This key is never sent to a host, only used to sign.
- Click Save secret, then open the secret and turn on Allow JWT use. Host approval alone doesn't permit signing.
Never paste the key into chat. See /docs/secrets .
Then add a second secret, originToken, only if you also want the single-token route below.
Mint a token and call the API
Sign an app JWT with minion.secrets.jwtSign, exchange it for an installation token, and call the API with that token. Use the claims and header Origin's docs specify.
export default async function main() {
const now = Math.floor(Date.now() / 1000)
const { jwt } = await minion.secrets.jwtSign({
keySecret: 'originAppKey',
algorithm: 'EdDSA',
claims: { iss: 'app_01...', iat: now - 30, exp: now + 240 },
})
const mint = await fetch('https://api.cursor.com/v1/origin/app/installations/i_01.../access_tokens', {
method: 'POST',
headers: { Authorization: `Bearer ${jwt}` },
})
if (!mint.ok) throw new Error(`Origin returned ${mint.status}: ${await mint.text()}`)
const { token } = await mint.json()
const res = await fetch('https://api.cursor.com/v1/origin/rate_limit', {
headers: { Authorization: `Bearer ${token}` },
})
if (!res.ok) throw new Error(`Origin returned ${res.status}: ${await res.text()}`)
return await res.json()
} The JWT and the installation token are short-lived and not stored. Check Origin's docs for the exact claim names and the field that holds the token in the mint response, and adjust the example to match. The first time you run it, fetch is not secret-filled here: the token travels as an ordinary header value your code holds for a few minutes. That is fine for a token this short-lived, but don't log it or return it.
Use it in a package
Put the mint step in a package export and call it from your jobs and subscriptions. Each run signs a fresh JWT and mints a fresh installation token, so nothing expires underneath a scheduled job. Say in the package's README that it needs the originAppKey secret with JWT use allowed.
Quick try: a single token as a secret
To try the API without any signing, mint an installation token yourself outside Minion and store it as a secret.
- With your own tooling, sign an app JWT and call
POST https://api.cursor.com/v1/origin/app/installations/{installationId}/access_tokens. The response has theoit_...token and itsexpiresAt. - In Minion, open app.minion.codes/app/secrets . Set Name to
originToken, paste theoit_...token into Value, and set Approved hosts toapi.cursor.com. - Leave Expires (UTC, optional) empty. The field takes a date and treats it as midnight UTC, so today's date would mark the secret expired at once. Origin expires the token on its own anyway.
- Click Save secret.
There's no connect step. Use it from execute:
export default async function main() {
const res = await fetch('https://api.cursor.com/v1/origin/rate_limit', {
headers: { Authorization: 'Bearer {{secret:originToken}}' },
})
if (!res.ok) throw new Error(`Origin returned ${res.status}: ${await res.text()}`)
const body = await res.json()
return { limit: body.resources?.core?.limit ?? null, remaining: body.resources?.core?.remaining ?? null }
} The token stops working within 15 minutes. To keep going, mint a new one, open Manage on the secret, paste the new value and click Rotate. Don't build a scheduled job on this route; use the signing setup above instead.
To list the repositories the installation can see, call https://api.cursor.com/v1/origin/installation/repos the same way.
Troubleshooting
- 401 saying the request is missing a Bearer token. Origin didn't accept the credential. Check that you're sending an
oit_...installation token (or, for the mint call, a valid app JWT), not a Cursor API key. - 401 that started a few minutes after it worked. The token expired. Mint a new one, or use the signing setup so each run mints its own.
- 401 on a fresh token. The installation was removed or the app was deleted. Reinstall the app and mint a new token.
- jwtSign refuses the key. The secret needs Allow JWT use turned on, and the value must be a PKCS#8 PEM private key.
- Secret "originToken" isn't approved for api.cursor.com. Open the secret on the Secrets page and approve
api.cursor.com. - Secret "originToken" has expired. You set an expiry date on the secret. Open Manage on the secret, clear the date and click Set expiry.
- 403 on a mirrored repository. Installations can only read mirrored repositories. Pull requests, checks and every write are rejected there.
- 403 on an endpoint. The installation doesn't have the scope that endpoint needs. Ask the workspace admin to approve it, then mint a new token.
For how secrets work, see /docs/secrets . To run code, see /docs/execute .