POST /v1/jobs/:jobId/cancel
Best-effort cancel on a running job. Some stages refuse cancellation; the response tells you which.
/v1/jobs/{jobId}/cancel- Auth
- Bearer
- Scope
- jobs:cancel
Signals the workflow behind a jobId to cancel. This is cooperative, not forceful — the job checks at stage boundaries and exits cleanly. An in-flight platform API call will usually complete first, and some terminal stages refuse cancellation entirely because rolling them back would create orphaned state (a half-uploaded asset, a PR opened against your repo).
The response tells you whether the signal landed. It does not tell you the job is already canceled — keep polling GET /v1/jobs/:jobId until status flips to canceled or a terminal state.
jobIdstring (job_<ULID>)requiredThe jobId returned from the originating 202 response.
Example request
curl -X POST https://api.layers.com/v1/jobs/job_01HXA1NHKJZXPV8R7Q6WSM5BCD/cancel \
-H "Authorization: Bearer lp_live_01HX9Y6K7EJ4T2_4QZpN..."const res = await fetch(
`https://api.layers.com/v1/jobs/${jobId}/cancel`,
{ method: "POST", headers: { Authorization: `Bearer ${apiKey}` } },
);
const { accepted, reason } = await res.json();import httpx
r = httpx.post(
f"https://api.layers.com/v1/jobs/{job_id}/cancel",
headers={"Authorization": f"Bearer {api_key}"},
)
payload = r.json()Response
{
"jobId": "job_01HXA1NHKJZXPV8R7Q6WSM5BCD",
"accepted": true
}reason is only present on the 200 "already terminal" shape below. On a fresh 202 accept, it's omitted.
{
"jobId": "job_01HXA1NHKJZXPV8R7Q6WSM5BCD",
"accepted": false,
"reason": "ALREADY_COMPLETED"
}{
"error": {
"code": "CONFLICT",
"message": "Job cannot be canceled during this stage.",
"requestId": "req_01HXA1NHZ4KYE8GP9Q2WX3BCDE",
"details": {
"subcode": "JOB_CANCEL_UNAVAILABLE",
"jobId": "job_01HXA1NHKJZXPV8R7Q6WSM5BCD",
"stage": "opening_pr"
}
}
}Branch on error.details.subcode === "JOB_CANCEL_UNAVAILABLE" and read details.stage for the current non-cancelable stage.
Non-cancelable stages
Some stages would leave external state in an inconsistent place if interrupted. The API refuses cancellation during them and returns STAGE_NOT_CANCELABLE with the current stage.
| Kind | Stages that refuse cancel |
|---|---|
project_ingest_github | opening_pr, finalizing |
content_generate / content_regenerate / content_clone_from_post | finalizing |
influencer_create | persisting |
appstore_ingest | persisting |
Everything else is fair game. If you caught the job early in cloning, analyzing, planning, or generating_visuals, cancel lands within a second or two.
Notes
accepted: truemeans the cancel signal was delivered, not that the job is already canceled. PollGET /v1/jobs/:jobIdfor the terminal flip.- Canceled jobs are sticky — once
status: "canceled", they never revert. - Cancel is best-effort. If a platform API call is already in flight, it will finish before the job exits.
- Canceling a
content_generatejob beforefinalizingreverses any reserved credits. Canceling after does not — the content exists.
See also
GET /v1/jobs/:jobId— poll for terminal state- Jobs — the 202 → poll pattern