DocsJobs & Queue

Jobs & Queue

The async job lifecycle: webhooks (recommended), polling, and the job object.

Every extraction is a job. Creating one returns instantly with the job already in pending; the model runs in a queue behind the scenes. You get the result by receiving a webhook (recommended) or by polling the job. This design means a slow or large document never blocks your request, and nothing is silently dropped — a job always ends in completed or failed.

Lifecycle

text
create ──▶ pending ──▶ completed
                  └──▶ failed
StatusMeaning
pendingThe job was accepted and is being processed. output and settled usage are not final yet.
completedDone. output holds the result; usage.cost is the settled charge.
failedThe job could not be completed. The balance hold is refunded.

POST /jobs/create returns the full job object — the same shape as [GET /jobs/:id](/docs/api-reference) — already in pending, with engine, target, and the precharge hold populated. output and the final token usage fill in once it finishes.

Use webhooks, not polling. If your service can receive an HTTPS request, register a webhook_url and let Veralens push the result the moment the job finishes. It is lower latency, costs you no extra requests, and takes load off both your app and the API. Reach for polling only when you genuinely can't receive a webhook — a local script, a notebook, a one-off.

Pass a webhook_url when creating the job and Veralens will POST the result to it as soon as the job finishes. Webhooks require API-key auth — they are not available for playground jobs.

bash
curl -X POST https://api.veralens.ai/v1/jobs/create \
  -H "Authorization: Bearer vl_your_secret_key" \
  -H "Content-Type: application/json" \
  -d '{
    "sources": [{ "type": "text", "data": "..." }],
    "target": "markdown",
    "webhook_url": "https://yourapp.com/hooks/veralens"
  }'

Delivery payload — the body is one of:

json
{ "status": "success", "job_id": "V1StGXR8_Z5jdHi6B", "data": { "merchant": "Acme", "total": 19.9 } }
json
{ "status": "error", "job_id": "V1StGXR8_Z5jdHi6B", "error_code": "parsing_failed" }

For success, data is the parsed result (already an object for json targets, a string for markdown/plaintext).

Headers — verify authenticity before trusting a delivery:

HeaderDescription
x-veralens-signaturet=<unix-seconds>,v1=<hmac-sha256> computed over the raw body.
x-veralens-delivery-attemptThe attempt number for this delivery.

Deliveries retry with exponential backoff if your endpoint does not respond with a 2xx. Two rules for a robust handler:

  • Acknowledge fast, process after. Return 200 immediately, then do the work — a slow handler looks like a failure and triggers retries.
  • **Be idempotent on job_id.** A retry can deliver the same event twice; dedupe so you never double-process.

Polling (fallback)

When you can't receive a webhook, fetch the job until status is no longer pending. Poll about once per second with a ceiling — most jobs finish in a few seconds. Don't hammer the endpoint; it's strictly less efficient than a webhook.

js
async function waitForJob(jobId, key) {
  const BASE = "https://api.veralens.ai/v1"
  for (let attempt = 0; attempt < 60; attempt++) {
    const res = await fetch(`${BASE}/jobs/${jobId}/get`, {
      headers: { Authorization: `Bearer ${key}` },
    })
    const job = (await res.json()).data
    if (job.status === "completed") return JSON.parse(job.output ?? "null")
    if (job.status === "failed") throw new Error(`job ${jobId} failed`)
    await new Promise((r) => setTimeout(r, 1000))
  }
  throw new Error("timed out waiting for job")
}

Billing per job

Creating a job places an estimated hold on your balance. When the job completes, the hold settles to the actual token cost; if it fails, the hold is refunded in full. The final usage.cost on the job is what you were charged. See Pricing & Funds.

Listing and traceability

Use [GET /jobs/list](/docs/api-reference) to page through past jobs, filter by status or source, or search with q. Every job keeps its target, engine, token usage, and cost, so you can audit exactly what each extraction did and what it cost.