Jobs.
Every asynchronous operation — article generation, knowledge-base ingestion — is a job. Jobs report per-stage progress while running and carry a typed result when finished.
Lifecycle#
| Field | Type | Description |
|---|---|---|
| queued | initial | Created, credits debited, waiting for a runner. |
| processing | running | Pipeline executing; stage and stages update live. |
| succeeded | terminal | Finished. result is populated; a job.succeeded webhook fires. |
| failed | terminal | Something broke. error explains; charged credits are auto-refunded; a job.failed webhook fires. |
| canceled | terminal | Reserved status for operator-canceled jobs. Not currently reachable via the public API. |
A single retrieval endpoint covers all job types:
/v1/jobs/{id}{
"id": "4d1a8f36-9e02-47b8-b7c4-3a92d5e60f18",
"object": "job",
"type": "article",
"status": "processing",
"stage": "research",
"stages": [
{
"key": "recon",
"label": "SERP recon — finding the information gap",
"status": "done",
"startedAt": "2026-07-06T09:41:13.412Z",
"endedAt": "2026-07-06T09:41:36.108Z"
},
{
"key": "plan",
"label": "Planning outline & research questions",
"status": "done",
"meta": { "outline": ["Why churn dashboards mislead", "…"] }
},
{
"key": "research",
"label": "Researching (web + knowledge base)",
"status": "running",
"meta": { "questions": 3 }
},
{ "key": "draft", "label": "Writing the draft", "status": "pending" },
{ "key": "edit", "label": "Editorial pass — voice & rhythm", "status": "pending" },
{ "key": "seo", "label": "SEO finishing — meta, links, schema", "status": "pending" },
{ "key": "score", "label": "Quality scoring", "status": "pending" }
],
"result": {},
"error": null,
"credits_charged": 10,
"created_at": "2026-07-06T09:41:12.000Z",
"finished_at": null
}Job fields#
| Field | Type | Description |
|---|---|---|
| id | string | Job ID (UUID). |
| object | "job" | Resource type. |
| type | string | article | ingest_source. |
| status | string | queued | processing | succeeded | failed | canceled. |
| stage | string | null | Key of the stage currently (or last) running. |
| stages | stage[] | Ordered pipeline plan. Each stage: key, label, status (pending | running | done | error), optional startedAt / endedAt ISO timestamps and a meta object with stage-specific detail (outline headings, fact counts, quality score). |
| result | object | Empty {} until success — shapes below. |
| error | string | null | Failure reason when status is failed. |
| credits_charged | integer | Credits debited for this job. |
| created_at | string | ISO 8601. |
| finished_at | string | null | Set on success or failure. |
Article pipeline stages#
Article jobs plan up to nine stages. image and derivatives only appear when requested — the stages array always reflects the actual plan for that job.
| Field | Type | Description |
|---|---|---|
| recon | always | SERP recon — what ranks today and where the information gap is. |
| plan | always | Outline, differentiating angle, and research questions. |
| research | always | Parallel web + knowledge-base research; every fact traced to a source. |
| draft | always | Full draft written against the outline and research. |
| edit | always | Line edit for voice and rhythm; strips AI-tell phrases and banned vocabulary. |
| seo | always | Title + variants, slug, meta tags, FAQ, citation extraction. |
| score | always | 0–100 quality rubric: clarity, hook, specificity, information gain. |
| image | if image: true | Hero image. An image failure marks this stage error but does not fail the job. |
| derivatives | if derivatives ≠ [] | X thread / LinkedIn / newsletter repurposing. |
Result shapes#
// type: "article" — on success
"result": {
"article_id": "9e72c4b0-51af-4c3d-8e6a-2d94b7f01c55",
"title": "SaaS Churn Drops When You Instrument These 5 Moments",
"word_count": 1584,
"quality_score": 86,
"citations": 9
}
// type: "ingest_source" — on success
"result": { "pages": 42, "chunks": 318 }Polling guidance#
Standard articles usually finish within a few minutes; deep_research with add-ons takes longer, and the runner enforces a hard 30-minute ceiling. Start polling at 1.5–2s and back off multiplicatively toward a 10s cap — that keeps you far inside the default 60 req/min rate limit even with several concurrent jobs:
async function waitForJob(jobId, { headers, timeoutMs = 15 * 60_000 } = {}) {
const deadline = Date.now() + timeoutMs;
let delay = 1500;
for (;;) {
const res = await fetch("https://scribe.whizztech.ai/v1/jobs/" + jobId, { headers });
const job = await res.json();
if (job.status === "succeeded") return job;
if (job.status === "failed") throw new Error("job failed: " + job.error);
if (Date.now() > deadline) throw new Error("timed out waiting for job " + jobId);
await new Promise((r) => setTimeout(r, delay));
delay = Math.min(delay * 1.6, 10_000);
}
}The SDK ships this as jobs.waitFor(id, options), including timeout, abort, and progress callbacks.