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¶
POST
/v1/origin/repos/{ownerSlug}/{repoName}/mirror:transition
Requires scope repository:mirror:write (user access token).
Starts 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
Owning entity’s unique slug.
repoName string Required
Repo name, unique to the owner entity.
Request Body¶
transition string Required
The mirror-state change to start. Allowed values: initial_to_inbound, which retries a mirror whose initial sync failed and brings it to inbound.
Response Fields¶
repository object
The repository, reflecting its transitioning mirror state. Carries the same fields as Get Repo.
job object
The job tracking the transition, carrying the same fields as Get Mirror Transition Job. Poll it until it reaches a terminal status.
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¶
DELETE
/v1/origin/repos/{ownerSlug}/{repoName}/mirror
Requires scope repository:mirror:delete (user access token).
Permanently 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
Owning entity’s unique slug.
repoName string Required
Repo name, unique to the owner entity.
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 Content
Get Mirror Transition Job¶
GET
/v1/origin/repos/{ownerSlug}/{repoName}/mirror/transition-jobs/{jobId}
Requires scope repository:mirror:read (user access token).
Returns one mirror transition job by id. An unknown job id returns 404.
Path Parameters¶
ownerSlug string Required
Owning entity’s unique slug.
repoName string Required
Repo name, unique to the owner entity.
jobId string Required
Identifier of the transition job, as returned in job.id.
Response Fields¶
id string
Unique identifier of the job.
transition string
The mirror-direction change this job performs. Allowed values: initial_to_inbound.
status string
Lifecycle state. Allowed values: queued, running, succeeded, failed_rolled_back, requires_attention, superseded. succeeded, failed_rolled_back, and superseded are terminal. requires_attention needs operator intervention.
phase string
Progress detail within 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
Number of times this job has been attempted.
drainUntil string
RFC 3339 timestamp of when the write-drain window of an in-progress transition ends. Absent outside the draining phase.
lastErrorCode string
Stable code identifying why the job last failed, such as InboundMirrorDrainTimeout or MirrorIntegrityMismatch. Absent while the job has not failed.
lastErrorMessage string
Human-readable detail for lastErrorCode. Absent while the job has not failed.
startedAt string
RFC 3339 timestamp of when the job started running. Absent while queued.
completedAt string
RFC 3339 timestamp of when the job reached a terminal status. Absent until then.
createdAt string
RFC 3339 job creation timestamp.
updatedAt string
RFC 3339 job update timestamp.
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¶
GET
/v1/origin/repos/{ownerSlug}/{repoName}/mirror/transition-jobs:active
Requires scope repository:mirror:read (user access token).
Returns 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
Owning entity’s unique slug.
repoName string Required
Repo name, unique to the owner entity.
Response Fields¶
activeJob object
The currently active transition job, carrying the same fields as Get Mirror Transition Job. Absent when no transition is in progress.
lastJob object
The most recent job that reached a terminal status, carrying the same fields as Get Mirror Transition Job. Absent when the repository has never completed a transition.
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"
}
}