Custom AI Models

Custom AI models let you bring your own AI provider into the app without writing code. You describe the provider in one JSON file, a manifest: its API keys, its base URL and every model with its input fields and the HTTP calls that submit a job, check on it and read the result. Once the manifest is verified and enabled, its models appear next to the built-in ones in the generation panels, the Video Editor and for the agents.

Use it for a provider the app doesn’t include, a model a built-in provider hasn’t added yet, or your own in-house endpoint.

Add a manifest

  1. Click the App settings gear in the header and open the Custom AI models tab.
  2. Drop your manifest .json onto Add a manifest, or click choose a file.
  3. The app checks the file straight away and shows its name, version, how many models and keys it has, and a chip per output type (for example “2 image”, “1 video”). Any problems are listed by field. Use View to read the file, Replace to pick another one, or ✕ to discard it.
  4. Click Verify. The checklist below fills in step by step.
  5. When every required step has passed, click Enable in app.

Verification

Verification runs its steps in order and stops at the first failure. Nothing is generated unless you run the test.

StepWhat it checks
JSON syntaxThe file parses as JSON.
Manifest schemaRequired fields are present, ids are unique, every model type is known and every {{template}} resolves to a field or a built-in value.
API keysEvery key the manifest names is set. Missing keys can be pasted right in this row; Save & retry stores them and verifies again.
EndpointsYour key is accepted and every endpoint answers. Only requests that cannot start a job are sent: the manifest’s own verify probe (GET or HEAD) and a HEAD request to each model’s submit URL.
Test generationOptional, and the only step that costs money: Run test (costs one generation) runs the cheapest text-only model once and checks that the result maps to real files.

In the Endpoints step, a rejected key (401 or 403) or an unreachable host fails the step. A 404 answer to the HEAD request is only a warning, because many servers don’t answer HEAD on a path that only takes POST; the models can still be enabled. Each endpoint row shows its method, path, response time and status.

Enable in app unlocks only when the schema, keys and endpoints steps have all passed against the manifest and keys as they are now.

How custom models appear

  • Each model lands in the panel its model_type belongs to: Generate Image, Generate Video or Generate Voice, under the matching category.
  • In the model picker, your models sit next to the built-in ones and get their own provider chip, named after the manifest’s id.
  • The price next to Generate is an estimate from the price in your manifest. A model without a price shows $0.
  • Results are downloaded and land on the board like any other generation.
  • show_in decides where a model is offered: generate covers the generation panels and the canvas card actions, video_editor covers the Video Editor’s voice, transcription, sound-design and AI-fill pickers, and agent covers the agent API.

Manage installed manifests

The Installed list shows every manifest with its status, model count and output types:

  • The On / Off switch hides a manifest’s models everywhere without deleting it. Turning an unverified manifest on runs verification first.
  • The chevron (Show models) lists each model with its type and where it appears (Generate, Video Editor, Agents).
  • The ⋯ menu offers Re-verify, Update from file… (replace the manifest in place; the new file must have the same id), Show manifest, Edit API keys and Remove. Removing a manifest takes its models out of the app; the file on your disk is not touched.

Changing a key or updating the manifest takes its models out of every picker until Verify passes again. They come back on their own once it does, without switching the manifest on again.

Manifest reference

A complete, minimal manifest with one text-to-image model:

{
  "manifest_version": 1,
  "id": "acme",
  "name": "Acme AI",
  "version": "1.0.0",
  "base_url": "https://api.acme.example/v1",
  "auth": {
    "ACME_API_KEY": { "label": "Acme API Key", "help_url": "https://acme.example/keys" }
  },
  "verify": { "method": "GET", "path": "/models", "expect_status": [200] },
  "models": [
    {
      "id": "flux-ultra",
      "name": "Acme Flux Ultra",
      "description": "Photoreal text-to-image, up to 2K.",
      "output_type": "image",
      "model_type": "text_to_image",
      "price": { "amount": 0.04, "unit": "per_image" },
      "max_items": 4,
      "fields": [
        { "key": "prompt", "label": "Prompt", "type": "textarea", "required": true },
        { "key": "aspect_ratio", "label": "Aspect", "type": "select", "options": ["1:1", "16:9", "9:16"], "default": "1:1" }
      ],
      "request": {
        "submit": { "method": "POST", "path": "/flux/ultra", "body": { "prompt": "{{prompt}}", "aspect_ratio": "{{aspect_ratio}}", "n": "{{count}}" } },
        "poll": { "method": "GET", "path": "/predictions/{{submit.id}}", "done_when": "$.status == 'succeeded'", "failed_when": "$.status == 'failed'", "error": "$.error" },
        "result": { "urls": "$.output[*].url" }
      }
    }
  ]
}

Top level

KeyRequiredMeaning
manifest_versionyesAlways 1.
idyesProvider slug, 2–40 characters of lowercase letters, digits, - and _. It prefixes every model id and names the provider chip in the picker.
nameyesProvider name shown in the app.
versionnoYour manifest version, shown in the Installed list.
homepage, iconnohttp(s) URLs. icon is used for every model without its own.
base_urlnoPrefix for every relative path. Required unless every request uses a full url.
authyesThe API keys, as key name ▸ settings. At least one.
defaultsnopoll_interval_ms (default 2000), timeout_s (default 300, the whole generation), headers (sent with every request) and media_input (data_uri or base64).
verifynoOne authenticated probe for the Endpoints step: method (GET or HEAD only), path or url, expect_status and, with several keys, auth. Point it at something cheap that needs the key, such as a models or account endpoint. Strongly recommended: without it the step can only prove that the host answers.
modelsyes1–100 models.

Keys (auth)

Key names are UPPER_SNAKE_CASE, for example ACME_API_KEY. Each key can set a label and a help_url (shown as Get a key ↗ next to the key field). By default a key is sent as Authorization: Bearer <key>; set header and prefix to send it another way, or query (for example "query": "api_key") to send it as a query parameter. With more than one key, each request must name the key it uses in its auth.

Models

KeyRequiredMeaning
idyesUnique within the manifest: letters, digits, ., _, / and -.
name, descriptionnameShown in the model picker.
output_typeyesimage, video, audio or text.
model_typeyesThe category, which decides the panel: for example text_to_image, image_to_image, text_to_video, image_to_video, omni, video_to_video, text_to_speech, text_to_music, speech_to_text. A type that normally produces a different output type is reported as a warning.
show_innoAny of generate, video_editor and agent. Default: all three.
pricenoamount and unit: per_image, per_video, per_second, per_generation, per_megapixel or per_1000_chars.
max_itemsnoHow many results one run may ask for, 1–20 (default 4). Sent as {{count}}.
iconnoAn http(s) URL.
fieldsyesThe inputs the panel shows.
sizesnoA list of label, width, height and aspect_ratio entries; the panel offers a size picker and sends the choice as {{width}}, {{height}} and {{aspect_ratio}}.
requestyessubmit, an optional poll, and result.

Fields

Each field has a key (snake_case), a label and a type:

TypeWhat the panel shows
text, textareaA text box. A prompt or text field is required unless you set "required": false.
selectA dropdown of options.
number, rangeA number, with optional min, max and step.
booleanA true / false choice.
image, video, audioA media slot. max_files above 1 (up to 20) makes a multi-slot input that arrives as a list.

Fields also take required, default and placeholder. Media reaches your API inline, as a data: URI (media_input: "data_uri", the default) or as bare base64 ("base64").

Request

  • submit — method (GET, POST, PUT or PATCH), path (joined to base_url) or a full url, optional headers, and a JSON body. Any answer outside the 2xx range fails the generation with the provider’s message.
  • poll (optional) — repeated every interval_ms until done_when is true, or failed_when is true (the message at error is shown), or timeout_s runs out. {{submit.id}} and similar read values from the submit response.
  • result — read from the last poll response, or from the submit response when there is no poll:
    • urls — a path to one URL or a list of URLs, with optional width and height aligned by position. Every URL must be http:// or https://; anything else is refused, so a provider can never point the app at a file on your disk.
    • base64 — instead of urls, a path to inline base64 or a data: URI; mime overrides the file type.
    • text — the text answer, for models with output_type text (transcription, describe).
    • download_auth — true sends the model’s key when the app downloads the result files.

Templates

{{name}} is replaced anywhere in path, url, headers and body:

  • A value that is exactly one template keeps its type: "n": "{{count}}" sends a number, and a multi-file media field sends a list.
  • A field left empty removes its key from the body, so optional inputs never arrive as null.
  • Built-in values: {{count}}, {{width}}, {{height}}, {{aspect_ratio}}, {{image_size}} and {{seed}}. A field with the same name wins over the built-in.

Paths and conditions

Paths: $.a.b, $.items[0].url, and $.items[*].url for a list. Conditions: $.status == 'succeeded', $.state != 'queued', $.state in ['done', 'completed'], $.progress >= 100, $.ready (true when set), and not $.pending.

Limits

  • Up to 20 installed manifests, each with up to 100 models.
  • Inputs are always sent inline. A provider that only accepts public URLs for its inputs can’t be used.
  • The whole generation stops after timeout_s (5–3600 s, default 300).
  • The price is always the app’s estimate from your manifest; there is no exact quote from the provider.
  • Keys you paste here are kept in the app’s local database, apart from the manifests and from the keys in API Keys. No other part of the app returns them, but they are not encrypted at rest, so treat the app’s data folder like any other credential store. A key can also be supplied as an environment variable of the app’s backend under the same name.

From a script or an agent

The same steps are available over the app’s local API, so an agent can install and verify a manifest for you:

GET    /api/custom-models                        installed manifests, key status, models
POST   /api/custom-models/validate   {manifest}  check a manifest without installing it
PUT    /api/custom-models/{id}       {manifest}  install or replace (stays off)
POST   /api/custom-models/{id}/keys  {keys}      set API keys
POST   /api/custom-models/{id}/verify {steps}    run schema, keys, endpoints, dry_run
POST   /api/custom-models/{id}/enable {enabled}  switch on or off
DELETE /api/custom-models/{id}                   remove

Enabling answers with an error until the schema, keys and endpoints steps have passed. Enabled models are listed and generated through the same AI endpoints as the built-in ones, with the model id <manifest id>/<model id>. See AI Generation Endpoints.

Where to go next

Was this helpful?

0

Updated

Comments

Leave a Reply

Your email address will not be published. Required fields are marked *