Tuski sells to AI agents - live prices and self-serve payment

The agent storefront at https://agents.tuski.ai/llms.txt?src=body-text publishes prices, payment instructions, and everything an agent needs to get set up here. Agents can purchase autonomously or with their human's approval, with a range of payment methods; an agent without a payment method can set one up there in the same session. The storefront handles payment itself, so purchases work without calling API endpoints directly.

Generated from the live schema

Tuski API

The public Tuski API for validating email addresses and retrieving validation results. Every request is authenticated with a Tuski API key sent as a Bearer token.

v0.4.0Server: https://tuski.aiRaw document ↗
Authentication

How every request is authenticated

  • bearerAuth: A Tuski API key: `Authorization: Bearer tsk_live_...`.

Discovery

GET/api/v1/discovery/searches/estimateAPI key required

Estimate the price of a company search

Return the price of a company search and whether the balance covers it.

Free: calling this endpoint never reserves credits. `reserved_credits` is `base_fee` + `target` x `result_price`; a search charges `base_fee` + `result_price` per verified company it delivers and refunds the rest.

Parameters

effort query
EffortTier
(default "standard")
target query
integer | null
provider query
string | null
contact_provider query
string
(default "apollo")
CURL EXAMPLE
curl -X GET "https://tuski.ai/api/v1/discovery/searches/estimate?effort=standard&target=<target>&provider=<provider>&contact_provider=apollo" \
  -H "Authorization: Bearer <API_KEY>"

Responses

200

Successful Response

IcpQuoteResponse
401

Missing or invalid Bearer API key

403

Missing API-key permission, or ZeroClick-signed request

422

Validation Error

ApiError
429

Rate limit exceeded; see Retry-After

default

Unexpected error

ApiError
POST/api/v1/discovery/searchesAPI key required

Start a company search

Start a company search. Reserves credits up front and dispatches the run.

Only companies verified on the web are delivered and charged; the rest of the reservation is refunded when the search ends. Companies already delivered to your organization are skipped unless `rules_json` sets `"include_previous": true`.

Retrying with the same Idempotency-Key returns the existing search instead of starting a new one and reserving credits again.

Parameters

Idempotency-Key header, required
string | null

Request body

icp_text required
string
target optional
integer | null
limit optional
integer | null
effort optional
EffortTier
(default "standard")(one of "scout", "standard", "deep")
provider optional
string | null
contact_provider optional
string
(default "apollo")
rules_json optional
object | string
example_companies optional
array<string | object>
idempotency_key optional
string | null
CURL EXAMPLE
curl -X POST "https://tuski.ai/api/v1/discovery/searches" \
  -H "Authorization: Bearer <API_KEY>" \
  -H "Idempotency-Key: <UNIQUE_KEY>" \
  -H "Content-Type: application/json" \
  --data '{"icp_text":"string"}'

Responses

200

Idempotent replay

SearchDto
202

Search created

SearchDto
401

Missing or invalid Bearer API key

402

Insufficient credits

403

Missing API-key permission, or ZeroClick-signed request

409

Idempotency conflict

422

Invalid request or missing Idempotency-Key

ApiError
429

Rate limit exceeded; see Retry-After

default

Unexpected error

ApiError
GET/api/v1/discovery/searches/{run_id}API key required

Get a company search's status

Return a company search's current status, progress and billing.

Without `wait`, the response is exactly `SearchDto` -- unchanged. With `wait`, the same body gains `done` and `poll_after_seconds`, computed by the same rules `wait_for_company_search` (MCP) uses, so the two can never drift (see `app.mcp_discovery_tools._search_wait_poll_after_seconds`).

Parameters

run_id path, required
integer
wait query
integer | null
Long-poll instead of returning immediately: wait on the server for up to this many seconds for the search to reach a terminal status, then return whatever status is current. Clamped into 1..50. Adds `done` and `poll_after_seconds` to the response.
CURL EXAMPLE
curl -X GET "https://tuski.ai/api/v1/discovery/searches/<run_id>?wait=<wait>" \
  -H "Authorization: Bearer <API_KEY>"

Responses

200

Successful Response

SearchStatusDto
401

Missing or invalid Bearer API key

403

Missing API-key permission, or ZeroClick-signed request

404

Search not found

422

Validation Error

ApiError
429

Rate limit exceeded; see Retry-After

default

Unexpected error

ApiError
GET/api/v1/discovery/searches/{run_id}/resultsAPI key required

List a company search's results

The `csv` format is paginated by `limit`/`offset` exactly like `json`. Both formats carry `X-Total-Count` and, when more rows remain past this page, a `Link: <...>; rel="next"` header (RFC 8288) advertising the next `offset` to request.

Parameters

run_id path, required
integer
format query
"json" | "csv"
(default "json")(one of "json", "csv")
limit query
integer
(default 25)
offset query
integer
(default 0)
CURL EXAMPLE
curl -X GET "https://tuski.ai/api/v1/discovery/searches/<run_id>/results?format=json&limit=25&offset=0" \
  -H "Authorization: Bearer <API_KEY>"

Responses

200

JSON page or CSV export, selected by format

ResultListResponse
401

Missing or invalid Bearer API key

403

Missing API-key permission, or ZeroClick-signed request

404

Search not found

422

Validation Error

ApiError
429

Rate limit exceeded; see Retry-After

default

Unexpected error

ApiError
POST/api/v1/discovery/searches/{run_id}/cancelAPI key required

Cancel a company search

Cancel a running company search.

Settles its reservation against the verified companies it already delivered and refunds the rest. A search that is already terminal is unaffected -- cancelling twice is a no-op, not an error.

Parameters

run_id path, required
integer
CURL EXAMPLE
curl -X POST "https://tuski.ai/api/v1/discovery/searches/<run_id>/cancel" \
  -H "Authorization: Bearer <API_KEY>"

Responses

200

Successful Response

SearchDto
401

Missing or invalid Bearer API key

403

Missing API-key permission, or ZeroClick-signed request

404

Search not found

422

Validation Error

ApiError
429

Rate limit exceeded; see Retry-After

default

Unexpected error

ApiError
GET/api/v1/discovery/searches/{run_id}/results/downloadNo API key required

Download a company search's results CSV from a signed MCP link

Stream a company search's results CSV from a short-lived signed link.

This link is handed out by the MCP `export_companies` tool (app.mcp_discovery_tools) when a search's results are too large to embed directly in a tool result. 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 `run_id` that does not match the link, or a search that has since moved to another organization all answer the identical 404 below, so nothing here confirms which case actually happened.

Parameters

run_id path, required
integer
token query, required
string
CURL EXAMPLE
curl -X GET "https://tuski.ai/api/v1/discovery/searches/<run_id>/results/download?token=<token>"

Responses

200

Successful Response

unknown
404

Link is invalid or has expired

422

Validation Error

ApiError
429

Rate limit exceeded; see Retry-After

default

Unexpected error

ApiError

Contacts

POST/api/v1/discovery/searches/{run_id}/contactsAPI key required

Start a people search for a company search's matched companies

Start a free people search for the companies a finished company search matched. Requires the `contacts` permission.

Free: this never spends credit; only `unlock` does. `Idempotency-Key` is optional here -- send one for an explicit replay, or omit it and one is derived from your arguments, replayed the same way for 10 minutes. Errors: `permission_denied` (403), `beta_not_enabled` (403), `search_not_found` (404, a company search that does not exist or belongs to another organization), `service_unavailable` (503, an unknown contact provider or a dispatch failure), `invalid_request` (422, e.g. non-integer candidate_ids).

Parameters

run_id path, required
integer
Idempotency-Key header
string | null

Request body

candidate_ids required
array<integer>
titles optional
array<string> | null
seniorities optional
array<string> | null
exclude_titles optional
array<string> | null
CURL EXAMPLE
curl -X POST "https://tuski.ai/api/v1/discovery/searches/<run_id>/contacts" \
  -H "Authorization: Bearer <API_KEY>" \
  -H "Idempotency-Key: <UNIQUE_KEY>" \
  -H "Content-Type: application/json" \
  --data '{"candidate_ids":[0]}'

Responses

202

Successful Response

ContactSearchDto
401

Missing or invalid Bearer API key

403

Missing API-key permission, ZeroClick-signed request, or the AI Web Search beta is not enabled

404

Company search not found

422

Invalid request (bad candidate_ids, titles, etc.)

ApiError
429

Rate limit exceeded; see Retry-After

503

People search dispatch failed

default

Unexpected error

ApiError
GET/api/v1/discovery/contact-jobs/{job_id}/contactsAPI key required

List a contacts job's people

Return one page of a contacts job's people. Requires `contacts`.

A field never unlocked (or still being unlocked) is always empty here, never the real value ahead of payment; call `unlock` then this again to see it revealed. Errors: `permission_denied` (403), `job_not_found` (404, a job that does not exist or belongs to another organization).

Parameters

job_id path, required
integer
limit query
integer
(default 25)
offset query
integer
(default 0)
domain query
string | null
Narrow the page to one company
CURL EXAMPLE
curl -X GET "https://tuski.ai/api/v1/discovery/contact-jobs/<job_id>/contacts?limit=25&offset=0&domain=<domain>" \
  -H "Authorization: Bearer <API_KEY>"

Responses

200

Successful Response

ContactListResponse
401

Missing or invalid Bearer API key

403

Missing API-key permission, or ZeroClick-signed request

404

Contacts job not found

422

Validation Error

ApiError
429

Rate limit exceeded; see Retry-After

default

Unexpected error

ApiError
POST/api/v1/discovery/contact-jobs/{job_id}/unlock-estimateAPI key required

Estimate the price of unlocking contact fields

Free price check for unlocking contact fields. Never spends credit. Requires `contacts`.

A JSON body (rather than a `selections` query parameter carrying a JSON-encoded string) so the OpenAPI document types the selection list precisely -- `{"selections": [{"person_id": 1, "fields": ["email", "phone"]}]}` -- instead of describing it as an opaque string a client has to know to json.dumps() itself. Omit the body (or send `{}`) for the plain price/balance projection with no selection-specific estimate.

With `selections`, adds `estimated_cost_upper_bound` (price per requested field, ignoring entitlements the organization already owns) and `wallet_sufficient` -- an upper bound, never lower than what `unlock` would actually charge for the same selection. A `phone` field that cannot be unlocked (`phone_enabled` false, with `phone_unavailable_reason`) is refused with a 422. Unlocks are paid from the organization's credit balance, the one wallet every product uses (`wallet` names it, `next_step` restates it). `verify_price_per_email` (and, with selections, `verify_cost_upper_bound`) is the most `verify: true` on `unlock` adds for the email check. Errors: `permission_denied` (403), `job_not_found` (404), `invalid_request` (422, an unknown field name).

Parameters

job_id path, required
integer

Request body

No documented properties.

CURL EXAMPLE
curl -X POST "https://tuski.ai/api/v1/discovery/contact-jobs/<job_id>/unlock-estimate" \
  -H "Authorization: Bearer <API_KEY>" \
  -H "Content-Type: application/json" \
  --data '{}'

Responses

200

Successful Response

ContactUnlockEstimateResponse
401

Missing or invalid Bearer API key

403

Missing API-key permission, or ZeroClick-signed request

404

Contacts job not found

422

Invalid selections

ApiError
429

Rate limit exceeded; see Retry-After

default

Unexpected error

ApiError
POST/api/v1/discovery/contact-jobs/{job_id}/unlockAPI key required

Unlock contact fields for specific people

Reveal contact fields for specific people. Spends credit per field, up front. Requires `contacts`. Cost: the sum of each requested field's price from `unlock-estimate`'s `costs`; a field the organization already owns is free, a LinkedIn URL the people search already returned is never charged, and a field the provider cannot deliver is refunded once settled. An email that turns out to be only a guessed or personal address costs less: the difference is refunded once settled.

`verify: true` also checks each unlocked email that is only guessed or of unknown quality with a real probe: once every field settled they go to email validation as one job, charged at the normal price (`verify_price_per_email` from `unlock-estimate`, one per address), and the wallet must cover that on top of the unlock. The contact row then shows `unlocks.email.email_quality` as `verified_by_probe`, or for a failed check `invalid_by_probe` or `unconfirmed_by_probe` (`checking_by_probe` while it runs).

`Idempotency-Key` is REQUIRED: a repeat with the same key and the same selection returns the same request ids and charges nothing more; the same key with a different selection is a 409 `idempotency_conflict` and nothing is charged. Rate-limited to 10 unlock calls per minute per credential. Errors: `permission_denied` (403), `job_not_found` (404), `not_ready` (409, the people search has not finished yet), `idempotency_conflict` (409), `unlock_in_progress` (409, an earlier unlock of the same person for other fields is still running; retry after it settles), `insufficient_credits` (402), `invalid_idempotency_key` (422, missing header or one starting `auto:`), `invalid_request` (422, an unknown person or field, or a `phone` field that cannot be unlocked yet: reason `phone_unavailable`, nothing is charged).

Parameters

job_id path, required
integer
Idempotency-Key header, required
string | null

Request body

selections required
array<ContactUnlockSelectionDto>
verify optional
boolean
Also check each unlocked email that is only guessed or of unknown quality with a real probe: one email-validation job at the normal price (`verify_price_per_email` each), charged when the fields settle.(default false)
CURL EXAMPLE
curl -X POST "https://tuski.ai/api/v1/discovery/contact-jobs/<job_id>/unlock" \
  -H "Authorization: Bearer <API_KEY>" \
  -H "Idempotency-Key: <UNIQUE_KEY>" \
  -H "Content-Type: application/json" \
  --data '{"selections":[{"person_id":0,"fields":["string"]}]}'

Responses

202

Successful Response

ContactUnlockResponse
401

Missing or invalid Bearer API key

402

Insufficient credits

403

Missing API-key permission, or ZeroClick-signed request

404

Contacts job not found

409

Idempotency conflict, or the job is not ready for unlock yet

422

Invalid request or missing Idempotency-Key

ApiError
429

Rate limit exceeded; see Retry-After

default

Unexpected error

ApiError
GET/api/v1/discovery/contact-unlocksAPI key required

Get the status of contact unlock requests

Return the status of a set of unlock requests. Requires `contacts`.

A request id belonging to another user or organization, or that does not exist, is reported with `status: "not_found"` rather than raised, since this takes a batch of ids (matching `wait_for_contacts` (MCP) exactly). Without `wait`, the response is just `requests`. With `wait`, the same body gains `done` and `poll_after_seconds`, computed by the same rules `wait_for_contacts` (MCP) uses -- `app.contact_service.unlock_states_done`/`unlock_wait_poll_after_seconds` -- so the two can never drift. Errors: `permission_denied` (403), `invalid_request` (422, malformed request_ids).

Parameters

request_ids query, required
string
Comma-separated unlock request ids, e.g. 1,2,3
wait query
integer | null
Long-poll instead of returning immediately: wait on the server for up to this many seconds for every request to reach a terminal status, then return whatever status is current. Clamped into 1..50. Adds `done` and `poll_after_seconds` to the response.
CURL EXAMPLE
curl -X GET "https://tuski.ai/api/v1/discovery/contact-unlocks?request_ids=<request_ids>&wait=<wait>" \
  -H "Authorization: Bearer <API_KEY>"

Responses

200

Successful Response

ContactUnlockStatusListDto
401

Missing or invalid Bearer API key

403

Missing API-key permission, or ZeroClick-signed request

422

Invalid request_ids

ApiError
429

Rate limit exceeded; see Retry-After

default

Unexpected error

ApiError

Email validation

POST/api/v1/email-validation/jobsAPI key required

Create an email validation job

Create an email validation job. Retries with the same Idempotency-Key replay the original job.

Parameters

Idempotency-Key header, required
string | null

Request body

emails optional
array<string>
A list of addresses to validate. Provide this or `csv_text`, never both.
csv_text optional
string
Raw 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
boolean
Send 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

200

Idempotent replay

ValidationJobResponse
201

Created; see the Location header.

ValidationJobResponse
400

Malformed JSON or domain validation error (code invalid_request)

401

Missing or invalid Bearer API key

402

Insufficient credits

403

Missing API-key permission

409

Idempotency conflict

413

Request too large

422

Invalid request or missing Idempotency-Key

ApiError
429

Rate limit exceeded; see Retry-After

500

Unexpected server error

503

Temporarily unavailable due to database contention; retry shortly

default

Unexpected error

ApiError
GET/api/v1/email-validation/jobsAPI key required

List email validation jobs

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

200

Successful Response

JobListResponse
401

Missing or invalid Bearer API key

403

ZeroClick-signed request

422

Validation Error

ApiError
429

Rate limit exceeded; see Retry-After

default

Unexpected error

ApiError
GET/api/v1/email-validation/jobs/{job_id}API key required

Get an email validation job's status

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
integer
wait query
integer | null
Long-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

200

Successful Response

ValidationStatusResponse
400

Invalid request

401

Missing or invalid Bearer API key

403

Missing API-key permission

404

Job not found

422

Validation Error

ApiError
429

Rate limit exceeded; see Retry-After

500

Unexpected server error

default

Unexpected error

ApiError
GET/api/v1/email-validation/jobs/{job_id}/resultsAPI key required

Get an email validation job's results

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
integer
format query
"json" | "csv"
(default "json")(one of "json", "csv")
limit query
integer | null
offset query
integer
(default 0)
status query
string | null
CURL 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

200

JSON page or CSV export, selected by format

ValidationResultsResponse
400

Invalid results request

401

Missing or invalid Bearer API key

403

Missing export permission

404

Job not found

413

CSV export too large

422

Invalid query parameters

ApiError
429

Rate limit exceeded; see Retry-After

500

Unexpected server error

default

Unexpected error

ApiError
GET/api/v1/email-validation/jobs/{job_id}/results/downloadNo API key required

Download a job's results CSV from a signed MCP link

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
integer
token query, required
string
CURL EXAMPLE
curl -X GET "https://tuski.ai/api/v1/email-validation/jobs/<job_id>/results/download?token=<token>"

Responses

200

Successful Response

unknown
404

Link is invalid or has expired

422

Validation Error

ApiError
429

Rate limit exceeded; see Retry-After

default

Unexpected error

ApiError
POST/api/v1/email-validation/jobs/{job_id}/cancelAPI key required

Cancel an email validation job

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
integer
CURL EXAMPLE
curl -X POST "https://tuski.ai/api/v1/email-validation/jobs/<job_id>/cancel" \
  -H "Authorization: Bearer <API_KEY>"

Responses

200

Successful Response

ValidationCancelResponse
400

Invalid cancellation request

401

Missing or invalid Bearer API key

403

Missing API-key permission

404

Job not found

422

Validation Error

ApiError
429

Rate limit exceeded; see Retry-After

500

Unexpected server error

default

Unexpected error

ApiError
POST/api/v1/email-validation/jobs/{job_id}/pauseAPI key required

Pause an email validation job

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
integer
CURL EXAMPLE
curl -X POST "https://tuski.ai/api/v1/email-validation/jobs/<job_id>/pause" \
  -H "Authorization: Bearer <API_KEY>"

Responses

200

Successful Response

PauseJobResponse
401

Missing or invalid Bearer API key

403

Missing API-key permission, or ZeroClick-signed request

404

Job not found

422

Validation Error

ApiError
429

Rate limit exceeded; see Retry-After

default

Unexpected error

ApiError
POST/api/v1/email-validation/jobs/{job_id}/resumeAPI key required

Resume a paused email validation job

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
integer
CURL EXAMPLE
curl -X POST "https://tuski.ai/api/v1/email-validation/jobs/<job_id>/resume" \
  -H "Authorization: Bearer <API_KEY>"

Responses

200

Successful Response

ResumeJobResponse
401

Missing or invalid Bearer API key

403

Missing API-key permission, or ZeroClick-signed request

404

Job not found

422

Cannot resume a cancelled job (code invalid_request)

ApiError
429

Rate limit exceeded; see Retry-After

default

Unexpected error

ApiError

Account

GET/api/v1/creditsAPI key required

Read the caller's credit balance

Read-only credit balance for the API key's bound organization.

Reports the same wallet `POST /api/v1/email-validation/jobs` spends from -- one credit per accepted address. `credits` (and its aliases `payg_credits`/`total`) is the organization wallet balance; `monthly_tokens` and `free_credits_remaining` are deprecated and always 0 now that every grant lands in that one wallet. Any API-key permission may call this route.

Errors: 401 missing/invalid key, 429 rate limited, 403 `permission_denied` for a ZeroClick-signed request -- credits are available to API keys only and rejected before authentication.

CURL EXAMPLE
curl -X GET "https://tuski.ai/api/v1/credits" \
  -H "Authorization: Bearer <API_KEY>"

Responses

200

Successful Response

CreditsResponse
401

Missing or invalid Bearer API key

403

ZeroClick-signed request

422

Validation error

ApiError
429

Rate limit exceeded; see Retry-After

default

Unexpected error

ApiError

Errors

Every error response body follows this shape.

detail required
string
Human-readable error message.
code optional
string
Machine-readable error code.
API Reference — Tuski