Docs · API · Jobs

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#

FieldTypeDescription
queuedinitialCreated, credits debited, waiting for a runner.
processingrunningPipeline executing; stage and stages update live.
succeededterminalFinished. result is populated; a job.succeeded webhook fires.
failedterminalSomething broke. error explains; charged credits are auto-refunded; a job.failed webhook fires.
canceledterminalReserved status for operator-canceled jobs. Not currently reachable via the public API.

A single retrieval endpoint covers all job types:

GET/v1/jobs/{id}
Response · 200 OK (mid-pipeline)
{
  "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#

FieldTypeDescription
idstringJob ID (UUID).
object"job"Resource type.
typestringarticle | ingest_source.
statusstringqueued | processing | succeeded | failed | canceled.
stagestring | nullKey of the stage currently (or last) running.
stagesstage[]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).
resultobjectEmpty {} until success — shapes below.
errorstring | nullFailure reason when status is failed.
credits_chargedintegerCredits debited for this job.
created_atstringISO 8601.
finished_atstring | nullSet 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.

FieldTypeDescription
reconalwaysSERP recon — what ranks today and where the information gap is.
planalwaysOutline, differentiating angle, and research questions.
researchalwaysParallel web + knowledge-base research; every fact traced to a source.
draftalwaysFull draft written against the outline and research.
editalwaysLine edit for voice and rhythm; strips AI-tell phrases and banned vocabulary.
seoalwaysTitle + variants, slug, meta tags, FAQ, citation extraction.
scorealways0–100 quality rubric: clarity, hook, specificity, information gain.
imageif image: trueHero image. An image failure marks this stage error but does not fail the job.
derivativesif derivatives ≠ []X thread / LinkedIn / newsletter repurposing.

Result shapes#

result by job type
// 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:

Node — poll with backoff
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.