Origin Migration API
Origin is in Early Beta and subject to change. Review the OpenAPI specification when updating an integration.
The Origin Migration API is for operators migrating repositories between GitHub and Origin mirrors. Call these endpoints from a migration script you run as a Cursor user, not from an Origin App.
Sign in with origin auth login, or pass a Cursor API key through origin api. See User-authenticated CLI requests.
The endpoints use the Origin API base URL, https://api.cursor.com/v1/origin, and the same error model.
Scopes
repository:mirror:write, repository:mirror:delete, and repository:mirror:read are user policy scopes. Transition Repo Mirror requires Write (repository:mirror:write). Detach Repo Mirror requires Admin (repository:mirror:delete). Get Mirror Transition Job and Get Active Mirror Transition Job require repository:mirror:read. You must also administer the repository on its upstream GitHub source.
An Origin App can't request repository:mirror:write or repository:mirror:delete during installation, and it can't call these endpoints. An app can request repository:mirror:read, but only to read mirror on Get Repo.
Endpoint reference
Transition Repo Mirror
/v1/origin/repos/{ownerSlug}/{repoName}/mirror:transitionStarts a mirror-state transition on a mirrored repository and returns the job tracking it. The repository enters a transitioning mirror status while the job runs, so poll Get Active Mirror Transition Job or Get Mirror Transition Job until the job reaches a terminal status.
A repository that is not in the transition's expected start state, or that already has an active transition job, returns FailedPrecondition (HTTP 400). The caller must administer the repository on the mirror's upstream source; a caller without that access returns 403.
Path Parameters
ownerSlug string Required
repoName string Required
Request Body
transition string Required
initial_to_inbound, which retries a mirror whose initial sync failed and brings it to inbound.Response Fields
repository object
job object
curl --request POST \ --url 'https://api.cursor.com/v1/origin/repos/OWNER_SLUG/REPO_NAME/mirror:transition' \ --header 'Authorization: Bearer YOUR_ORIGIN_TOKEN' \ --header 'Content-Type: application/json' \ --data '{ "transition": "initial_to_inbound"}'Response shape:
{ "repository": { "id": "repo_01k2ja2000e0080000000000q4", "name": "rocket", "fullName": "acme/rocket", "webUrl": "https://cursor.com/codebase/acme/rocket", "owner": { "slug": "acme", "id": "ns_01k2ja2000e0080000000000p3", "type": "team" }, "defaultBranch": "main", "createdAt": "2026-08-01T09:30:00Z", "updatedAt": "2026-08-02T15:00:00Z", "pushedAt": "2026-08-02T14:45:00Z", "cloneUrl": "https://origin.cursor.com/git/acme/rocket.git", "mirror": { "source": "github", "sourceId": "R_kgDOAbc123", "status": "inbound" } }, "job": { "id": "rmt_01k2ja2000e0080000000000m3", "transition": "initial_to_inbound", "status": "running", "phase": "initializing-mirror-fetch", "attemptCount": 1, "startedAt": "2026-08-02T15:00:00Z", "createdAt": "2026-08-02T14:59:30Z", "updatedAt": "2026-08-02T15:01:00Z" }}Detach Repo Mirror
/v1/origin/repos/{ownerSlug}/{repoName}/mirrorPermanently disconnects a mirrored repository from its upstream source. The repository keeps its current contents and becomes a native repository, and syncing stops in both directions. Origin keeps the mirror's deploy key but doesn't use it while the repository is detached. The response body is empty.
Before it detaches an inbound repository, Origin stops the mirror and waits up to 2 minutes for its last fetch from GitHub, so a fetch already underway can't overwrite pushes made after the detach. If that fetch fails, or Origin can't resolve the repository's GitHub remote, the request returns FailedPrecondition (HTTP 400). Other failures, such as a timeout, return an error such as Unavailable (HTTP 503) or DeadlineExceeded (HTTP 408). In each case the repository stays mirrored; fix the cause and retry. When the GitHub App installation is gone or suspended, or the GitHub repository no longer exists, Origin detaches without a last fetch.
Detaching is not reversible through this API. A repository that never had a mirror returns FailedPrecondition (HTTP 400); detaching an already-detached repository succeeds without effect. A detach that races another change to the repository's mirror state returns FailedPrecondition (HTTP 400).
Path Parameters
ownerSlug string Required
repoName string Required
Response Fields
Successful requests return no response body.
curl --request DELETE \ --url 'https://api.cursor.com/v1/origin/repos/OWNER_SLUG/REPO_NAME/mirror' \ --header 'Authorization: Bearer YOUR_ORIGIN_TOKEN'Response:
204 No ContentGet Mirror Transition Job
/v1/origin/repos/{ownerSlug}/{repoName}/mirror/transition-jobs/{jobId}Returns one mirror transition job by id. An unknown job id returns 404.
Path Parameters
ownerSlug string Required
repoName string Required
jobId string Required
job.id.Response Fields
id string
transition string
initial_to_inbound.status string
queued, running, succeeded, failed_rolled_back, requires_attention, superseded. succeeded, failed_rolled_back, and superseded are terminal. requires_attention needs operator intervention.phase string
status, for display and debugging. One of queued, starting, draining-writes, initializing-mirror-fetch, finalizing-mirror-fetch, finalizing-mirror-push, snapshotting-refs, verifying-integrity, reopening-inbound-mirror, committing-target-status, rolling-back, or completed. New phases can appear as the transition process evolves, so poll status for completion rather than matching on phases.attemptCount integer
drainUntil string
lastErrorCode string
InboundMirrorDrainTimeout or MirrorIntegrityMismatch. Absent while the job has not failed.lastErrorMessage string
lastErrorCode. Absent while the job has not failed.startedAt string
completedAt string
createdAt string
updatedAt string
curl --request GET \ --url 'https://api.cursor.com/v1/origin/repos/OWNER_SLUG/REPO_NAME/mirror/transition-jobs/JOB_ID' \ --header 'Authorization: Bearer YOUR_ORIGIN_TOKEN'Response shape:
{ "id": "rmt_01k2ja2000e0080000000000m3", "transition": "initial_to_inbound", "status": "succeeded", "phase": "completed", "attemptCount": 1, "startedAt": "2026-08-02T15:00:00Z", "completedAt": "2026-08-02T15:12:00Z", "createdAt": "2026-08-02T14:59:30Z", "updatedAt": "2026-08-02T15:12:00Z"}Get Active Mirror Transition Job
/v1/origin/repos/{ownerSlug}/{repoName}/mirror/transition-jobs:activeReturns the repository's currently active mirror transition job and its most recent terminal one. Both fields are optional, so a repository that has never transitioned returns an empty object. Poll this endpoint to follow a transition: once activeJob disappears, lastJob tells you how it ended.
Path Parameters
ownerSlug string Required
repoName string Required
Response Fields
activeJob object
lastJob object
curl --request GET \ --url 'https://api.cursor.com/v1/origin/repos/OWNER_SLUG/REPO_NAME/mirror/transition-jobs:active' \ --header 'Authorization: Bearer YOUR_ORIGIN_TOKEN'Response shape:
{ "activeJob": { "id": "rmt_01k2ja2000e0080000000000m4", "transition": "initial_to_inbound", "status": "running", "phase": "initializing-mirror-fetch", "attemptCount": 2, "startedAt": "2026-08-02T15:00:00Z", "createdAt": "2026-08-02T14:59:30Z", "updatedAt": "2026-08-02T15:01:00Z" }, "lastJob": { "id": "rmt_01k2ja2000e0080000000000m3", "transition": "initial_to_inbound", "status": "failed-rolled-back", "phase": "completed", "attemptCount": 1, "startedAt": "2026-08-01T10:00:00Z", "completedAt": "2026-08-01T10:12:00Z", "createdAt": "2026-08-01T09:59:30Z", "updatedAt": "2026-08-01T10:12:00Z" }}