POST /v1/parse (or an email) creates a job and answers 202 at once. The job runs in the background:
Every finished job sends a webhook event. A job still running after an hour fails, with its
event.
Waiting for a result
- Webhooks are the recommended way.
- Long polling:
GET /v1/jobs/{id}?wait=60holds the request until the job finishes or the seconds pass (at most 600).POST /v1/parse?wait=60does the same at submission. - Listing:
GET /v1/jobslists jobs newest first, filtered bystatus,source,since(created at or after),end_user_idandexternal_business_id, and paged withcursor(the previous page’snext_cursor, with the same filters). Each row carries the job’sintake. - Polling for finished jobs:
GET /v1/jobs?finished_since=lists the jobs that finished (completed, review_required or failed) at or after a time, oldest finish first. Each finished job appears once across the pages, so it is the fallback for a missed webhook. See the sync recipe.
Intake and files
Every job, whatever its status, carriesintake: its source and when it arrived, the broker, the merchant
details and ids the request declared (business_name, business_ein, external_business_id, end_user_id,
contact_email, requested_advance_amount), an emailed job’s envelope (sender, recipients, subject, message id,
mailbox, thread, routing) and its files. A failed job has it too, and so does every job event. See
Integrating a platform.
GET /v1/jobs/{id}/files lists the files the job was sent (position, filename, content type, SHA-256, size,
whether it was an email attachment) and GET /v1/jobs/{id}/files/{position} returns one’s bytes, as sent. For an
emailed job, intake.email.email_id is the tracked email that started it, and its raw message
is GET /v1/emails/{email_id}/raw; intake.email.thread_id lists the whole thread.
Idempotency
Send anIdempotency-Key header (for example your deal id) with POST /v1/parse. Resending with the same key
returns the same job: 202 with created: false while it runs, the finished job once it is done. The same key
with different files, file order, metadata or options is refused with 409; use a new key for a new packet.
Options
The multipart fieldoptions takes JSON:
Limits
- A tenant may have
max_active_jobs(20 by default) jobs queued or running; one more is429withRetry-After. - Each job has a spend cap (
max_cost_usd_per_job, $2.00 by default), and a tenant can have a monthly budget; once it is spent, new jobs are429until the month turns.
Erasure
DELETE /v1/jobs/{id} (jobs:delete) erases a finished job and everything derived from it, at once: its request
and result, its input files, the cached reads of its documents, the model answers it paid for, its metering,
its corrections, email thread entries, the emails that fed it with their raw messages, and
webhook events. This is how a merchant’s deletion request is honoured: erase each of
its jobs. DELETE /v1/jobs?end_user_id= erases every finished job submitted with that end_user_id the same
way, and lists the ones still running, to erase once they end
(details). Erasures are recorded in the audit log. A tenant can also set a retention period
(PATCH /v1/tenant/settings), after which old jobs and emails are erased automatically.
Corrections
A reviewer can correct a finished job: re-categorize a row, override a position’s status, payment, frequency, funder or kind, or leave a month, or one account’s part of a month, out of the averages. The corrections are saved on the job, andGET /v1/jobs/{id} returns the result with them applied (result.corrections says what changed;
?view=computed returns the machine’s result). They never change the job’s status or review_reasons. See
Review and corrections.
Re-running the underwriting
POST /v1/underwriting/withhold recomputes withhold, EBITDA and capacity from months and positions you send,
without reading the documents again or saving anything on a job. To keep a reviewer’s changes on the job
instead, use corrections.