openapi: 3.2.0 info: title: SpicyAPI Public OpenAI Compatible API version: 1.0.0 description: Contract for the implemented `/api/v1` integration surface. servers: - url: https://api.spicyapi.ai description: Production security: - bearerAuth: [] tags: - name: OpenAI Compatible description: 'OpenAI / Anthropic protocol shells under `/v1` and the Google Gemini shell under `/v1beta`. They share API keys, limits, validation and billing with `/api/v1` but use the external protocol''s own request, response and error shapes. On these paths the API key may be sent as `Authorization: Bearer `, `x-api-key: ` (Anthropic SDKs) or `x-goog-api-key: ` (Google GenAI SDKs); `/api/v1` accepts only `Authorization: Bearer`. Keys in the URL query string are never accepted.' paths: /v1/models: get: tags: - OpenAI Compatible operationId: listOpenAIModels summary: List callable models in the OpenAI `model` list shape description: 'Returns every model that can be called right now. `id` is the SpicyAPI model identifier accepted by every `/v1` and `/api/v1` operation; `owned_by` is the model publisher (for example a research lab). Pricing is not included; use `/api/v1/models`. Besides the four OpenAI fields, every item carries `modality` and `task`; chat models (`task: chat`, the only ones the chat, Responses, Messages and Gemini operations accept) also carry `context_length`, `max_output_tokens` and `supported_parameters`. OpenAI SDKs ignore the extra fields. Pass `modality=text` to list only text models.' parameters: - name: modality in: query required: false description: Only list models with this output modality. An unknown value returns `400` with `param` set to `modality`. schema: type: string enum: - text - image - video - audio responses: '200': description: OpenAI-style model list. content: application/json: schema: $ref: '#/components/schemas/OpenAIModelList' '401': $ref: '#/components/responses/OpenAIError' '403': $ref: '#/components/responses/OpenAIError' '429': $ref: '#/components/responses/OpenAIError' '500': $ref: '#/components/responses/OpenAIError' '503': $ref: '#/components/responses/OpenAIError' /v1/chat/completions: post: tags: - OpenAI Compatible operationId: createChatCompletion summary: OpenAI Chat Completions on a SpicyAPI text model description: 'Accepts an OpenAI Chat Completions request. `messages`, `tools`, `tool_choice`, `parallel_tool_calls`, `response_format`, `max_tokens` (or `max_completion_tokens`), `temperature`, `top_p`, `top_k`, `seed` and `stop` map onto the model''s public input fields of the same name when the model declares them (each model page lists its own set); `stream`, `stream_options`, `user`, `metadata` and `store` are handled by this layer. Pure sampling parameters the model does not declare (`temperature`, `top_p`, `top_k`, `min_p`, `top_a`, `frequency_penalty`, `presence_penalty`, `repetition_penalty`), `n: 1`, an empty `stop`, `stop` on a model without stop sequences, `reasoning_effort` on a model without reasoning levels, and an OpenRouter-style `reasoning` object that cannot be translated are ignored, and the response lists them in the `X-Spicy-Ignored-Params` header. Values the model declares but cannot take as sent are changed to the nearest accepted value and listed as `name=value` in the `X-Spicy-Adjusted-Params` header: a sampling value outside the model''s range is clamped to the bound, a `stop` list longer than the model allows keeps its first entries, a `reasoning_effort` level the model does not offer becomes the nearest level it does (the lower one on a tie), and `reasoning.effort` (or `reasoning.enabled: false`, as `none`) becomes `reasoning_effort`. Any other parameter is forwarded to the model''s input schema and rejected with `400` when the model does not declare it. `stream: true` returns a Server-Sent Events stream of `chat.completion.chunk` objects terminated by `data: [DONE]`. Every chunk has a non-empty `choices` array and each choice ends with exactly one chunk carrying `finish_reason`. Usage follows OpenAI: a stream carries no `usage` unless `stream_options.include_usage` is `true`, in which case one extra chunk with `choices: []` and the final `usage` comes right before `[DONE]` and every other chunk has `usage: null`. `stream: false` returns one `chat.completion` object that always has `usage`. The same hold, settlement and refund rules as `/api/v1/jobs/stream` apply; the `X-Spicy-Task-Id` response header names the task that holds the charge. A non-streaming call that is still running after about 30 seconds is kept open: the `200` status and the `X-Spicy-Keepalive: 1` header go out first, followed by a newline every 15 seconds and then the JSON body. A failure after that point is the usual `{"error": {...}}` object in the body of that `200` response, so check for an `error` key when the header is present. The same applies to `/v1/responses`, `/v1/messages` (Anthropic error object) and `:generateContent` (Gemini error object, whose `error.code` keeps the HTTP status the error would have had). Errors a client can act on: an unknown `model` returns `404` with `code: model_not_found`, the ID shape (`publisher/model/chat`) and the closest callable chat model; a model that is not a chat model returns `400` with `param: model`; a field or message part the model does not accept (for example an `image_url` part on a text-only model) returns `400` with `code: unsupported_parameter` and `param` pointing at it; a content-policy refusal returns `400` with `code: content_rejected`.' parameters: - $ref: '#/components/parameters/IdempotencyKey' requestBody: required: true content: application/json: schema: $ref: '#/components/schemas/ChatCompletionRequest' responses: '200': description: One `chat.completion` object, or an SSE stream when `stream` is true. headers: X-Spicy-Task-Id: $ref: '#/components/headers/SpicyTaskId' X-Spicy-Keepalive: $ref: '#/components/headers/SpicyKeepalive' content: application/json: schema: $ref: '#/components/schemas/ChatCompletion' text/event-stream: schema: type: string '400': $ref: '#/components/responses/OpenAIError' '401': $ref: '#/components/responses/OpenAIError' '402': $ref: '#/components/responses/OpenAIError' '403': $ref: '#/components/responses/OpenAIError' '404': $ref: '#/components/responses/OpenAIError' '409': $ref: '#/components/responses/OpenAIError' '413': $ref: '#/components/responses/OpenAIError' '429': $ref: '#/components/responses/OpenAIError' '500': $ref: '#/components/responses/OpenAIError' '503': $ref: '#/components/responses/OpenAIError' /v1/responses: post: tags: - OpenAI Compatible operationId: createResponse summary: OpenAI Responses API subset on a SpicyAPI text model description: 'Accepts the stateless subset of the OpenAI Responses API: `model`, `input` (string or item array with `message`, `function_call` and `function_call_output` items), `instructions`, `max_output_tokens`, `temperature`, `top_p`, `tools` (function tools), `tool_choice`, `text.format`, `reasoning.effort` and `stream`. The request is translated to a chat completion internally. `previous_response_id` is rejected with `400`; send the full history in `input` instead. As on Chat Completions, sampling parameters the model does not declare are ignored and listed in the `X-Spicy-Ignored-Params` response header, and values outside the model''s range or levels are changed to the nearest accepted value and listed in `X-Spicy-Adjusted-Params`. Non-streaming calls return a `response` object with `output[]` (`message` and `function_call` items) and `usage`. Streaming calls emit `response.created`, `response.output_text.delta` and `response.completed` events.' parameters: - $ref: '#/components/parameters/IdempotencyKey' requestBody: required: true content: application/json: schema: $ref: '#/components/schemas/ResponsesRequest' responses: '200': description: One `response` object, or an SSE stream when `stream` is true. headers: X-Spicy-Task-Id: $ref: '#/components/headers/SpicyTaskId' X-Spicy-Keepalive: $ref: '#/components/headers/SpicyKeepalive' content: application/json: schema: $ref: '#/components/schemas/ResponseObject' text/event-stream: schema: type: string '400': $ref: '#/components/responses/OpenAIError' '401': $ref: '#/components/responses/OpenAIError' '402': $ref: '#/components/responses/OpenAIError' '403': $ref: '#/components/responses/OpenAIError' '404': $ref: '#/components/responses/OpenAIError' '409': $ref: '#/components/responses/OpenAIError' '413': $ref: '#/components/responses/OpenAIError' '429': $ref: '#/components/responses/OpenAIError' '500': $ref: '#/components/responses/OpenAIError' '503': $ref: '#/components/responses/OpenAIError' /v1/messages: post: tags: - OpenAI Compatible operationId: createMessage summary: Anthropic Messages API on a SpicyAPI text model description: 'Accepts an Anthropic Messages request (`model`, `system`, `messages` with text / image / tool_use / tool_result blocks, `max_tokens`, `temperature`, `top_p`, `top_k`, `stop_sequences`, `tools`, `tool_choice`, `stream`) and translates it to a chat completion internally. `stop_sequences` becomes the model''s `stop` field, and `tool_choice.disable_parallel_tool_use: true` becomes `parallel_tool_calls: false`; a model that does not declare the target field rejects the request with `400` instead of ignoring it. Non-streaming calls return an Anthropic `message` object. Streaming calls emit `message_start`, `content_block_start`, `content_block_delta`, `content_block_stop`, `message_delta` and `message_stop` events. Errors use the Anthropic `{type:"error",error:{type,message}}` shape.' parameters: - $ref: '#/components/parameters/IdempotencyKey' requestBody: required: true content: application/json: schema: $ref: '#/components/schemas/AnthropicMessageRequest' responses: '200': description: One Anthropic `message` object, or an SSE stream when `stream` is true. headers: X-Spicy-Task-Id: $ref: '#/components/headers/SpicyTaskId' X-Spicy-Keepalive: $ref: '#/components/headers/SpicyKeepalive' content: application/json: schema: $ref: '#/components/schemas/AnthropicMessage' text/event-stream: schema: type: string '400': $ref: '#/components/responses/AnthropicError' '401': $ref: '#/components/responses/AnthropicError' '402': $ref: '#/components/responses/AnthropicError' '403': $ref: '#/components/responses/AnthropicError' '404': $ref: '#/components/responses/AnthropicError' '409': $ref: '#/components/responses/AnthropicError' '413': $ref: '#/components/responses/AnthropicError' '429': $ref: '#/components/responses/AnthropicError' '500': $ref: '#/components/responses/AnthropicError' '503': $ref: '#/components/responses/AnthropicError' /v1beta/models/{model}:generateContent: post: tags: - OpenAI Compatible operationId: geminiGenerateContent summary: Google Gemini generateContent on a SpicyAPI text model description: 'Accepts a Google Gemini `generateContent` request (`contents` with text / inlineData / functionCall / functionResponse parts, `systemInstruction`, `tools.functionDeclarations`, `toolConfig`, `generationConfig` such as `temperature`, `topP`, `maxOutputTokens`, `stopSequences`, `responseMimeType`, `responseSchema`, `thinkingConfig`). `{model}` is a SpicyAPI text model identifier from `/v1/models` and may contain a slash (for example `google/gemini-3-flash-preview`). Validation, pricing and availability are identical to `/v1/chat/completions`. The response is one `GenerateContentResponse`. Errors use the Google `{error:{code,message,status}}` shape. A call still running after about 30 seconds is kept open with leading whitespace and the `X-Spicy-Keepalive: 1` header, as described on `/v1/chat/completions`; a failure after that point is the same error object inside a `200` response.' parameters: - name: model in: path required: true schema: type: string - $ref: '#/components/parameters/IdempotencyKey' requestBody: required: true content: application/json: schema: $ref: '#/components/schemas/GeminiGenerateContentRequest' responses: '200': description: One `GenerateContentResponse`. headers: X-Spicy-Task-Id: $ref: '#/components/headers/SpicyTaskId' X-Spicy-Keepalive: $ref: '#/components/headers/SpicyKeepalive' content: application/json: schema: $ref: '#/components/schemas/GeminiGenerateContentResponse' '400': $ref: '#/components/responses/GeminiError' '401': $ref: '#/components/responses/GeminiError' '402': $ref: '#/components/responses/GeminiError' '403': $ref: '#/components/responses/GeminiError' '404': $ref: '#/components/responses/GeminiError' '409': $ref: '#/components/responses/GeminiError' '413': $ref: '#/components/responses/GeminiError' '429': $ref: '#/components/responses/GeminiError' '500': $ref: '#/components/responses/GeminiError' '503': $ref: '#/components/responses/GeminiError' /v1beta/models/{model}:streamGenerateContent: post: tags: - OpenAI Compatible operationId: geminiStreamGenerateContent summary: Google Gemini streamGenerateContent on a SpicyAPI text model description: 'Same request as `generateContent`. With `?alt=sse` (what the Google GenAI SDKs send) the response is a server-sent event stream where each `data:` line is one `GenerateContentResponse`; the final one carries `finishReason` and `usageMetadata`. There is no `[DONE]` sentinel. Without `alt=sse` the response is a JSON array of `GenerateContentResponse` objects written progressively.' parameters: - name: model in: path required: true schema: type: string - name: alt in: query required: false schema: type: string enum: - sse - $ref: '#/components/parameters/IdempotencyKey' requestBody: required: true content: application/json: schema: $ref: '#/components/schemas/GeminiGenerateContentRequest' responses: '200': description: An SSE stream (`alt=sse`) or a JSON array of `GenerateContentResponse`. headers: X-Spicy-Task-Id: $ref: '#/components/headers/SpicyTaskId' content: text/event-stream: schema: type: string application/json: schema: type: array items: $ref: '#/components/schemas/GeminiGenerateContentResponse' '400': $ref: '#/components/responses/GeminiError' '401': $ref: '#/components/responses/GeminiError' '402': $ref: '#/components/responses/GeminiError' '403': $ref: '#/components/responses/GeminiError' '404': $ref: '#/components/responses/GeminiError' '409': $ref: '#/components/responses/GeminiError' '413': $ref: '#/components/responses/GeminiError' '429': $ref: '#/components/responses/GeminiError' '500': $ref: '#/components/responses/GeminiError' '503': $ref: '#/components/responses/GeminiError' /v1/videos: post: tags: - OpenAI Compatible operationId: createVideo summary: Create a video generation task in the OpenAI Videos shape description: 'Projection of `createTask` onto the OpenAI Videos API. `model` must be a video endpoint identifier from `/v1/models`. `prompt` maps to the model''s `prompt`; `input_reference` (an HTTPS URL or a committed `spicy://` upload) maps to `image_url` / `image_urls`; `seconds` maps to `duration_seconds`; `size` (`WIDTHxHEIGHT`) maps to `resolution` and/or `aspect_ratio`, or to `width` / `height` when the model declares pixel dimensions. A parameter the selected model does not declare is rejected with `400`. The same hold and idempotency rules as `createTask` apply; the returned object never contains prices.' parameters: - $ref: '#/components/parameters/IdempotencyKey' requestBody: required: true content: application/json: schema: $ref: '#/components/schemas/VideoCreateRequest' responses: '200': description: The accepted task as a `video` object in `queued` state (an idempotent replay reports the current state). content: application/json: schema: $ref: '#/components/schemas/Video' '400': $ref: '#/components/responses/OpenAIError' '401': $ref: '#/components/responses/OpenAIError' '402': $ref: '#/components/responses/OpenAIError' '403': $ref: '#/components/responses/OpenAIError' '404': $ref: '#/components/responses/OpenAIError' '409': $ref: '#/components/responses/OpenAIError' '413': $ref: '#/components/responses/OpenAIError' '429': $ref: '#/components/responses/OpenAIError' '500': $ref: '#/components/responses/OpenAIError' '503': $ref: '#/components/responses/OpenAIError' /v1/videos/{videoId}: get: tags: - OpenAI Compatible operationId: retrieveVideo summary: Retrieve a video task as an OpenAI `video` object description: 'Task states map to `queued` → `queued`, `running` → `in_progress`, `succeeded` → `completed`, and `failed` / `canceled` / `expired` → `failed` with an `error` object. Only the account and API key that created the task can read it; other identifiers return `404`.' parameters: - name: videoId in: path required: true schema: type: string responses: '200': description: Current video task state. content: application/json: schema: $ref: '#/components/schemas/Video' '401': $ref: '#/components/responses/OpenAIError' '403': $ref: '#/components/responses/OpenAIError' '404': $ref: '#/components/responses/OpenAIError' '429': $ref: '#/components/responses/OpenAIError' '500': $ref: '#/components/responses/OpenAIError' '503': $ref: '#/components/responses/OpenAIError' /v1/videos/{videoId}/content: get: tags: - OpenAI Compatible operationId: downloadVideoContent summary: Redirect to the generated video file description: 'Responds `302 Found` with a short-lived signed download URL for the task''s primary output, the same address `/api/v1/common/download-url` would issue. `409` while the task is still queued or running; `404` when the task failed or has no downloadable output.' parameters: - name: videoId in: path required: true schema: type: string responses: '302': description: Redirect to the signed media URL. The redirect must not be cached. headers: Location: description: Short-lived signed download URL. schema: type: string format: uri Cache-Control: schema: type: string const: no-store '401': $ref: '#/components/responses/OpenAIError' '403': $ref: '#/components/responses/OpenAIError' '404': $ref: '#/components/responses/OpenAIError' '409': $ref: '#/components/responses/OpenAIError' '429': $ref: '#/components/responses/OpenAIError' '500': $ref: '#/components/responses/OpenAIError' '503': $ref: '#/components/responses/OpenAIError' components: schemas: ResponsesRequest: type: object required: - model - input properties: model: type: string input: oneOf: - type: string - type: array items: type: object instructions: type: string max_output_tokens: type: integer temperature: type: number top_p: type: number tools: type: array items: type: object tool_choice: oneOf: - type: string - type: object text: type: object reasoning: type: object properties: effort: type: string stream: type: boolean default: false OpenAIModelList: type: object required: - object - data properties: object: type: string const: list data: type: array items: type: object required: - id - object - created - owned_by properties: id: type: string description: SpicyAPI model identifier; may contain slashes. object: type: string const: model created: type: integer description: Unix seconds of the last catalog update. owned_by: type: string description: Model publisher. modality: type: string enum: - text - image - video - audio description: SpicyAPI extension. Output modality. task: type: string description: SpicyAPI extension. The task this identifier runs, for example `chat` or `text-to-video`. Only `chat` models work on the chat, Responses, Messages and Gemini operations. context_length: type: integer description: SpicyAPI extension chat models only. The most input tokens one request can send.: null max_output_tokens: type: integer description: SpicyAPI extension chat models only. The largest `max_tokens` value the model accepts.: null supported_parameters: type: array items: type: string description: 'SpicyAPI extension, chat models only. Request fields this model accepts (the top-level fields of its input schema). `stream`, `stream_options`, `user`, `metadata` and `store` are handled by the compatible layer for every model and are not listed. Any other field returns `400` with `code: unsupported_parameter`.' AnthropicMessageRequest: type: object required: - model - messages - max_tokens properties: model: type: string system: oneOf: - type: string - type: array items: type: object messages: type: array minItems: 1 items: type: object required: - role - content properties: role: type: string enum: - user - assistant content: oneOf: - type: string - type: array items: type: object max_tokens: type: integer temperature: type: number top_p: type: number top_k: type: integer stop_sequences: type: array items: type: string tools: type: array items: type: object tool_choice: type: object description: '`disable_parallel_tool_use: true` is translated to `parallel_tool_calls: false`.' stream: type: boolean default: false AnthropicErrorBody: type: object required: - type - error properties: type: type: string const: error error: type: object required: - type - message properties: type: type: string enum: - invalid_request_error - authentication_error - billing_error - permission_error - not_found_error - request_too_large - rate_limit_error - api_error - overloaded_error message: type: string code: type: string description: 'SpicyAPI extension: the same stable reason as `error.code` in the OpenAI shape (for example `content_rejected`, `unsupported_parameter`, `invalid_request`). Official Anthropic SDKs ignore it; read it from the raw error body to tell a content-policy refusal from a bad parameter.' GeminiErrorBody: type: object required: - error properties: error: type: object required: - code - message - status properties: code: type: integer message: type: string status: type: string details: type: array description: 'One `google.rpc.ErrorInfo` whose `reason` is the upper-case form of the OpenAI-shape `error.code` (for example `CONTENT_REJECTED`, `UNSUPPORTED_PARAMETER`, `INVALID_REQUEST`) and whose `domain` is `api.spicyapi.ai`. `status` alone cannot tell a content-policy refusal from a bad parameter: both are `INVALID_ARGUMENT`.' items: type: object required: - '@type' - reason - domain properties: '@type': type: string const: type.googleapis.com/google.rpc.ErrorInfo reason: type: string domain: type: string GeminiGenerateContentRequest: type: object required: - contents description: Google Gemini `generateContent` request. Which generation parameters a model accepts comes from that model's `inputSchema` in `/v1/models` and the catalog. Sampling parameters the model does not declare (temperature, topP, topK, penalties) are ignored and listed in the `X-Spicy-Ignored-Params` response header; any other unsupported parameter returns 400. properties: contents: type: array items: type: object properties: role: type: string enum: - user - model parts: type: array items: type: object description: One of `text`, `inlineData {mimeType,data}`, `fileData {mimeType,fileUri}`, `functionCall {name,args}`, `functionResponse {name,response}`. systemInstruction: type: object tools: type: array items: type: object toolConfig: type: object generationConfig: type: object properties: temperature: type: number topP: type: number maxOutputTokens: type: integer stopSequences: type: array items: type: string responseMimeType: type: string responseSchema: type: object thinkingConfig: type: object VideoCreateRequest: type: object required: - model - prompt additionalProperties: false properties: model: type: string description: Video endpoint identifier from `/v1/models`. prompt: type: string input_reference: type: string description: Reference image URL; mapped to the model's `image_url` / `image_urls`. seconds: oneOf: - type: string - type: integer description: Clip length in seconds; mapped to `duration_seconds`. size: type: string pattern: ^[0-9]+x[0-9]+$ description: WIDTHxHEIGHT, mapped to `resolution` / `aspect_ratio` (or `width` / `height`). OpenAIErrorBody: type: object required: - error properties: error: type: object required: - message - type - code properties: message: type: string type: type: string enum: - invalid_request_error - authentication_error - permission_error - insufficient_quota - rate_limit_error - server_error param: type: - string - 'null' description: The request field the error is about, as a dot path (for example `temperature`, `messages.1.content.0`, `stream_options.include_usage`). `null` when the error is not about a single field. code: type: string description: Stable machine-readable reason, for example `invalid_request`, `unsupported_parameter` (the model does not accept this field, value or message part), `content_rejected` (the model's content policy refused the request; HTTP 400, and retrying the same input will be refused again), `model_not_found`, `invalid_api_key`, `insufficient_balance`, `model_not_allowed`, `model_unavailable`, `rate_limit_exceeded`, `upstream_failed`. AnthropicMessage: type: object required: - id - type - role - model - content - stop_reason - usage properties: id: type: string type: type: string const: message role: type: string const: assistant model: type: string content: type: array items: type: object required: - type properties: type: type: string enum: - text - tool_use text: type: string id: type: string name: type: string input: type: object stop_reason: type: string enum: - end_turn - max_tokens - tool_use stop_sequence: type: 'null' usage: type: object properties: input_tokens: type: integer output_tokens: type: integer ChatCompletion: type: object required: - id - object - created - model - choices properties: id: type: string object: type: string const: chat.completion created: type: integer model: type: string choices: type: array items: type: object required: - index - message - finish_reason properties: index: type: integer message: type: object properties: role: type: string content: type: - string - 'null' refusal: type: string reasoning_content: type: string description: Optional reasoning text returned by models that expose it. Streaming chunks use the same field in delta. tool_calls: type: array items: type: object additionalProperties: true finish_reason: type: string logprobs: type: 'null' usage: type: object properties: prompt_tokens: type: integer completion_tokens: type: integer total_tokens: type: integer prompt_tokens_details: type: object properties: cached_tokens: type: integer cache_creation_input_tokens: type: integer cache_read_input_tokens: type: integer completion_tokens_details: type: object properties: reasoning_tokens: type: integer description: Included in completion_tokens; do not add it again when calculating usage. Video: type: object required: - id - object - model - status - progress - created_at properties: id: type: string description: Task identifier; also accepted by `/api/v1/jobs/recordInfo`. object: type: string const: video model: type: string status: type: string enum: - queued - in_progress - completed - failed progress: type: integer minimum: 0 maximum: 100 description: 100 when completed otherwise 0; no fabricated intermediate values.: null created_at: type: integer completed_at: type: integer seconds: type: string size: type: string error: type: object required: - code - message properties: code: type: string message: type: string GeminiGenerateContentResponse: type: object properties: candidates: type: array items: type: object properties: index: type: integer content: type: object properties: role: type: string parts: type: array items: type: object finishReason: type: string usageMetadata: type: object properties: promptTokenCount: type: integer candidatesTokenCount: type: integer totalTokenCount: type: integer cachedContentTokenCount: type: integer thoughtsTokenCount: type: integer modelVersion: type: string description: The SpicyAPI model identifier. responseId: type: string ChatCompletionRequest: type: object required: - model - messages properties: model: type: string messages: type: array minItems: 1 items: type: object required: - role properties: role: type: string content: oneOf: - type: - string - 'null' - type: array items: type: object additionalProperties: true tool_call_id: type: string tool_calls: type: array items: type: object additionalProperties: true reasoning_content: type: string description: Optional assistant reasoning text; availability and message roles follow the selected model input schema. additionalProperties: true stream: type: boolean default: false max_tokens: type: integer max_completion_tokens: type: integer description: Alias of `max_tokens`. temperature: type: number top_p: type: number top_k: type: integer description: Sample only from the k most likely next tokens; only on models that declare it. seed: type: integer description: Best-effort repeatable sampling; only on models that declare it. stop: description: Up to 4 stop sequences (some models accept only one); only on models that declare it. oneOf: - type: string - type: array items: type: string tools: type: array items: type: object additionalProperties: true tool_choice: oneOf: - type: string - type: object additionalProperties: true parallel_tool_calls: type: boolean description: Set false to allow at most one tool call per turn; only on models that declare it. response_format: type: object additionalProperties: true additionalProperties: description: Forwarded to the model input schema and rejected when undeclared. ResponseObject: type: object required: - id - object - created_at - status - model - output properties: id: type: string object: type: string const: response created_at: type: integer status: type: string enum: - completed - incomplete - in_progress model: type: string output: type: array items: type: object required: - id - type - status properties: id: type: string type: type: string enum: - message - function_call status: type: string role: type: string content: type: array items: type: object properties: type: type: string const: output_text text: type: string annotations: type: array items: type: object call_id: type: string name: type: string arguments: type: string error: type: 'null' incomplete_details: type: - object - 'null' properties: reason: type: string usage: type: object properties: input_tokens: type: integer output_tokens: type: integer total_tokens: type: integer parameters: IdempotencyKey: name: Idempotency-Key in: header required: false description: Stable key for one logical task submission; retained for 24 hours per account and bound to both the submitting API key and normalized request fingerprint. A sibling key receives 409 without the original task ID. schema: type: string minLength: 1 maxLength: 128 headers: SpicyKeepalive: description: Present, with the value `1`, only on a non-streaming text response that was still running after about 30 seconds. The `200` status was sent early to keep the connection open, the body starts with whitespace (newlines sent every 15 seconds), and then comes the JSON answer — or, if the request failed after that point, the protocol's own error object with the code and message it would otherwise have had. Absent on responses that finished sooner, which are unchanged. schema: type: string enum: - '1' SpicyTaskId: description: The SpicyAPI task (`job_…`) that ran this synchronous text request — the same `taskId` that `GET /api/v1/jobs` and `recordInfo` return, so a call can be matched to its charge. Set as soon as the request is accepted, including on error responses returned after acceptance (the task shows the refund). Absent when the request was rejected before a task was created (for example a validation error), and on a streamed response whose headers were already flushed by a keep-alive before acceptance. schema: type: string responses: AnthropicError: description: Error in the Anthropic `{type:"error",error:{type,message}}` shape. content: application/json: schema: $ref: '#/components/schemas/AnthropicErrorBody' GeminiError: description: Error in the Google `{error:{code,message,status}}` shape. `status` is the google.rpc code name (for example `INVALID_ARGUMENT`, `UNAUTHENTICATED`, `RESOURCE_EXHAUSTED`). content: application/json: schema: $ref: '#/components/schemas/GeminiErrorBody' OpenAIError: description: 'Error in the OpenAI `{error:{message,type,param,code}}` shape. The HTTP status is the same one `/api/v1` would return. A content-policy refusal is HTTP 400 with `code: content_rejected`, so it can be told apart from a parameter error (`invalid_request` / `unsupported_parameter`, with `param` set when the error is about one field).' content: application/json: schema: $ref: '#/components/schemas/OpenAIErrorBody' securitySchemes: bearerAuth: type: http scheme: bearer bearerFormat: sk-spicy-<48 lowercase hex characters> description: 'Send `Authorization: Bearer sk-spicy-...`. Keep keys server-side.'