Client API
Every /v1/* route, for models, jobs, files and batches.
Routes under /v1/*. Encoding, headers, paging and body limits are in API conventions; the error envelope and retry rule are in Errors.
Models
GET /v1/models
The model catalog. GET /evm/models returns the same body.
Auth: public · Index-backed: yes · Paged: yes
Response 200
{
"object": "list",
"data": [
{ "id": "model-name", "object": "model", "owned_by": "vorq", "vorq": { "model_id": "3", "enabled": true } }
],
"as_of_block": "12345672"
}| Field | Meaning |
|---|---|
id | The model's name. |
vorq.model_id | The id orders use (model_id). |
vorq.enabled | Whether new orders for it are accepted. |
curl -s https://api.vorq.co/v1/modelsGET /v1/models/{name}
One model by name. Names may contain /; send them as-is or percent-encoded.
Auth: public · Index-backed: yes
Response 200: one model object, as in GET /v1/models, plus as_of_block.
Errors
| Status | code | Cause |
|---|---|---|
404 | model_not_found | No model has this name. This body has no param field and no x-vorq-retryable header. |
Jobs
POST /v1/jobs
Quote or submit an order. The same body, sent without and then with a payment, first returns a 402 quote and then posts the job. See Submit a job.
Auth: public (the order and payment signatures are the authority) · Index-backed: no · Body limit: 20 MiB
Order fields (every request)
| Field | Type | Rule |
|---|---|---|
c | bytes32 | Commitment to the sealed container. |
model_id | uint32 | An enabled model. |
sla_secs | uint32 | An SLA the chain allows; at most 86400. |
rate_in, rate_out | uint128 | Prices per 10^6 units, in base units of the payment token. |
units_in, units_out | uint32 | Unit counts the order pays for at most. |
designated | uint32 | 0 for an open bid, or the provider id the job is reserved for. |
expires_at | uint64 | Unix seconds, in (now, now + 86400]. |
owner | address | The order's owner and payer. |
job_id | bytes32 | Must equal keccak256(owner ‖ c). |
signature | 65 bytes | The owner's Order signature. |
Submission fields (second request only)
| Field | Type | Rule |
|---|---|---|
auth_sig | 65 bytes | The owner's ReceiveWithAuthorization signature over the quoted authorization. |
amount | uint256 | The quoted amount. |
container | base64 | The sealed container, up to 15 679 488 bytes decoded. |
container_cid | string | Instead of container: the vorq.cid of an input upload by owner. |
Send exactly one of container and container_cid.
Market probe (a body with neither rate_in nor rate_out): model_id, sla_secs, units_in, units_out and optional designated, unsigned. The answer is 402 with {"candidates": [...]} only: every live ask for the model and window, ranked as below, with no quote. See Matching and leases.
Response 402: the quote, when auth_sig is absent. x-vorq-retryable: false.
{
"quote": {
"cap": "9600",
"fee_bps": 100,
"fee": "96",
"gas_fee": "2000",
"amount": "11696",
"authorization": {
"domain": { "name": "…", "version": "…", "chainId": 84532, "verifyingContract": "0x…" },
"to": "0x…",
"value": "11696",
"valid_after": "0",
"valid_before": "1786003601",
"nonce": "0x…"
}
},
"candidates": [{ "provider_id": "7", "box_key": "0x…", "rate_in": "1500", "rate_out": "6000" }],
"accepts": [{ "scheme": "eip3009", "network": "eip155:84532" }]
}| Field | Meaning |
|---|---|
quote.cap | The escrow ceiling: max(1, ceil((rate_in·units_in + rate_out·units_out) / 10^6)). |
quote.fee | floor(cap · fee_bps / 10000). |
quote.gas_fee | The flat relay charge, read from the JobRegistry. |
quote.amount | cap + fee + gas_fee. |
quote.authorization | The payment to sign: token domain, to (the JobRegistry), value (= amount), valid_after (0), valid_before (expires_at + 1), nonce (= job_id). |
candidates | Up to MATCH_CANDIDATES live providers whose ask clears the order, cheapest first, with the ask's rates. Empty when none match. See Matching and leases. |
accepts | The payment scheme and CAIP-2 network. |
Response 201: the job was posted.
{ "job_id": "0x…", "task_cid": "bafy…", "tx_hash": "0x…" }task_cid is the IPFS CID the container was stored under. The job is normally readable at GET /v1/jobs/{id} as soon as this returns.
Errors
| Status | code | Cause |
|---|---|---|
400 | null | A malformed field (param names it); job_id mismatch; expires_at out of range; sla_secs over 86400 or not allowed; model not enabled. |
400 | cap_overflow | cap does not fit a uint128. |
400 | invalid_order_signature | signature does not recover to owner. |
400 | container_without_payment | A container was sent without auth_sig. |
400 | container_required | auth_sig was sent without a container. |
400 | container_ambiguous | Both container and container_cid. |
400 | invalid_payment_signature | auth_sig does not recover to owner over the authorization for amount. |
400 | payer_has_code | owner has contract code (a smart account or an EIP-7702 delegation). Pay from an account with no code. |
400 | unknown_container | container_cid names no input upload by owner, or it expired. |
400 | too_short, bad_version, commitment_mismatch | The container is malformed or does not reproduce c. |
409 | (quote body) | amount is not the current cap + fee + gas_fee, usually because gas_fee or fee_bps changed. The body is a fresh {quote, candidates, accepts}. Sign a new payment and resubmit. |
409 | contract error name | The chain refuses the post, for example DuplicateJob (use a fresh c). |
429, 503, 504 | See Errors. |
The quote is answered after the order signature is checked and before model, SLA-allowlist and payer checks, so a 402 does not guarantee the submission will pass them.
GET /v1/jobs/{id}
The client view of one job.
Auth: public · Index-backed: yes
| Path | Meaning |
|---|---|
id | The job_id, 32-byte hex. |
Response 200
{
"id": "0x…",
"object": "job",
"model": "model-name",
"status": "completed",
"in_progress_at": "1786000100",
"result_cid": "bafy…",
"vorq": {
"job_id": "0x…", "owner": "0x…", "model_id": "3", "sla_secs": "3600",
"rate_in": "1500", "rate_out": "6000", "units_in": "1200", "units_out": "800",
"designated": "0", "provider_id": "7", "expires_at": "1786003600", "task_cid": "bafy…",
"completion_tok": "800", "state": 2, "ended_because": 1
},
"as_of_block": "12345700"
}| Field | Meaning |
|---|---|
status | queued, in_progress, completed, failed or cancelled. See Job lifecycle. |
in_progress_at | Claim time in Unix seconds, or null before a claim. |
result_cid | The sealed result's IPFS CID once settled, else null. |
model | The model's name, or null if the catalog does not know it. |
Errors: 400 for a malformed id; 404 until the coordinator has a record of the job.
curl -s https://api.vorq.co/v1/jobs/0x…POST /v1/jobs/{id}/cancel
Cancel an open job. See Cancel a job.
Auth: public (the owner's signature is the authority) · Index-backed: no · Body limit: 8 KiB
Request
| Field | Type | Rule |
|---|---|---|
issued_at | uint64 | Unix seconds, within ±600 of the coordinator's clock. |
signature | 65 bytes | The owner's Cancel signature over (id, issued_at). |
Response 200
{ "job_id": "0x…", "tx_hash": "0x…" }Cancelling a job that has already settled or ended succeeds and changes nothing.
Errors
| Status | code | Cause |
|---|---|---|
409 | StaleOp | issued_at outside ±600 seconds. |
409 | NotCancellable | The job is claimed. |
409 | NotTheOwner | The signature does not recover to the job's owner, or the job does not exist. |
409 | other contract error name | The chain refused the cancel. |
429, 503, 504 | See Errors. |
Files
POST /v1/files
Upload a sealed container, a sealed result or a batch input. The file is streamed to the object store.
Auth: session · Index-backed: no · Body: multipart/form-data
| Part | Rule |
|---|---|
purpose | input, result or batch. Must come before the file part. |
file | One file part, up to MAX_BLOB_BYTES (200 MiB by default). |
purpose | Contents | Checks |
|---|---|---|
input | A sealed container | Framing; the commitment is computed during upload so POST /v1/jobs can compare it with c. |
result | A sealed result | Non-empty. |
batch | JSONL, one request per line | At least one and at most 50 000 non-blank lines. |
Response 200
{
"id": "file-3b1f…",
"object": "file",
"bytes": 1048576,
"created_at": 1786000000,
"expires_at": 1786000300,
"filename": "container",
"purpose": "input",
"status": "uploaded",
"vorq": { "cid": "bafy…", "lines": 0 }
}vorq.cid is what container_cid and result_cid reference; id is what input_file_id references. expires_at is 300 seconds after upload until the file is attached; see File retention. lines counts non-blank lines for batch files and is 0 otherwise.
Errors
| Status | code | Cause |
|---|---|---|
400 | invalid_purpose | purpose is not input, result or batch. |
400 | empty_file | No bytes, or a batch file with no requests. |
400 | too_many_lines | A batch file over 50 000 lines. |
400 | bad_container | An input file that is not a valid container. |
400 | null | Missing purpose or file part, or a malformed form. |
413 | file_too_large | Over MAX_BLOB_BYTES. |
503 | store_unavailable, store_rejected | The object store failed. Retryable. |
curl -s -X POST https://api.vorq.co/v1/files \
-H "authorization: Bearer $TOKEN" \
-F purpose=input -F file=@container.binGET /v1/files/{id}
The file object, as returned by POST /v1/files. Batch output files have purpose: "batch_output" and status: "processed".
Auth: session; only the file's owner · Index-backed: no
Errors: 404 for an unknown file or one owned by another address.
GET /v1/files/{id}/content
The file's bytes.
Auth: session; only the file's owner · Index-backed: no
Response 200: application/jsonl for batch uploads, application/octet-stream for every other file, including batch output files.
Errors: 404 for an unknown file or one owned by another address; 503 with code: "object_missing" when the object store no longer holds it.
Batches
Batch objects follow the OpenAI batch shape. Every line of the input file is a complete POST /v1/jobs submission body; see Run a batch.
POST /v1/batches
Create a batch from an uploaded batch file.
Auth: session · Index-backed: no
Request
{ "input_file_id": "file-…", "endpoint": "/v1/responses", "completion_window": "24h", "metadata": { "run": "nightly" } }| Field | Rule |
|---|---|
input_file_id | A batch upload by this session's address. |
endpoint | /v1/responses or /v1/embeddings. A line's url, when present, must equal it. |
completion_window | 1h or 24h. The batch expires this long after creation. |
metadata | Optional; at most 16 string pairs, keys up to 64 and values up to 512 characters. |
Response 200: a batch object with status: "validating".
Batch plan (a body with no input_file_id): for lines that name no bid, ask how the network would take them before sealing anything.
{ "completion_window": "24h", "models": [{ "model_id": 3, "lines": 120, "units_in": 48000, "units_out": 491520 }] }units_in and units_out are the totals over that model's lines; models lists 1–16 models. The answer is 402 with, per model, the providers to seal to, their ask and how many lines each takes:
{ "plan": [{ "model_id": "3", "lines": "120", "allocation": [{ "provider_id": "1", "box_key": "0x…", "rate_in": "2118400", "rate_out": "10626400", "lines": "120" }] }] }Each live provider's share is at most its on-chain capacity less what it already holds: claimed jobs and open orders designated to it. Providers are filled cheapest first for the model's unit mix, and equal prices take turns. Nothing is reserved. When the allocation lines add up to fewer than lines, the network cannot take the batch in that window now; the SDKs refuse it before sealing.
Errors
| Status | code | Cause |
|---|---|---|
400 | invalid_endpoint | Unsupported endpoint. |
400 | invalid_completion_window | Not 1h or 24h. |
400 | invalid_input_file | No batch file with this id for this address. |
400 | null | Malformed metadata or missing field. |
GET /v1/batches
This address's batches, newest first.
Auth: session · Index-backed: yes
| Query | Default | Rule |
|---|---|---|
limit | 20 | 1–100 |
after | — | A batch id; returns the batches after it. |
Response 200
{ "object": "list", "data": [ { "id": "batch_…", "object": "batch", "…": "…" } ], "first_id": "batch_…", "last_id": "batch_…", "has_more": false, "as_of_block": "12345700" }Pass last_id as after to get the next page while has_more is true.
GET /v1/batches/{id}
One batch object plus as_of_block.
Auth: session; only the batch's owner · Index-backed: yes
Errors: 404 for an unknown batch or one owned by another address.
POST /v1/batches/{id}/cancel
Stop a batch. Lines not yet posted are not posted. Lines already on chain are not cancelled: open ones stay open until claimed or expired, and claimed ones run to completion. The owner can cancel each job. Repeating the call is a no-op.
Auth: session; only the batch's owner · Index-backed: yes
Response 200: the batch object plus as_of_block, with cancelling_at set.
Errors
| Status | code | Cause |
|---|---|---|
400 | batch_not_cancellable | The batch is not validating, in_progress or cancelling. |
404 | null | Unknown batch, or owned by another address. |
Batch object
{
"id": "batch_5e2a…",
"object": "batch",
"endpoint": "/v1/responses",
"input_file_id": "file-…",
"completion_window": "24h",
"status": "in_progress",
"errors": null,
"output_file_id": null,
"error_file_id": null,
"created_at": 1786000000,
"expires_at": 1786086400,
"in_progress_at": 1786000010,
"finalizing_at": null,
"completed_at": null,
"failed_at": null,
"expired_at": null,
"cancelling_at": null,
"cancelled_at": null,
"request_counts": { "completed": 10, "failed": 1, "total": 50 },
"metadata": {},
"vorq": { "sla": "24h" }
}| Field | Meaning |
|---|---|
status | validating (not yet processed), in_progress, finalizing (every line ended, files being written), completed, failed, expired (the window ended first), cancelling, cancelled. |
request_counts | total lines recorded; completed settled; failed skipped, cancelled, failed, reclaimed or expired. |
output_file_id, error_file_id | Set when the batch finishes; null when that file would be empty. |
| Timestamps | Unix seconds, or null. |
Status and counts are computed from the member jobs on every read.
Batch output files
Both files are JSONL, read with GET /v1/files/{id}/content. Line ids have the form batch_req_<batch>_<line number>.
Output file: one line per settled job.
{"id":"batch_req_5e2a…_1","custom_id":null,"response":{"status_code":200,"request_id":"0x<job_id>","body":null},"error":null,"vorq":{"job_id":"0x…","result_cid":"bafy…","provider":7,"rate_in":"1500","rate_out":"6000","completion_tok":800}}Results are sealed to the owner, so body is always null: fetch each result by result_cid. Your own request ids travel inside the sealed payload and result.
Error file: one line per input line that did not settle.
{"id":"batch_req_5e2a…_2","custom_id":null,"response":null,"error":{"code":"invalid_payment_signature","message":"…"},"vorq":{"job_id":null,"line":2}}error.code | Cause |
|---|---|
invalid_json, invalid_line | The line is not a JSON object, or a field is malformed. Other field-level codes from POST /v1/jobs may appear. |
endpoint_mismatch | The line's url differs from the batch endpoint. |
container_required, unknown_container, too_short, bad_version, commitment_mismatch | Missing, unknown or invalid container. |
order_expired, expiry_too_far, sla_too_long, cap_overflow | Order terms out of range when the batch was processed. |
insufficient_payment | amount is not exactly cap + fee + gas_fee. |
invalid_order_signature, invalid_payment_signature, payer_has_code | Signature or payer checks failed. |
duplicate_job_id | Another line in the file has the same job_id. |
invalid_model | The model is not enabled. |
cancelled | The batch was cancelled before the line was posted, or the owner cancelled the job. |
provider_fail, reclaim, expired | Posted, but the provider failed it, missed its SLA, or nobody claimed it in time. |
| contract error name | The chain refused to post the line. |