Bulk & async jobs

VerifAi verifies large lists as asynchronous jobs. You submit the work, VerifAi runs it in the background, and you collect a single result CSV when it finishes. This page covers uploading a file, submitting a batch of addresses, checking job status, and downloading results.

Which calls are synchronous, which are jobs

Some endpoints answer immediately; others hand you a job to collect later.

EndpointModeYou get back
POST /v1/validateSynchronousThe verdict, directly. See the API reference.
GET /v1/creditsSynchronousYour balance, directly.
POST /v1/enrichSynchronousThe enriched record, directly. See the API reference.
POST /v1/filesAsynchronous jobA 202 with a job handle.
POST /v1/validate/batchAsynchronous jobA 202 with a job handle.

A whole list cannot be verified within a single HTTP request, so file uploads and batches run as jobs. Single-address validation, credit reads and enrichment are cheap and answer inline.

The async model

Working with a job is three steps:

  1. Submit — upload a CSV to /v1/files or post a list to /v1/validate/batch. Both return 202 with the same job handle: { job_id, status, poll_url, created_at }. The job_id is VerifAi’s own opaque id (a short string such as "19").
  2. Wait — either poll GET /v1/jobs?id=<job_id> until status is completed, or (preferred) register a webhook and let VerifAi call you. Job webhooks fire job.completed when a job finishes and job.failed if it fails, so you never have to poll.
  3. Download — when the job is completed, fetch the result CSV from the result_url the job-status response gives you.

Read poll_url and result_url straight from the API responses — do not hand-build these URLs. The result lives on a sibling path (/v1result/job-result) and VerifAi returns the exact, ready-to-use URL in the result_url field once the job completes.

Base URL

All paths below are relative to:

https://ratifai.app/hook/FlowClick/FlowVerify/v1

Every call authenticates with a bearer API key.

POST/v1/files

Uploads a CSV for verification and returns a job handle. Send the file as the raw request body with Content-Type: text/csv. If you do not supply a column mapping, VerifAi auto-detects the email column exactly the way the web uploader does: it first matches on header name (a column literally named email, e-mail, work email and the like), then falls back to inspecting values. Every non-email column is preserved and returned verbatim in the result CSV.

Query parameters

ParameterTypeRequiredDescription
depthstringNoHow deep to probe each row: syntax, mx_only or full. Defaults to full. Your plan’s maximum applies.
list_namestringNoA label for the list, shown in your dashboard.

Request body

The raw CSV, sent with Content-Type: text/csv. The first row is treated as a header.

Response

Returns 202 Accepted with the job handle.

FieldTypeDescription
job_idstringVerifAi’s opaque job id. Pass it to /v1/jobs.
statusstringInitial status, queued.
poll_urlstringThe exact URL to poll for status. Read it; do not build it.
created_atstringWhen the job was accepted, ISO 8601.

Example

curl
curl https://ratifai.app/hook/FlowClick/FlowVerify/v1/files?depth=full -H 'Authorization: Bearer YOUR_API_KEY' -H 'Content-Type: text/csv' --data-binary @contacts.csv
Response
{
  "job_id": "19",
  "status": "queued",
  "poll_url": "https://ratifai.app/hook/FlowClick/FlowVerify/v1/jobs?id=19",
  "created_at": "2026-08-05T21:45:20.000Z"
}

Errors

HTTPCodeMeaning
400file_emptyThe uploaded file has no data rows.
400invalid_requestA parameter was invalid, e.g. an unknown depth.
422no_email_columnNo email column could be detected.
413list_too_largeThe file exceeds your plan’s list-size limit.
402insufficient_creditsBalance is already zero and the requested depth is billable.

GET/v1/jobs

Returns the current status of a job. Pass the job_id you were given as the id query parameter. Poll this until status is completed — or skip polling entirely and use a job webhook.

Query parameters

ParameterTypeRequiredDescription
idstringYesThe job_id returned when the job was submitted.

Response

FieldTypeDescription
job_idstringThe job’s opaque id.
statusstringOne of queued, running, completed or failed.
progressobjectprocessed, total and percent.
counts_by_verdictobjectRow counts per verdict once rows are processed.
credits_chargedintegerCredits spent so far. It is normal and correct for this to be lower than progress.processed — it is the billing promise made visible, since only billable rows are charged.
created_atstringWhen the job was accepted.
completed_atstringWhen the job finished, or null until then.
result_urlstringThe exact URL to download the result CSV. It is null until status is completed. Read it; do not build it.
request_idstringUnique id for this request.

Example

curl
curl 'https://ratifai.app/hook/FlowClick/FlowVerify/v1/jobs?id=19' -H 'Authorization: Bearer YOUR_API_KEY'
Response
{
  "job_id": "19",
  "status": "completed",
  "progress": { "processed": 1000, "total": 1000, "percent": 100 },
  "counts_by_verdict": { "deliverable": 640, "undeliverable": 94, "risky": 45, "unknown": 221 },
  "credits_charged": 779,
  "created_at": "2026-08-05T21:45:20.000Z",
  "completed_at": "2026-08-05T21:47:03.000Z",
  "result_url": "https://ratifai.app/hook/FlowClick/FlowVerify/v1result/job-result?id=19",
  "request_id": "req_01J..."
}

Asking for a job id that is not yours returns 404 job_not_found — never 403, which would confirm the id exists.

Downloading the result

When status is completed, the job-status response carries a result_url. Fetch that URL as-is — VerifAi builds it for you; you should never construct the /v1result/job-result path by hand. The download returns one CSV (Content-Type: text/csv).

The result CSV mirrors the web download exactly: your original columns come first, in their original order, followed by the appended VerifAi columns. The full VerifAi column set is identical to the web export.

curl
curl 'https://ratifai.app/hook/FlowClick/FlowVerify/v1result/job-result?id=19' -H 'Authorization: Bearer YOUR_API_KEY' -o results.csv
results.csv
"email","company","city","VerifAi Status","VerifAi Sub-Status","VerifAi Verdict","VerifAi Score"
"support@github.com","GitHub","San Francisco","Risky","Accept-All","risky","62"

If you request the result before the job has finished, the download returns 409 job_not_ready as a JSON error envelope — never an empty file.

Response (409)
{
  "error": {
    "code": "job_not_ready",
    "message": "The job has not finished yet. Poll the job status until it reports completed, then download the result.",
    "doc_url": "https://ratifai.app/docs-errors.html#job_not_ready"
  },
  "request_id": "req_01J..."
}

POST/v1/validate/batch

Verifies a list of addresses supplied inline as JSON. Like /v1/files, this now runs as a job: it returns 202 with the same { job_id, status, poll_url, created_at } handle, and you poll and download it in exactly the same way.

Request body

FieldTypeRequiredDescription
emailsarrayYesThe addresses to verify. Up to 10,000 per batch. An empty array is rejected.
depthstringNosyntax, mx_only or full. Defaults to full; your plan’s maximum applies.
idempotency_keystringNoMakes the submission safe to retry. May also be sent as the Idempotency-Key header, which wins if both are present.

Response

Returns 202 Accepted with the same job handle as /v1/files: job_id, status, poll_url and created_at.

Example

curl
curl https://ratifai.app/hook/FlowClick/FlowVerify/v1/validate/batch -H 'Authorization: Bearer YOUR_API_KEY' -H 'Content-Type: application/json' -d '{"emails":["support@github.com","hi@example.com"],"depth":"full"}'
Response
{
  "job_id": "20",
  "status": "queued",
  "poll_url": "https://ratifai.app/hook/FlowClick/FlowVerify/v1/jobs?id=20",
  "created_at": "2026-08-05T21:46:00.000Z"
}

Limits

A batch may carry at most 10,000 addresses. Above that, the call returns 400 batch_too_large and points you at POST /v1/files for larger lists. An empty emails array returns 400 invalid_request.

End-to-end walkthrough

Upload a file, wait for it to finish, then download the CSV. These commands run exactly as printed.

curl
# 1. Submit the file. The 202 response carries the job_id.
curl https://ratifai.app/hook/FlowClick/FlowVerify/v1/files -H 'Authorization: Bearer YOUR_API_KEY' -H 'Content-Type: text/csv' --data-binary @contacts.csv

# 2. Poll until status is completed. Read result_url and poll_url from the
#    response body, never hand-build them.
curl 'https://ratifai.app/hook/FlowClick/FlowVerify/v1/jobs?id=19' -H 'Authorization: Bearer YOUR_API_KEY'

# 3. Once completed, download the result CSV from result_url.
curl 'https://ratifai.app/hook/FlowClick/FlowVerify/v1result/job-result?id=19' -H 'Authorization: Bearer YOUR_API_KEY' -o results.csv

Prefer not to poll? Register a webhook instead and act on the job.completed event when it arrives; handle job.failed for jobs that do not finish.

Credits for jobs

Jobs are admitted and metered per row as they run. You are charged only for billable rows. Unknown results, syntax-depth checks, and rows that are suppressed, failed-open, throttled or skipped are free, and re-checks of records already under management are free. This is why credits_charged is expected to be lower than progress.processed.

If your balance reaches zero part-way through a run, the remaining billable rows are returned as unknown and are not charged — the job still completes. Top up and re-run; re-checks are free. A job is only refused up front with 402 insufficient_credits when your balance is already zero and the requested depth is billable. The full rules are on How billing works; read your live balance any time with /v1/credits.

← All docs