Providers · Reference
Capability descriptors
How a model's request body is described: params, inputs, constants, enum maps, derivations and omissions.
A capability descriptor describes one model on one provider for one kind of canvas node: which of the node's settings the model accepts, what each is called in your API, the fixed values every request carries, and which files arrive from earlier steps. For sync-rest and submit-poll providers, the request body of every run is built from it. openai-compatible providers need none.
The settings a customer sees on a node come from the same descriptor, so a control is only shown if your API can receive it.
Who writes descriptors#
- A provider Sushibelt sells or you claimed: Sushibelt keeps its descriptors. Before a handover it checks your endpoint can take the request for every model; a model it cannot is given a descriptor your endpoint serves, or retired, before the handover goes ahead.
- A private provider — an endpoint only your team uses — gets its descriptors from you, with the request below.
The descriptor row#
| Field | Description |
|---|---|
modelstring · Required | Your model's id, as the customer picks it on the node. |
node_typestring · Required | The canvas node it serves, such as DiffusionWorker, TwoDToThreeD, AudioGen, Upscaler. The provider must already be set up for that node. |
endpointstring · Required | The full URL the request is sent to. It must be on the same origin as your transport's baseUrl: your key is only ever sent there. |
outputsstring · Required | What the model makes: images, model_mesh, audio, text, embedding. |
descriptorobject · Required | The mapping. Fields below. |
The descriptor#
| Field | Description |
|---|---|
paramsobject · Optional | The node settings the model accepts, keyed by Sushibelt's setting name. See params. |
inputsobject[] · Optional | Files that arrive from earlier steps. See inputs. |
constantsobject · Optional | Fields sent with every request, as they are. They override a setting with the same field name. |
deriveobject[] · Optional | One setting turned into several fields. See derive. |
omitstring[] · Optional | Setting names to remove, when the descriptor builds on a shared one for your provider. Each must be a known setting name. |
promptPrefixstring · Optional | Text put before the prompt, such as a trigger word the model needs. Joined with , . |
promptSuffixstring · Optional | Text put after the prompt. Joined with , . |
params#
The keys are Sushibelt's own setting names, and only these are accepted: prompt, negativePrompt, systemPrompt, seed, variations, steps, guidance, strength, temperature, maxTokens, aspectRatio, width, height, outputFormat, scale, conditionScale, voiceId, stability, similarityBoost, duration, stage, action, mapType, faceLimit, quadTopology, style, tileable. A key outside this list is refused, because the canvas has no control to draw for it.
| Field | Description |
|---|---|
wirestring · Required | The field name in your API's request body. |
typestring · Required | One of text, int, float, enum, bool. |
minnumber · Optional | Numbers below it are raised to it before sending. Must not be above max. |
maxnumber · Optional | Numbers above it are lowered to it before sending. |
stepnumber · Optional | The control's step size. |
defaultany · Optional | Sent when the customer leaves the setting empty. |
enumMapobject · Optional | For enum: Sushibelt's value → your value. A mapped value that is an object is merged into the body, so "3:2": { "width": 1365, "height": 910 } sets two fields. |
labelsobject · Optional | For enum: Sushibelt's value → the name the customer sees in the picker, such as { "af_bella": "Bella — American, female" } for a voice. Display only: it never changes what is sent. With an enumMap, it names the map's values; without one, its keys are the values the picker offers, and a value outside them is still sent as it is. |
requiredboolean · Optional | The step fails before calling you if this is empty and has no default. |
advancedboolean · Optional | Shows the control under Advanced. Display only. |
inputs#
| Field | Description |
|---|---|
handlesstring[] · Required | The node's input connectors to read from, tried in order, such as ["image", "input"]. |
wirestring · Required | The field the file's URL goes in. |
kindstring · Required | One of image, mesh, audio. |
arrayboolean · Optional | Send the URL inside a list: "images": ["https://…"]. |
requiredboolean · Optional | The step fails before calling you, and asks the customer to connect a node, if nothing is connected. |
derive#
A derivation turns one setting into one or more fields, using a named function — one of `aspectToDimensions`, `tileableToAxes`, `secondsToMilliseconds`. The setting's own field is then not sent, unless to names it. A number is first held to the setting's min and max.
aspectToDimensions— an aspect ratio such as16:9becomes a width and height, the long side 1024 and both rounded to a multiple of 8.tileableToAxes— thetileableswitch becomestile_xandtile_y, both on or both off.secondsToMilliseconds— a length in seconds, such asduration, is sent in milliseconds on the first field into:{ "from": "duration", "to": ["music_length_ms"], "using": "secondsToMilliseconds" }sends 45 seconds as45000.
| Field | Description |
|---|---|
fromstring · Required | A setting name from the params list. |
tostring[] · Required | The field names it produces, at least one. |
usingstring · Required | One of aspectToDimensions, tileableToAxes, secondsToMilliseconds. |
{
"params": {
"aspectRatio": {
"wire": "aspect_ratio",
"type": "enum",
"default": "1:1"
}
},
"derive": [
{
"from": "aspectRatio",
"to": [
"width",
"height"
],
"using": "aspectToDimensions"
}
]
}How a request body is built#
- Each param: the customer's value, or the
default; numbers held tomin/max(and whole forint); enums mapped. An enum value with no mapping falls back to the default's mapping, or is left out. - Derivations, which may replace fields from step 1.
- The prompt prefix and suffix.
- Input file URLs.
- Constants, which win over everything before them.
Settings the descriptor does not declare are never sent.
Complete examples#
{
"model": "example-image-v1",
"node_type": "DiffusionWorker",
"endpoint": "https://api.example.com/v1/images/generations",
"outputs": "images",
"descriptor": {
"params": {
"prompt": {
"wire": "prompt",
"type": "text",
"required": true
},
"negativePrompt": {
"wire": "negative_prompt",
"type": "text",
"advanced": true
},
"seed": {
"wire": "seed",
"type": "int",
"min": 0,
"max": 2147483647
},
"steps": {
"wire": "num_inference_steps",
"type": "int",
"min": 1,
"max": 50,
"default": 28,
"advanced": true
},
"aspectRatio": {
"wire": "size",
"type": "enum",
"default": "1:1",
"enumMap": {
"1:1": "1024x1024",
"16:9": "1344x768",
"9:16": "768x1344"
}
}
},
"inputs": [],
"omit": [],
"derive": [],
"constants": {
"response_format": "url"
}
}
}{
"model": "example-mesh-v1",
"node_type": "TwoDToThreeD",
"endpoint": "https://api.example.com/v2/task",
"outputs": "model_mesh",
"descriptor": {
"params": {
"seed": {
"wire": "seed",
"type": "int",
"min": 0,
"max": 2147483647
},
"faceLimit": {
"wire": "face_limit",
"type": "int",
"min": 1000,
"max": 100000,
"default": 30000,
"advanced": true
}
},
"inputs": [
{
"handles": [
"image",
"input"
],
"wire": "image_url",
"kind": "image",
"required": true
}
],
"omit": [],
"derive": [],
"constants": {
"type": "image_to_model"
}
}
}What a descriptor may not contain#
- A setting name outside the list above, or a
typeoutside the five. - A derivation function outside `aspectToDimensions`, `tileableToAxes`, `secondsToMilliseconds`.
- Code or expressions of any kind. Anything that needs code is written by Sushibelt.
- An endpoint on another origin than your transport's
baseUrl. - A price. Prices are declared separately and never live in a descriptor or a transport.
Saving a descriptor for a private provider#
A team that owns a private sync-rest or submit-poll provider saves a descriptor with a PUT, signed in, as a team owner or admin (see how the requests work). One descriptor per model and node; saving again replaces it.
await fetch('/api/providers/YOUR_PROVIDER_KEY/capabilities', {
method: 'PUT',
headers: {
'x-team-slug': 'YOUR_TEAM_SLUG',
'Content-Type': 'application/json',
},
body: JSON.stringify({
"model": "example-image-v1",
"node_type": "DiffusionWorker",
"endpoint": "https://api.example.com/v1/images/generations",
"outputs": "images",
"descriptor": {
"params": {
"prompt": {
"wire": "prompt",
"type": "text",
"required": true
},
"negativePrompt": {
"wire": "negative_prompt",
"type": "text",
"advanced": true
},
"seed": {
"wire": "seed",
"type": "int",
"min": 0,
"max": 2147483647
},
"steps": {
"wire": "num_inference_steps",
"type": "int",
"min": 1,
"max": 50,
"default": 28,
"advanced": true
},
"aspectRatio": {
"wire": "size",
"type": "enum",
"default": "1:1",
"enumMap": {
"1:1": "1024x1024",
"16:9": "1344x768",
"9:16": "768x1344"
}
}
},
"inputs": [],
"omit": [],
"derive": [],
"constants": {
"response_format": "url"
}
}
}),
}).then((r) => r.json())The answer is { "ok": true, "probe": "required" }: a new descriptor is a new request, so the provider stops serving until it passes a probe again.
Note: refusals
400 Invalid capabilitywithproblems, such asparams.quantumFlux: not a known ParamKeyorderive[0].using: … is not a registered derivation.400 The endpoint must be on …— the endpoint is off yourbaseUrl's origin.400 This provider is not routed for …— the node type is not one the provider serves; the message lists those it does.400 A "openai-compatible" provider needs no descriptor.404 Provider not found— not a private provider your team owns.