Skip to main content
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=60 holds the request until the job finishes or the seconds pass (at most 600). POST /v1/parse?wait=60 does the same at submission.
  • Listing: GET /v1/jobs lists jobs newest first, filtered by status, source, since (created at or after), end_user_id and external_business_id, and paged with cursor (the previous page’s next_cursor, with the same filters). Each row carries the job’s intake.
  • 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, carries intake: 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 an Idempotency-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 field options takes JSON:

Limits

  • A tenant may have max_active_jobs (20 by default) jobs queued or running; one more is 429 with Retry-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 are 429 until 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, and GET /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.