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

# Jobs and the job lifecycle

> How Wibble runs humanization as an asynchronous job: statuses, tracking, and the job object.

A job represents one humanization request. When you submit text to `POST /humanize`, Wibble reserves the required API words, creates a job, and returns it with status `queued`. The job moves through its lifecycle in the background, and you read the result once it finishes.

## The async model

Humanization takes time. Rewriting text well is not instant, and the work runs on Wibble's infrastructure rather than in the request that submitted it. Instead of holding the HTTP connection open until the rewrite is done, `POST /humanize` returns immediately with HTTP `202` and a job in status `queued`. Your request never blocks waiting for the engine.

You learn the outcome in one of two ways:

* **Poll** `GET /humanize/{id}` until the job reaches a terminal status.
* **Register a webhook** by passing `webhook_url` on submit, and Wibble notifies you when the job finishes.

Polling returns the full job object. Webhooks deliver the terminal result in the same billing/result shape plus a `type` field, but omit transient polling fields such as `current_stage` and `detected_language`.

## Statuses

A job is always in exactly one of four statuses. Two are terminal: once a job reaches `succeeded` or `failed`, it stays there and the status will not change.

| Status      | Meaning                                                                            | Terminal |
| ----------- | ---------------------------------------------------------------------------------- | -------- |
| `queued`    | The job is accepted and waiting to start.                                          | No       |
| `running`   | The engine is humanizing the text.                                                 | No       |
| `succeeded` | The job finished. The humanized text is in `output`.                               | Yes      |
| `failed`    | The job did not finish. The reason is in `error`, and reserved words are refunded. | Yes      |

When you poll or handle a webhook, branch on `status`. Treat any status other than `succeeded` and `failed` as "not done yet."

### current\_stage is informational

While a job is `running`, `current_stage` carries a short, human-readable label describing what the engine is doing right now, such as `detecting_language` or `refining_draft`. It exists to give you something to show in a progress UI or a log line.

<Warning>
  `current_stage` is not a stable enumerated contract. The set of stages can change, and labels can be added, renamed, or removed without notice. Do not branch your code on specific stage strings. Use `status` to decide what to do, and treat `current_stage` as display text only.
</Warning>

## Polling vs webhooks

Both approaches deliver the same result state. Choose based on how your code is structured.

<Columns cols={2}>
  <Card title="Polling" icon="arrows-rotate">
    Best for scripts, short-lived processes, and anywhere you cannot accept an inbound HTTP request. You stay in control of timing and need no public endpoint.
  </Card>

  <Card title="Webhooks" icon="bell">
    Best for servers that can receive an HTTPS callback. You avoid repeated lookups and learn the result as soon as the job finishes.
  </Card>
</Columns>

When you poll, wait between requests. A poll interval of about **2.5 seconds** balances responsiveness against your rate limit. `GET` requests are limited to **120 per minute per key**, so polling faster than that risks `429 rate_limited` responses without learning the result any sooner.

```bash Poll a job theme={null}
curl https://www.wibbleai.com/api/v1/humanize/7b42f1d6-0a8c-4e8f-9a21-c6d9f47c2b10 \
  -H "Authorization: Bearer wib_live_..."
```

<Tip>
  Webhooks and polling are not mutually exclusive. Treat a webhook as the fast path and keep polling available as a fallback in case a delivery is delayed or missed. See [Webhooks](/concepts/webhooks) for verification, delivery behavior, and retries.
</Tip>

## The job object

Both `POST /humanize` and `GET /humanize/{id}` return the same polling job object. Webhook payloads contain the terminal job fields you need to process the result plus a `type` field, but do not include `current_stage` or `detected_language`.

<ResponseField name="id" type="string">
  The job identifier, for example `7b42f1d6-0a8c-4e8f-9a21-c6d9f47c2b10`. Use it to retrieve the job.
</ResponseField>

<ResponseField name="status" type="string">
  One of `queued`, `running`, `succeeded`, or `failed`. `succeeded` and `failed` are terminal.
</ResponseField>

<ResponseField name="mode" type="string">
  Always `humanize`.
</ResponseField>

<ResponseField name="input_words" type="integer">
  The word count of the submitted text.
</ResponseField>

<ResponseField name="words_reserved" type="integer">
  API words held from your balance for this job at submission, equal to `input_words`.
</ResponseField>

<ResponseField name="words_charged" type="integer">
  The net words billed: reserved words minus any refund, floored at 0. A `failed` job is charged 0. See [Words and billing](/concepts/words-and-billing).
</ResponseField>

<ResponseField name="status_url" type="string">
  The full URL to retrieve this job with `GET /humanize/{id}`.
</ResponseField>

<ResponseField name="current_stage" type="string">
  An informational, human-readable progress label. Not a stable enum. Display it if useful, but do not branch on it.
</ResponseField>

<ResponseField name="detected_language" type="string | null">
  The detected input language once detection has run, for example `en`. `null` before detection completes.
</ResponseField>

<ResponseField name="created_at" type="string">
  When the job was created, as an ISO 8601 timestamp.
</ResponseField>

<ResponseField name="completed_at" type="string | null">
  When the job reached a terminal status, as an ISO 8601 timestamp. `null` until the job finishes.
</ResponseField>

<ResponseField name="output" type="string">
  The humanized text. Present only when `status` is `succeeded`.
</ResponseField>

<ResponseField name="error" type="object">
  The failure reason, with `code` and `message`. Present only when `status` is `failed`. See [Errors](/concepts/errors).
</ResponseField>

A succeeded job looks like this:

```json Succeeded job theme={null}
{
  "id": "7b42f1d6-0a8c-4e8f-9a21-c6d9f47c2b10",
  "status": "succeeded",
  "mode": "humanize",
  "input_words": 9,
  "words_reserved": 9,
  "words_charged": 9,
  "status_url": "https://www.wibbleai.com/api/v1/humanize/7b42f1d6-0a8c-4e8f-9a21-c6d9f47c2b10",
  "current_stage": "completed",
  "detected_language": "en",
  "created_at": "2026-06-14T12:00:00.000Z",
  "completed_at": "2026-06-14T12:00:42.000Z",
  "output": "Mitochondria are the cell's powerhouses."
}
```

<Note>
  `output` appears only on `succeeded` jobs, and `error` appears only on `failed` jobs. Read `status` first, then read the field that matches.
</Note>

## Expiry

A job that never reaches a terminal status on its own is failed after about **one hour** with the error code `job_expired`. The reserved words are refunded, so an expired job is charged 0, the same as any other failure.

```json Expired job theme={null}
{
  "id": "7b42f1d6-0a8c-4e8f-9a21-c6d9f47c2b10",
  "status": "failed",
  "mode": "humanize",
  "input_words": 9,
  "words_reserved": 9,
  "words_charged": 0,
  "status_url": "https://www.wibbleai.com/api/v1/humanize/7b42f1d6-0a8c-4e8f-9a21-c6d9f47c2b10",
  "current_stage": null,
  "detected_language": null,
  "created_at": "2026-06-14T12:00:00.000Z",
  "completed_at": "2026-06-14T13:00:00.000Z",
  "error": {
    "code": "job_expired",
    "message": "The job expired before completion."
  }
}
```

If you poll, this guarantees your loop terminates: a job you are tracking always reaches `succeeded` or `failed`.

## Next steps

<Columns cols={2}>
  <Card title="Retrieve a job" icon="magnifying-glass" href="/api-reference/humanize/retrieve">
    Reference for `GET /humanize/{id}`.
  </Card>

  <Card title="Webhooks" icon="bell" href="/concepts/webhooks">
    Get notified when a job finishes, and verify deliveries.
  </Card>

  <Card title="Words and billing" icon="coins" href="/concepts/words-and-billing">
    How words are reserved, charged, and refunded.
  </Card>

  <Card title="Errors" icon="triangle-exclamation" href="/concepts/errors">
    Error codes and how to handle them.
  </Card>
</Columns>
