Каталог моделей и схемы
Как выбирать доступные модели по актуальному авторизованному каталогу, читать цены и строить запросы на основе текущей JSON Schema.
createTask.model принимает точное значение model из авторизованного ответа GET /api/v1/models. Формат input определяет текущая inputSchema той же записи. Именно этот ответ во время выполнения — единственный достоверный источник ID, доступных для вызова.
Не копируйте ID со статической страницы
Снимки каталога при запуске, страницы семейств и каталоги разработчиков моделей помогают находить модели. Они могут включать запланированные, отключённые или переименованные записи и никогда не должны заменять пустой актуальный каталог. Перед платной работой запросите авторизованный каталог и проверьте одновременно enabled: true и available: true.
В примерах этой страницы MODEL_ID_FROM_CATALOG — заполнитель, а не настоящая модель: замените его значением model из своего актуального каталога. Чтение каталога бесплатно.
Выберите модель вместе с проверенными входными данными
Схемы запросов различаются между моделями, поэтому для надёжного запуска выбирайте одновременно доступную модель и один из её примеров, проверенных сервером:
const response = await fetch(
'https://api.spicyapi.ai/api/v1/models?includeSchema=1&includeExamples=1',
{ headers: { Authorization: `Bearer ${process.env.SPICY_API_KEY}` } },
);
const envelope = await response.json();
if (!response.ok || envelope.code !== 200) throw new Error(envelope.msg);
const selected = envelope.data.items.find(
(item) => item.enabled && item.available && item.examples?.length,
);
if (!selected) throw new Error('No callable model with a validated example');
const createTaskBody = {
model: selected.model,
input: selected.examples[0].input,
};Каждый пример — полный input, который сервер уже проверил по текущей схеме, поэтому его можно отправлять без изменений.
Читайте каталог программно
GET /api/v1/models
GET /api/v1/models/{model}curl "https://api.spicyapi.ai/api/v1/models?modality=video" \
-H "Authorization: Bearer $SPICY_API_KEY"Список не разделён на страницы: один ответ содержит все видимые вашему ключу модели, соответствующие фильтрам. Все параметры необязательны.
| Параметр | Значение |
|---|---|
modality | image, video, audio или text |
provider | Разработчик модели, с точным совпадением и учётом регистра, например ByteDance. Копируйте из provider записи |
task | Один тип задачи, например text-to-image, edit (редактирование изображений), image-to-video, text-to-video, reference-to-video, speech-to-text, video-analyze, video-edit, video-extend, character-animation или chat. Текущий набор смотрите в tasks каталога. Для редактирования изображений допустимы два написания: модели публикуются как .../edit, но tasks относит их к image-to-image; фильтр принимает оба варианта |
search | Текст без учёта регистра, встречающийся в любой части ID модели или отображаемого названия; до 128 символов |
includeSchema | true или 1 добавляет inputSchema каждой записи |
includeExamples | true или 1 добавляет examples каждой записи, проверенные по текущей схеме |
Эндпоинт одной модели, GET /api/v1/models/{model}, всегда включает inputSchema и examples; includeSchema и includeExamples имеют значение только для списка. Косые черты в ID можно оставлять как есть; кодируйте их через процентное представление только если HTTP-клиент считает параметр пути одним сегментом. Несуществующая или сейчас не предлагаемая модель возвращает 404.
Сокращённый ответ со списком:
{
"code": 200,
"msg": "success",
"data": {
"total": 1,
"items": [
{
"model": "MODEL_ID_FROM_CATALOG",
"family": "…",
"displayName": "…",
"provider": "…",
"modality": "video",
"tasks": ["image-to-video"],
"async": true,
"enabled": true,
"available": true,
"availability": "available",
"taskTimeoutSeconds": 1200,
"maxOutputDurationSeconds": 10,
"quantityField": "duration_seconds",
"pricing": [
{ "variant": "generate_audio=false;resolution=720p", "unit": "per_second", "price": "0.05", "currency": "USD" },
{ "variant": "generate_audio=true;resolution=720p", "unit": "per_second", "price": "0.08", "currency": "USD" }
],
"startingPrice": { "variant": "generate_audio=false;resolution=720p", "unit": "per_second", "price": "0.05", "currency": "USD" },
"badges": ["audio_output"],
"relatedModels": [],
"version": "v1",
"updatedAt": "2026-09-16T16:47:57Z"
}
]
},
"request_id": "req_01k3m8x9q2z4v7n5p6r8s0t1w2"
}Цены выше приведены для иллюстрации. inputSchema и examples появляются в элементах списка только по запросу.
Поля, которые следует использовать клиентам
| Поле | Назначение |
|---|---|
model | Точное значение для createTask.model, в формате publisher/model/task |
family | Стабильный идентификатор семейства продукта для группировки связанных эндпоинтов |
displayName | Понятное человеку название эндпоинта |
provider | Разработчик модели, например создавшая её лаборатория |
modality / tasks | Вид медиа, к которому эндпоинт относится в каталоге, и выполняемая задача. modality определяет просмотр и фильтр modality, но не гарантирует тип результата: эндпоинт speech-to-text относится к audio, а возвращает текст. См. Формат результата |
toolkit | Семейство инструментов, к которому относится эндпоинт; сейчас только subject-swap. Поле отсутствует, если модель не относится ни к одному набору, поэтому проверяйте значение, а не наличие ключа. Как и badges, это классификация, которая не влияет на принятие запроса, цену или валидацию. См. Замена персонажа |
async | true: создайте задачу и получите результат опросом или вебхуком. false: ответ возвращается непосредственно в ответе на запрос |
inputSchema | JSON Schema 2020-12 для построения форм и валидации запросов |
examples | Входные данные, проверенные по текущей схеме; в списке выдаются только по запросу |
enabled | Можно ли вызвать запись сейчас. Каталог также перечисляет опубликованные, но ещё не открытые записи и записи, приостановленные платформой; у них возвращается false. Проверяйте вместе с available перед отправкой задачи |
available | Можно ли вызвать модель сейчас. false означает, что вызов завершится ошибкой, например из-за отсутствия текущей цены |
availability | Заявленный этап: planned, available, preview (поля и результаты ещё могут меняться) или maintenance (временная неисправность с ожидаемым восстановлением) |
pricing / startingPrice | Ваши цены; см. Как читать цены |
quantityField | Поле входных данных, значение которого задаёт оплачиваемое количество; для видео обычно duration_seconds. Пустое значение означает одну единицу на вызов. Всегда читайте его из каталога |
taskTimeoutSeconds | Крайний срок выполнения одной задачи на платформе. Это не длительность медиафайла |
maxOutputDurationSeconds | Максимальная длительность результата модели, взятая из maximum её свойства duration_seconds; поле не выдаётся, если максимум не объявлен |
badges | Метки возможностей и лицензирования, например commercial_use или no_watermark. Незнакомые значения игнорируйте |
relatedModels | Подобранные вручную альтернативы или следующие шаги в виде доступных для вызова ID моделей |
version / updatedAt | Отслеживание изменений: сравнивайте значения, чтобы понять, изменилось ли что-либо при следующем чтении |
mature и policyTier — информационные метки. Они никак не участвуют в решении о принятии запроса.
Как читать цены
Каждая запись в pricing — одна ценовая категория:
| Поле | Значение |
|---|---|
unit | per_image, per_second (длительности выходного медиафайла), per_request или per_1k_tokens |
price | Ваша цена за единицу в долларах США, в виде десятичной строки. Она уже учитывает тарифы аккаунта, которые могут различаться между аккаунтами |
variant | Настройки, выбирающие категорию. Пусто при единственной цене; только значение (например, 720p), если категорию выбирает одно поле; пары field=value, соединённые через ; и отсортированные по имени поля (например, generate_audio=false;resolution=480p), если полей несколько |
currency | Всегда USD |
regularPrice, offerLabel, offerPercent, offerEndsAt | Присутствуют только во время действия предложения для клиента: цена до предложения, его название, процент и время окончания |
startingPrice — самая дешёвая категория, а не цена ваших настроек. Поля выбора категории перечислены в inputSchema["x-pricing"].variantFields, если оно есть, а иначе — это поля с пометкой x-ui.affects_price: true (кроме quantityField). Пропущенное поле учитывается со своим значением default из схемы.
- Медиамодели: стоимость равна
price × quantity, где количество — значениеquantityFieldво входных данных (или одна единица, еслиquantityFieldпусто). Некоторые эндпоинты затем добавляют отдельную плату за каждый дополнительный входной файл, чаще всего за каждое референсное изображение. Это полноценная часть счёта, а не мелочь округления: на эндпоинте редактирования, принимающем массив референсов, заполнение массива может увеличить сумму в несколько раз. При этомunit,quantity,variantи цена категорииpriceостаются такими же, как для одного изображения, — поэтому формула выше занижает сумму без видимого признака. Машиночитаемый признак — поле с пометкойx-ui.affects_price: true, которое не являетсяquantityFieldи не входит вx-pricing.variantFields: оно меняет сумму, но не выбирает категорию. Для такого эндпоинта считайтеpricingнижней границей, а сумму берите из расчёта цены. - Текстовые модели (
per_1k_tokens): одно число не описывает всю оплату.price— только тариф выходных токенов за 1000 токенов. Для входных и кешированных входных токенов действуют отдельные, обычно меньшие тарифы; они оплачиваются по измеренному использованию каждого вызова. Не умножайтеpriceна общее число токенов. Цены за миллион входных, кешированных входных и выходных токенов показаны на странице каждой модели и вprice.perMillionTokensпубличного каталога.
Чтобы узнать сумму конкретного запроса нативного API до оплаты, используйте POST /api/v1/jobs/quote. Итоговые списания находятся в записи задачи (cost с settled: true) и в разделе Оплата.
Кеширование каталога
- Ответ каталога можно кешировать до 60 секунд (
Cache-Control: private, max-age=60). Храните его только в приватном кеше отдельного аккаунта: цены зависят от аккаунта, поэтому общий или публичный CDN-кеш использовать нельзя. - После этого не перечитывайте каталог перед каждой задачей. Чтение каталога расходует лимит чтения аккаунта независимо от отправки задач. Запрос каталога перед каждым вызовом всё равно расходует лишнюю квоту чтения; см. Ограничения частоты запросов.
- Обновляйте его по таймеру (например, раз в несколько минут), после ошибки валидации
400и при отклонении отправки запроса к этой модели. - Сохраняйте
versionиupdatedAtиспользованной записи, чтобы понять, изменилось ли что-либо при последующем чтении. - Не распространяйте жёстко зафиксированный снимок схемы и никогда не подменяйте каталог статической страницей, если в актуальном каталоге нет доступной для вызова записи.
Общие правила Schema и Playground
inputSchema использует JSON Schema 2020-12. Стандартные ключевые слова управляют серверной валидацией; объект x-ui на уровне свойства описывает только порядок в Playground, отображение и подсказки об обновлении цены. Клиенты должны игнорировать неизвестные расширения отображения; они никогда не заменяют required, перечисления, границы и условные ветви. См. полный Контракт Spicy Schema.
Создайте собственную таблицу параметров модели
На этом сайте нет статической таблицы параметров для каждой модели. Дело не в нежелании её делать: модели и поля меняются каждую неделю, любая статическая копия устаревает, а чтение устаревшего параметра приводит к 400 или неверной цене. У параметров ровно один источник — inputSchema из каталога. Playground отображает тот же документ, а SDK проверяет данные по нему.
Как части схемы превращаются в таблицу
| В схеме | В вашей таблице параметров |
|---|---|
ключ properties | название параметра. Имена полей едины между моделями: один смысл — одно имя везде |
наличие в required | обязательное или необязательное поле |
anyOf / allOf | условно обязательное поле. anyOf = передайте хотя бы одно из этих полей; в allOf правило if → then = если передаёте A, нужно передать B |
type | тип |
description | подсказка. Это английский текст, который можно показывать без изменений; писать свой не требуется |
enum | допустимые значения. При наличии x-ui.enum_labels используйте его для понятных человеку подписей |
minimum / maximum | допустимый диапазон; x-ui.step — шаг, а x-ui.unit — суффикс (s, px) |
minItems / maxItems | для массива — допустимое число записей. Без этого строка ничего не говорит о лимите, с которым столкнётся читатель |
items | для массива — формат одной записи. Когда items.type равно object, эта подсхема сама является полной схемой объекта: прочитайте её required, additionalProperties, а также type, description, диапазон и default каждого вложенного поля. Показывайте их вложенной таблицей, а не полем свободного текста |
default | значение по умолчанию, применяемое при отсутствии поля |
x-ui.order | ключ сортировки: сначала большие значения. Не сортируйте по порядку ключей в properties — порядок ключей JSON-объекта не входит в контракт |
x-ui.primary | закрепить сверху как основное поле |
x-ui.advanced | можно свернуть в раздел дополнительных настроек |
x-ui.affects_price | отметить влияние на цену; изменение значения требует нового расчёта |
x-ui.widget | элемент управления для редактируемой формы (тринадцать значений, см. Spicy Schema) |
Условные ветви появляются только в корне inputSchema и имеют две формы: anyOf, где каждая ветвь перечисляет только поля required («передайте хотя бы одно из этих полей»), и allOf, где каждая ветвь — if → then («если передаёте A, нужно передать B»). Не выводите условия из семейства модели и не ожидайте oneOf, not или dependentSchemas. Новые эндпоинты следуют единым полям Spicy Schema; старые — своей актуальной схеме.
Получение схемы
const modelId = 'MODEL_ID_FROM_CATALOG'; // the exact `model` value from GET /api/v1/models
const res = await fetch(`https://api.spicyapi.ai/api/v1/models/${modelId}`, {
headers: { Authorization: `Bearer ${process.env.SPICY_API_KEY}` },
});
const { data } = await res.json();
const schema = data.inputSchema;
const rows = Object.entries(schema.properties)
.sort(([, a], [, b]) => (b['x-ui']?.order ?? 0) - (a['x-ui']?.order ?? 0))
.map(([name, prop]) => ({
name,
required: (schema.required ?? []).includes(name),
type: prop.type,
description: prop.description,
values: prop.enum
? prop.enum.map((v) => prop['x-ui']?.enum_labels?.[v] ?? v)
: prop.minimum !== undefined || prop.maximum !== undefined
? [`${prop.minimum ?? ''}–${prop.maximum ?? ''}${prop['x-ui']?.unit ?? ''}`]
: [],
default: prop.default,
affectsPrice: prop['x-ui']?.affects_price === true,
advanced: prop['x-ui']?.advanced === true,
}));Этот пример намеренно ограничен скалярными значениями: для массива он оставляет values пустым, поскольку допустимое содержимое массива задаётся через items, minItems и maxItems, а не через enum или диапазон. Расширьте пример до использования в рабочем продукте: если строка показывает параметр лишь как array, без формата записи и ограничения длины, читатель не сможет по ней подготовить данные.
Запись одной модели также возвращает examples: каждый пример — полный набор входных данных, уже проверенный сервером по текущей схеме. Он подходит и как минимальный рабочий пример документации, и как тело запроса, которое можно отправить без изменений. Это надёжнее ручного составления: ручной пример устаревает при изменении схемы, а examples — нет.
Тот же код охватывает любую модель при замене modelId и не устаревает при добавлении новых моделей. Чтобы построить таблицу для всего каталога, переберите items из GET /api/v1/models?includeSchema=1 и кешируйте результат, как описано в разделе Кеширование каталога.
Более подробные пояснения, чем description
description — одно предложение, рассчитанное на подпись поля формы. Если параметру нужны дополнительные пояснения, публичный каталог содержит вторую заметку для каждого параметра:
curl "https://api.spicyapi.ai/console/v1/catalog/models/MODEL_ID_FROM_CATALOG?locale=en"Запись одной модели возвращает paramNotes — объект с именами параметров в качестве ключей; через locale доступен текст на любом из десяти языков документации. Здесь объясняется то, что не помещается в подпись: относительно чего на самом деле измеряется значение, какая настройка заметно замедляет вызов и какие входные данные проходят схему, но всё равно дают неудовлетворительный результат. Для параметра, указывающего на размещённый вами файл, это обычно единственное место, где объясняется, загружаем ли мы его, когда именно и что будет, если файл окажется непригодным.
Две границы: paramNotes находится в публичном каталоге, не требующем ключа, а не в авторизованном GET /api/v1/models. Поэтому читайте его отдельно и кешируйте рядом со схемой. Кроме того, это поясняющий текст, а не правило: только inputSchema определяет, принимается ли запрос.

