Skip to content
SBDocs

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#

Row fields
FieldDescription
modelstring · RequiredYour model's id, as the customer picks it on the node.
node_typestring · RequiredThe canvas node it serves, such as DiffusionWorker, TwoDToThreeD, AudioGen, Upscaler. The provider must already be set up for that node.
endpointstring · RequiredThe 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 · RequiredWhat the model makes: images, model_mesh, audio, text, embedding.
descriptorobject · RequiredThe mapping. Fields below.

The descriptor#

Descriptor fields
FieldDescription
paramsobject · OptionalThe node settings the model accepts, keyed by Sushibelt's setting name. See params.
inputsobject[] · OptionalFiles that arrive from earlier steps. See inputs.
constantsobject · OptionalFields sent with every request, as they are. They override a setting with the same field name.
deriveobject[] · OptionalOne setting turned into several fields. See derive.
omitstring[] · OptionalSetting names to remove, when the descriptor builds on a shared one for your provider. Each must be a known setting name.
promptPrefixstring · OptionalText put before the prompt, such as a trigger word the model needs. Joined with , .
promptSuffixstring · OptionalText 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.

One param
FieldDescription
wirestring · RequiredThe field name in your API's request body.
typestring · RequiredOne of text, int, float, enum, bool.
minnumber · OptionalNumbers below it are raised to it before sending. Must not be above max.
maxnumber · OptionalNumbers above it are lowered to it before sending.
stepnumber · OptionalThe control's step size.
defaultany · OptionalSent when the customer leaves the setting empty.
enumMapobject · OptionalFor 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 · OptionalFor 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 · OptionalThe step fails before calling you if this is empty and has no default.
advancedboolean · OptionalShows the control under Advanced. Display only.

inputs#

One input
FieldDescription
handlesstring[] · RequiredThe node's input connectors to read from, tried in order, such as ["image", "input"].
wirestring · RequiredThe field the file's URL goes in.
kindstring · RequiredOne of image, mesh, audio.
arrayboolean · OptionalSend the URL inside a list: "images": ["https://…"].
requiredboolean · OptionalThe 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 as 16:9 becomes a width and height, the long side 1024 and both rounded to a multiple of 8.
  • tileableToAxes — the tileable switch becomes tile_x and tile_y, both on or both off.
  • secondsToMilliseconds — a length in seconds, such as duration, is sent in milliseconds on the first field in to: { "from": "duration", "to": ["music_length_ms"], "using": "secondsToMilliseconds" } sends 45 seconds as 45000.
One derivation
FieldDescription
fromstring · RequiredA setting name from the params list.
tostring[] · RequiredThe field names it produces, at least one.
usingstring · RequiredOne of aspectToDimensions, tileableToAxes, secondsToMilliseconds.
An aspect ratio sent as width and heightJSON
{
  "params": {
    "aspectRatio": {
      "wire": "aspect_ratio",
      "type": "enum",
      "default": "1:1"
    }
  },
  "derive": [
    {
      "from": "aspectRatio",
      "to": [
        "width",
        "height"
      ],
      "using": "aspectToDimensions"
    }
  ]
}

How a request body is built#

  1. Each param: the customer's value, or the default; numbers held to min/max (and whole for int); enums mapped. An enum value with no mapping falls back to the default's mapping, or is left out.
  2. Derivations, which may replace fields from step 1.
  3. The prompt prefix and suffix.
  4. Input file URLs.
  5. Constants, which win over everything before them.

Settings the descriptor does not declare are never sent.

Complete examples#

Text to imageJSON
{
  "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"
    }
  }
}
Image to 3D, with an input from an earlier stepJSON
{
  "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 type outside 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.

Save a descriptor (browser console)TypeScript
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 capability with problems, such as params.quantumFlux: not a known ParamKey or derive[0].using: … is not a registered derivation.
  • 400 The endpoint must be on … — the endpoint is off your baseUrl'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.

Next steps