Deployments
A deployment is a model that you serve at one precision in one region. Register it, state its limits, and move it from UNLISTED to the public book.
A registration has no prices. Your session quotes the deployment, and an operator decides whether to list it. Every endpoint here takes the provider key as a bearer token.
Register a deployment
curl https://router.inference.ai.exchange/v1/provider/models \
-H "Authorization: Bearer sk-liquid-..." \
-H "Content-Type: application/json" \
-d '{
"deployment_name": "gpt-oss-120b",
"model": "gpt-oss-120b",
"creator": "OpenAI",
"precision": "fp8",
"region": "eu-west",
"max_input_tokens": 128000,
"max_output_tokens": 4096,
"tokenizer_id": "o200k_base",
"input_modalities": [{ "type": "text" }, { "type": "image", "max_bytes": 20000000 }],
"output_modalities": [{ "type": "text" }]
}'
| Parameter | Type | Required | Description |
|---|---|---|---|
deployment_name |
string | Yes | Your name for the deployment. The book shows <your handle>/<deployment_name>. |
model |
string | No | Legacy upstream identifier. Default: deployment_name. It does not approve a public name. |
upstream_model |
string | No | Opaque handle that the exchange returns at dispatch. Not part of the identity. Default: model. |
registry_model_id |
UUID | No | An approved registry identity of the same kind. See Select an approved identity. |
creator |
string | No | The company that made the model. Buyers see it, and the exchange.candidates.creators.allow rule uses it. Ids are lowercase. Where models.dev lists the company, use its lab id, such as alibaba for Qwen or zhipuai for GLM. |
model_family, base_model |
string | No | The model that this one is a variant of, and the model that a quantization or fine-tune comes from. |
precision |
string | Yes | Weight precision, for example fp8, bf16, int4. |
region |
string | Yes | Serving region. The exchange.privacy.regions rule uses it. |
max_input_tokens |
integer | Yes | Context limit in the deployment tokenizer. Input and output of one job must fit in it together. Must be positive. |
max_output_tokens |
integer | No | Maximum tokens for each job. Must be positive. Each registration writes this field, so omitting it removes a limit that you set before. |
tokenizer_id |
string | No | Tokenizer of the usage reports. Default: provider-native. |
kind |
string | No | chat (per token), image_generation (per image), audio_speech (per character) or video_generation (per second). Default: chat. The first registration sets it. |
input_modalities |
array | No | Input kinds with limits. See What the deployment takes. An empty list states nothing. |
output_modalities |
array | No | Output kinds with limits. See What the deployment produces. An empty list means text only. |
pricing_unit |
string | No | An image_generation deployment can state tokens when its upstream bills for tokens. Omit it to keep the current unit. |
image_output_tokens_max |
integer | No | Most output tokens for one image. Required with pricing_unit tokens, and refused otherwise. The cap of a job holds this many tokens for each image. |
description |
string | No | Text that buyers see. Omit it to keep the current text. Send "" to clear it. |
hardware |
object | No | See Hardware. Omit it to keep the current claim. |
serving |
object | No | See Serving. Omit it to keep the current claim. |
byok |
boolean | No | true makes the exchange serve the deployment with your own cloud key. upstream_model must be a model id that a live key lists. See Sell with bring your own key. |
The response is 201 with every field above, and also:
| Field | Meaning |
|---|---|
id |
The registration id. Buyers see it as the offer id. |
model_deployment |
<your handle>/<deployment_name>. It follows a handle change. |
canonical_model |
The public name. It is null without a registry link. |
status, status_reason |
The listing status. A new registration is UNLISTED. |
markup_bps, paused_at, created_at, listed_at, suspended_at |
Margin, pause and status times. Unset values are null. |
Repeat registrations:
- Registration is idempotent on
(provider_id, deployment_name, precision, region). A repeat call returns the current registration and clears no suspension or pause. - A change to the deployment identity clears its registry link and public admission.
kindcannot change. A new kind returns409 kind_changed. Use a new deployment name.
Errors: 400 with invalid_registration, invalid_bands, invalid_modalities, invalid_output_modalities, invalid_pricing, invalid_hardware or invalid_serving_claims; 409 kind_changed; 401; 503 storage_unavailable.
Hardware
State the hardware of this deployment only, not your fleet. The exchange does not use the claim to rank offers, route a job or break a tie.
| Field | Type | Required | Description |
|---|---|---|---|
accelerator |
string | Yes | For example nvidia-h100-80 or amd-mi300x. |
interconnect |
string | With more than 1 GPU | How devices in one node connect, for example nvlink or infinity-fabric. |
gpus_per_node |
integer | Yes | Devices in one node, 1 to 72. A rack system, such as an NVL72, is one node. |
nodes |
integer | Yes | Nodes that serve one replica, 1 to 1024. |
network |
object | No | Link between nodes. Allowed only when nodes is more than 1. |
host |
string | Yes | bare_metal, or vm_passthrough. |
instance_type, notes |
string | No | Instance type, such as p5.48xlarge, and other facts that change speed (500 characters at most). |
network field |
Type | Required | Description |
|---|---|---|---|
fabric |
string | Yes | For example infiniband, roce or efa. |
topology |
string | No | rail_optimized, fat_tree, torus, full_mesh or other. |
bandwidth_gbps |
integer | No | Bandwidth of one node in Gbit/s, 1 to 100000. Add every card of the node. |
placement |
string | No | same_rack, same_cluster or same_zone. |
An operator claim stays when you register again. A count out of range, a missing accelerator, no interconnect for more than 1 device, or a bad network returns 400 invalid_hardware.
Serving
State how you serve the deployment. The exchange cannot check the values and does not use them to rank or route. Every field is optional.
| Field | Type | Description |
|---|---|---|
engine |
string | vllm, sglang, tensorrt_llm, dynamo, llama_cpp or other. |
engine_version |
string | For example 0.10.2. It needs an engine. At most 32 characters. |
speculative_decoding |
boolean | true for speculative decoding or multi-token prediction. |
disaggregated_serving |
boolean | true when prefill and decode run on separate machines. |
kv_cache_precision |
string | bf16, fp16, fp8, int8 or fp4. Separate from the weight precision. |
prompt_caching |
string | none, automatic (the engine finds a repeated prefix) or explicit (the buyer marks it). |
An engine version with no engine, or one that is too long, returns 400 invalid_serving_claims.
What the deployment takes
The type of each entry in input_modalities is text, image, audio, video or file. Each type can occur once. An absent limit means that you stated none.
| Field | Applies to | Checked | Description |
|---|---|---|---|
sources |
image, audio, video, file | Yes | url, base64. |
formats |
image, audio, video, file | Yes | Accepted MIME types, for example image/png. |
max_bytes |
image, audio, video, file | Yes | Largest single part, in bytes. |
max_references |
image | Yes | Most images that one request can reference. |
max_duration_seconds |
audio, video | Audio | Longest clip, in seconds. |
supported_frame_images |
image | Yes | Frames that the deployment reads: first_frame, last_frame. See Frame images. |
detail_levels |
image | No | image_url.detail levels: auto, low, high. |
The list has two effects:
- It limits what you can quote. Your agent quotes a media feature only for a kind that the list names. The list cannot add to what your upstream accepts.
- It excludes your offer from a job that breaks a limit. The reason is
media_unsupported, and the error names the constraint, such asimage.max_bytes. This happens before the award, so the job can go to another deployment.
Frame images
A buyer can fix the first or last frame of a video with frame_images. Declare each frame that your upstream reads in supported_frame_images on the image input. A deployment that declares no frame never gets an image that has one.
"input_modalities": [
{ "type": "text" },
{ "type": "image", "max_references": 2, "supported_frame_images": ["first_frame", "last_frame"] }
]
What the deployment produces
The type of each entry in output_modalities is text, image, audio or video, once each. Image, speech and video deployments name image, audio and video, and must also name text.
| Field | Applies to | Checked | Description |
|---|---|---|---|
formats |
image, audio, video | Yes | Formats it produces. Write png or image/png. |
sizes |
image, video | Yes | Pixel sizes, for example 1024x1024. |
max_count |
image | Yes | Most images in one response. |
response_formats |
image | Yes | url, b64_json or both. If you list none, a request for a form can still reach it. |
voices |
audio | Yes | Voices it can speak in. |
seconds |
video | Yes | Lengths it renders, for example [4, 8]. |
max_duration_seconds |
audio, video | Video only | Longest clip. For audio, the exchange only records it, because it cannot measure a clip first. |
audio |
video | Yes | Sound of the video: none, always or optional. See Sound. |
resolutions |
video | Yes | Resolutions it renders, such as 720p. See Sound, resolution and price. |
A video request states its length before the job starts, from 1 to 30 seconds. List every length that the deployment renders in seconds, for example [4, 5, 6, …, 30]. A request for a length that you did not list does not reach you, and registration refuses a length above 30. If you set only max_duration_seconds, a request for a length below your real minimum can reach you and fail. A request with no seconds gets the shortest length that any offer lists, or 4 when none lists one. The exchange does not accept seconds: -1, because the buyer must state the length.
Sound, resolution and price
A buyer can send generate_audio (true or false) and resolution (such as 720p). A request that sends one reaches only a deployment that states the option and can make it. A deployment that states nothing serves only a request that does not send it.
audioisnone(every video is silent),always(every video has sound) oroptional(the buyer chooses, and a request that says nothing gets sound).resolutionslists the labels that the deployment renders, such as["720p", "1080p"].
If the price depends on the sound or the resolution, quote several output_video lines with a variant. Quote a media model has the rules and an example. The exchange refuses a quote with a variant that names a sound without audio as optional, or a resolution that resolutions does not list. Send the choice to your upstream: the dispatch holds it in audio and resolution of the video object.
A request for a format, size, count or voice that you did not list goes to another offer before the award. The charge is known before dispatch:
| Kind | pricing line |
Charge |
|---|---|---|
| Image, each image | output_image, no token line |
Rate times the images that the buyer asked for. |
Image, pricing_unit tokens |
input_text and output_text, no output_image |
The tokens that you meter, up to the cap: the prompt at the input rate plus image_output_tokens_max per image at the generation rate. |
| Speech | output_speech |
Rate times the characters in the text. |
| Video | output_video |
Rate times the seconds that the buyer asked for. |
| Reference images (image or video) | input_reference_image |
Rate times the reference images that you metered, at most the number the exchange counted. Without the rate they are free. A token-priced image deployment does not quote it. Start and frame images do not count. |
| Reference video | input_reference_video |
Rate times the seconds of reference video that you metered, at most the seconds sent. |
| Reference audio | input_reference_audio |
Rate times the seconds of reference audio that you metered, at most the seconds sent (30 at most). |
operator_locked names each field that an operator set. A later registration does not undo it.
List deployments
GET /v1/provider/models with your provider key returns each registration in all statuses. The listed frame of the session lists what you can quote now.
From registration to the public book
A listing puts a deployment on the public book, where buyers reach it by model name. A registration never lists itself.
- Register. The deployment starts
UNLISTED. - Quote it. It rests as an unverified offer.
- The exchange measures it with benchmarks and quality checks.
- An operator lists it, or the exchange does. The exchange sets
listed_at. See Automatic listing. - After the next quote, it competes under the model name.
Listing statuses
Two independent fields control availability. A paused LISTED deployment keeps its listing, but you do not quote it.
| Field | Who sets it | Meaning |
|---|---|---|
status |
An operator, and you when you resume a paused deployment | Whether the deployment is on the public book. |
paused_at |
You | Set while you do not quote it. |
| Status | Quotable | Description | Operator transitions |
|---|---|---|---|
UNLISTED |
Yes, unverified | Not in the public book. Only your workspace and the exchange buy from it, by model_deployment. |
LISTED, SUSPENDED |
LISTED |
Yes | In the public book. Buyers can ask for it by model name. | UNLISTED, SUSPENDED |
SUSPENDED |
No | No buyer can reach it. status_reason and suspended_at give the reason and time. |
UNLISTED (reinstate) |
Public admission requires an approved registry identity. status_reason and suspended_at are set only when the status is SUSPENDED. listed_at is set only when it is LISTED.
The session rejects a quote for a SUSPENDED or paused deployment with quote_reject. A new registration does not end a suspension. Repair the cause and contact the operators.
Automatic listing
When automatic listing is on, the exchange checks every deployment every 60 seconds. An operator can still list by hand. It lists an UNLISTED deployment when all of these are true:
- Your provider is admitted and public.
- Your signed provider agreement is on record. Ask the operators to record it.
- The deployment has an approved registry identity of its kind, and it names no other identity than its link.
- You did not pause it, and no reservation commits it.
- Your last decision was not an unlisting.
- You list no other deployment of the same identity under the same name. Case and punctuation do not count.
Unverified offers
A quote for an UNLISTED deployment rests as an unverified offer. Only your workspace and the exchange can buy from it.
- Send its
model_deployment, such asacme/gpt-oss-120b, as the model, with an API key of the owning workspace. Another workspace getsmodel_not_found. - The exchange buys from it to measure it. It never chooses it for a model name, router model or preset, and its quotes stay out of the price history.
- Use it to test end to end with a real auction and settlement.
To change the owner workspace:
- Open the provider in the console and go to Access.
- Choose Move to another workspace. You must be an owner or admin of both workspaces.
- Restart the agent. The exchange reads the owner when the agent connects.
Select an approved identity
A public deployment must have an approved identity from the model registry, of the same kind.
- Send
GET /v1/provider/model-registrywith your provider key. Each record has a UUID, canonical name, creator, revision, kind and aliases. - Send the UUID as
registry_model_idwhen you register. - Keep the dispatch handle in
upstream_model.
/v1/provider/modelsand the session return the public name ascanonical_model. Read the dispatch handle fromupstream_model, notmodel.- If an operator retires an identity, the linked deployments are unlisted and their names blocked. Your workspace can still use each exact symbol.
Without an approved identity, use a private deployment by its exact symbol. The model request panel in the console accepts evidence for review.
Pause and resume
A pause stops the quotes for one deployment and keeps its listing.
curl -X POST https://router.inference.ai.exchange/v1/provider/models/3f0c.../pause \
-H "Authorization: Bearer sk-liquid-..."
This sets paused_at and removes the resting offer at once. Use /resume to clear the pause. Resume does not grant public admission, and it changes nothing for a SUSPENDED deployment.
The account API does the same:
PATCH /v1/account/providers/{provider_id}/models/{model_id}
{ "paused": true }
Send { "paused": false } to resume. An unknown id returns 404 no_model.
Set a margin
The console stores a margin for each deployment, in basis points above your upstream cost.
PATCH /v1/account/providers/{provider_id}/models/{model_id}
{ "markup_bps": 1500 }
An integer from 0 to 100000 sets the margin (1500 is 15%). null restores the agent default. An absent field changes nothing. A value out of range returns 400 invalid_markup. The agent reads the margin on its next refresh.