Layers
Partner APIAPI referenceJobs

POST /v1/jobs/:jobId/cancel

Best-effort cancel on a running job. Some stages refuse cancellation; the response tells you which.

View as Markdown
POST/v1/jobs/{jobId}/cancel
Phase 1stable
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.

Path
  • jobId
    string (job_<ULID>)required
    The 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

202Cancel signal accepted. Poll the job to see status flip to canceled.
{
  "jobId": "job_01HXA1NHKJZXPV8R7Q6WSM5BCD",
  "accepted": true
}

reason is only present on the 200 "already terminal" shape below. On a fresh 202 accept, it's omitted.

200Already terminal — nothing to cancel. Flat rejection shape with the terminal reason.
{
  "jobId": "job_01HXA1NHKJZXPV8R7Q6WSM5BCD",
  "accepted": false,
  "reason": "ALREADY_COMPLETED"
}
409Current stage refuses cancellation. Canonical CONFLICT envelope with details.subcode = JOB_CANCEL_UNAVAILABLE.
{
  "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.

404Unknown jobId in your organization.

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.

KindStages that refuse cancel
project_ingest_githubopening_pr, finalizing
content_generate / content_regenerate / content_clone_from_postfinalizing
influencer_createpersisting
appstore_ingestpersisting

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: true means the cancel signal was delivered, not that the job is already canceled. Poll GET /v1/jobs/:jobId for 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_generate job before finalizing reverses any reserved credits. Canceling after does not — the content exists.

See also

On this page