openapi: 3.2.0 info: title: SpicyAPI Public Models 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: Models paths: /api/v1/models: get: tags: - Models operationId: listModels summary: List callable models and account-specific prices description: 'Returns all enabled models without pagination. The response is private because prices include the authenticated account''s group multiplier. The server sends `Cache-Control: private, max-age=60`.' parameters: - name: modality in: query schema: type: string enum: - image - video - audio - text - name: provider in: query schema: type: string description: Exact model creator/author identifier, such as the research lab that built the model. - name: task in: query schema: type: string description: 'Exact supported task, for example `text-to-image`. Image editing accepts either spelling: `edit` (the published call suffix) and `image-to-image` (the value returned in `tasks`) select the same endpoints.' - name: search in: query schema: type: string maxLength: 128 - name: includeSchema in: query schema: oneOf: - type: boolean - type: integer enum: - 0 - 1 description: Include each model's input JSON Schema when `true` or `1`. - name: includeExamples in: query schema: oneOf: - type: boolean - type: integer enum: - 0 - 1 description: Include validated example inputs when `true` or `1`. responses: '200': description: Model list. headers: Cache-Control: schema: type: string const: private, max-age=60 content: application/json: schema: $ref: '#/components/schemas/ModelListEnvelope' '400': $ref: '#/components/responses/BadRequest' '401': $ref: '#/components/responses/Unauthorized' '403': $ref: '#/components/responses/Forbidden' '429': $ref: '#/components/responses/RateLimited' '500': $ref: '#/components/responses/ServerError' /api/v1/models/{model}: get: tags: - Models operationId: getModel summary: Get one model, including its input JSON Schema description: '`model` is the exact value used by `createTask.model` and may contain slashes. URL-encode embedded slashes when using clients that treat path parameters as one segment (for example `family%2Fversion%2Ftask`).' parameters: - name: model in: path required: true allowReserved: true schema: type: string minLength: 1 responses: '200': description: Complete model record. headers: Cache-Control: schema: type: string const: private, max-age=60 content: application/json: schema: $ref: '#/components/schemas/ModelEnvelope' '400': $ref: '#/components/responses/BadRequest' '401': $ref: '#/components/responses/Unauthorized' '403': $ref: '#/components/responses/Forbidden' '404': $ref: '#/components/responses/NotFound' '429': $ref: '#/components/responses/RateLimited' '500': $ref: '#/components/responses/ServerError' /console/v1/catalog/models: get: tags: - Models operationId: listPublicCatalog summary: Anonymous evaluation catalogue (no API key) security: [] description: 'The one integration-relevant path that reads without credentials. It exists so that "which models are there and what do they cost" can be answered before signing up, which is the order integrations actually evaluate in. Use it while evaluating; use `GET /api/v1/models` once you hold a key. The authenticated catalogue is still the only source for callable model IDs at integration time: it is scoped to the key, carries the account''s group multiplier, the full input JSON Schema and validated examples, and it is the surface `createTask.model` is contractually tied to. This one is public, cached at the edge and priced at the standard rate. Each item carries both `model` and `slug` with the same value, so parsing code written here keeps working against `/api/v1/models`. The complete item shape (marketing copy, assets, family routing) is documented in `console-openapi.yaml`; the fields below are the ones an integration cares about, and the object carries more.' parameters: - name: locale in: query schema: type: string enum: - en - ja - ko - de - fr - es - pt-BR - ru - it - pl default: en description: Content language. Defaults to `en` when omitted. An unsupported value is rejected with 400 rather than silently falling back. - name: strict in: query schema: oneOf: - type: boolean - type: integer enum: - 0 - 1 description: When `1` or `true`, return only models whose copy is published in that exact locale, with no fallback. Sitemap generation needs this; integrations normally do not. responses: '200': description: Published catalogue for one locale. headers: Cache-Control: schema: type: string const: public, max-age=60, s-maxage=300, stale-while-revalidate=86400 content: application/json: schema: $ref: '#/components/schemas/PublicCatalogEnvelope' '400': $ref: '#/components/responses/BadRequest' '429': $ref: '#/components/responses/RateLimited' '500': $ref: '#/components/responses/ServerError' components: headers: RetryAfter: description: Seconds to wait before retrying. schema: type: integer minimum: 1 RateLimitLimit: description: Capacity of the active account-level rate-limit window. schema: type: integer RateLimitRemaining: description: Remaining requests in the active window. schema: type: integer schemas: EnvelopeBase: type: object required: - code - msg - request_id properties: code: type: integer msg: type: string request_id: type: string description: Correlation identifier. Include it in support requests. PublicCatalogItem: type: object description: Evaluation view of one endpoint. Additional presentation fields are returned and documented in console-openapi.yaml. required: - model - slug - family - displayName - provider - modality - tasks - status - callable properties: model: type: string description: Same value as `slug`, under the field name `/api/v1/models` uses. This is the value `createTask.model` takes. slug: type: string aliases: type: array items: type: string description: Previously published identifiers that still resolve to this endpoint. family: type: string familyPageSlug: type: string familyDisplayName: type: string displayName: type: string provider: type: string description: The model's creator, for example `ByteDance`. Never an execution channel. modality: type: string enum: - image - video - audio - text tasks: type: array items: type: string description: Same values and meaning as APIModel.tasks, including the `edit` / `image-to-image` split. mature: type: boolean policyTier: type: string enum: - unrestricted - borderline - softened - filtered - unspecified description: Same values and meaning as APIModel.policyTier. Treat the set as open. toolkit: type: string enum: - subject-swap - studio-tools description: Which toolkit family this model belongs to (subject-swap for identity swaps; studio-tools for single-purpose image and video tools such as background removal or upscaling). Informational classification for catalogue grouping only; it does not participate in routing pricing or authorization. Absent when the model is not part of a toolkit.: null async: type: boolean status: type: string enum: - published - live - paused description: Operator intent. `live` is the only status that can be callable. callable: type: boolean description: Whether a fully priced, routable execution path exists right now. A callable endpoint always has a price; an endpoint without a price is never callable. price: oneOf: - $ref: '#/components/schemas/PublicCatalogPrice' - type: 'null' description: Standard-rate starting price, or absent when the endpoint is not currently sellable. It is never reported as `0`. APIModelList: type: object required: - total - items properties: total: type: integer minimum: 0 items: type: array items: $ref: '#/components/schemas/APIModel' ModelEnvelope: allOf: - $ref: '#/components/schemas/EnvelopeBase' - type: object required: - data properties: code: type: integer const: 200 msg: type: string const: success data: $ref: '#/components/schemas/APIModel' APIModelPrice: type: object required: - variant - unit - price - currency properties: variant: type: string description: Price tier key. Empty when the model has a single price. When one input field sets the price, it is that field's value as is (for example `720p`). When several fields do, it is `field=value` pairs sorted by field name and joined with `;` (for example `duration=5;resolution=720p`), with values URL-encoded; `~` stands for an optional field that was not sent and has no default. unit: type: string enum: - per_image - per_second - per_request - per_1k_tokens - per_1k_characters price: $ref: '#/components/schemas/USDString' regularPrice: $ref: '#/components/schemas/USDString' offerLabel: type: string offerPercent: type: string offerEndsAt: type: string format: date-time currency: type: string const: USD officialFallbackPrice: allOf: - $ref: '#/components/schemas/USDString' description: 'Present only for models with an economy route. `price` is then the economy-route price and this is the same tier''s official price: the most a request is charged when `official_fallback` is `true` and the economy route fails.' PublicCatalogEnvelope: allOf: - $ref: '#/components/schemas/EnvelopeBase' - type: object required: - data properties: code: type: integer const: 200 msg: type: string const: success data: $ref: '#/components/schemas/PublicCatalogList' ErrorEnvelope: allOf: - $ref: '#/components/schemas/EnvelopeBase' - type: object properties: code: type: integer enum: - 400 - 40003 - 40004 - 401 - 40201 - 40202 - 403 - 40301 - 40302 - 40303 - 40310 - 404 - 409 - 40901 - 413 - 429 - 500 - 503 - 50301 - 50302 description: 'Business code. `40003`: an uploaded file''s actual size, media type or signature does not match its upload ticket (file commit). `40004`: the request is valid but no deployment can serve this exact parameter combination; change the named parameter and retry. `503`: a dependency is temporarily unavailable (for example a list or usage query timed out, or inline image storage failed); honour `Retry-After` when present. `50301`: the model has no usable deployment or effective price right now. `50302`: a synchronous generation (streaming) failed upstream and the charge was refunded. Retry with a **new** `Idempotency-Key` — the original key replays the same failure, so a transport-level retry that keeps the key can never succeed. `40301`: this key is not allowed to call this model — check the key''s model restrictions in the Console. `40302`: the caller''s address is not in this key''s IP allow-list. `40303`: the request comes from a region this service is not offered in; the message deliberately does not state how that is determined, and neither does this description. `40310`: the account has not verified its email address yet, so it cannot run models. Open the verification link that was emailed at sign-up, or send a new one from the Console; the key starts working as soon as the address is verified, with no change on your side.' msg: type: string description: Human-readable explanation, English unless `Accept-Language` or the account's API error language selects another supported language. Never parse it; branch on `code`. APIModelExample: type: object required: - id - input - sortWeight properties: id: type: string input: type: object additionalProperties: true sortWeight: type: integer PublicCatalogPrice: type: object required: - amount - unit - tiers properties: amount: type: string description: Lowest tier's unit price as a decimal string. Decimal strings, not numbers, because these values are displayed and binary floats cannot represent them exactly. unit: type: string description: Billing unit identifier, for example per_image or per_second. variant: type: string description: The tier this price belongs to; absent when the model has one tier. tiers: type: integer description: Number of currently effective tiers. Greater than one means this is a starting price. regularAmount: type: string description: Pre-discount price present only when a customer offer applies.: null offerLabel: type: string offerPercent: type: string offerEndsAt: type: string format: date-time perMillionTokens: type: object description: Standard per-million-token prices for text models. required: - input - output properties: input: type: string cachedInput: type: string output: type: string cacheWrite: type: string listPrice: type: object description: The model creator's own published price, present only when ours is at least 1% lower. It is not our pre-discount price. variants: type: array description: 'Every priced tier of a per-second, per-image or per-request model, cheapest first (the first entry is the starting price). Absent for per-token models. `variant` is the raw tier key: a single value such as `720p`, or a compound key such as `generate_audio=false;resolution=1080p`.' items: type: object required: - variant - amount - unit properties: variant: type: string amount: type: string regularAmount: type: string unit: type: string listPrice: type: object description: This tier's own reference price, under the same rules as listPrice above. APIModel: type: object required: - model - family - displayName - provider - modality - tasks - async - mature - policyTier - taskTimeoutSeconds - enabled - available - quantityField - pricing - version - availability - badges - relatedModels - updatedAt properties: model: type: string description: Preferred callable identifier in publisher/model/task form. Image editing uses the edit suffix; previously published identifiers remain accepted. example: bytedance/seedream-5.0-lite/edit family: type: string description: Stable product-family identifier; endpoint variants are listed in `tasks` and expressed by `inputSchema`. displayName: type: string provider: type: string description: Model creator/author identifier, such as the research lab that built the model. modality: type: string enum: - image - video - audio - text tasks: type: array items: type: string description: 'The internal task classification of this one endpoint. Always exactly one element, and it is not always the suffix of `model`: image editing endpoints are published as `.../edit` but classified as `image-to-image`, because renaming the call did not reclassify the work. Use `model` to call, and `tasks[0]` only to group or filter. Other values match their suffix: `text-to-image`, `text-to-video`, `image-to-video`, `reference-to-video`, `video-edit`, `video-extend`, `video-analyze`, `character-animation`, `talking-avatar`, `lip-sync`, `video-to-audio` (returns the same video with a generated sound track), `frame-interpolation` (returns the same video at twice its frame rate, same length and audio), `layer-decomposition` (returns a base image plus transparent layers, see `TaskOutputAsset.layer`), `speech-to-text`, `chat`. New values appear as models are added, so treat the set as open.' async: type: boolean mature: type: boolean description: Informational model-creator capability metadata only. It does not participate in authorization, availability decisions, or request rejection. policyTier: type: string enum: - unrestricted - borderline - softened - filtered - unspecified description: 'Informational model-creator policy metadata only. It does not participate in authorization, availability decisions, or request rejection. `unrestricted`: the model adds no refusal layer of its own. `borderline`: its own policy lets some requests through and may still decline others. `softened`: it does not refuse and does not fail — it quietly returns a toned-down result, and that task settles and is charged like any other success. `filtered`: it declines requests its own policy restricts, which surfaces as a `content_rejected` failure. `unspecified`: no tier has been recorded for this model. Treat the set as open: a value you do not recognise must not break your client.' toolkit: type: string enum: - subject-swap - studio-tools description: Which toolkit family this model belongs to (subject-swap for identity swaps; studio-tools for single-purpose image and video tools such as background removal or upscaling). Informational classification for catalogue grouping only; it does not participate in routing, pricing or authorization. Absent when the model is not part of a toolkit. taskTimeoutSeconds: type: integer minimum: 1 description: Absolute platform execution deadline before timeout/refund; not media output duration. maxOutputDurationSeconds: type: integer minimum: 1 description: Maximum output duration the model schema allows, taken from the `maximum` of its `duration_seconds` property; omitted when that property is absent or has no declared maximum. Distinct from `taskTimeoutSeconds`, which is the platform execution deadline. enabled: type: boolean available: type: boolean quantityField: type: string pricing: type: array items: $ref: '#/components/schemas/APIModelPrice' startingPrice: $ref: '#/components/schemas/APIModelPrice' inputSchema: type: object description: 'JSON Schema draft 2020-12 used by `createTask.input` validation and Playground rendering. Standard JSON Schema keywords validate. Presentation hints live only in a per-property `x-ui` object whose keys are snake_case and drawn from a closed set: `widget`, `order`, `label`, `placeholder`, `rows`, `step`, `unit`, `accept`, `max_size_mb`, `advanced`, `primary`, `affects_price`, `visible_when`, `enum_labels`. `x-ui.order` is the only source of field order (descending first). `x-ui.widget` is one of `textarea`, `text`, `upload`, `multi-upload`, `select`, `radio`, `slider`, `number`, `switch`, `json`, `object-list`, `chat-messages`, `hidden`. Root-level composition is limited to `anyOf`, where every branch contains only `required`, and `allOf`, where each branch is `if` (`required` or `properties.*.const`) plus `then` (`required`); `oneOf`, `not` and `dependentSchemas` never appear. Unknown hints must be ignored.' additionalProperties: true version: type: string description: Public model-catalog version label used to detect schema or metadata changes. availability: type: string enum: - planned - available - preview - maintenance badges: type: array description: Closed capability and licensing vocabulary; clients should ignore unknown future IDs. items: type: string enum: - commercial_use - no_watermark - commercial_restricted - reference_input - audio_output - streaming - fast_tier - high_resolution - long_context - mature_capable - policy_unrestricted - policy_borderline relatedModels: type: array description: Curated model identifiers suitable for alternatives or workflow steps. items: type: string examples: type: array description: Inputs validated against this model's current input schema. items: $ref: '#/components/schemas/APIModelExample' updatedAt: type: string format: date-time USDString: type: string pattern: ^-?[0-9]+(?:\.[0-9]+)?$ description: Decimal USD amount encoded as a string to avoid binary floating-point loss. ModelListEnvelope: allOf: - $ref: '#/components/schemas/EnvelopeBase' - type: object required: - data properties: code: type: integer const: 200 msg: type: string const: success data: $ref: '#/components/schemas/APIModelList' PublicCatalogList: type: object required: - locale - items - total properties: locale: type: string description: The locale the copy was resolved to. total: type: integer items: type: array items: $ref: '#/components/schemas/PublicCatalogItem' responses: RateLimited: description: Account-level request limit exceeded. headers: X-RateLimit-Limit: $ref: '#/components/headers/RateLimitLimit' X-RateLimit-Remaining: $ref: '#/components/headers/RateLimitRemaining' Retry-After: $ref: '#/components/headers/RetryAfter' content: application/json: schema: $ref: '#/components/schemas/ErrorEnvelope' Unauthorized: description: Missing, malformed, expired, revoked, or otherwise invalid API key. content: application/json: schema: $ref: '#/components/schemas/ErrorEnvelope' Forbidden: description: Account, model, API-key, IP, or region authorization failed. content: application/json: schema: $ref: '#/components/schemas/ErrorEnvelope' BadRequest: description: Invalid JSON, query, callback URL, model input, or media declaration. Invalid callBackUrl values return code 400 and msg "Invalid callback URL" with request_id. Submitted hosts, IP addresses, and DNS diagnostics are not echoed. content: application/json: schema: $ref: '#/components/schemas/ErrorEnvelope' ServerError: description: Internal failure. Details are not exposed; report `request_id` to support. content: application/json: schema: $ref: '#/components/schemas/ErrorEnvelope' NotFound: description: Resource does not exist or is not visible to this account and API key. These cases are intentionally indistinguishable. content: application/json: schema: $ref: '#/components/schemas/ErrorEnvelope' securitySchemes: bearerAuth: type: http scheme: bearer bearerFormat: sk-spicy-<48 lowercase hex characters> description: 'Send `Authorization: Bearer sk-spicy-...`. Keep keys server-side.'