POST/api/v1/email-validation/jobsAPI key required
Create an email validation job. Retries with the same Idempotency-Key replay the original job.
Parameters
Idempotency-Key header, required
string | nullRequest body
emails optional
array<string>A list of addresses to validate. Provide this or `csv_text`, never both.
csv_text optional
stringRaw CSV text with a header row and an email column, up to `settings.max_csv_rows` data rows. Provide this or `emails`, never both.
fresh_probe optional
booleanSend a fresh probe to every address, even one whose result is remembered from a recent check.(default false)
CURL EXAMPLE
curl -X POST "https://tuski.ai/api/v1/email-validation/jobs" \
-H "Authorization: Bearer <API_KEY>" \
-H "Idempotency-Key: <UNIQUE_KEY>" \
-H "Content-Type: application/json" \
--data '{}'Responses
200Idempotent replay
ValidationJobResponse201Created; see the Location header.
ValidationJobResponse400Malformed JSON or domain validation error (code invalid_request)
401Missing or invalid Bearer API key
403Missing API-key permission
422Invalid request or missing Idempotency-Key
ApiError429Rate limit exceeded; see Retry-After
500Unexpected server error
503Temporarily unavailable due to database contention; retry shortly
defaultUnexpected error
ApiErrorGET/api/v1/email-validation/jobsAPI key required
List the caller's email-validation jobs, most recent first, paginated.
Any API-key permission may call this route. `limit` (default 20) is clamped into 1..100 and `offset` (default 0) is floored at 0; the response always echoes the EFFECTIVE `limit`/`offset` actually used, so a request outside range is never silently short without a signal. `has_more` is true only when a next page actually has rows.
Errors: 401 missing/invalid key, 429 rate limited, 403 `permission_denied` for a ZeroClick-signed request -- job listing is available to API keys only and is rejected before authentication.
Parameters
limit query
integer(default 20)
offset query
integer(default 0)
CURL EXAMPLE
curl -X GET "https://tuski.ai/api/v1/email-validation/jobs?limit=20&offset=0" \
-H "Authorization: Bearer <API_KEY>"
Responses
200Successful Response
JobListResponse401Missing or invalid Bearer API key
403ZeroClick-signed request
422Validation Error
ApiError429Rate limit exceeded; see Retry-After
defaultUnexpected error
ApiErrorGET/api/v1/email-validation/jobs/{job_id}API key required
Return a job's status and per-outcome counters, plus an ETA.
Any API-key permission may call this route. Errors: 401 missing/ invalid key, 404 `job_not_found` for a job that does not exist or belongs to another organization, 429 rate limited. A ZeroClick-signed caller uses the same path; a guard failure (bad signature, payment required, allowance unavailable) comes back in ZeroClick's own `{"error": ...}` envelope built by the ZeroClick SDK, and every other error (for example `job_not_found`, raised by `_zc_read`) comes back as `{"detail": ...}` (plus `code` only when `ZEROCLICK_ERROR_ENVELOPE_V2` is on). `wait` is ignored for that caller (see `_zc_read`).
Parameters
job_id path, required
integerwait query
integer | nullLong-poll instead of returning immediately: wait on the server for up to this many seconds for the job to reach a terminal status, then return whatever status is current. Clamped into 1..50. Adds `done` and `poll_after_seconds` to the response. Ignored on a ZeroClick-signed request, which always returns immediately.
CURL EXAMPLE
curl -X GET "https://tuski.ai/api/v1/email-validation/jobs/<job_id>?wait=<wait>" \
-H "Authorization: Bearer <API_KEY>"
Responses
200Successful Response
ValidationStatusResponse401Missing or invalid Bearer API key
403Missing API-key permission
422Validation Error
ApiError429Rate limit exceeded; see Retry-After
500Unexpected server error
defaultUnexpected error
ApiErrorGET/api/v1/email-validation/jobs/{job_id}/resultsAPI key required
Requires the `export` permission. The `csv` format is paginated by `limit`/`offset` exactly like `json`, but the CSV body carries no `has_more` field. To make truncation visible, the response includes an `X-Total-Count` header (the true row count for this job/status filter) and, when more rows remain past this page, a `Link: <...>; rel="next"` header (RFC 8288) advertising the next `offset` to request.
`status` filters rows by `leads.status` and must be one of the values the pipeline actually writes: terminal outcomes -- valid, invalid, unknown, unreachable, mailbox_full, blocked, soft_temp, routing_error, other_bounce -- or in-flight/job-level states that are not a deliverability judgement -- pending, queued, sending, sent, cancelled. Omit `status` to get every row. An unrecognized value is rejected with 422 rather than silently returning zero rows. Each row also carries a human-facing `verdict` field (see app/results.py) derived from `status`/`preflight`/`reason`, always one of `deliverable`, `catch_all`, `role`, `typo`, `invalid`, `full_box`, `no_mx`, `burner`, `no_reply`, `blocked`, `unknown`, `soft_temp`, `routing_error`, `other_bounce`, `not_judged`. A typo of a well-known mail provider's domain is `typo`; a domain with no mail server is `no_mx`.
Errors: 401 missing/invalid key, 403 missing `export` permission, 404 `job_not_found`, 413 CSV export too large, 422 `invalid_request`/`unknown_status_filter`, 429 rate limited. A ZeroClick-signed caller uses the same path; a guard failure (bad signature, payment required, allowance unavailable) comes back in ZeroClick's own `{"error": ...}` envelope built by the ZeroClick SDK, and every other error comes back as `{"detail": ...}` (plus `code` only when `ZEROCLICK_ERROR_ENVELOPE_V2` is on).
Parameters
job_id path, required
integerformat query
"json" | "csv"(default "json")(one of "json", "csv")
limit query
integer | nulloffset query
integer(default 0)
status query
string | nullCURL EXAMPLE
curl -X GET "https://tuski.ai/api/v1/email-validation/jobs/<job_id>/results?format=json&limit=<limit>&offset=0&status=<status>" \
-H "Authorization: Bearer <API_KEY>"
Responses
200JSON page or CSV export, selected by format
ValidationResultsResponse400Invalid results request
401Missing or invalid Bearer API key
403Missing export permission
422Invalid query parameters
ApiError429Rate limit exceeded; see Retry-After
500Unexpected server error
defaultUnexpected error
ApiErrorGET/api/v1/email-validation/jobs/{job_id}/results/downloadNo API key required
Stream a results CSV from a short-lived signed link.
This link is handed out by the MCP `get_results`/`get_results_csv` tools when a selection is too large to embed directly in a tool result (app/mcp_server.py's `_get_results_csv_impl`). No API key is required here: the signed `token` itself is the credential, valid for a few minutes, so a person can open it directly in a browser while a headless agent fetches it with curl or its own HTTP client. A bad signature, an expired token, a `job_id` that does not match the link, or a job that has since moved to another organization or been deleted all answer the identical 404 below, so nothing here confirms which case actually happened.
Parameters
job_id path, required
integertoken query, required
stringCURL EXAMPLE
curl -X GET "https://tuski.ai/api/v1/email-validation/jobs/<job_id>/results/download?token=<token>"
Responses
200Successful Response
unknown404Link is invalid or has expired
422Validation Error
ApiError429Rate limit exceeded; see Retry-After
defaultUnexpected error
ApiErrorPOST/api/v1/email-validation/jobs/{job_id}/cancelAPI key required
Cancel a job and refund one credit per unsent lead. Requires `validate`.
`refunded` is the credits this call returned for the charged leads it stopped before they were sent. Leads already sent still get their verdict after the bounce check, and the job's no-MX and send-failure refunds follow then.
A job already 'done' or already 'cancelled' is a no-op: nothing changes and nothing is refunded again. A ZeroClick-funded job's `zeroclick_refund_note` explains the refund landed in Tuski's shared wallet, not the caller's. Errors: 401 missing/invalid key, 403 missing `validate` permission, 404 `job_not_found`, 429 rate limited. A ZeroClick-signed caller uses the same path; a guard failure (bad signature, payment required, allowance unavailable) comes back in ZeroClick's own `{"error": ...}` envelope built by the ZeroClick SDK, and every other error comes back as `{"detail": ...}` (plus `code` only when `ZEROCLICK_ERROR_ENVELOPE_V2` is on).
Parameters
job_id path, required
integerCURL EXAMPLE
curl -X POST "https://tuski.ai/api/v1/email-validation/jobs/<job_id>/cancel" \
-H "Authorization: Bearer <API_KEY>"
Responses
200Successful Response
ValidationCancelResponse400Invalid cancellation request
401Missing or invalid Bearer API key
403Missing API-key permission
422Validation Error
ApiError429Rate limit exceeded; see Retry-After
500Unexpected server error
defaultUnexpected error
ApiErrorPOST/api/v1/email-validation/jobs/{job_id}/pauseAPI key required
Pause a job so its senders stop claiming new leads. Requires `validate`.
A job already 'done' or already 'cancelled' is a no-op: pausing never rewrites a terminal job's status, and in particular never erases a cancelled job's terminal state.
Errors: 401 missing/invalid key, 403 missing `validate` permission (or a ZeroClick-signed request, rejected before authentication -- pausing is available to API keys only), 404 `job_not_found`, 429 rate limited.
Parameters
job_id path, required
integerCURL EXAMPLE
curl -X POST "https://tuski.ai/api/v1/email-validation/jobs/<job_id>/pause" \
-H "Authorization: Bearer <API_KEY>"
Responses
200Successful Response
PauseJobResponse401Missing or invalid Bearer API key
403Missing API-key permission, or ZeroClick-signed request
422Validation Error
ApiError429Rate limit exceeded; see Retry-After
defaultUnexpected error
ApiErrorPOST/api/v1/email-validation/jobs/{job_id}/resumeAPI key required
Resume a paused job and restart its senders. Requires `validate`.
Cancelled jobs cannot be resumed (422). A 'done' job is a no-op: it reports `resumed: false, status: "done"` rather than a false success, and no workers are spawned for it. Workers are started only when this call actually transitioned the job back to sending.
Errors: 401 missing/invalid key, 403 missing `validate` permission (or a ZeroClick-signed request, rejected before authentication -- resuming is available to API keys only), 404 `job_not_found`, 422 `invalid_request` for a cancelled job, 429 rate limited.
Parameters
job_id path, required
integerCURL EXAMPLE
curl -X POST "https://tuski.ai/api/v1/email-validation/jobs/<job_id>/resume" \
-H "Authorization: Bearer <API_KEY>"
Responses
200Successful Response
ResumeJobResponse401Missing or invalid Bearer API key
403Missing API-key permission, or ZeroClick-signed request
422Cannot resume a cancelled job (code invalid_request)
ApiError429Rate limit exceeded; see Retry-After
defaultUnexpected error
ApiError