> ## Documentation Index
> Fetch the complete documentation index at: https://apidocs.scripe.io/llms.txt
> Use this file to discover all available pages before exploring further.

# Jobs

# Jobs

Long-running operations on the Scripe API (post generation, knowledge
base ingest, source ingest from file) run as **async jobs**. The
endpoint that submits one returns a `Job` envelope; you poll
`GET /v1/jobs/{jobId}` (or use `wait_for_completion_ms` for short jobs)
until the status leaves `QUEUED`/`RUNNING`.

The job lifecycle is:

| Status      | Meaning                                                 |
| ----------- | ------------------------------------------------------- |
| `QUEUED`    | Submitted, not picked up yet. Cancellable.              |
| `RUNNING`   | Worker is processing it. Not cancellable in v1.         |
| `DONE`      | Succeeded. `result` populated; shape depends on `type`. |
| `FAILED`    | Failed. `errorCode` + `errorMessage` populated.         |
| `CANCELLED` | You called the cancel endpoint while it was QUEUED.     |

Workers update progress to a separate Redis key with a 1-hour TTL, so
the `progress` snapshot on `GET /v1/jobs/{jobId}` may be `null` if the
job hasn't started or has already finished.

***

## Job types

| `type`               | Submitted via                             | `result` shape           |
| -------------------- | ----------------------------------------- | ------------------------ |
| `POST_GENERATION`    | `POST /v1/posts/generations`              | `{ postId: "post_…" }`   |
| `KB_INGEST_TEXT`     | `POST /v1/knowledge` (Wave 4)             | `{ documentId: "kb_…" }` |
| `KB_INGEST_FILE`     | `POST /v1/knowledge` (Wave 4)             | `{ documentId: "kb_…" }` |
| `KB_INGEST_URL`      | `POST /v1/knowledge` (Wave 4)             | `{ documentId: "kb_…" }` |
| `KB_INGEST_YOUTUBE`  | `POST /v1/knowledge` (Wave 4)             | `{ documentId: "kb_…" }` |
| `SOURCE_INGEST_FILE` | `POST /v1/sources` (Wave 4, file uploads) | `{ sourceId: "src_…" }`  |

Wave 3 ships only `POST_GENERATION`. The other types appear in the
enum / OpenAPI spec for forward-compat but the endpoints land in Wave 4.

***

## `GET /v1/jobs`

List jobs in the workspace, newest first.

```bash theme={null}
curl -i 'https://api.scripe.io/v1/jobs?type=POST_GENERATION&status=DONE,FAILED&limit=20' \
  -H "Authorization: Bearer scripe_sk_live_…" \
  -H "Scripe-Api-Version: 2026-08-01"
```

### Query parameters

| Name       | Type    | Required | Default | Notes                                                                          |
| ---------- | ------- | -------- | ------- | ------------------------------------------------------------------------------ |
| `type`     | string  | no       | —       | Comma-separated list of `type` values. Unknown values → `400 invalid_request`. |
| `status`   | string  | no       | —       | Comma-separated list of statuses. Unknown values → `400 invalid_request`.      |
| `dateFrom` | string  | no       | —       | `YYYY-MM-DD`, filters by `createdAt`. Inclusive.                               |
| `dateTo`   | string  | no       | —       | `YYYY-MM-DD`. Inclusive end-of-day UTC.                                        |
| `limit`    | integer | no       | 50      | 1…200.                                                                         |
| `cursor`   | string  | no       | —       | Opaque cursor from a previous response.                                        |

### Response

```json theme={null}
{
  "data": [
    {
      "id": "job_01J9ZA…",
      "type": "POST_GENERATION",
      "status": "DONE",
      "projectId": "proj_01J9ZA…",
      "startedAt": "2026-05-15T08:21:30.000Z",
      "completedAt": "2026-05-15T08:21:55.000Z",
      "progress": null,
      "result": { "postId": "post_01J9ZA…" },
      "errorCode": null,
      "errorMessage": null,
      "attemptCount": 1,
      "estimatedCompletionMs": 25000,
      "createdAt": "2026-05-15T08:21:00.000Z",
      "updatedAt": "2026-05-15T08:21:55.000Z"
    }
  ],
  "pagination": { "next_cursor": null, "has_more": false }
}
```

The list endpoint omits live progress to keep it cheap. Fetch
`/v1/jobs/{jobId}` for the freshest snapshot.

### Errors

| Status | Code                                                                      |
| ------ | ------------------------------------------------------------------------- |
| 400    | `invalid_request` (bad `type` / `status`), `bad_cursor`, `bad_pagination` |
| 401    | `unauthenticated`, `invalid_token`, `key_revoked`, `key_expired`          |
| 429    | `rate_limited`                                                            |

***

## `GET /v1/jobs/{jobId}`

Fetch a single job + its latest progress snapshot.

```bash theme={null}
curl -i https://api.scripe.io/v1/jobs/job_01J9ZA… \
  -H "Authorization: Bearer scripe_sk_live_…" \
  -H "Scripe-Api-Version: 2026-08-01"
```

### Response

While the job is running you'll see a progress payload:

```json theme={null}
{
  "data": {
    "id": "job_01J9ZA…",
    "type": "POST_GENERATION",
    "status": "RUNNING",
    "projectId": "proj_01J9ZA…",
    "startedAt": "2026-05-15T08:21:30.000Z",
    "completedAt": null,
    "progress": {
      "progress": 0.4,
      "message": "Generating post",
      "updatedAt": "2026-05-15T08:21:42.000Z"
    },
    "result": null,
    "errorCode": null,
    "errorMessage": null,
    "attemptCount": 1,
    "estimatedCompletionMs": 25000,
    "createdAt": "2026-05-15T08:21:00.000Z",
    "updatedAt": "2026-05-15T08:21:42.000Z"
  }
}
```

`progress` is `null` when the job is still `QUEUED` or has already
finished (the Redis key has a 1-hour TTL).

### Polling cadence

Workers emit \~10 progress updates per `POST_GENERATION`. Poll once per
second while `status` is `RUNNING`. Long-running types (`KB_INGEST_*`)
emit fewer updates — back off to once every 5 seconds after the first
30 seconds.

### Errors

| Status | Code                                                             |
| ------ | ---------------------------------------------------------------- |
| 401    | `unauthenticated`, `invalid_token`, `key_revoked`, `key_expired` |
| 404    | `not_found`                                                      |
| 429    | `rate_limited`                                                   |

***

## `POST /v1/jobs/{jobId}/cancel`

Cancel a `QUEUED` job. v1 does NOT support cancelling `RUNNING` jobs
(workers don't poll for cancellation mid-run); attempting to cancel one
returns `409 not_cancellable`.

```bash theme={null}
curl -i https://api.scripe.io/v1/jobs/job_01J9ZA…/cancel \
  -X POST \
  -H "Authorization: Bearer scripe_sk_live_…" \
  -H "Scripe-Api-Version: 2026-08-01"
```

### Response

The freshly-cancelled job, with `status: "CANCELLED"` and a populated
`completedAt`:

```json theme={null}
{
  "data": {
    "id": "job_01J9ZA…",
    "type": "POST_GENERATION",
    "status": "CANCELLED",
    "projectId": "proj_01J9ZA…",
    "startedAt": null,
    "completedAt": "2026-05-15T08:21:05.000Z",
    "progress": null,
    "result": null,
    "errorCode": null,
    "errorMessage": null,
    "attemptCount": 0,
    "estimatedCompletionMs": 25000,
    "createdAt": "2026-05-15T08:21:00.000Z",
    "updatedAt": "2026-05-15T08:21:05.000Z"
  }
}
```

### Errors

| Status | Code                                                                           |
| ------ | ------------------------------------------------------------------------------ |
| 401    | `unauthenticated`, `invalid_token`, `key_revoked`, `key_expired`               |
| 404    | `not_found`                                                                    |
| 409    | `not_cancellable` (job is `RUNNING`, `DONE`, `FAILED`, or already `CANCELLED`) |
| 429    | `rate_limited`                                                                 |

***

## AI spend cap

Every job that runs an AI workload (currently: `POST_GENERATION`) is
gated by a **daily workspace spend cap** (per UTC day). The pre-flight
check happens at submit time:

| Plan         | Cap (per day) |
| ------------ | ------------- |
| `SOLO`       | \$5 (500¢)    |
| `ADVANCED`   | \$20 (2,000¢) |
| `BUSINESS`   | \$50 (5,000¢) |
| `ENTERPRISE` | unlimited     |

When a submit would put the workspace over the cap, the API returns
`402 spend_cap_exceeded` with the current `spentCents` and `capCents`
in the error `meta`. The bucket resets at 00:00 UTC.
