{
  "openapi": "3.1.0",
  "info": {
    "title": "AI on Artur.Work Gateway API",
    "version": "1.2.0",
    "summary": "Multi-provider Chat, Image and Audio gateway, speaking both the OpenAI and the Anthropic dialect.",
    "description": "A single gateway in front of OpenAI, Anthropic, xAI, Google, OpenRouter, Ideogram, Recraft, Luma, Runway, Runware, GoApi, BlackForestLabs and NanoBananaApi.\n\n**Two dialects, one gateway.** `POST /chat/completions` is the OpenAI (and xAI) Chat Completions contract; `POST /messages` is the Anthropic Messages contract. Both work for *every* model — ask `/messages` for `gpt-5` and the answer comes back in Anthropic's envelope; ask `/chat/completions` for `claude-sonnet-5` and it comes back in OpenAI's. Pick whichever matches the SDK you already have.\n\n**Two base URLs, same API.** Everything is served under `/api/v1` and under `/v1`. The second exists because the vendor SDKs assume it: the Anthropic client appends `/v1/messages` to its `base_url`, and the OpenAI client wants the `/v1` in the `base_url` itself. So both of these work:\n\n```python\nOpenAI(base_url=\"https://ai.artur.work/api/v1\", api_key=\"<gateway key>\")\nanthropic.Anthropic(base_url=\"https://ai.artur.work\", api_key=\"<gateway key>\")\n```\n\n**Not supported:** streaming. Every response is buffered and returned whole, and a request with `stream: true` is refused with a 400 rather than answered as if the flag had not been sent — an SDK in streaming mode would otherwise wait for `text/event-stream` frames and fail inside the client library on a plain JSON body.\n\nEvery call is metered against the caller's balance; a request with an insufficient balance is refused before any provider is contacted. All generated images are re-hosted on the gateway CDN, so `url` values point at `https://cdn-ai.artur.work/...`, never at the upstream provider.\n\n**Not covered here:** the peer-compute marketplace at `/api/v1/mesh/*` (job submission, listings, worker registration). It is a private contract between the compute daemon and the site, with its own request and error shapes. Its one model, `comfy-flux1-schnell`, is reachable through the image endpoints below.",
    "contact": {
      "name": "Artur Kyryliuk",
      "email": "mail@artur.work"
    },
    "license": {
      "name": "Proprietary",
      "identifier": "LicenseRef-Proprietary"
    }
  },
  "servers": [
    {
      "url": "https://ai.artur.work/api/v1",
      "description": "Canonical prefix. Use as base_url for an OpenAI or xAI SDK."
    },
    {
      "url": "https://ai.artur.work/v1",
      "description": "Vendor-canonical alias, same controllers. An Anthropic SDK reaches it by using https://ai.artur.work as base_url."
    }
  ],
  "tags": [
    {
      "name": "Chat",
      "description": "Text completion in either dialect, across OpenAI, Anthropic, xAI, Google and OpenRouter models."
    },
    {
      "name": "Images",
      "description": "Generation, editing, variation and upscaling. Some providers are asynchronous; `?wait=1` hides that."
    },
    {
      "name": "Audio",
      "description": "Text-to-speech and speech-to-text."
    },
    {
      "name": "Catalogue",
      "description": "Public model, price and latency catalogue."
    },
    {
      "name": "Decisions",
      "description": "Typed decisions with probabilities (Jev)."
    },
    {
      "name": "Tasks",
      "description": "Cancelling a task that has not started."
    },
    {
      "name": "Batches",
      "description": "Thousands of tasks as one batch: quote, progress, cancel, results. Each item is a normal task."
    },
    {
      "name": "Account",
      "description": "Balance, key cap and usage for a plugin, and the low-balance webhook."
    },
    {
      "name": "Assets",
      "description": "Private uploads, run provenance and capabilities: what a CMS needs to treat results as production assets."
    }
  ],
  "security": [
    {
      "ApiKeyHeader": []
    },
    {
      "BearerApiKey": []
    },
    {
      "SessionCookie": []
    }
  ],
  "paths": {
    "/chat/completions": {
      "post": {
        "tags": [
          "Chat"
        ],
        "operationId": "createChatCompletion",
        "summary": "Create a chat completion (OpenAI dialect)",
        "description": "The OpenAI Chat Completions contract, for every model this gateway serves.\n\nFor OpenAI, xAI, Google and OpenRouter models the body is forwarded to the provider and the provider's response is returned verbatim, so any field the upstream vendor accepts may be sent.\n\nFor Anthropic (`claude-*`) models the gateway translates in both directions: a `{\"role\":\"system\"}` message is lifted into Anthropic's top-level `system` field, `max_tokens` is defaulted to 4096 if absent, `stop` becomes `stop_sequences`, and the reply is converted back into the OpenAI envelope. A top-level `system` string is accepted here too, and is merged with any system message rather than replacing it, so either spelling works.\n\nWhat the OpenAI envelope cannot carry, it does not carry: for a Claude answer with `tool_use` or `thinking` blocks, only the text blocks survive here. Use `/messages` when you need the whole reply.",
        "x-compatibility": {
          "openai": "Drop-in for non-streaming `POST /v1/chat/completions`, including the error envelope. `stream` is unsupported.",
          "grok": "Identical to OpenAI — xAI's API is itself OpenAI-shaped, and fields like `reasoning_effort` and `search_parameters` pass straight through.",
          "anthropic": "Accepted here in either dialect, but the reply is the OpenAI envelope. An Anthropic SDK should call `/messages` instead."
        },
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/ChatCompletionRequest"
              },
              "examples": {
                "anyModel": {
                  "summary": "One body shape, any model",
                  "value": {
                    "model": "gpt-5",
                    "messages": [
                      {
                        "role": "system",
                        "content": "Always answer in English."
                      },
                      {
                        "role": "user",
                        "content": "Name three primary colours."
                      }
                    ]
                  }
                },
                "claudeSameShape": {
                  "summary": "The same body against a Claude model",
                  "value": {
                    "model": "claude-sonnet-5",
                    "messages": [
                      {
                        "role": "system",
                        "content": "Always answer in English."
                      },
                      {
                        "role": "user",
                        "content": "Name three primary colours."
                      }
                    ]
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Completion produced.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ChatCompletionResponse"
                },
                "example": {
                  "id": "chatcmpl-CX1",
                  "object": "chat.completion",
                  "created": 1771286400,
                  "model": "claude-sonnet-5",
                  "choices": [
                    {
                      "index": 0,
                      "finish_reason": "stop",
                      "message": {
                        "role": "assistant",
                        "content": "Red, yellow and blue."
                      }
                    }
                  ],
                  "usage": {
                    "input_tokens": 24,
                    "output_tokens": 7,
                    "prompt_tokens": 24,
                    "completion_tokens": 7,
                    "total_tokens": 31
                  }
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/ValidationError"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "$ref": "#/components/responses/PaymentRequired"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "500": {
            "$ref": "#/components/responses/ProviderError"
          },
          "409": {
            "$ref": "#/components/responses/IdempotencyConflict"
          },
          "422": {
            "$ref": "#/components/responses/IdempotencyReused"
          }
        },
        "parameters": [
          {
            "$ref": "#/components/parameters/IdempotencyKey"
          }
        ]
      }
    },
    "/messages": {
      "post": {
        "tags": [
          "Chat"
        ],
        "operationId": "createMessage",
        "summary": "Create a message (Anthropic dialect)",
        "description": "The Anthropic Messages contract, for every model this gateway serves.\n\nFor `claude-*` models nothing is translated in either direction: the provider's response is returned whole, so `content` keeps every block — `text`, `tool_use`, `thinking` — along with `stop_reason` and `stop_sequence`.\n\nFor every other model the gateway converts: `system` becomes a leading system message, `stop_sequences` becomes `stop`, and the OpenAI reply is rebuilt as a Messages response with one `text` block and a mapped `stop_reason`.\n\n`usage` always carries both vendors' field names, so `usage.input_tokens` and `usage.prompt_tokens` are both readable whatever the model was.",
        "x-compatibility": {
          "anthropic": "Drop-in for `POST /v1/messages`, including the error envelope and the `x-api-key` header. `stream` is unsupported. `/v1/messages/count_tokens` and the Batches API are not implemented.",
          "openai": "Not the OpenAI shape by design — that is what `/chat/completions` is for. An OpenAI SDK pointed here would send a valid request and fail to parse the reply.",
          "grok": "As OpenAI."
        },
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/MessagesRequest"
              },
              "examples": {
                "claude": {
                  "summary": "Anthropic model — answered natively",
                  "value": {
                    "model": "claude-sonnet-5",
                    "system": "Always answer in English.",
                    "max_tokens": 1024,
                    "messages": [
                      {
                        "role": "user",
                        "content": "Name three primary colours."
                      }
                    ]
                  }
                },
                "nonClaude": {
                  "summary": "The same body against an OpenAI model — converted both ways",
                  "value": {
                    "model": "gpt-5",
                    "system": "Always answer in English.",
                    "max_tokens": 1024,
                    "messages": [
                      {
                        "role": "user",
                        "content": "Name three primary colours."
                      }
                    ]
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Message produced.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/MessagesResponse"
                },
                "example": {
                  "id": "msg_01ABC",
                  "type": "message",
                  "role": "assistant",
                  "model": "claude-sonnet-5",
                  "content": [
                    {
                      "type": "text",
                      "text": "Red, yellow and blue."
                    }
                  ],
                  "stop_reason": "end_turn",
                  "stop_sequence": null,
                  "usage": {
                    "input_tokens": 24,
                    "output_tokens": 7,
                    "prompt_tokens": 24,
                    "completion_tokens": 7,
                    "total_tokens": 31
                  }
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/ValidationError"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "$ref": "#/components/responses/PaymentRequired"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "500": {
            "$ref": "#/components/responses/ProviderError"
          },
          "409": {
            "$ref": "#/components/responses/IdempotencyConflict"
          },
          "422": {
            "$ref": "#/components/responses/IdempotencyReused"
          }
        },
        "parameters": [
          {
            "$ref": "#/components/parameters/IdempotencyKey"
          }
        ]
      }
    },
    "/audio/speech": {
      "post": {
        "tags": [
          "Audio"
        ],
        "operationId": "createSpeech",
        "summary": "Synthesise speech from text",
        "description": "Returns the raw audio stream, not JSON. The body is forwarded to the provider unchanged; only `model`, a non-empty `input` and a non-empty `voice` are validated by the gateway. **Community machines (Mesh):** model `mesh-kokoro` (or `mesh-kokoro-<voice>`) with one of the curated voices answers JSON instead - `{id, status, url, content_type, duration_sec, characters, voice, model, mesh}` - because the audio is stored on the CDN like every generated result. `200` with `status: completed` when an idle machine finished within about a minute; `202` with `status: queued` and a `poll` path otherwise (`GET /audio/speech/{id}`). Priced per 1,000 characters of `input` (at most 8,000 per request), charged when the machine finishes and not at all if it fails; only `response_format: wav` is available. `503 mesh_unavailable` when no live machine serves the voice.",
        "x-compatibility": {
          "openai": "Drop-in for `POST /v1/audio/speech`.",
          "grok": "xAI has no TTS endpoint; no equivalent.",
          "anthropic": "Anthropic has no TTS endpoint; no equivalent."
        },
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/SpeechRequest"
              },
              "example": {
                "model": "gpt-4o-mini-tts",
                "input": "Hello there.",
                "voice": "alloy",
                "response_format": "mp3",
                "speed": 1.0
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Audio stream. `Content-Type` mirrors the provider's, driven by `response_format`. A Mesh model answers `application/json` (`MeshSpeechResult`) instead.",
            "content": {
              "audio/mpeg": {
                "schema": {
                  "type": "string",
                  "format": "binary"
                }
              },
              "audio/wav": {
                "schema": {
                  "type": "string",
                  "format": "binary"
                }
              },
              "audio/opus": {
                "schema": {
                  "type": "string",
                  "format": "binary"
                }
              },
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/MeshSpeechResult"
                }
              }
            }
          },
          "202": {
            "description": "A community-machine speech job is queued; poll `GET /audio/speech/{id}`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/MeshSpeechResult"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/ValidationError"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "$ref": "#/components/responses/PaymentRequired"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "500": {
            "$ref": "#/components/responses/ProviderError"
          },
          "409": {
            "$ref": "#/components/responses/IdempotencyConflict"
          },
          "422": {
            "$ref": "#/components/responses/IdempotencyReused"
          }
        },
        "parameters": [
          {
            "$ref": "#/components/parameters/IdempotencyKey"
          }
        ]
      }
    },
    "/audio/speech/{id}": {
      "get": {
        "tags": [
          "Audio"
        ],
        "operationId": "getSpeech",
        "summary": "State of a community-machine speech job",
        "description": "The job `id` a Mesh speech request returned. Only its owner may read it. `status` is `queued` (202), `completed` (200, with `url`) or `failed` (502, nothing charged).",
        "x-compatibility": {
          "openai": "No equivalent: OpenAI answers speech synchronously with the bytes.",
          "grok": "No equivalent.",
          "anthropic": "No equivalent."
        },
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Finished.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/MeshSpeechResult"
                }
              }
            }
          },
          "202": {
            "description": "Still queued or running.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/MeshSpeechResult"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "502": {
            "description": "The community machine could not make the audio; nothing was charged."
          }
        }
      }
    },
    "/audio/transcriptions": {
      "post": {
        "tags": [
          "Audio"
        ],
        "operationId": "createTranscription",
        "summary": "Transcribe audio to text",
        "description": "Multipart upload. `webm` and `ogg` uploads are transcoded to mp3 with `sox` before being forwarded; every other container is passed through as uploaded. `response_format` defaults to `json` when omitted.",
        "x-compatibility": {
          "openai": "Drop-in for `POST /v1/audio/transcriptions`. The gateway reads the *first* uploaded file regardless of its field name, so a client that names the part something other than `file` still works.",
          "grok": "No equivalent.",
          "anthropic": "No equivalent."
        },
        "requestBody": {
          "required": true,
          "content": {
            "multipart/form-data": {
              "schema": {
                "$ref": "#/components/schemas/TranscriptionRequest"
              },
              "encoding": {
                "file": {
                  "contentType": "audio/mpeg, audio/wav, audio/webm, audio/ogg, audio/mp4"
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Transcript. Shape follows the requested `response_format`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/TranscriptionResponse"
                },
                "example": {
                  "text": "Name three primary colours."
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/ValidationError"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "$ref": "#/components/responses/PaymentRequired"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "500": {
            "$ref": "#/components/responses/ProviderError"
          },
          "409": {
            "$ref": "#/components/responses/IdempotencyConflict"
          },
          "422": {
            "$ref": "#/components/responses/IdempotencyReused"
          }
        },
        "parameters": [
          {
            "$ref": "#/components/parameters/IdempotencyKey"
          }
        ]
      }
    },
    "/images/generations": {
      "post": {
        "tags": [
          "Images"
        ],
        "operationId": "createImage",
        "summary": "Generate images from a prompt",
        "description": "JSON body (unlike `/images/edits`, `/images/variations` and `/images/upscale`, which are multipart).\n\nSynchronous providers (OpenAI, xAI, Google, Ideogram, Recraft, Runware) answer with `{\"created\", \"data\": [{\"url\"}]}`. Asynchronous providers (GoApi/Midjourney, Luma, Runway, BlackForestLabs, NanoBananaApi, Mesh) answer with `{\"taskId\"}`; either poll `/images/status/{model}/{taskId}` or pass `?wait=1` to have the gateway do it for you.\n\nFor OpenAI models `response_format` is not forwarded upstream (the parameter no longer exists there), but it is still honoured locally: `b64_json` adds the base64 bytes to each item alongside the CDN url.\n\n**Normalised `options`** (feature 23) and **private inputs/outputs** (feature 24): see `ImageOptions` and `AssetPrivacy`. Every finished result carries `expires_at` per item, `provenance` and `moderation: null`.",
        "x-compatibility": {
          "openai": "Drop-in for `POST /v1/images/generations`, including `created`, `revised_prompt` and `response_format=b64_json`. Add `?wait=1` for models whose provider is asynchronous, otherwise those return a `taskId` an SDK cannot interpret.",
          "grok": "xAI's `/v1/images/generations` is OpenAI-shaped. `response_format` is forced to `url` upstream and the image is re-hosted, so `b64_json` is not available for Grok image models.",
          "anthropic": "Anthropic has no image-generation endpoint; no equivalent."
        },
        "parameters": [
          {
            "$ref": "#/components/parameters/Wait"
          },
          {
            "$ref": "#/components/parameters/IdempotencyKey"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/ImageGenerationRequest"
              },
              "examples": {
                "openaiStyle": {
                  "summary": "Synchronous provider",
                  "value": {
                    "model": "gpt-image-1.5",
                    "prompt": "A lighthouse at dusk",
                    "n": 1,
                    "size": "1024x1024",
                    "quality": "high",
                    "background": "auto"
                  }
                },
                "asyncStyle": {
                  "summary": "Asynchronous provider — returns taskId unless ?wait=1",
                  "value": {
                    "model": "midjourney",
                    "prompt": "A lighthouse at dusk --ar 16:9",
                    "n": 1
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Either a finished result or an accepted async task.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ImageResultOrTask"
                },
                "examples": {
                  "finished": {
                    "value": {
                      "created": 1771286400,
                      "data": [
                        {
                          "url": "https://cdn-ai.artur.work/public/images/9f2c.png",
                          "revised_prompt": "A tall lighthouse at dusk…"
                        }
                      ],
                      "usage": {
                        "input_tokens": 22,
                        "output_tokens": 1568,
                        "total_tokens": 1590
                      }
                    }
                  },
                  "accepted": {
                    "value": {
                      "taskId": "e2b1f0c4-77aa-4a11-9d1e-3f7c2b9a0d55"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "An option is malformed (`invalid_option`) or, under strict, cannot be honoured by the model (`option_unsupported`). `error.param` names the option, e.g. `options.aspectRatio`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "$ref": "#/components/responses/PaymentRequired"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "500": {
            "$ref": "#/components/responses/ProviderError"
          },
          "409": {
            "$ref": "#/components/responses/IdempotencyConflict"
          },
          "422": {
            "$ref": "#/components/responses/IdempotencyReused"
          }
        }
      }
    },
    "/images/edits": {
      "post": {
        "tags": [
          "Images"
        ],
        "operationId": "createImageEdit",
        "summary": "Edit uploaded images with a prompt",
        "description": "Multipart upload. Every uploaded file is forwarded as an `image` part; the remaining form fields are forwarded as-is. Async providers answer with `{\"taskId\"}` unless `?wait=1` is passed.\n\n**Normalised `options`** (feature 23) and **private inputs/outputs** (feature 24): see `ImageOptions` and `AssetPrivacy`. Every finished result carries `expires_at` per item, `provenance` and `moderation: null`.",
        "x-compatibility": {
          "openai": "Drop-in for `POST /v1/images/edits` for the gpt-image family. `mask` is passed through as an ordinary upload.",
          "grok": "xAI has no image-edit endpoint; no equivalent.",
          "anthropic": "No equivalent."
        },
        "parameters": [
          {
            "$ref": "#/components/parameters/Wait"
          },
          {
            "$ref": "#/components/parameters/IdempotencyKey"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "multipart/form-data": {
              "schema": {
                "$ref": "#/components/schemas/ImageEditRequest"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Either a finished result or an accepted async task.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ImageResultOrTask"
                }
              }
            }
          },
          "400": {
            "description": "An option is malformed (`invalid_option`) or, under strict, cannot be honoured by the model (`option_unsupported`). `error.param` names the option, e.g. `options.aspectRatio`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "$ref": "#/components/responses/PaymentRequired"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "500": {
            "$ref": "#/components/responses/ProviderError"
          },
          "409": {
            "$ref": "#/components/responses/IdempotencyConflict"
          },
          "422": {
            "$ref": "#/components/responses/IdempotencyReused"
          }
        }
      }
    },
    "/images/variations": {
      "post": {
        "tags": [
          "Images"
        ],
        "operationId": "createImageVariation",
        "summary": "Produce variations of an uploaded image",
        "description": "Multipart upload. For OpenAI only the uploaded files are forwarded — the form fields are dropped, matching the upstream endpoint, which takes no prompt.",
        "x-compatibility": {
          "openai": "Drop-in for `POST /v1/images/variations`.",
          "grok": "No equivalent.",
          "anthropic": "No equivalent."
        },
        "parameters": [
          {
            "$ref": "#/components/parameters/Wait"
          },
          {
            "$ref": "#/components/parameters/IdempotencyKey"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "multipart/form-data": {
              "schema": {
                "$ref": "#/components/schemas/ImageVariationRequest"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Either a finished result or an accepted async task.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ImageResultOrTask"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/ValidationError"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "$ref": "#/components/responses/PaymentRequired"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "500": {
            "$ref": "#/components/responses/ProviderError"
          },
          "409": {
            "$ref": "#/components/responses/IdempotencyConflict"
          },
          "422": {
            "$ref": "#/components/responses/IdempotencyReused"
          }
        }
      }
    },
    "/images/upscale": {
      "post": {
        "tags": [
          "Images"
        ],
        "operationId": "upscaleImage",
        "summary": "Upscale an image sent as a file, a data: URI or a URL",
        "description": "Gateway-specific endpoint with no vendor counterpart. Send the image like `/images/edits`: as a multipart file (`image`), as a `data:image/...;base64,` URI in `imageUrl`, or as an https `imageUrl`. A file or a data: URI is **never published**: it is kept privately and the provider gets a signed link that expires after an hour; the copy is deleted when the run finishes. `model` defaults to `Qubico/image-toolkit` (GoApi); a model that cannot upscale is a 400. `outputs[private]=1` (or `output_private=1`) keeps the result private as well: its `url` is then a signed link with an `expires_at`, and the file has to be downloaded before that time. Always asynchronous: poll `/images/status/upscale/{taskId}`, or pass `?wait=1`.",
        "x-compatibility": {
          "openai": "No equivalent.",
          "grok": "No equivalent.",
          "anthropic": "No equivalent."
        },
        "parameters": [
          {
            "$ref": "#/components/parameters/Wait"
          },
          {
            "$ref": "#/components/parameters/IdempotencyKey"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "multipart/form-data": {
              "schema": {
                "$ref": "#/components/schemas/UpscaleRequest"
              },
              "example": {
                "imageUrl": "https://cdn-ai.artur.work/public/images/9f2c.png",
                "scale": 2
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Accepted async task, or the finished result when `?wait=1` was passed.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ImageResultOrTask"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "$ref": "#/components/responses/PaymentRequired"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "500": {
            "$ref": "#/components/responses/ProviderError"
          },
          "400": {
            "description": "No image, an image that is not PNG, JPEG, WebP or GIF, a non-https `imageUrl`, or a model that cannot upscale.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "$ref": "#/components/responses/IdempotencyConflict"
          },
          "422": {
            "$ref": "#/components/responses/IdempotencyReused"
          }
        }
      }
    },
    "/images/analyze": {
      "post": {
        "tags": [
          "Images"
        ],
        "operationId": "analyzeImage",
        "summary": "Alt text, focal point, colours, text and a moderation score for one of your own images",
        "description": "Gateway-specific (no vendor counterpart). Name **one** image of yours: `upload` (an id from `POST /uploads`), `runId` (the task id of one of your runs - its first stored result) or `outputUrl` (a URL of one of your own results on the gateway CDN, or the signed link of a private result). A URL or id that is unknown, expired or somebody else's answers 404 `image_not_found`; the server never fetches an address you choose.\n\n`want` picks the parts (default: all). **alt** is written by a vision chat model (`analysis.altModel`) directly in `lang`, with `context` in the prompt, and is a normal run charged by its tokens (`run_id` names it). **colors** and **focal** are measured in PHP (free); the focal point says its `source` (`saliency`, or `mesh-features` when the features rig measured it). **ocr** (text in the image) and **moderation** (`nsfw` score) come only from a trusted features rig, at a small fixed price per image (`analysis.featuresPriceMicros`, charged only when the rig measured something; 402 `insufficient_balance` up front when the balance cannot pay it). Whatever cannot be measured right now is listed in `missing[]` with a reason in `notes[]` - nothing is invented. An alt-text model that fails leaves `alt` in `missing` (the failed run is free).\n\nAlso runnable in a batch as `images.analyze` (the item must ask for `alt`).",
        "x-compatibility": {
          "openai": "No equivalent.",
          "grok": "No equivalent.",
          "anthropic": "No equivalent."
        },
        "parameters": [
          {
            "$ref": "#/components/parameters/IdempotencyKey"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/AnalyzeRequest"
              },
              "example": {
                "upload": "9f2c0a7e51b34d6c8a1e0f2b7c3d4e5f",
                "want": [
                  "alt",
                  "focal",
                  "colors",
                  "ocr",
                  "moderation"
                ],
                "lang": "sk",
                "context": "product: oak chair"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The analysis.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ImageAnalysis"
                }
              }
            }
          },
          "400": {
            "description": "A malformed request: not exactly one image, an unknown `want` part, a bad `lang`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "$ref": "#/components/responses/PaymentRequired"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "description": "The image is unknown, expired, or not yours (`image_not_found`).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "$ref": "#/components/responses/IdempotencyConflict"
          },
          "422": {
            "$ref": "#/components/responses/IdempotencyReused"
          }
        }
      }
    },
    "/images/status/{model}/{taskId}": {
      "get": {
        "tags": [
          "Images"
        ],
        "operationId": "getImageTaskStatus",
        "summary": "Poll an asynchronous image task",
        "description": "The provider is resolved from `{model}`, so every async provider is reachable through this one route. Use the literal segment `upscale` as `{model}` for tasks created by `/images/upscale`.\n\nWhile the task is still running the provider is polled server-side for a bounded number of iterations before answering; a call may therefore block for several seconds. An unfinished task answers `200` with an empty body object — keep polling. A finished task answers with `data`.",
        "x-compatibility": {
          "openai": "No equivalent — OpenAI image generation is synchronous. `?wait=1` on the submission endpoints is the compatibility route.",
          "grok": "No equivalent.",
          "anthropic": "No equivalent."
        },
        "parameters": [
          {
            "name": "model",
            "in": "path",
            "required": true,
            "description": "The model the task was submitted with, or `upscale`.",
            "schema": {
              "type": "string",
              "pattern": "^[a-zA-Z0-9._-]+$"
            },
            "example": "midjourney"
          },
          {
            "name": "taskId",
            "in": "path",
            "required": true,
            "description": "The `taskId` returned when the task was submitted.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Finished result, or an empty object while still running.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ImageTaskStatus"
                },
                "examples": {
                  "running": {
                    "value": {}
                  },
                  "finished": {
                    "value": {
                      "created": 1771286400,
                      "data": [
                        {
                          "url": "https://cdn-ai.artur.work/public/images/9f2c.png"
                        }
                      ]
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "500": {
            "$ref": "#/components/responses/ProviderError"
          }
        }
      }
    },
    "/models": {
      "get": {
        "tags": [
          "Catalogue"
        ],
        "operationId": "listModels",
        "summary": "List available models with price and latency",
        "security": [],
        "description": "Public and unauthenticated. Served from a daily forecast snapshot, cached for an hour with an `ETag`; send `If-None-Match` to get a `304`.\n\nThe response carries two views of the same list. `data` is the OpenAI `GET /v1/models` envelope, so `client.models.list()` works. `models` is the richer view this endpoint exists for, with the price basis and the latency distribution behind each entry — read `legend` before trusting a number.\n\nModality filters are AND, not OR: `?input=i,t` means \"accepts both a picture and text\".",
        "x-compatibility": {
          "openai": "`object` + `data[]` satisfy `client.models.list()`. `created` is the snapshot time, not a vendor release date — the catalogue does not know when a model was published.",
          "grok": "Same as OpenAI — xAI mirrors the OpenAI models envelope.",
          "anthropic": "Not the Anthropic envelope (`{data:[{id, display_name}], has_more, first_id, last_id}`); `data[].id` is the same, the pagination fields are absent."
        },
        "parameters": [
          {
            "name": "input",
            "in": "query",
            "description": "Comma-separated input modalities, all of which the model must accept. `t` text, `i` image, `s` sound. Unknown letters are dropped.",
            "schema": {
              "type": "string"
            },
            "example": "i,t"
          },
          {
            "name": "output",
            "in": "query",
            "description": "Comma-separated output modalities, all of which the model must produce.",
            "schema": {
              "type": "string"
            },
            "example": "t"
          },
          {
            "name": "action",
            "in": "query",
            "description": "Restrict to models supporting one action.",
            "schema": {
              "type": "string"
            },
            "example": "generateImage"
          },
          {
            "name": "provider",
            "in": "query",
            "description": "Restrict to one upstream provider (case-insensitive).",
            "schema": {
              "type": "string"
            },
            "example": "OpenAi"
          },
          {
            "name": "model",
            "in": "query",
            "description": "Restrict to one model name (case-insensitive).",
            "schema": {
              "type": "string"
            },
            "example": "gpt-image-1.5"
          }
        ],
        "responses": {
          "200": {
            "description": "Catalogue snapshot.",
            "headers": {
              "ETag": {
                "schema": {
                  "type": "string"
                },
                "description": "Over the rendered body, so different filters are different entities."
              },
              "Cache-Control": {
                "schema": {
                  "type": "string"
                },
                "example": "public, max-age=3600"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ModelCatalogue"
                }
              }
            }
          },
          "304": {
            "description": "Caller's cached copy is current."
          }
        }
      }
    },
    "/models/auto/quote": {
      "get": {
        "tags": [
          "Catalogue"
        ],
        "operationId": "quoteAutoFromQuery",
        "summary": "Which model Auto would choose, from query parameters",
        "security": [],
        "description": "Which model `model: \"auto\"` would run this request on, what it costs, how long it typically takes and **why** - before anything runs (docs/features/13). Public like the other quotes; a signed-in caller or a key gets their own routing settings (no-training, Mesh, trusted rigs), saved priority and balance.\n\nThe router keeps only candidates that can do the request, that the caller's settings allow and that have **measurements in the request's category** (`GET /admin/routing-evidence` on the gateway), then ranks them by measured quality, price and typical wait under the priority profile: `economy`, `balanced` (default), `quality` or `fast`. TypeSafe's Jev (through OpenRouter, never training) classifies the request when its language is enabled and there is time; otherwise, or when Jev is unsure, rules and measurements decide.\n\nSend the answer's `routeToken` back as `routing.routeToken` on the real call and the run is bound to exactly this choice (15 minutes). If that choice cannot run any more the call is refused with 409 `route_expired` - it is never quietly routed elsewhere. Without a token, `model: \"auto\"` on a task endpoint decides then and there and names the model in the `x-routed-model` response header, with `x-route-candidate` and an English `x-route-reason`.\n\nWhen the top two options measured almost the same and the caller asked for `routing.choices`, `choices` is true and `cards` holds both, each with its own token.",
        "parameters": [
          {
            "name": "endpoint",
            "in": "query",
            "required": true,
            "schema": {
              "type": "string",
              "enum": [
                "chat.completions",
                "messages",
                "images.generations",
                "images.edits",
                "audio.transcriptions",
                "audio.speech",
                "decisions",
                "images.analyze"
              ]
            }
          },
          {
            "name": "priority",
            "in": "query",
            "schema": {
              "type": "string",
              "enum": [
                "economy",
                "balanced",
                "quality",
                "fast"
              ]
            }
          },
          {
            "name": "choices",
            "in": "query",
            "schema": {
              "type": "boolean"
            }
          },
          {
            "name": "n",
            "in": "query",
            "schema": {
              "type": "integer"
            }
          },
          {
            "name": "chars",
            "in": "query",
            "schema": {
              "type": "integer"
            },
            "description": "Prompt length, instead of the prompt."
          }
        ],
        "responses": {
          "200": {
            "description": "The choice.",
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {
                      "type": "object",
                      "properties": {
                        "chosen": {
                          "type": "object",
                          "properties": {
                            "id": {
                              "type": "string",
                              "example": "vendor:OpenAi:gpt-image-1"
                            },
                            "model": {
                              "type": "string"
                            },
                            "provider": {
                              "type": "string"
                            },
                            "title": {
                              "type": "string"
                            },
                            "kind": {
                              "type": "string",
                              "enum": [
                                "vendor",
                                "mesh"
                              ]
                            }
                          }
                        },
                        "price": {
                          "type": "object",
                          "properties": {
                            "totalMicros": {
                              "type": "integer"
                            },
                            "total": {
                              "type": "number"
                            },
                            "currency": {
                              "type": "string"
                            },
                            "exact": {
                              "type": "boolean"
                            }
                          }
                        },
                        "wait": {
                          "type": "object",
                          "properties": {
                            "p50": {
                              "type": [
                                "number",
                                "null"
                              ]
                            },
                            "n": {
                              "type": "integer"
                            }
                          }
                        },
                        "reason": {
                          "type": "array",
                          "items": {
                            "type": "object"
                          },
                          "description": "The reason as fragments, for a client that renders its own words."
                        },
                        "why": {
                          "type": "string",
                          "description": "The reason as one line in the caller's language.",
                          "example": "GPT Image 1 · Product kept intact: 97% over 4 tests · €0.120 · ~25 s · Priority: Quality"
                        },
                        "routeToken": {
                          "type": "string"
                        },
                        "expiresAt": {
                          "type": "integer"
                        }
                      }
                    },
                    {
                      "type": "object",
                      "properties": {
                        "model": {
                          "const": "auto"
                        },
                        "endpoint": {
                          "type": "string"
                        },
                        "profile": {
                          "type": "string"
                        },
                        "category": {
                          "type": "string",
                          "example": "product-edit"
                        },
                        "language": {
                          "type": "string",
                          "example": "en"
                        },
                        "mode": {
                          "type": "string",
                          "enum": [
                            "direct",
                            "translate",
                            "off"
                          ]
                        },
                        "jev": {
                          "type": "string",
                          "description": "`answered`, `low-confidence` or `skipped:<reason>`."
                        },
                        "alternatives": {
                          "type": "array",
                          "items": {
                            "type": "object"
                          },
                          "description": "The next options by utility, best first."
                        },
                        "choices": {
                          "type": "boolean"
                        },
                        "cards": {
                          "type": "array",
                          "items": {
                            "type": "object",
                            "properties": {
                              "chosen": {
                                "type": "object",
                                "properties": {
                                  "id": {
                                    "type": "string",
                                    "example": "vendor:OpenAi:gpt-image-1"
                                  },
                                  "model": {
                                    "type": "string"
                                  },
                                  "provider": {
                                    "type": "string"
                                  },
                                  "title": {
                                    "type": "string"
                                  },
                                  "kind": {
                                    "type": "string",
                                    "enum": [
                                      "vendor",
                                      "mesh"
                                    ]
                                  }
                                }
                              },
                              "price": {
                                "type": "object",
                                "properties": {
                                  "totalMicros": {
                                    "type": "integer"
                                  },
                                  "total": {
                                    "type": "number"
                                  },
                                  "currency": {
                                    "type": "string"
                                  },
                                  "exact": {
                                    "type": "boolean"
                                  }
                                }
                              },
                              "wait": {
                                "type": "object",
                                "properties": {
                                  "p50": {
                                    "type": [
                                      "number",
                                      "null"
                                    ]
                                  },
                                  "n": {
                                    "type": "integer"
                                  }
                                }
                              },
                              "reason": {
                                "type": "array",
                                "items": {
                                  "type": "object"
                                },
                                "description": "The reason as fragments, for a client that renders its own words."
                              },
                              "why": {
                                "type": "string",
                                "description": "The reason as one line in the caller's language.",
                                "example": "GPT Image 1 · Product kept intact: 97% over 4 tests · €0.120 · ~25 s · Priority: Quality"
                              },
                              "routeToken": {
                                "type": "string"
                              },
                              "expiresAt": {
                                "type": "integer"
                              }
                            }
                          },
                          "minItems": 2,
                          "maxItems": 2
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "400": {
            "description": "No or unknown `endpoint`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "422": {
            "description": "`auto_unsupported`: nothing has been measured for this kind of request yet - name a model.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "503": {
            "description": "`auto_unavailable`: no measured option passes the caller's settings right now.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      },
      "post": {
        "tags": [
          "Catalogue"
        ],
        "operationId": "quoteAuto",
        "summary": "Which model Auto would choose for this exact request",
        "security": [],
        "description": "Which model `model: \"auto\"` would run this request on, what it costs, how long it typically takes and **why** - before anything runs (docs/features/13). Public like the other quotes; a signed-in caller or a key gets their own routing settings (no-training, Mesh, trusted rigs), saved priority and balance.\n\nThe router keeps only candidates that can do the request, that the caller's settings allow and that have **measurements in the request's category** (`GET /admin/routing-evidence` on the gateway), then ranks them by measured quality, price and typical wait under the priority profile: `economy`, `balanced` (default), `quality` or `fast`. TypeSafe's Jev (through OpenRouter, never training) classifies the request when its language is enabled and there is time; otherwise, or when Jev is unsure, rules and measurements decide.\n\nSend the answer's `routeToken` back as `routing.routeToken` on the real call and the run is bound to exactly this choice (15 minutes). If that choice cannot run any more the call is refused with 409 `route_expired` - it is never quietly routed elsewhere. Without a token, `model: \"auto\"` on a task endpoint decides then and there and names the model in the `x-routed-model` response header, with `x-route-candidate` and an English `x-route-reason`.\n\nWhen the top two options measured almost the same and the caller asked for `routing.choices`, `choices` is true and `cards` holds both, each with its own token.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "endpoint"
                ],
                "additionalProperties": true,
                "properties": {
                  "endpoint": {
                    "type": "string",
                    "enum": [
                      "chat.completions",
                      "messages",
                      "images.generations",
                      "images.edits",
                      "audio.transcriptions",
                      "audio.speech",
                      "decisions",
                      "images.analyze"
                    ]
                  },
                  "routing": {
                    "type": "object",
                    "properties": {
                      "priority": {
                        "type": "string",
                        "enum": [
                          "economy",
                          "balanced",
                          "quality",
                          "fast"
                        ],
                        "description": "The profile. Default: the account's saved one, else `balanced`."
                      },
                      "choices": {
                        "type": "boolean",
                        "description": "The caller can show two options; ask for both when they are a close call."
                      },
                      "routeToken": {
                        "type": "string",
                        "description": "On a task endpoint: the quote's token, which binds the run to the quoted choice."
                      }
                    }
                  }
                },
                "description": "The body the real call would send, plus `endpoint`."
              }
            },
            "multipart/form-data": {
              "schema": {
                "type": "object",
                "required": [
                  "endpoint"
                ],
                "additionalProperties": true,
                "description": "The same fields plus the input file (image or audio), measured like the real call's."
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The choice.",
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {
                      "type": "object",
                      "properties": {
                        "chosen": {
                          "type": "object",
                          "properties": {
                            "id": {
                              "type": "string",
                              "example": "vendor:OpenAi:gpt-image-1"
                            },
                            "model": {
                              "type": "string"
                            },
                            "provider": {
                              "type": "string"
                            },
                            "title": {
                              "type": "string"
                            },
                            "kind": {
                              "type": "string",
                              "enum": [
                                "vendor",
                                "mesh"
                              ]
                            }
                          }
                        },
                        "price": {
                          "type": "object",
                          "properties": {
                            "totalMicros": {
                              "type": "integer"
                            },
                            "total": {
                              "type": "number"
                            },
                            "currency": {
                              "type": "string"
                            },
                            "exact": {
                              "type": "boolean"
                            }
                          }
                        },
                        "wait": {
                          "type": "object",
                          "properties": {
                            "p50": {
                              "type": [
                                "number",
                                "null"
                              ]
                            },
                            "n": {
                              "type": "integer"
                            }
                          }
                        },
                        "reason": {
                          "type": "array",
                          "items": {
                            "type": "object"
                          },
                          "description": "The reason as fragments, for a client that renders its own words."
                        },
                        "why": {
                          "type": "string",
                          "description": "The reason as one line in the caller's language.",
                          "example": "GPT Image 1 · Product kept intact: 97% over 4 tests · €0.120 · ~25 s · Priority: Quality"
                        },
                        "routeToken": {
                          "type": "string"
                        },
                        "expiresAt": {
                          "type": "integer"
                        }
                      }
                    },
                    {
                      "type": "object",
                      "properties": {
                        "model": {
                          "const": "auto"
                        },
                        "endpoint": {
                          "type": "string"
                        },
                        "profile": {
                          "type": "string"
                        },
                        "category": {
                          "type": "string",
                          "example": "product-edit"
                        },
                        "language": {
                          "type": "string",
                          "example": "en"
                        },
                        "mode": {
                          "type": "string",
                          "enum": [
                            "direct",
                            "translate",
                            "off"
                          ]
                        },
                        "jev": {
                          "type": "string",
                          "description": "`answered`, `low-confidence` or `skipped:<reason>`."
                        },
                        "alternatives": {
                          "type": "array",
                          "items": {
                            "type": "object"
                          },
                          "description": "The next options by utility, best first."
                        },
                        "choices": {
                          "type": "boolean"
                        },
                        "cards": {
                          "type": "array",
                          "items": {
                            "type": "object",
                            "properties": {
                              "chosen": {
                                "type": "object",
                                "properties": {
                                  "id": {
                                    "type": "string",
                                    "example": "vendor:OpenAi:gpt-image-1"
                                  },
                                  "model": {
                                    "type": "string"
                                  },
                                  "provider": {
                                    "type": "string"
                                  },
                                  "title": {
                                    "type": "string"
                                  },
                                  "kind": {
                                    "type": "string",
                                    "enum": [
                                      "vendor",
                                      "mesh"
                                    ]
                                  }
                                }
                              },
                              "price": {
                                "type": "object",
                                "properties": {
                                  "totalMicros": {
                                    "type": "integer"
                                  },
                                  "total": {
                                    "type": "number"
                                  },
                                  "currency": {
                                    "type": "string"
                                  },
                                  "exact": {
                                    "type": "boolean"
                                  }
                                }
                              },
                              "wait": {
                                "type": "object",
                                "properties": {
                                  "p50": {
                                    "type": [
                                      "number",
                                      "null"
                                    ]
                                  },
                                  "n": {
                                    "type": "integer"
                                  }
                                }
                              },
                              "reason": {
                                "type": "array",
                                "items": {
                                  "type": "object"
                                },
                                "description": "The reason as fragments, for a client that renders its own words."
                              },
                              "why": {
                                "type": "string",
                                "description": "The reason as one line in the caller's language.",
                                "example": "GPT Image 1 · Product kept intact: 97% over 4 tests · €0.120 · ~25 s · Priority: Quality"
                              },
                              "routeToken": {
                                "type": "string"
                              },
                              "expiresAt": {
                                "type": "integer"
                              }
                            }
                          },
                          "minItems": 2,
                          "maxItems": 2
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "400": {
            "description": "No or unknown `endpoint`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "422": {
            "description": "`auto_unsupported`: nothing has been measured for this kind of request yet - name a model.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "503": {
            "description": "`auto_unavailable`: no measured option passes the caller's settings right now.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/models/{model}/quote": {
      "get": {
        "tags": [
          "Catalogue"
        ],
        "operationId": "quoteModelFromQuery",
        "summary": "What a request would cost, from query parameters",
        "security": [],
        "description": "Public and unauthenticated, like the catalogue: the point of publishing a price is that someone can check it before signing up. Nothing is reserved, charged, or sent to a provider.\n\n`/models` says what a model costs per unit; this says what **this** request costs — the same numbers the gateway reserves against the balance a moment later, computed the same way, plus the latency the daily forecast expects.\n\n**POST** takes the body the real call would have taken, so you can quote the exact request you are about to make. **GET** takes only the parameters that move the number, for a page that wants a figure to display as the user types without putting a prompt in a URL — and therefore in a log.\n\nRead `exact` before showing the figure as a price. It is `true` only when every priced item is a fixed per-piece tariff. For a token-metered model the unit prices are exact and the token counts are predicted from the request, which is why `estimatedQuantities` is returned next to the money rather than folded into it; the final charge comes from the counters the provider reports.",
        "parameters": [
          {
            "name": "model",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "Canonical model name, as `GET /models` lists it.",
            "example": "gpt-image-1"
          },
          {
            "name": "n",
            "in": "query",
            "required": false,
            "schema": {
              "type": "integer",
              "minimum": 1,
              "default": 1
            },
            "description": "How many results. Multiplies a per-piece tariff."
          },
          {
            "name": "max_tokens",
            "in": "query",
            "required": false,
            "schema": {
              "type": "integer",
              "minimum": 1
            },
            "description": "Output ceiling. Without one, a conservative default is assumed."
          },
          {
            "name": "chars",
            "in": "query",
            "required": false,
            "schema": {
              "type": "integer",
              "minimum": 0
            },
            "description": "Prompt length in characters, standing in for a prompt you do not want to put in a URL. Only the length is read."
          }
        ],
        "responses": {
          "200": {
            "description": "The quote.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Quote"
                }
              }
            }
          },
          "404": {
            "description": "No such model. `GET /models` lists the ones there are.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      },
      "post": {
        "tags": [
          "Catalogue"
        ],
        "operationId": "quoteModel",
        "summary": "What a request would cost, from the request itself",
        "security": [],
        "description": "Public and unauthenticated, like the catalogue: the point of publishing a price is that someone can check it before signing up. Nothing is reserved, charged, or sent to a provider.\n\n`/models` says what a model costs per unit; this says what **this** request costs — the same numbers the gateway reserves against the balance a moment later, computed the same way, plus the latency the daily forecast expects.\n\n**POST** takes the body the real call would have taken, so you can quote the exact request you are about to make. **GET** takes only the parameters that move the number, for a page that wants a figure to display as the user types without putting a prompt in a URL — and therefore in a log.\n\nRead `exact` before showing the figure as a price. It is `true` only when every priced item is a fixed per-piece tariff. For a token-metered model the unit prices are exact and the token counts are predicted from the request, which is why `estimatedQuantities` is returned next to the money rather than folded into it; the final charge comes from the counters the provider reports.",
        "parameters": [
          {
            "name": "model",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "Canonical model name, as `GET /models` lists it.",
            "example": "gpt-image-1"
          }
        ],
        "requestBody": {
          "required": false,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "additionalProperties": true,
                "description": "The body the real call would have taken. `messages`, `system`, `prompt`, `input`, `max_tokens` and `n` are the fields that move the number; everything else is ignored."
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The quote.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Quote"
                }
              }
            }
          },
          "404": {
            "description": "No such model. `GET /models` lists the ones there are.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/media/{id}": {
      "get": {
        "tags": [
          "Images"
        ],
        "operationId": "getPrivateMedia",
        "summary": "Download a private file by its signed link",
        "description": "The link comes from the gateway (a private result's `url`). It needs no API key: the signature and the expiry are the guard, and any failure is the same 404.",
        "security": [],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "pattern": "^[a-f0-9]{32}$"
            }
          },
          {
            "name": "exp",
            "in": "query",
            "required": true,
            "schema": {
              "type": "integer"
            }
          },
          {
            "name": "sig",
            "in": "query",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The file.",
            "content": {
              "image/png": {},
              "image/jpeg": {},
              "image/webp": {},
              "image/gif": {}
            }
          },
          "404": {
            "description": "Unknown id, wrong signature or expired link."
          }
        }
      }
    },
    "/decisions": {
      "post": {
        "tags": [
          "Decisions"
        ],
        "operationId": "decide",
        "summary": "Typed decisions with probabilities",
        "description": "Gateway-specific. Yes/no (`noul`), one-of-several (`choice`) and ordered (`score`) questions about a `state`, answered with probabilities you can threshold. Served by Jev (TypeSafe, via OpenRouter); `model` is `auto` or `jev-1.13`. Up to 20 questions. Billed per input token (`usage.input_tokens`); output is free. Synchronous.\n\n`provider.data_collection: \"deny\"` is sent to OpenRouter unless the account has unticked \"Ask not to train\" for OpenRouter on its profile (feature 19). An account limited to no-training providers is refused (403 `routing_refused`) only in that case.",
        "x-compatibility": {
          "openai": "No equivalent.",
          "grok": "No equivalent.",
          "anthropic": "No equivalent."
        },
        "parameters": [
          {
            "$ref": "#/components/parameters/IdempotencyKey"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/DecisionRequest"
              },
              "example": {
                "model": "auto",
                "state": "Checkout throws a 500 when a card is declined.",
                "questions": {
                  "is_bug": {
                    "type": "noul",
                    "instructions": "Is this a bug report?"
                  },
                  "team": {
                    "type": "choice",
                    "criteria": {
                      "payments": "money",
                      "frontend": "UI"
                    }
                  },
                  "urgency": {
                    "type": "score",
                    "criteria": [
                      "low",
                      "medium",
                      "high"
                    ]
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The answers.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/DecisionResponse"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/ValidationError"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "description": "Insufficient balance, or the API key's spending cap (`key_spend_cap_reached`).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "The account is limited to providers that do not train on user input, and the decision provider is not labelled as one.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "type": "error",
                  "error": {
                    "type": "permission_error",
                    "message": "Your account is limited to providers that do not train on user input, and OpenRouter is not labelled as one. Change that on your profile to use this model.",
                    "code": "routing_refused"
                  }
                }
              }
            }
          },
          "409": {
            "$ref": "#/components/responses/IdempotencyConflict"
          },
          "422": {
            "$ref": "#/components/responses/IdempotencyReused"
          },
          "502": {
            "description": "The decision provider failed. Nothing was charged.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "type": "error",
                  "error": {
                    "type": "api_error",
                    "message": "The decision provider failed. Nothing was charged.",
                    "code": "provider_error"
                  }
                }
              }
            }
          },
          "503": {
            "description": "Decisions are unavailable on our side (the upstream credit is exhausted, or no endpoint accepts the no-retention policy). Nothing was charged; retry later.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "type": "error",
                  "error": {
                    "type": "api_error",
                    "message": "Decisions are temporarily unavailable. Nothing was charged; please retry later.",
                    "code": "decisions_unavailable"
                  }
                }
              }
            }
          }
        }
      }
    },
    "/decisions/batch": {
      "post": {
        "tags": [
          "Decisions"
        ],
        "operationId": "decideBatch",
        "summary": "The same questions over up to 50 states",
        "description": "One result per item, in the order sent; an item that fails carries its own `error` without failing the batch. The whole batch is one task and one charge, on the input tokens of the answered items.",
        "x-compatibility": {
          "openai": "No equivalent.",
          "grok": "No equivalent.",
          "anthropic": "No equivalent."
        },
        "parameters": [
          {
            "$ref": "#/components/parameters/IdempotencyKey"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/DecisionBatchRequest"
              },
              "example": {
                "model": "auto",
                "questions": {
                  "is_bug": {
                    "type": "noul",
                    "instructions": "Is this a bug report?"
                  }
                },
                "items": [
                  {
                    "id": "ticket-1",
                    "state": "Checkout throws a 500."
                  },
                  {
                    "id": "ticket-2",
                    "state": "Please add dark mode."
                  }
                ]
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The answers.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/DecisionBatchResponse"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/ValidationError"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "description": "Insufficient balance, or the API key's spending cap (`key_spend_cap_reached`).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "The account is limited to providers that do not train on user input, and the decision provider is not labelled as one.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "type": "error",
                  "error": {
                    "type": "permission_error",
                    "message": "Your account is limited to providers that do not train on user input, and OpenRouter is not labelled as one. Change that on your profile to use this model.",
                    "code": "routing_refused"
                  }
                }
              }
            }
          },
          "409": {
            "$ref": "#/components/responses/IdempotencyConflict"
          },
          "422": {
            "$ref": "#/components/responses/IdempotencyReused"
          },
          "502": {
            "description": "The decision provider failed. Nothing was charged.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "type": "error",
                  "error": {
                    "type": "api_error",
                    "message": "The decision provider failed. Nothing was charged.",
                    "code": "provider_error"
                  }
                }
              }
            }
          },
          "503": {
            "description": "Decisions are unavailable on our side (the upstream credit is exhausted, or no endpoint accepts the no-retention policy). Nothing was charged; retry later.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "type": "error",
                  "error": {
                    "type": "api_error",
                    "message": "Decisions are temporarily unavailable. Nothing was charged; please retry later.",
                    "code": "decisions_unavailable"
                  }
                }
              }
            }
          }
        }
      }
    },
    "/tasks/{taskId}/cancel": {
      "post": {
        "tags": [
          "Tasks"
        ],
        "operationId": "cancelTask",
        "summary": "Take back a task that has not started",
        "description": "Only a Mesh job still queued with no worker holding its lease can be cancelled: it becomes `CANCELLED`, its reservation is released and nothing is charged; a callback, if one was asked for, fires with `state: CANCELLED`. A leased Mesh job and every vendor async task answer 409 `not_cancellable`: the vendor is already billing for it. A task that is not yours is a 404.",
        "parameters": [
          {
            "name": "taskId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Cancelled.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "taskId": {
                      "type": "string"
                    },
                    "state": {
                      "type": "string",
                      "enum": [
                        "CANCELLED"
                      ]
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "description": "No such task on this account.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "type": "error",
                  "error": {
                    "type": "not_found_error",
                    "message": "No such task.",
                    "code": "task_not_found"
                  }
                }
              }
            }
          },
          "409": {
            "description": "Already running at a provider or on a worker, or already finished.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "type": "error",
                  "error": {
                    "type": "api_error",
                    "message": "A worker has already taken this job, so it cannot be cancelled.",
                    "code": "not_cancellable"
                  }
                }
              }
            }
          }
        }
      }
    },
    "/batches/quote": {
      "post": {
        "tags": [
          "Batches"
        ],
        "operationId": "quoteBatch",
        "summary": "What a batch would cost, without running anything",
        "description": "Each item is priced the way `/models/{model}/quote` prices it, so the total is the sum of the single quotes. Items that cannot run are listed in `warnings`. The `quoteToken` binds these prices for an hour.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/BatchRequest"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The quote.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/BatchQuote"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/ValidationError"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          }
        }
      }
    },
    "/batches": {
      "post": {
        "tags": [
          "Batches"
        ],
        "operationId": "createBatch",
        "summary": "Submit a batch",
        "description": "Stored and answered at once (202); the worker runs the items. Refused whole (400 `batch_items_invalid`, `error.items` lists the first 20) if any item cannot run. Honours `Idempotency-Key`.",
        "parameters": [
          {
            "name": "Idempotency-Key",
            "in": "header",
            "schema": {
              "type": "string",
              "maxLength": 128
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/BatchRequest"
              }
            }
          }
        },
        "responses": {
          "202": {
            "description": "Accepted.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/BatchStatus"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/ValidationError"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "409": {
            "$ref": "#/components/responses/IdempotencyConflict"
          },
          "422": {
            "$ref": "#/components/responses/IdempotencyReused"
          }
        }
      },
      "get": {
        "tags": [
          "Batches"
        ],
        "operationId": "listBatches",
        "summary": "The caller's batches, newest first",
        "parameters": [
          {
            "name": "limit",
            "in": "query",
            "schema": {
              "type": "integer",
              "default": 20,
              "maximum": 100
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The list.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "object": {
                      "const": "list"
                    },
                    "data": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/BatchStatus"
                      }
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          }
        }
      }
    },
    "/batches/{id}": {
      "get": {
        "tags": [
          "Batches"
        ],
        "operationId": "getBatch",
        "summary": "Progress of a batch",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "pattern": "^batch_[a-f0-9]{24}$"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The batch.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/BatchStatus"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      }
    },
    "/batches/{id}/results": {
      "get": {
        "tags": [
          "Batches"
        ],
        "operationId": "getBatchResults",
        "summary": "Finished items, in submission order",
        "description": "The cursor waits at the first unfinished item, so paging never skips one. `format=jsonl` answers one result per line with `X-Next-Cursor` / `X-Has-More` headers.",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "pattern": "^batch_[a-f0-9]{24}$"
            }
          },
          {
            "name": "cursor",
            "in": "query",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "limit",
            "in": "query",
            "schema": {
              "type": "integer",
              "default": 100,
              "maximum": 500
            }
          },
          {
            "name": "format",
            "in": "query",
            "schema": {
              "type": "string",
              "enum": [
                "json",
                "jsonl"
              ]
            }
          }
        ],
        "responses": {
          "200": {
            "description": "A page.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "object": {
                      "const": "list"
                    },
                    "batchId": {
                      "type": "string"
                    },
                    "data": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/BatchResult"
                      }
                    },
                    "nextCursor": {
                      "type": [
                        "string",
                        "null"
                      ]
                    },
                    "hasMore": {
                      "type": "boolean"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      }
    },
    "/batches/{id}/cancel": {
      "post": {
        "tags": [
          "Batches"
        ],
        "operationId": "cancelBatch",
        "summary": "Stop every item that has not started",
        "description": "Unstarted items become `cancelled` and are never charged; running items finish and are charged.",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "pattern": "^batch_[a-f0-9]{24}$"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Cancelled.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/BatchStatus"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "409": {
            "description": "Already finished or cancelled (`batch_not_cancellable`).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/account": {
      "get": {
        "tags": [
          "Account"
        ],
        "operationId": "getAccount",
        "summary": "Balance, low-balance flag, and this key's cap and spend",
        "responses": {
          "200": {
            "description": "The account.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Account"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          }
        }
      }
    },
    "/account/usage": {
      "get": {
        "tags": [
          "Account"
        ],
        "operationId": "getAccountUsage",
        "summary": "Runs and money per day, model, endpoint or key",
        "parameters": [
          {
            "name": "from",
            "in": "query",
            "schema": {
              "type": "string",
              "format": "date"
            }
          },
          {
            "name": "to",
            "in": "query",
            "schema": {
              "type": "string",
              "format": "date"
            }
          },
          {
            "name": "groupBy",
            "in": "query",
            "schema": {
              "type": "string",
              "enum": [
                "day",
                "model",
                "endpoint",
                "key"
              ],
              "default": "day"
            }
          },
          {
            "name": "key",
            "in": "query",
            "schema": {
              "type": "string",
              "enum": [
                "current",
                "all"
              ],
              "default": "current"
            },
            "description": "`all` (and `groupBy=key`) needs a key marked on the profile as allowed to read every key."
          }
        ],
        "responses": {
          "200": {
            "description": "The report.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/AccountUsage"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/ValidationError"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          }
        }
      }
    },
    "/account/usage.csv": {
      "get": {
        "tags": [
          "Account"
        ],
        "operationId": "getAccountUsageCsv",
        "summary": "The usage report as CSV",
        "parameters": [
          {
            "name": "from",
            "in": "query",
            "schema": {
              "type": "string",
              "format": "date"
            }
          },
          {
            "name": "to",
            "in": "query",
            "schema": {
              "type": "string",
              "format": "date"
            }
          },
          {
            "name": "groupBy",
            "in": "query",
            "schema": {
              "type": "string",
              "enum": [
                "day",
                "model",
                "endpoint",
                "key"
              ],
              "default": "day"
            }
          },
          {
            "name": "key",
            "in": "query",
            "schema": {
              "type": "string",
              "enum": [
                "current",
                "all"
              ],
              "default": "current"
            },
            "description": "`all` (and `groupBy=key`) needs a key marked on the profile as allowed to read every key."
          }
        ],
        "responses": {
          "200": {
            "description": "One line per group, then the total.",
            "content": {
              "text/csv": {
                "schema": {
                  "type": "string"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/ValidationError"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          }
        }
      }
    },
    "/account/webhook": {
      "post": {
        "tags": [
          "Account"
        ],
        "operationId": "setAccountWebhook",
        "summary": "Where this key's low-balance webhook goes",
        "description": "Each fall of the balance below the threshold set on the profile sends one signed `balance.low` callback to every key with a webhook. `null` removes it.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "callback_url": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "format": "uri"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Saved.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "callback_url": {
                      "type": [
                        "string",
                        "null"
                      ]
                    },
                    "events": {
                      "type": "array",
                      "items": {
                        "type": "string"
                      }
                    },
                    "lowBalanceThresholdEur": {
                      "type": "number"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/ValidationError"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          }
        }
      }
    },
    "/uploads": {
      "post": {
        "tags": [
          "Assets"
        ],
        "operationId": "createUpload",
        "summary": "Keep an image for later requests to name by id",
        "description": "A multipart file (any field name) or JSON `{\"data\": \"data:image/...;base64,...\"}`. Stored privately - it has no URL at all, only an id its owner can use in `options.references`, `upload` on `/images/upscale`, and batch items, for 24 hours. A per-user allowance of live uploads applies.",
        "requestBody": {
          "required": true,
          "content": {
            "multipart/form-data": {
              "schema": {
                "type": "object",
                "properties": {
                  "image": {
                    "type": "string",
                    "format": "binary"
                  }
                }
              }
            },
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "data"
                ],
                "properties": {
                  "data": {
                    "type": "string"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Stored.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Upload"
                }
              }
            }
          },
          "400": {
            "description": "No image, or not PNG, JPEG, WebP or GIF (`upload_missing`, `upload_invalid`).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "413": {
            "description": "Larger than 25 MB (`upload_too_large`), or the live uploads would exceed the allowance (`upload_allowance_exceeded`).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/runs/{id}": {
      "get": {
        "tags": [
          "Assets"
        ],
        "operationId": "getRun",
        "summary": "One run: state, charge and provenance, for its owner",
        "description": "`{id}` is a run id (`provenance.runId`, batch results) or the `taskId` an async answer handed out. Somebody else's run is the same 404 as none.",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The run.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Run"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "description": "No such run (`run_not_found`).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/models/capabilities": {
      "get": {
        "tags": [
          "Catalogue"
        ],
        "operationId": "findModelsByCapability",
        "security": [],
        "summary": "The image models that satisfy every need",
        "description": "Public. `need` is a comma list of `generate`, `edit`, `variations`, `upscale`, `transparent`, `negativePrompt`, `seed`, `commercialUse`, `aspectRatio:W:H`, `n:N`, `references[:N]`, `quality:draft|standard|high`, `outputFormat:png|webp|jpeg`, `textRendering:good|poor|unknown`. A need counts as met only when strict `options` would honour it. `/models` carries the same `capabilities` on every image model.",
        "parameters": [
          {
            "name": "need",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string"
            },
            "example": "edit,transparent,aspectRatio:4:5"
          }
        ],
        "responses": {
          "200": {
            "description": "Matching models.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CapabilityMatches"
                }
              }
            }
          },
          "400": {
            "description": "A need it does not understand (`need_invalid`).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/embeddings": {
      "post": {
        "tags": [
          "Images"
        ],
        "operationId": "createEmbeddings",
        "summary": "Create embeddings (OpenAI)",
        "description": "The OpenAI Embeddings contract (docs/features/25-embeddings.md): vectors for product matching, deduplication, \"similar products\" and semantic search. `input` is one string or up to 2,048 strings, each at most 8,192 tokens (a text that is certainly longer is refused 400; a borderline one is left to the vendor, whose refusal is a free failed run). `dimensions` shortens the vector (`text-embedding-3-*`); `encoding_format` is `float` (default) or `base64`. Token-id arrays are not supported.\n\nModels: `text-embedding-3-small` (1536 dimensions) and `text-embedding-3-large` (3072), served by OpenAI, billed per input token (`usage.prompt_tokens`; the vector is free) with the usual 10 % margin. There is no `model: \"auto\"`: a vector space is the model, and two models' vectors must never share an index. Use `GET /models` to see what is offered. Synchronous; also runnable in a batch as the `embeddings` endpoint. Find candidate pairs with embeddings, then let `/decisions` judge the uncertain ones.",
        "x-compatibility": {
          "openai": "Drop-in for `POST /v1/embeddings` (`client.embeddings.create()`), including the error envelope and `usage`."
        },
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/EmbeddingRequest"
              },
              "examples": {
                "products": {
                  "summary": "Two product titles",
                  "value": {
                    "model": "text-embedding-3-small",
                    "input": [
                      "Red running shoe, size 42",
                      "Crimson jogging sneaker 42"
                    ]
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "One embedding per input, in order.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/EmbeddingResponse"
                }
              }
            }
          },
          "400": {
            "description": "A malformed request: no or unknown model, empty or too many inputs, a text over the limit, bad `dimensions`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "$ref": "#/components/responses/PaymentRequired"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "409": {
            "$ref": "#/components/responses/IdempotencyConflict"
          }
        }
      }
    }
  },
  "components": {
    "parameters": {
      "Wait": {
        "name": "wait",
        "in": "query",
        "required": false,
        "description": "`1`, `true` or `yes` (case-insensitive) to have the gateway poll an asynchronous provider itself and answer with the finished `{created, data}` envelope, rather than a `taskId` the caller must poll. Ignored by synchronous providers.\n\nA request may block for tens of seconds as a result. If the wait runs out the task is left running and the ordinary `{taskId}` answer is returned, so nothing is lost — poll `/images/status/{model}/{taskId}` from there.\n\nQuery parameter rather than a body field on purpose: the request body is forwarded to the provider, and an unrecognised parameter is a hard 400 at OpenAI.",
        "schema": {
          "type": "string",
          "enum": [
            "1",
            "true",
            "yes"
          ]
        },
        "example": "1"
      },
      "IdempotencyKey": {
        "name": "Idempotency-Key",
        "in": "header",
        "required": false,
        "description": "Up to 128 printable ASCII characters. The same key with the same request (method, path and canonical body, uploaded files by hash) answers the stored response again, with the header `Idempotent-Replayed: true`, and starts no new task. The same key with a different request is a 422 `idempotency_key_reused`; while the first request is still running, 409 `idempotency_in_progress`. A 5xx answer is not stored, so retrying it is a real retry. Keys are kept 24 hours, per account.",
        "schema": {
          "type": "string",
          "maxLength": 128
        }
      }
    },
    "securitySchemes": {
      "ApiKeyHeader": {
        "type": "apiKey",
        "in": "header",
        "name": "X-Api-Key",
        "description": "A user-issued API key. This is also the header the Anthropic SDK sends, so it needs no special configuration. Grants the `user` role only — never `admin`, even for an account that holds it. A key may be pinned to a client IP range; a call from outside it is refused with 403. Query-string keys are deliberately not accepted."
      },
      "BearerApiKey": {
        "type": "http",
        "scheme": "bearer",
        "description": "The same user-issued API key, sent as `Authorization: Bearer <key>` — what the OpenAI and xAI SDKs send. This header is shared with the mesh worker tokens, which are resolved first; a string that is not a worker token falls through to the user-key store."
      },
      "SessionCookie": {
        "type": "apiKey",
        "in": "cookie",
        "name": "PHPSESSID",
        "description": "Browser session, established through the interactive login at `/auth`. This is what the first-party web UI uses."
      }
    },
    "responses": {
      "ValidationError": {
        "description": "The gateway rejected the request before contacting a provider (empty prompt, missing voice, missing file).",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            },
            "example": {
              "type": "error",
              "error": {
                "type": "invalid_request_error",
                "message": "Nothing to send: the prompt is empty."
              }
            }
          }
        }
      },
      "Unauthorized": {
        "description": "No credential was presented, or the API key is not valid.",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            },
            "example": {
              "type": "error",
              "error": {
                "type": "authentication_error",
                "message": "Invalid API key"
              }
            }
          }
        }
      },
      "Forbidden": {
        "description": "The API key is valid but not permitted from this client address.",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            },
            "example": {
              "type": "error",
              "error": {
                "type": "permission_error",
                "message": "API key \"laptop\" is not permitted from 203.0.113.9"
              }
            }
          }
        }
      },
      "PaymentRequired": {
        "description": "Insufficient balance. Raised before any provider call.",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            },
            "example": {
              "type": "error",
              "error": {
                "type": "billing_error",
                "message": "Insufficient balance: 0",
                "code": "insufficient_quota"
              }
            }
          }
        }
      },
      "NotFound": {
        "description": "No such endpoint, model or task.",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            },
            "example": {
              "type": "error",
              "error": {
                "type": "not_found_error",
                "message": "Unknown task: e2b1f0c4"
              }
            }
          }
        }
      },
      "ProviderError": {
        "description": "The upstream provider rejected or failed the request. Where the provider sent its own `{\"error\": {...}}` body it is passed through; otherwise its message is wrapped and the original body is kept under `error.provider`. The run is recorded as failed.",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            },
            "example": {
              "type": "error",
              "error": {
                "type": "api_error",
                "message": "Unknown parameter: 'style'."
              }
            }
          }
        }
      },
      "KeySpendCap": {
        "description": "The API key has a spending cap and this request would take it over (finished charges plus tasks still running, in the current day or month). Refused before any provider call.",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            },
            "example": {
              "type": "error",
              "error": {
                "type": "billing_error",
                "message": "API key \"shop-a\" has reached its spending cap of 5.00 EUR per day (4.98 EUR spent or reserved). Raise the cap on your profile or wait for the next day.",
                "code": "key_spend_cap_reached"
              }
            }
          }
        }
      },
      "IdempotencyConflict": {
        "description": "A request with this Idempotency-Key is still being processed.",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            },
            "example": {
              "type": "error",
              "error": {
                "type": "api_error",
                "message": "A request with this Idempotency-Key is still being processed. Retry once it has answered.",
                "code": "idempotency_in_progress"
              }
            }
          }
        }
      },
      "IdempotencyReused": {
        "description": "This Idempotency-Key was already used for a different request.",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            },
            "example": {
              "type": "error",
              "error": {
                "type": "invalid_request_error",
                "message": "This Idempotency-Key was already used for a different request.",
                "code": "idempotency_key_reused"
              }
            }
          }
        }
      }
    },
    "schemas": {
      "Error": {
        "type": "object",
        "description": "One envelope for both dialects: OpenAI and xAI clients read `error.message` and `error.type`; the Anthropic client reads the top-level `type: \"error\"` plus `error.type` and `error.message`.",
        "required": [
          "type",
          "error"
        ],
        "properties": {
          "type": {
            "const": "error"
          },
          "error": {
            "type": "object",
            "required": [
              "type",
              "message"
            ],
            "properties": {
              "type": {
                "type": "string",
                "enum": [
                  "invalid_request_error",
                  "authentication_error",
                  "permission_error",
                  "not_found_error",
                  "billing_error",
                  "api_error"
                ]
              },
              "message": {
                "type": "string"
              },
              "code": {
                "type": "string",
                "description": "Present only where a machine-readable sub-code is meaningful, e.g. `insufficient_quota`."
              },
              "provider": {
                "description": "The upstream provider's own error body, when it was not already in a recognised shape. Kept so nothing is lost in the wrapping."
              }
            },
            "additionalProperties": true
          }
        },
        "additionalProperties": true
      },
      "ChatMessage": {
        "type": "object",
        "required": [
          "role",
          "content"
        ],
        "properties": {
          "role": {
            "type": "string",
            "enum": [
              "system",
              "user",
              "assistant",
              "tool"
            ],
            "description": "`system` works for every model, Claude included — the gateway lifts it into Anthropic's top-level `system` field."
          },
          "content": {
            "description": "String, or a provider-specific content-part array.",
            "oneOf": [
              {
                "type": "string"
              },
              {
                "type": "array",
                "items": {
                  "type": "object",
                  "additionalProperties": true
                }
              }
            ]
          },
          "name": {
            "type": "string"
          }
        },
        "additionalProperties": true
      },
      "ChatCompletionRequest": {
        "type": "object",
        "required": [
          "model",
          "messages"
        ],
        "description": "Forwarded to the provider (translated first, for Anthropic models). Any field the upstream vendor accepts may be sent; the ones below are what the gateway itself looks at or overrides.",
        "properties": {
          "model": {
            "type": "string",
            "description": "A gateway model name, e.g. `gpt-5`, `claude-sonnet-5`, `grok-4.5`, `gemini-3.1-pro`, `llama-4-maverick`. See `GET /models`.",
            "examples": [
              "gpt-5",
              "claude-sonnet-5",
              "grok-4.5"
            ]
          },
          "messages": {
            "type": "array",
            "minItems": 1,
            "items": {
              "$ref": "#/components/schemas/ChatMessage"
            },
            "description": "At least one message must carry non-empty content, otherwise the gateway answers 400 without contacting the provider."
          },
          "system": {
            "type": "string",
            "description": "Anthropic-style system prompt. Accepted as an alternative to a system message for Claude models; if both are sent they are concatenated. Ignored by OpenAI-family models."
          },
          "max_tokens": {
            "type": "integer",
            "minimum": 1,
            "description": "Optional for every model. Anthropic requires it upstream, so the gateway defaults it to 4096 for Claude models when absent."
          },
          "max_completion_tokens": {
            "type": "integer",
            "minimum": 1,
            "description": "Accepted as a synonym of `max_tokens` for Claude models, which would otherwise reject the unknown field."
          },
          "temperature": {
            "type": "number"
          },
          "top_p": {
            "type": "number"
          },
          "stop": {
            "description": "Becomes `stop_sequences` for Claude models.",
            "oneOf": [
              {
                "type": "string"
              },
              {
                "type": "array",
                "items": {
                  "type": "string"
                }
              }
            ]
          },
          "stream": {
            "type": "boolean",
            "default": false,
            "description": "Not supported. `true` is refused with a 400 (`invalid_request_error`); omit it, or send `false`."
          },
          "tools": {
            "type": "array",
            "items": {
              "type": "object",
              "additionalProperties": true
            },
            "description": "Passed through untouched. For Claude models the reply is reduced to its text blocks here, so tool_use blocks are lost — use `/messages` for tool calling."
          },
          "callback_url": {
            "$ref": "#/components/schemas/CallbackUrl"
          },
          "retry": {
            "$ref": "#/components/schemas/RetryRequest"
          }
        },
        "additionalProperties": true
      },
      "ChatCompletionResponse": {
        "type": "object",
        "description": "For OpenAI, xAI, Google and OpenRouter this is the provider's response verbatim. For Anthropic it is rebuilt: id, object, created, model, one choice with `finish_reason` mapped from `stop_reason`, and `usage` carrying both vendors' field names.",
        "required": [
          "choices"
        ],
        "properties": {
          "id": {
            "type": "string"
          },
          "object": {
            "type": "string"
          },
          "created": {
            "type": "integer"
          },
          "model": {
            "type": "string",
            "description": "The model name as requested, not the provider's internal id."
          },
          "choices": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "index": {
                  "type": "integer"
                },
                "finish_reason": {
                  "type": [
                    "string",
                    "null"
                  ]
                },
                "message": {
                  "type": "object",
                  "required": [
                    "content"
                  ],
                  "properties": {
                    "role": {
                      "type": "string"
                    },
                    "content": {
                      "type": [
                        "string",
                        "null"
                      ]
                    }
                  },
                  "additionalProperties": true
                }
              },
              "additionalProperties": true
            }
          },
          "usage": {
            "$ref": "#/components/schemas/Usage"
          }
        },
        "additionalProperties": true
      },
      "MessagesRequest": {
        "type": "object",
        "required": [
          "model",
          "messages"
        ],
        "properties": {
          "model": {
            "type": "string",
            "examples": [
              "claude-sonnet-5",
              "claude-opus-5",
              "gpt-5",
              "grok-4.5"
            ]
          },
          "messages": {
            "type": "array",
            "minItems": 1,
            "items": {
              "$ref": "#/components/schemas/ChatMessage"
            },
            "description": "At least one must carry content; a content-block array counts. A `{\"role\":\"system\"}` message here is lifted into `system` rather than rejected."
          },
          "system": {
            "description": "String or an array of text blocks. For non-Anthropic models it becomes a leading system message.",
            "oneOf": [
              {
                "type": "string"
              },
              {
                "type": "array",
                "items": {
                  "type": "object",
                  "additionalProperties": true
                }
              }
            ]
          },
          "max_tokens": {
            "type": "integer",
            "minimum": 1,
            "description": "Required by Anthropic; defaulted to 4096 when absent."
          },
          "stop_sequences": {
            "type": "array",
            "items": {
              "type": "string"
            },
            "description": "Becomes `stop` for non-Anthropic models."
          },
          "temperature": {
            "type": "number"
          },
          "top_p": {
            "type": "number"
          },
          "tools": {
            "type": "array",
            "items": {
              "type": "object",
              "additionalProperties": true
            },
            "description": "Forwarded untouched to Anthropic models, whose `tool_use` blocks survive on this route."
          },
          "stream": {
            "type": "boolean",
            "default": false,
            "description": "Not supported. `true` is refused with a 400 (`invalid_request_error`); omit it, or send `false`."
          },
          "callback_url": {
            "$ref": "#/components/schemas/CallbackUrl"
          },
          "retry": {
            "$ref": "#/components/schemas/RetryRequest"
          }
        },
        "additionalProperties": true
      },
      "MessagesResponse": {
        "type": "object",
        "description": "For Anthropic models this is the provider's response verbatim, apart from `model` (echoed as the name you asked for) and the extra alias keys in `usage`. For every other model it is built from the OpenAI reply and carries exactly one `text` block.",
        "required": [
          "type",
          "role",
          "content"
        ],
        "properties": {
          "id": {
            "type": "string"
          },
          "type": {
            "const": "message"
          },
          "role": {
            "const": "assistant"
          },
          "model": {
            "type": "string"
          },
          "content": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "type": {
                  "type": "string"
                },
                "text": {
                  "type": "string"
                }
              },
              "additionalProperties": true
            },
            "description": "Every block the model produced, for Anthropic models — `text`, `tool_use`, `thinking`. A single `text` block for every other provider."
          },
          "stop_reason": {
            "type": [
              "string",
              "null"
            ],
            "enum": [
              "end_turn",
              "max_tokens",
              "stop_sequence",
              "tool_use",
              "refusal",
              null
            ]
          },
          "stop_sequence": {
            "type": [
              "string",
              "null"
            ]
          },
          "usage": {
            "$ref": "#/components/schemas/Usage"
          }
        },
        "additionalProperties": true
      },
      "Usage": {
        "type": "object",
        "description": "Carries both vendors' spellings of the same counts, whichever provider answered, so `prompt_tokens` and `input_tokens` are both readable. The provider's own object is added to, never rebuilt, so vendor-specific detail (`prompt_tokens_details.cached_tokens`, `input_tokens_details.image_tokens`) is preserved.\n\nAnthropic's counters are disjoint — `input_tokens` excludes the cached ones — so the OpenAI-facing `prompt_tokens` is their sum, not a copy of `input_tokens`.",
        "properties": {
          "prompt_tokens": {
            "type": "integer"
          },
          "completion_tokens": {
            "type": "integer"
          },
          "total_tokens": {
            "type": "integer"
          },
          "input_tokens": {
            "type": "integer"
          },
          "output_tokens": {
            "type": "integer"
          },
          "cache_creation_input_tokens": {
            "type": "integer"
          },
          "cache_read_input_tokens": {
            "type": "integer"
          }
        },
        "additionalProperties": true
      },
      "SpeechRequest": {
        "type": "object",
        "required": [
          "model",
          "input",
          "voice"
        ],
        "properties": {
          "model": {
            "type": "string",
            "examples": [
              "gpt-4o-mini-tts",
              "tts-1",
              "tts-1-hd"
            ]
          },
          "input": {
            "type": "string",
            "minLength": 1,
            "description": "The text to speak. Empty is rejected with 400."
          },
          "voice": {
            "type": "string",
            "minLength": 1,
            "description": "Provider voice id. Empty is rejected with 400.",
            "examples": [
              "alloy",
              "nova",
              "shimmer"
            ]
          },
          "response_format": {
            "type": "string",
            "default": "mp3",
            "examples": [
              "mp3",
              "opus",
              "aac",
              "flac",
              "wav",
              "pcm"
            ]
          },
          "speed": {
            "type": "number",
            "default": 1.0,
            "minimum": 0.25,
            "maximum": 4.0
          },
          "instructions": {
            "type": "string",
            "description": "Provider-specific; forwarded untouched."
          },
          "callback_url": {
            "$ref": "#/components/schemas/CallbackUrl"
          },
          "retry": {
            "$ref": "#/components/schemas/RetryRequest"
          }
        },
        "additionalProperties": true
      },
      "MeshSpeechResult": {
        "type": "object",
        "description": "What a community-machine (Mesh) speech request answers: the audio's CDN URL, not the bytes.",
        "properties": {
          "id": {
            "type": "string",
            "description": "The job id; poll `GET /audio/speech/{id}`."
          },
          "object": {
            "type": "string",
            "enum": [
              "audio.speech"
            ]
          },
          "status": {
            "type": "string",
            "enum": [
              "queued",
              "completed",
              "failed"
            ]
          },
          "url": {
            "type": [
              "string",
              "null"
            ],
            "description": "The audio on the CDN once `completed`."
          },
          "content_type": {
            "type": [
              "string",
              "null"
            ],
            "examples": [
              "audio/wav"
            ]
          },
          "duration_sec": {
            "type": [
              "number",
              "null"
            ]
          },
          "characters": {
            "type": [
              "integer",
              "null"
            ],
            "description": "What was charged: per 1,000 characters at the listing's price."
          },
          "voice": {
            "type": "string"
          },
          "model": {
            "type": "string",
            "examples": [
              "mesh-kokoro-af_heart"
            ]
          },
          "mesh": {
            "type": "object",
            "properties": {
              "listingId": {
                "type": "integer"
              }
            }
          },
          "eta_sec": {
            "type": [
              "integer",
              "null"
            ]
          },
          "poll": {
            "type": "string",
            "description": "Only while `queued`."
          }
        }
      },
      "TranscriptionRequest": {
        "type": "object",
        "required": [
          "file",
          "model"
        ],
        "properties": {
          "file": {
            "type": "string",
            "format": "binary",
            "description": "The audio file. The gateway reads the first uploaded part whatever it is named. `webm`/`ogg` are transcoded to mp3 with sox first."
          },
          "model": {
            "type": "string",
            "examples": [
              "whisper-1",
              "gpt-4o-transcribe",
              "gpt-4o-mini-transcribe"
            ]
          },
          "response_format": {
            "type": "string",
            "default": "json",
            "enum": [
              "json",
              "text",
              "srt",
              "verbose_json",
              "vtt"
            ]
          },
          "language": {
            "type": "string",
            "description": "ISO-639-1 code.",
            "example": "en"
          },
          "prompt": {
            "type": "string"
          },
          "temperature": {
            "type": "number"
          },
          "callback_url": {
            "$ref": "#/components/schemas/CallbackUrl"
          },
          "retry": {
            "$ref": "#/components/schemas/RetryRequest"
          }
        },
        "additionalProperties": true
      },
      "TranscriptionResponse": {
        "type": "object",
        "description": "Provider response, forwarded unchanged. With `response_format=json` this is `{\"text\": ...}`; `verbose_json` adds segments and timings.",
        "properties": {
          "text": {
            "type": "string"
          }
        },
        "additionalProperties": true
      },
      "ImageGenerationRequest": {
        "type": "object",
        "required": [
          "model",
          "prompt"
        ],
        "properties": {
          "model": {
            "type": "string",
            "examples": [
              "gpt-image-1.5",
              "grok-imagine-image",
              "imagen-4",
              "ideogram-3",
              "recraft-3",
              "flux1.1-pro",
              "midjourney",
              "nanobanana",
              "gen4-image",
              "photon-1",
              "comfy-flux1-schnell"
            ]
          },
          "prompt": {
            "type": "string"
          },
          "n": {
            "type": "integer",
            "minimum": 1,
            "default": 1,
            "description": "Number of images. Some providers accept `num_images` instead; both spellings are recorded."
          },
          "size": {
            "type": "string",
            "description": "e.g. `1024x1024`, `1024x1536`. Which values are legal depends on the model.",
            "examples": [
              "1024x1024",
              "1536x1024"
            ]
          },
          "aspect_ratio": {
            "type": "string",
            "examples": [
              "16:9",
              "1:1"
            ]
          },
          "quality": {
            "type": "string",
            "description": "gpt-image vocabulary is `auto|low|medium|high`; other providers use their own.",
            "examples": [
              "low",
              "medium",
              "high"
            ]
          },
          "background": {
            "type": "string",
            "examples": [
              "auto",
              "transparent",
              "opaque"
            ]
          },
          "style": {
            "type": "string",
            "description": "Stripped before forwarding to OpenAI (the parameter no longer exists there); honoured by providers that still define it."
          },
          "response_format": {
            "type": "string",
            "enum": [
              "url",
              "b64_json"
            ],
            "description": "For OpenAI models, honoured by the gateway rather than forwarded: `b64_json` adds `data[].b64_json` alongside the CDN url. Not available for providers that return only a url (xAI, and the async providers)."
          },
          "negative_prompt": {
            "type": "string"
          },
          "seed": {
            "type": "integer"
          },
          "resolution": {
            "type": "string"
          },
          "rendering_speed": {
            "type": "string"
          },
          "listingId": {
            "type": "integer",
            "description": "Only for the Mesh model `comfy-flux1-schnell`, which is fulfilled by a peer GPU rather than a vendor: it names the enabled `mesh_listing` offer to buy. Without it the cheapest eligible community listing serves the request (Auto); a mismatched listing is rejected. The dedicated `/api/v1/mesh/*` endpoints are the fuller interface to that marketplace and are outside this document."
          },
          "callback_url": {
            "$ref": "#/components/schemas/CallbackUrl"
          },
          "options": {
            "$ref": "#/components/schemas/ImageOptions"
          },
          "inputs": {
            "$ref": "#/components/schemas/AssetPrivacy"
          },
          "outputs": {
            "$ref": "#/components/schemas/AssetPrivacy"
          },
          "retry": {
            "$ref": "#/components/schemas/RetryRequest"
          }
        },
        "additionalProperties": true
      },
      "ImageEditRequest": {
        "type": "object",
        "required": [
          "model",
          "prompt"
        ],
        "properties": {
          "image": {
            "type": "string",
            "format": "binary",
            "description": "The image to edit. Repeat the part to send several. Optional when `options.references` names the image."
          },
          "mask": {
            "type": "string",
            "format": "binary"
          },
          "model": {
            "type": "string",
            "examples": [
              "gpt-image-1.5",
              "nanobanana",
              "flux1-kontext-pro",
              "midjourney"
            ]
          },
          "prompt": {
            "type": "string"
          },
          "n": {
            "type": "integer",
            "default": 1
          },
          "size": {
            "type": "string"
          },
          "quality": {
            "type": "string"
          },
          "background": {
            "type": "string"
          },
          "response_format": {
            "type": "string",
            "enum": [
              "url",
              "b64_json"
            ]
          },
          "callback_url": {
            "$ref": "#/components/schemas/CallbackUrl"
          },
          "options": {
            "type": "string",
            "description": "`ImageOptions` as a JSON string (or `options[...]` form fields). `options.references` can name the images instead of uploading them, so no file is needed."
          },
          "inputs[private]": {
            "type": "string",
            "enum": [
              "1",
              "0"
            ],
            "description": "Input images never get a public URL: a URL-only vendor gets a signed link that dies with the run."
          },
          "outputs[private]": {
            "type": "string",
            "enum": [
              "1",
              "0"
            ],
            "description": "The result is a signed link with `expires_at` instead of a public CDN URL."
          },
          "retry": {
            "$ref": "#/components/schemas/RetryRequest"
          }
        },
        "additionalProperties": true
      },
      "ImageVariationRequest": {
        "type": "object",
        "required": [
          "model",
          "image"
        ],
        "properties": {
          "image": {
            "type": "string",
            "format": "binary"
          },
          "model": {
            "type": "string"
          },
          "n": {
            "type": "integer",
            "default": 1
          },
          "size": {
            "type": "string"
          },
          "response_format": {
            "type": "string",
            "enum": [
              "url",
              "b64_json"
            ]
          },
          "prompt": {
            "type": "string",
            "description": "Accepted by providers that support prompted variation; dropped for OpenAI, whose variations endpoint takes no prompt."
          },
          "callback_url": {
            "$ref": "#/components/schemas/CallbackUrl"
          },
          "retry": {
            "$ref": "#/components/schemas/RetryRequest"
          }
        },
        "additionalProperties": true
      },
      "UpscaleRequest": {
        "type": "object",
        "description": "Exactly one image: `image` (file) wins, then `upload`, then `imageUrl`.",
        "properties": {
          "image": {
            "type": "string",
            "format": "binary",
            "description": "The image file. PNG, JPEG, WebP or GIF, at most 25 MB. Kept privately, never published."
          },
          "imageUrl": {
            "type": "string",
            "description": "A `data:image/...;base64,` URI (kept privately, like a file), a URL of one of your earlier results on the gateway CDN, or any https URL, which the provider fetches itself."
          },
          "model": {
            "type": "string",
            "default": "Qubico/image-toolkit",
            "description": "A model whose provider can upscale: `Qubico/image-toolkit` (GoApi, x2/x4, gets a signed link), `recraft-crisp-upscale` ($0.004) or `recraft-creative-upscale` ($0.25) (Recraft, receive the bytes directly - no link is ever made)."
          },
          "scale": {
            "type": "integer",
            "default": 2,
            "examples": [
              2,
              4
            ]
          },
          "outputs[private]": {
            "type": "string",
            "enum": [
              "1",
              "0"
            ],
            "description": "Keep the result private: a signed, expiring link instead of a public CDN URL. `output_private` is accepted as well."
          },
          "callback_url": {
            "$ref": "#/components/schemas/CallbackUrl"
          },
          "upload": {
            "type": "string",
            "pattern": "^[a-f0-9]{32}$",
            "description": "An id from `POST /uploads` (owner only)."
          },
          "inputs[private]": {
            "type": "string",
            "enum": [
              "1",
              "0"
            ],
            "description": "With private inputs, a foreign https `imageUrl` is fetched by the gateway through the SSRF guard and kept privately instead of being handed to the vendor."
          },
          "retry": {
            "$ref": "#/components/schemas/RetryRequest"
          }
        },
        "additionalProperties": true
      },
      "AnalyzeRequest": {
        "type": "object",
        "description": "Exactly one of `upload`, `runId`, `outputUrl`.",
        "properties": {
          "upload": {
            "type": "string",
            "description": "An id from `POST /uploads`."
          },
          "runId": {
            "type": "string",
            "description": "The task id (or id) of one of your runs; its first stored result is analysed."
          },
          "outputUrl": {
            "type": "string",
            "description": "One of your own results: a gateway CDN URL, or the signed link of a private result."
          },
          "want": {
            "type": "array",
            "items": {
              "type": "string",
              "enum": [
                "alt",
                "focal",
                "colors",
                "ocr",
                "moderation"
              ]
            },
            "description": "The parts to produce; all of them by default."
          },
          "lang": {
            "type": "string",
            "default": "en",
            "examples": [
              "sk",
              "pt-BR"
            ],
            "description": "The language the alt text is written in."
          },
          "context": {
            "type": "string",
            "maxLength": 500,
            "examples": [
              "product: oak chair"
            ],
            "description": "Where the image is used; goes into the alt-text prompt."
          },
          "callback_url": {
            "type": "string"
          }
        }
      },
      "ImageAnalysis": {
        "type": "object",
        "properties": {
          "object": {
            "type": "string",
            "enum": [
              "image.analysis"
            ]
          },
          "alt": {
            "type": "object",
            "properties": {
              "text": {
                "type": "string"
              },
              "lang": {
                "type": "string"
              }
            }
          },
          "focal": {
            "type": "object",
            "description": "Where a crop should stay centred, as 0..1 fractions of width and height.",
            "properties": {
              "x": {
                "type": "number"
              },
              "y": {
                "type": "number"
              },
              "source": {
                "type": "string",
                "enum": [
                  "saliency",
                  "mesh-features"
                ]
              }
            }
          },
          "colors": {
            "type": "array",
            "description": "Dominant colours, biggest first.",
            "items": {
              "type": "object",
              "properties": {
                "hex": {
                  "type": "string",
                  "examples": [
                    "#1f2a44"
                  ]
                },
                "share": {
                  "type": "number"
                }
              }
            }
          },
          "ocr": {
            "type": "object",
            "properties": {
              "text": {
                "type": "string"
              },
              "source": {
                "type": "string"
              }
            }
          },
          "moderation": {
            "type": "object",
            "properties": {
              "nsfw": {
                "type": "number"
              },
              "source": {
                "type": "string"
              }
            }
          },
          "missing": {
            "type": "array",
            "items": {
              "type": "string",
              "enum": [
                "alt",
                "focal",
                "colors",
                "ocr",
                "moderation"
              ]
            },
            "description": "Wanted parts that could not be measured right now."
          },
          "notes": {
            "type": "array",
            "items": {
              "type": "string"
            },
            "description": "Why a part is missing."
          },
          "run_id": {
            "type": [
              "string",
              "null"
            ],
            "description": "The alt-text run, when one ran and has a task id."
          }
        },
        "required": [
          "object",
          "missing"
        ]
      },
      "ImageItem": {
        "type": "object",
        "required": [
          "url"
        ],
        "properties": {
          "url": {
            "type": "string",
            "format": "uri",
            "description": "Gateway CDN url, or for a private result a signed link to `/media/{id}` that dies at `expires_at`. Never the upstream provider's url: results are always re-hosted."
          },
          "revised_prompt": {
            "type": "string",
            "description": "The prompt the model actually drew, where it rewrote the one it was given."
          },
          "b64_json": {
            "type": "string",
            "description": "The image bytes, base64. Present only when `response_format=b64_json` was requested and the provider returned base64."
          },
          "expires_at": {
            "type": [
              "integer",
              "null"
            ],
            "description": "Unix time after which the link may stop working: a private result's signed link dies then, and a public result is deleted by the owner's retention setting on or after it. `null` = kept until deleted. Rule for plugins: **download and import before this, never hotlink.**"
          }
        },
        "additionalProperties": true
      },
      "ImageResult": {
        "type": "object",
        "required": [
          "created",
          "data"
        ],
        "properties": {
          "created": {
            "type": "integer",
            "description": "Unix seconds. Present on every image result: taken from the provider where it reports one, and the time of the answer where it does not."
          },
          "data": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/ImageItem"
            }
          },
          "usage": {
            "$ref": "#/components/schemas/Usage"
          },
          "provenance": {
            "$ref": "#/components/schemas/Provenance"
          },
          "moderation": {
            "type": "null",
            "description": "Reserved for an NSFW score once something measures it; never invented, so null today."
          },
          "warnings": {
            "type": "array",
            "items": {
              "type": "string"
            },
            "description": "What `options` could not honour exactly, when `strict` was false."
          }
        },
        "additionalProperties": true
      },
      "ImageTaskAccepted": {
        "type": "object",
        "required": [
          "taskId"
        ],
        "properties": {
          "taskId": {
            "type": "string",
            "description": "Poll `/images/status/{model}/{taskId}`, or resubmit with `?wait=1` next time."
          },
          "eta": {
            "type": "integer",
            "description": "Seconds, where the provider reports one (mesh jobs)."
          }
        },
        "additionalProperties": true
      },
      "ImageResultOrTask": {
        "description": "Synchronous providers return `ImageResult`. Asynchronous providers return `ImageTaskAccepted`, unless `?wait=1` was passed and the wait completed in time. Branch on the presence of `taskId`.",
        "oneOf": [
          {
            "$ref": "#/components/schemas/ImageResult"
          },
          {
            "$ref": "#/components/schemas/ImageTaskAccepted"
          }
        ]
      },
      "ImageTaskStatus": {
        "description": "Three shapes, depending on the provider and the state.\n\nA populated `data` means the task is finished. Every provider except Mesh reports \"still running\" as an **empty object** `{}`; the Mesh provider (`comfy-flux1-schnell`) instead reports `{state, eta}` while queued, and `{error, state}` — with HTTP **200**, not an error status — when the job failed or expired. Treat an absent `data` as \"not finished\" and look at `error` before polling again.",
        "oneOf": [
          {
            "$ref": "#/components/schemas/ImageResult"
          },
          {
            "type": "object",
            "additionalProperties": false,
            "description": "Still running (every provider but Mesh)."
          },
          {
            "type": "object",
            "description": "Still running (Mesh).",
            "properties": {
              "state": {
                "type": "string",
                "examples": [
                  "QUEUED",
                  "LEASED"
                ]
              },
              "eta": {
                "type": [
                  "integer",
                  "null"
                ],
                "description": "Seconds, estimated."
              }
            },
            "required": [
              "state"
            ],
            "additionalProperties": true
          },
          {
            "type": "object",
            "description": "Failed or expired (Mesh). Answered with HTTP 200.",
            "properties": {
              "error": {
                "type": "string"
              },
              "state": {
                "type": "string",
                "examples": [
                  "FAILURE",
                  "EXPIRED"
                ]
              }
            },
            "required": [
              "error",
              "state"
            ],
            "additionalProperties": true
          }
        ]
      },
      "ModelCatalogue": {
        "type": "object",
        "properties": {
          "object": {
            "const": "list",
            "description": "OpenAI envelope discriminator, present so `client.models.list()` accepts the body."
          },
          "data": {
            "type": "array",
            "description": "The OpenAI `GET /v1/models` view of the same list.",
            "items": {
              "type": "object",
              "properties": {
                "id": {
                  "type": "string",
                  "description": "The model name to send as `model`."
                },
                "object": {
                  "const": "model"
                },
                "created": {
                  "type": "integer",
                  "description": "When the catalogue snapshot was built — not a vendor release date, which this API does not know."
                },
                "owned_by": {
                  "type": "string",
                  "description": "The upstream provider."
                }
              }
            }
          },
          "status": {
            "type": "string",
            "description": "`idle` means the daily rebuild has not run — an empty list then means \"no snapshot\", not \"no such model\"."
          },
          "generatedAt": {
            "type": [
              "string",
              "null"
            ]
          },
          "currency": {
            "type": "string"
          },
          "windowDays": {
            "type": [
              "integer",
              "null"
            ]
          },
          "filters": {
            "type": "object",
            "additionalProperties": true,
            "description": "Echo of the filters that were applied."
          },
          "count": {
            "type": "integer"
          },
          "legend": {
            "type": "object",
            "additionalProperties": true,
            "description": "What each `latency.source` and `price.basis` value means. Shipped with every response."
          },
          "models": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/CatalogueModel"
            },
            "description": "The rich view, with price and latency."
          }
        },
        "additionalProperties": true
      },
      "CatalogueModel": {
        "type": "object",
        "properties": {
          "model": {
            "type": "string"
          },
          "provider": {
            "type": "string"
          },
          "actions": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "input": {
            "type": "array",
            "items": {
              "type": "string",
              "enum": [
                "t",
                "i",
                "s"
              ]
            }
          },
          "output": {
            "type": "array",
            "items": {
              "type": "string",
              "enum": [
                "t",
                "i",
                "s"
              ]
            }
          },
          "price": {
            "type": "object",
            "properties": {
              "basis": {
                "type": "string",
                "enum": [
                  "tariff",
                  "tariff-max",
                  "rates",
                  "observed",
                  "estimated",
                  "unpriced"
                ]
              },
              "amount": {
                "type": [
                  "number",
                  "null"
                ],
                "description": "A whole-request price, present only where one request is one artefact. Null for chat models."
              },
              "exact": {
                "type": "boolean"
              },
              "rates": {
                "type": "object",
                "additionalProperties": true
              }
            },
            "additionalProperties": true
          },
          "latency": {
            "type": "object",
            "properties": {
              "source": {
                "type": "string",
                "enum": [
                  "measured",
                  "coarse",
                  "legacy",
                  "seeded",
                  "none"
                ]
              },
              "n": {
                "type": "integer"
              },
              "p50": {
                "type": [
                  "number",
                  "null"
                ]
              },
              "p90": {
                "type": [
                  "number",
                  "null"
                ],
                "description": "Where a timeout belongs."
              }
            },
            "additionalProperties": true
          }
        },
        "additionalProperties": true
      },
      "Quote": {
        "type": "object",
        "properties": {
          "model": {
            "type": "string"
          },
          "provider": {
            "type": "string",
            "description": "Which provider the request would be routed to."
          },
          "currency": {
            "type": "string",
            "example": "EUR",
            "description": "The balance currency the totals are in."
          },
          "exact": {
            "type": "boolean",
            "description": "True only when every priced item is a fixed per-piece tariff."
          },
          "totalMicros": {
            "type": "integer",
            "description": "The whole quote, in micros of `currency` (1 EUR = 1000000)."
          },
          "total": {
            "type": "number",
            "format": "double",
            "description": "The same figure in `currency`."
          },
          "estimatedQuantities": {
            "type": "object",
            "additionalProperties": {
              "type": "integer"
            },
            "description": "Per price item. Exact for per-piece tariffs and for TTS characters; predicted for token items."
          },
          "items": {
            "type": "object",
            "additionalProperties": true,
            "description": "Per-item breakdown in the same shape a finished run records, so a quote and a charge can be compared field by field."
          },
          "latency": {
            "type": "object",
            "nullable": true,
            "additionalProperties": true,
            "description": "The model's entry from the daily forecast, or null when the snapshot has not been built. See the `legend` on GET /models."
          },
          "disclaimer": {
            "type": "string",
            "description": "One sentence saying how much to trust the figure; safe to show verbatim."
          }
        }
      },
      "CallbackUrl": {
        "type": "string",
        "format": "uri",
        "maxLength": 2048,
        "description": "Optional on every task-creating request (images, chat, audio, decisions, Mesh jobs). https only; the host must resolve to public addresses only (checked on submit and again on delivery). When the task ends the gateway POSTs the JSON the status endpoint would give, plus `taskId`, `state`, `model` and `charged` (EUR micros), signed with the account's webhook secret: header `X-AiArtur-Signature: t=<unix>,v1=<hex HMAC-SHA256(secret, t + \".\" + body)>`. A 2xx answer delivers it; anything else is retried after 1, 5, 15, 60 and 240 minutes. Requires a webhook secret generated on the profile (400 `webhook_secret_missing` otherwise); a refused URL is a 400 `callback_url_invalid`. Removed from the body before it reaches a provider.",
        "example": "https://shop.example/aimage/callback"
      },
      "DecisionQuestion": {
        "type": "object",
        "required": [
          "type"
        ],
        "properties": {
          "type": {
            "type": "string",
            "enum": [
              "noul",
              "choice",
              "score"
            ],
            "description": "`noul`: yes/no, answered as a probability of true. `choice`: one of 2-20 options. `score`: an ordered scale of 2-10 levels."
          },
          "instructions": {
            "type": "string",
            "maxLength": 4000
          },
          "criteria": {
            "description": "`noul`: `{\"true\": ..., \"false\": ...}` (optional). `choice`: an object of option key (`[a-z0-9_]{1,64}`) => description. `score`: a list of level descriptions, lowest first.",
            "oneOf": [
              {
                "type": "object",
                "additionalProperties": {
                  "type": "string"
                }
              },
              {
                "type": "array",
                "items": {
                  "type": "string"
                }
              }
            ]
          }
        }
      },
      "DecisionRequest": {
        "type": "object",
        "required": [
          "state",
          "questions"
        ],
        "properties": {
          "model": {
            "type": "string",
            "enum": [
              "auto",
              "jev-1.13"
            ],
            "default": "auto"
          },
          "state": {
            "description": "What the questions are about: text, an object or an array, at most about 90 KB. Text only; English is the most accurate.",
            "oneOf": [
              {
                "type": "string"
              },
              {
                "type": "object"
              },
              {
                "type": "array"
              }
            ]
          },
          "questions": {
            "type": "object",
            "minProperties": 1,
            "maxProperties": 20,
            "propertyNames": {
              "pattern": "^[a-z0-9_]{1,64}$"
            },
            "additionalProperties": {
              "$ref": "#/components/schemas/DecisionQuestion"
            }
          },
          "user": {
            "type": "string",
            "maxLength": 256
          },
          "session_id": {
            "type": "string",
            "maxLength": 256
          },
          "callback_url": {
            "$ref": "#/components/schemas/CallbackUrl"
          },
          "retry": {
            "$ref": "#/components/schemas/RetryRequest"
          }
        }
      },
      "DecisionBatchRequest": {
        "type": "object",
        "required": [
          "questions",
          "items"
        ],
        "properties": {
          "model": {
            "type": "string",
            "enum": [
              "auto",
              "jev-1.13"
            ],
            "default": "auto"
          },
          "questions": {
            "type": "object",
            "minProperties": 1,
            "maxProperties": 20,
            "propertyNames": {
              "pattern": "^[a-z0-9_]{1,64}$"
            },
            "additionalProperties": {
              "$ref": "#/components/schemas/DecisionQuestion"
            }
          },
          "items": {
            "type": "array",
            "minItems": 1,
            "maxItems": 50,
            "items": {
              "type": "object",
              "required": [
                "id",
                "state"
              ],
              "properties": {
                "id": {
                  "oneOf": [
                    {
                      "type": "string",
                      "maxLength": 128
                    },
                    {
                      "type": "integer"
                    }
                  ]
                },
                "state": {
                  "description": "What the questions are about: text, an object or an array, at most about 90 KB. Text only; English is the most accurate.",
                  "oneOf": [
                    {
                      "type": "string"
                    },
                    {
                      "type": "object"
                    },
                    {
                      "type": "array"
                    }
                  ]
                }
              }
            }
          },
          "callback_url": {
            "$ref": "#/components/schemas/CallbackUrl"
          },
          "retry": {
            "$ref": "#/components/schemas/RetryRequest"
          }
        }
      },
      "DecisionResponse": {
        "type": "object",
        "required": [
          "answers",
          "model",
          "usage"
        ],
        "properties": {
          "answers": {
            "type": "object",
            "description": "One answer per question key, exactly as the decision model returns it: `{type: noul, noul}`, `{type: choice, choice, confidence, probabilities}`, `{type: score, score, confidence, probabilities, legend}`.",
            "additionalProperties": {
              "type": "object"
            }
          },
          "model": {
            "type": "string",
            "example": "jev-1.13"
          },
          "usage": {
            "type": "object",
            "properties": {
              "input_tokens": {
                "type": "integer"
              }
            }
          }
        }
      },
      "DecisionBatchResponse": {
        "type": "object",
        "required": [
          "results",
          "model",
          "usage"
        ],
        "properties": {
          "results": {
            "type": "array",
            "description": "One per item, in the order sent.",
            "items": {
              "type": "object",
              "properties": {
                "id": {
                  "oneOf": [
                    {
                      "type": "string"
                    },
                    {
                      "type": "integer"
                    }
                  ]
                },
                "answers": {
                  "type": "object",
                  "description": "One answer per question key, exactly as the decision model returns it: `{type: noul, noul}`, `{type: choice, choice, confidence, probabilities}`, `{type: score, score, confidence, probabilities, legend}`.",
                  "additionalProperties": {
                    "type": "object"
                  }
                },
                "error": {
                  "type": "object",
                  "properties": {
                    "status": {
                      "type": "integer"
                    },
                    "message": {
                      "type": "string"
                    }
                  }
                }
              }
            }
          },
          "model": {
            "type": "string",
            "example": "jev-1.13"
          },
          "usage": {
            "type": "object",
            "properties": {
              "input_tokens": {
                "type": "integer",
                "description": "Summed over the items that were answered."
              }
            }
          }
        }
      },
      "BatchItem": {
        "type": "object",
        "required": [
          "id",
          "endpoint",
          "body"
        ],
        "properties": {
          "id": {
            "type": [
              "string",
              "integer"
            ],
            "description": "The caller's own id, unique within the batch (1-128 characters)."
          },
          "endpoint": {
            "type": "string",
            "enum": [
              "chat.completions",
              "images.generations",
              "audio.speech",
              "decisions",
              "images.edits",
              "audio.transcriptions",
              "images.analyze",
              "embeddings"
            ],
            "description": "`images.edits` and `audio.transcriptions` need an uploaded file and are refused per item (`endpoint_needs_upload`)."
          },
          "body": {
            "type": "object",
            "description": "Exactly the body the single endpoint takes. No `callback_url` and no `stream`."
          }
        }
      },
      "BatchRequest": {
        "type": "object",
        "required": [
          "items"
        ],
        "properties": {
          "items": {
            "type": "array",
            "minItems": 1,
            "maxItems": 10000,
            "items": {
              "$ref": "#/components/schemas/BatchItem"
            }
          },
          "quoteToken": {
            "type": "string",
            "description": "From `/batches/quote`, within the hour and for these exact items: the batch runs at the quoted prices."
          },
          "ceilingEur": {
            "type": "number",
            "exclusiveMinimum": 0,
            "description": "Stop once the batch has cost (or reserved) this much; the rest is skipped."
          },
          "retry": {
            "oneOf": [
              {
                "type": "string",
                "enum": [
                  "none",
                  "once"
                ],
                "description": "Try a failed item once more, on the same model."
              },
              {
                "$ref": "#/components/schemas/RetryRequest"
              }
            ],
            "description": "A retry object applies \"retry with a better option\" to every item (an item may not carry its own). Omitted: the account's default, else the legacy \"accept a worse price\" profile setting as `on: failure` within `ceilingEur`, else `none`."
          },
          "callback_url": {
            "$ref": "#/components/schemas/CallbackUrl"
          },
          "callback": {
            "type": "string",
            "enum": [
              "batch",
              "item"
            ],
            "default": "batch",
            "description": "`item` adds a `batch.item` callback per finished item."
          }
        }
      },
      "BatchQuote": {
        "type": "object",
        "properties": {
          "object": {
            "const": "batch.quote"
          },
          "items": {
            "type": "integer",
            "description": "Items that could be priced."
          },
          "totalEur": {
            "type": "number"
          },
          "currency": {
            "const": "EUR"
          },
          "exact": {
            "type": "boolean",
            "description": "False as soon as one item is priced per token: token counts are estimated."
          },
          "perEndpoint": {
            "type": "object",
            "additionalProperties": {
              "type": "object",
              "properties": {
                "items": {
                  "type": "integer"
                },
                "totalEur": {
                  "type": "number"
                }
              }
            }
          },
          "maxTotalEurWithRetry": {
            "type": "number",
            "description": "A failed run is never charged, so retrying cannot cost more than the total."
          },
          "warnings": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "id": {
                  "type": "string"
                },
                "code": {
                  "type": "string"
                },
                "message": {
                  "type": "string"
                }
              }
            }
          },
          "quoteToken": {
            "type": "string"
          },
          "quoteExpiresAt": {
            "type": "string",
            "format": "date-time"
          }
        }
      },
      "BatchStatus": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string"
          },
          "object": {
            "const": "batch"
          },
          "state": {
            "type": "string",
            "enum": [
              "queued",
              "running",
              "done",
              "cancelled"
            ]
          },
          "items": {
            "type": "integer"
          },
          "counts": {
            "type": "object",
            "properties": {
              "queued": {
                "type": "integer"
              },
              "running": {
                "type": "integer"
              },
              "succeeded": {
                "type": "integer"
              },
              "failed": {
                "type": "integer"
              },
              "cancelled": {
                "type": "integer"
              },
              "skipped": {
                "type": "integer"
              }
            }
          },
          "spentEur": {
            "type": "number",
            "description": "Charged plus still reserved."
          },
          "ceilingEur": {
            "type": [
              "number",
              "null"
            ]
          },
          "retry": {
            "type": "string"
          },
          "etaSeconds": {
            "type": [
              "integer",
              "null"
            ],
            "description": "From the models' typical run times; null when none is known."
          },
          "createdAt": {
            "type": "string",
            "format": "date-time"
          },
          "startedAt": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          },
          "finishedAt": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          },
          "cancelledAt": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          },
          "errors": {
            "type": "array",
            "maxItems": 20,
            "items": {
              "type": "object",
              "properties": {
                "id": {
                  "type": "string"
                },
                "state": {
                  "type": "string"
                },
                "code": {
                  "type": "string"
                },
                "message": {
                  "type": "string"
                }
              }
            }
          }
        }
      },
      "BatchResult": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string"
          },
          "endpoint": {
            "type": "string"
          },
          "state": {
            "type": "string",
            "enum": [
              "succeeded",
              "failed",
              "cancelled",
              "skipped"
            ]
          },
          "result": {
            "description": "What the single endpoint would answer; speech is `{url, contentType, bytes}`. Null once retention purged it."
          },
          "error": {
            "type": [
              "object",
              "null"
            ],
            "properties": {
              "code": {
                "type": "string"
              },
              "message": {
                "type": "string"
              }
            }
          },
          "chargedEur": {
            "type": "number"
          },
          "taskId": {
            "type": [
              "string",
              "null"
            ]
          },
          "purged": {
            "type": "boolean"
          }
        }
      },
      "Account": {
        "type": "object",
        "properties": {
          "balanceEur": {
            "type": "number"
          },
          "lowBalance": {
            "type": "boolean"
          },
          "lowBalanceThresholdEur": {
            "type": "number"
          },
          "currency": {
            "const": "EUR"
          },
          "key": {
            "type": [
              "object",
              "null"
            ],
            "description": "The key that asked; null for a web session.",
            "properties": {
              "title": {
                "type": "string"
              },
              "app": {
                "type": [
                  "string",
                  "null"
                ]
              },
              "site": {
                "type": [
                  "string",
                  "null"
                ]
              },
              "capEur": {
                "type": [
                  "number",
                  "null"
                ]
              },
              "period": {
                "type": "string",
                "enum": [
                  "day",
                  "month"
                ]
              },
              "spentEur": {
                "type": "number"
              },
              "resetsAt": {
                "type": "string",
                "format": "date-time"
              },
              "isAccountAdmin": {
                "type": "boolean"
              }
            }
          },
          "topUpUrl": {
            "type": "string",
            "format": "uri"
          }
        }
      },
      "AccountUsage": {
        "type": "object",
        "properties": {
          "from": {
            "type": "string",
            "format": "date"
          },
          "to": {
            "type": "string",
            "format": "date"
          },
          "groupBy": {
            "type": "string"
          },
          "key": {
            "type": "string",
            "enum": [
              "current",
              "all"
            ]
          },
          "currency": {
            "const": "EUR"
          },
          "rows": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "group": {
                  "type": "string"
                },
                "runs": {
                  "type": "integer"
                },
                "succeeded": {
                  "type": "integer"
                },
                "failed": {
                  "type": "integer"
                },
                "chargedMicros": {
                  "type": "integer"
                },
                "chargedEur": {
                  "type": "number"
                }
              }
            }
          },
          "total": {
            "type": "object"
          }
        }
      },
      "ImageOptions": {
        "type": "object",
        "description": "The normalised image request (feature 23): say *what* you want once, and the gateway maps it onto whichever model serves it. A top-level key next to the vendor fields; where both are sent the option wins. On multipart routes send it as a JSON string or as `options[aspectRatio]=...` fields. `strict` (default **true**) refuses with a 400 naming the option and the model when it cannot be honoured exactly; `strict: false` delivers the closest result and lists what changed in `warnings`.",
        "additionalProperties": false,
        "properties": {
          "aspectRatio": {
            "type": "string",
            "pattern": "^\\d{1,4}:\\d{1,4}$",
            "examples": [
              "16:9",
              "4:5",
              "1:1"
            ],
            "description": "Mapped to the model's nearest size or ratio token. Under strict, a ratio the model can only approximate (beyond 2 %) is refused."
          },
          "n": {
            "type": "integer",
            "minimum": 1,
            "maximum": 10,
            "description": "Images wanted. Synchronous providers are called in a loop where one call yields fewer; an asynchronous provider is capped at its per-call maximum. All-or-nothing: a failed call fails the run, and nothing is charged."
          },
          "background": {
            "type": "string",
            "enum": [
              "auto",
              "transparent",
              "opaque"
            ]
          },
          "negativePrompt": {
            "type": "string",
            "maxLength": 2000,
            "description": "Dropped (with a warning, or refused under strict) where the model takes none."
          },
          "quality": {
            "type": "string",
            "enum": [
              "draft",
              "standard",
              "high"
            ],
            "description": "Mapped to the model's own words (gpt-image: low/medium/high). A model without a quality control honours `standard` only."
          },
          "outputFormat": {
            "type": "string",
            "enum": [
              "png",
              "webp",
              "jpeg"
            ],
            "description": "`jpeg` with a transparent background is refused."
          },
          "seed": {
            "type": "integer",
            "minimum": 0,
            "maximum": 4294967295
          },
          "references": {
            "type": "array",
            "maxItems": 16,
            "items": {
              "$ref": "#/components/schemas/ImageReference"
            },
            "description": "Input images by upload id or https URL. A generation with references is sent to the vendor as an edit, so only models with `capabilities.references` take them."
          },
          "strict": {
            "type": "boolean",
            "default": true
          }
        }
      },
      "ImageReference": {
        "type": "object",
        "required": [
          "role"
        ],
        "description": "Exactly one of `upload` or `url`.",
        "properties": {
          "upload": {
            "type": "string",
            "pattern": "^[a-f0-9]{32}$",
            "description": "An id from `POST /uploads`; honoured for its owner only, until it expires."
          },
          "url": {
            "type": "string",
            "format": "uri",
            "description": "https only. Fetched by the gateway through an SSRF guard (public addresses only, re-checked on each of at most 3 redirects, image content type, 25 MB)."
          },
          "role": {
            "type": "string",
            "enum": [
              "style",
              "subject",
              "edit"
            ],
            "default": "subject"
          }
        }
      },
      "AssetPrivacy": {
        "type": "object",
        "properties": {
          "private": {
            "type": "boolean",
            "description": "Where the request says nothing, the key's default applies (set on /profile; on for every key made by \"Connect a site\")."
          }
        }
      },
      "Provenance": {
        "type": "object",
        "description": "How a result was made (feature 24). Store it next to the imported media item.",
        "properties": {
          "runId": {
            "type": "string"
          },
          "taskId": {
            "type": "string"
          },
          "createdAt": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          },
          "endpoint": {
            "type": [
              "string",
              "null"
            ],
            "examples": [
              "images.generations",
              "images.edits",
              "images.upscale"
            ]
          },
          "model": {
            "type": "string"
          },
          "provider": {
            "type": "string"
          },
          "providerModel": {
            "type": "string",
            "description": "The vendor's own id for the model the request went out with."
          },
          "priceModel": {
            "type": [
              "string",
              "null"
            ]
          },
          "routeReason": {
            "type": [
              "string",
              "null"
            ],
            "description": "Why the model was chosen, once `model: \"auto\"` exists; null until then."
          },
          "params": {
            "type": "object",
            "description": "The normalised parameters recorded for pricing."
          },
          "options": {
            "type": [
              "object",
              "null"
            ],
            "description": "The normalised options; the negative prompt appears only as `true`."
          },
          "inputs": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "sha256": {
                  "type": "string"
                },
                "role": {
                  "type": "string"
                }
              }
            }
          },
          "c2pa": {
            "type": [
              "string",
              "null"
            ],
            "enum": [
              "present",
              "absent",
              null
            ],
            "description": "Whether a stored result carried a C2PA manifest (structural check of the bytes, not a signature validation). null = not checked."
          },
          "meshWorkflowVersion": {
            "type": [
              "string",
              "null"
            ]
          },
          "meshMachine": {
            "type": [
              "string",
              "null"
            ]
          }
        },
        "additionalProperties": true
      },
      "Upload": {
        "type": "object",
        "required": [
          "id",
          "object",
          "sha256",
          "bytes",
          "format",
          "expiresAt"
        ],
        "properties": {
          "id": {
            "type": "string"
          },
          "object": {
            "const": "upload"
          },
          "sha256": {
            "type": "string"
          },
          "bytes": {
            "type": "integer"
          },
          "format": {
            "type": "string",
            "enum": [
              "png",
              "jpg",
              "webp",
              "gif"
            ]
          },
          "expiresAt": {
            "type": "integer",
            "description": "Unix time; 24 hours after upload."
          }
        }
      },
      "Run": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string"
          },
          "object": {
            "const": "run"
          },
          "taskId": {
            "type": "string"
          },
          "state": {
            "type": "string",
            "examples": [
              "NEW",
              "RUNNING",
              "SUCCESS",
              "FAILURE"
            ]
          },
          "model": {
            "type": "string"
          },
          "provider": {
            "type": "string"
          },
          "startedAt": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          },
          "finishedAt": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          },
          "chargedEur": {
            "type": "number"
          },
          "contentPurged": {
            "type": "boolean"
          },
          "provenance": {
            "$ref": "#/components/schemas/Provenance"
          },
          "moderation": {
            "type": "null"
          }
        }
      },
      "ImageCapabilities": {
        "type": "object",
        "description": "What a model can do as its provider serves it; the same truth `options` obeys.",
        "properties": {
          "image": {
            "type": "boolean"
          },
          "generate": {
            "type": "boolean"
          },
          "edit": {
            "type": "boolean"
          },
          "variations": {
            "type": "boolean"
          },
          "upscale": {
            "type": "boolean"
          },
          "references": {
            "type": "integer",
            "description": "How many reference images an edit can carry."
          },
          "transparent": {
            "type": "boolean"
          },
          "aspectRatios": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "sizes": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "maxN": {
            "type": "integer"
          },
          "maxNPerCall": {
            "type": "integer"
          },
          "negativePrompt": {
            "type": "boolean"
          },
          "seed": {
            "type": "boolean"
          },
          "qualities": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "outputFormats": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "textRendering": {
            "type": "string",
            "enum": [
              "good",
              "poor",
              "unknown"
            ]
          },
          "commercialUse": {
            "type": [
              "boolean",
              "null"
            ]
          }
        }
      },
      "CapabilityMatches": {
        "type": "object",
        "properties": {
          "need": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "count": {
            "type": "integer"
          },
          "models": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "model": {
                  "type": "string"
                },
                "title": {
                  "type": "string"
                },
                "provider": {
                  "type": [
                    "string",
                    "null"
                  ]
                },
                "capabilities": {
                  "$ref": "#/components/schemas/ImageCapabilities"
                }
              }
            }
          }
        }
      },
      "RetryRequest": {
        "type": "object",
        "description": "Retry with a better option (docs/features/20). Optional on every task-creating request; omitted = the account's default from the profile (\"never\" until changed). Failure moves to the next option (the next `auto` candidate, or the next provider of a named model - never another model) and is not charged; `failure-or-unsatisfied` also retries a chat answer or transcript that Jev grades unacceptable with the language's confidence, onto a better-measured `auto` candidate (the unsatisfying result is charged). The answer lists `attempts`; `retry_stopped` says why no further attempt was made. On a multipart upload send the same object as a JSON string. Removed from the body before it reaches a provider. A bad object is a 400 `retry_invalid`.",
        "required": [
          "on"
        ],
        "properties": {
          "on": {
            "type": "string",
            "enum": [
              "never",
              "failure",
              "failure-or-unsatisfied"
            ]
          },
          "maxAttempts": {
            "type": "integer",
            "minimum": 1,
            "maximum": 3,
            "default": 1,
            "description": "Extra attempts after the first."
          },
          "maxTotalEur": {
            "type": "number",
            "exclusiveMinimum": 0,
            "maximum": 100,
            "description": "The most all attempts together may be charged, in EUR. Required unless `on` is `never`. A retry starts only when what was charged so far plus its quote fits."
          }
        },
        "example": {
          "on": "failure",
          "maxAttempts": 1,
          "maxTotalEur": 0.1
        }
      },
      "RetryAttempt": {
        "type": "object",
        "description": "One attempt of a request that asked for retries, oldest first, in the `attempts` list of the answer (and of a batch result line). Next to it the answer may carry `retry_stopped`: `ceiling`, `max_attempts`, `no_option`, `no_better_option` or `refused`.",
        "properties": {
          "runId": {
            "type": "integer"
          },
          "model": {
            "type": "string"
          },
          "provider": {
            "type": "string"
          },
          "state": {
            "type": "string",
            "enum": [
              "success",
              "failure",
              "running"
            ]
          },
          "charged": {
            "type": "number",
            "description": "EUR taken off the balance for this attempt."
          },
          "reason": {
            "type": [
              "string",
              "null"
            ],
            "enum": [
              "failure",
              "unsatisfied",
              null
            ]
          },
          "error": {
            "type": "string"
          },
          "grade": {
            "type": "object",
            "properties": {
              "checked": {
                "type": "boolean"
              },
              "acceptable": {
                "type": "number"
              },
              "counted": {
                "type": "boolean"
              },
              "note": {
                "type": "string"
              }
            }
          }
        }
      },
      "EmbeddingRequest": {
        "type": "object",
        "required": [
          "model",
          "input"
        ],
        "properties": {
          "model": {
            "type": "string",
            "enum": [
              "text-embedding-3-small",
              "text-embedding-3-large"
            ]
          },
          "input": {
            "oneOf": [
              {
                "type": "string"
              },
              {
                "type": "array",
                "minItems": 1,
                "maxItems": 2048,
                "items": {
                  "type": "string"
                }
              }
            ],
            "description": "Up to 2,048 texts, each at most 8,192 tokens."
          },
          "dimensions": {
            "type": "integer",
            "minimum": 1,
            "maximum": 3072,
            "description": "Shorter vector (text-embedding-3 models)."
          },
          "encoding_format": {
            "type": "string",
            "enum": [
              "float",
              "base64"
            ],
            "default": "float"
          },
          "user": {
            "type": "string"
          }
        }
      },
      "EmbeddingResponse": {
        "type": "object",
        "properties": {
          "object": {
            "type": "string",
            "enum": [
              "list"
            ]
          },
          "data": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "object": {
                  "type": "string",
                  "enum": [
                    "embedding"
                  ]
                },
                "index": {
                  "type": "integer"
                },
                "embedding": {
                  "oneOf": [
                    {
                      "type": "array",
                      "items": {
                        "type": "number"
                      }
                    },
                    {
                      "type": "string",
                      "description": "base64 when encoding_format is base64"
                    }
                  ]
                }
              }
            }
          },
          "model": {
            "type": "string"
          },
          "usage": {
            "type": "object",
            "properties": {
              "prompt_tokens": {
                "type": "integer"
              },
              "total_tokens": {
                "type": "integer"
              }
            }
          }
        }
      }
    }
  }
}
