Submit
client.submit — every argument, SLA windows, local validation, unit derivation and reference-asset limits.
client.submit(args: SubmitArgs): Promise<JobHandle>interface SubmitArgs {
model: string;
input: string | Record<string, unknown>;
sla?: string; // default "batch"
rateIn?: string | number | bigint | null;
rateOut?: string | number | bigint | null;
provider?: number;
validateParams?: boolean; // default true
confidential?: boolean; // default false
unitsOut?: number;
customId?: string;
}Submissions are always sealed: a client missing its signer or cipher raises
ValidationError before anything is sent. The returned handle is described in
Jobs and results; the steps a submission runs are in
Job lifecycle.
Arguments
| Argument | Type | Default | Meaning |
|---|---|---|---|
model | string | — | A model id from client.models.list(). A model the catalog gives no numeric model_id raises ValidationError. |
input | string | object | — | A string is shorthand for { input: string }. An object is the model's own input, sealed as-is. |
sla | string | "batch" | A tier ("async" / "batch") or a window ("1h" / "24h"). See SLA windows. |
rateIn | string | number | bigint | null | null | The input-side bid as a scaled integer. With both rates null, the order takes the market (see No bid named); with only one null, that side bids zero. |
rateOut | same | null | The output-side bid, same scaling. |
provider | number | — | A provider's registry id; the payload is sealed to its published key. With no rates named, the bid is this provider's ask, and ValidationError is raised when it is not live for the model. Omit, with rates named, for an open order. |
validateParams | boolean | true | Run local validation before sealing. |
confidential | boolean | false | Verify the named provider's attestation evidence before sealing to it. Requires a verifier. |
unitsOut | number | derived | The output unit count, exactly, overriding the derived value. A non-negative integer; 0 for an embedding. |
customId | string | — | Your own label, sealed inside the payload and returned as .customId on the opened result. Never sent in the clear. |
Rates are bigint in code and decimal strings on the wire. They are uint128 on chain,
which a number cannot hold exactly, so build large ones with BigInt("…"). A digits-only
string also works; a fraction ("0.03"), a negative or a non-integer number raises
ValidationError.
Recipient. With provider, or with no rates (the market pins one), the payload is sealed
to the key in that provider's registry record (GET /evm/providers/{id}); a record with no key
raises VerificationError. With rates and no provider, the payload is sealed to the
coordinator's verified escrow key, which requires a verifier; without one, or if the key does
not verify, EscrowKeyUnverified is raised and nothing is posted.
confidential: true without a verifier raises ValidationError before any request. With
one, the named provider's evidence must verify or VerificationError is raised; another
provider is never substituted. On an open order it adds nothing, since the escrow key is verified
on that path anyway.
stream, metadata and user are not job params: a sealed result is delivered once, at
settlement, and a caller-chosen identifier would link your jobs to each other across providers.
No bid named
With neither rateIn nor rateOut, submit first asks the coordinator for the market: a
POST /v1/jobs with no rates and no signature, so it costs no wallet prompt. The coordinator
answers with every live provider that has a free slot and an ask in the order's window, ranked
by what this job would cost at their ask, and among equal prices the provider picked least
recently first. submit bids the first candidate's own ask and pins it, so providers at the same
price take turns.
With provider as well, the probe is pinned too, and the bid is that provider's ask. With
confidential: true and no provider, the list is first filtered to candidates whose
attestation verifies. When no provider is live for the model in the window, ValidationError is
raised before anything is signed; name rates to post a bid that rests instead.
SLA windows
| Tier | Window | Seconds signed |
|---|---|---|
"async" | "1h" | 3600 |
"batch" | "24h" | 86400 |
Other windows of the form <n>h, <n>m or <n>s are signed as written and validated by the
network; a string of any other form is signed as 3600 s. Job rows report the window as vorq.sla_secs.
The order's expiry is the window plus the settlement margin ($VORQ_SETTLEMENT_MARGIN, default
3600 s), capped at 86400 s from signing.
Local validation
Before sealing, an object input is checked against the model's published params_schema
(client.models.paramsSchema(model)):
- A param the schema sets to
false, or a value that fails its subschema, raisesValidationErrorand nothing is sent. - A param the schema does not mention is sent, with a
console.warn: the serving provider may support it or ignore it. - Keywords evaluated:
type,minimum,maximum,enum,oneOf,items,properties. Any other keyword is skipped with a warning; the provider still applies it. - No published schema, or a failed schema read, skips the schema check.
One check needs no schema. When the input names an output cap (max_tokens,
max_output_tokens or max_completion_tokens; the smallest one counts), a
reasoning_max_tokens at or above the cap, or a min_tokens above it, raises ValidationError:
reasoning tokens are billed as output, out of the same cap, and would leave no room for an
answer.
validateParams: false turns off both checks.
Units
The order signs units_in and units_out, which settlement meters against rateIn and
rateOut. The SDK derives both from the input object.
Shape. The request is text if it names max_output_tokens, max_tokens or
max_completion_tokens; else video if it names duration or duration_secs; else image if
it names num_images, width or resolution, or carries a reference asset; else text.
units_out (unless unitsOut is given):
| Shape | units_out |
|---|---|
| Text | The first non-zero of max_output_tokens, max_tokens, max_completion_tokens; 4096 if none. |
| Image | Frame pixels × num_images (1 if absent). |
| Video | Frame pixels × duration seconds. |
Frame pixels come from explicit width × height (a missing one counts as 1024), else from
a resolution tier resolved against aspect_ratio, else 1024 × 1024.
- Tiers:
480p,720p,1080p,4k. Aspect ratios:21:9,16:9,4:3,1:1,3:4,9:16, plusautoandadaptive. Any other value raisesValidationError. - A tier is a pixel budget: at
16:9it is 854×480, 1280×720, 1920×1080 or 3840×2160, and every other ratio is the frame of that shape with about the same area. auto(or noaspect_ratio) takes the ratio closest to the first image or clip reference, or16:9with no references.adaptiveis priced at the tier's largest frame.
Duration is duration_secs, else duration: a number (truncated to whole seconds), a
digits-only string, or "auto" (priced as 15 s). Absent or not positive, it is 5 s. Anything
else raises ValidationError.
units_in:
- Text and embeddings: one unit per four bytes of the input's canonical JSON, at least 1.
- Image and video: the reference assets in pixel-seconds,
width × height × max(1, duration_secs)summed over the assets (a still counts as one second),0with none.
Reference assets
Reference keys are image, end_image and video, and the lists reference_images,
reference_videos and reference_audios. Each asset is an object:
| Member | Required | Meaning |
|---|---|---|
b64 | yes | The bytes, base64. |
media_type | yes | For example "image/png", "video/mp4". |
width, height | images and clips | Positive integers, the asset's true size. |
duration_secs | clips (video, reference_videos) | Positive integer seconds, rounded up. |
Limits, checked before anything is sealed or signed (ValidationError):
| Limit | Value |
|---|---|
reference_images | at most 9 |
reference_videos | at most 3 |
reference_audios | at most 3, each at most 15 MiB; audio counts zero units |
| Pixel-bearing assets per request | at most 12 |
| Pixels per asset | at most 8 294 400 (3840 × 2160) |
| Clip length | at most 60 s |
Assets are priced from the dimensions you declare, without decoding. Providers check them against the decoded bytes, so an asset larger or longer than declared can fail the job.
Size
A sealed container up to 15 679 488 bytes travels inline in the job body. A larger one is
uploaded first with client.uploadFile(…, "input", …) and referenced by content id. There is no
client-side size cap; the coordinator's own limits apply.
Low-level sealing
client.sealLine(args: SealLineArgs): Promise<SealedLine>
client.payLine(line: SealedLine, gasFee: bigint, ctx: ChainContext, feeBps: bigint): Promise<Record<string, unknown>>The two halves batches.submit is built from, public for callers assembling a batch file by
hand. sealLine seals one payload and signs its order; payLine signs the payment for it
(cap + floor(cap × feeBps / 10000) + gasFee) and returns the flat JSONL row. Prefer submit
for a job and batches.submit for a file.