Catalog Generation API

Товары на входе. Готовые тексты на выходе.

Публичная документация содержит только методы обработки каталога и получения результатов. Для интеграции достаточно API-ключа проекта.

ФорматJSON · UTF-8до 500 товаров в задании
АвторизацияBearer API keyключ определяет проект клиента
Результатjob_id + external_idpolling и повторное чтение
ВерсииАвтоматическибез template_version_id
01 · Назначение

Что входит в публичный API

API принимает подготовленные поля товаров из CMS/PIM, формирует несколько текстовых выходов и сохраняет результат по клиентскому external_id. Обход сайтов и методы личного кабинета сюда не входят.

1Передать каталог

Один JSON содержит шаблоны и до 500 товаров.

2Дождаться задания

Получите job_id и опрашивайте status_url с backoff.

3Забрать результат

Читайте страницы задания или актуальную версию по external_id.

02 · Авторизация

API-ключ проекта

Каждый ключ относится к одному проекту. Клиент не передаёт project_id в запросах.

Каждый запрос
Authorization: Bearer <API_KEY>
  • Храните ключ на backend клиента или в secret manager.
  • Не вставляйте ключ в браузерный JavaScript и публичный репозиторий.
  • Для замены выпустите новый ключ, проверьте его и только затем отзовите старый.
03 · Формат

Задание и товарные поля

ПолеТипОбязательностьОписание
channelstringНет · websiteКанал результата: website, ozon, wildberries, telegram или собственная стабильная метка. 2–48 символов.
localestringНет · ru-RUЛокаль обработки и правил, 2–16 символов.
generation_modestrict | flexibleНет · strictstrict применяет один шаблон; flexible стабильно выбирает варианты из блоков [[Купить|Приобрести]]. Оба режима работают без AI.
templatesobject<string,string>ДаОт 1 до 12 шаблонов, каждый до 4000 символов. Публично поддерживаются девять output-полей из списка ниже.
itemsJobItem[]ДаОт 1 до 500 товаров в одном JSON-задании.
ПолеТипОбязательностьОписание
external_idstringДаЕдинственное служебное поле товара: ID в системе клиента, 1–240 символов. Используется для получения результата и версий.
любое имяJSON scalarПо шаблонуВсе бизнес-поля называет клиент: nazvanie, «Название товара», «Цвет из 1С», maker, stoimost и другие. product_name, color и brand не являются обязательными или зарезервированными ключами. В имени нельзя использовать служебные символы шаблона { } | ?.

Поддерживаемые выходы

seo_titlemeta_descriptionh1yandex_market_nameozon_namewildberries_nameog_titleog_descriptiontelegram_message

Произвольные поля и согласование

Имя JSON-ключа не задаёт его грамматическую роль. Движок определяет главное поле и зависимые модификаторы по конструкции шаблона и форме фактических значений. Единственный служебный ключ товара — external_id.

Автоматическое согласование произвольных полей
Купить {{tsvet}} {{nazvanie}}
tsvet = "синий", nazvanie = "женская куртка"
→ Купить синюю женскую куртку

Для неоднозначного шаблона пометьте главное поле: {{nazvanie|head}}, связь: {{tsvet|agree:nazvanie}}, а бренд или модель: {{maker|lock}}. Это описание роли в шаблоне, а не требование переименовывать ключи клиента.

Числа и формы слов

amount выводит число и правильную русскую форму слова, а plural — только выбранную форму. Передайте три варианта в порядке: один, два–четыре, остальные.

Шаблоны цены
{{price|amount:рубль,рубля,рублей}}
{{price}} {{price|plural:рубль,рубля,рублей}}

Примеры: 1 рубль, 22 рубля, 125 рублей, 1,5 рубля. Правило учитывает исключения 11–14 и не вызывает AI.

Гибкий шаблон

В режиме flexible добавьте от 2 до 20 вариантов буквального текста через |. Для одного проекта, шаблона, output-поля и external_id выбор всегда одинаков.

Стабильные варианты
[[Купить|Приобрести]] {{color?}} {{product_name}}
с доставкой по [[РФ|России|России и СНГ]]

Поля {{field}} внутри блока вариантов, пустые, повторяющиеся и вложенные варианты запрещены. Изменение самого шаблона может изменить распределение.

Пример POST /v1/jobs
{
  "channel": "website",
  "locale": "ru-RU",
  "generation_mode": "strict",
  "templates": {
    "seo_title": "Купить {{color}} {{product_name}} {{product_brand}} — {{price|amount:рубль,рубля,рублей}}",
    "meta_description": "{{product_name|title}} {{product_brand}} — характеристики и цена"
  },
  "items": [
    {
      "external_id": "SKU-1001",
      "product_name": "куртка Армани",
      "product_brand": "Армани",
      "color": "синий",
      "price": 12990
    }
  ]
}
04 · До запуска

Проверка шаблона и объёма

GET/v1/generation/capabilities

Доступные режимы

API-ключ проекта

Возвращает доступность публичных режимов strict/flexible для текущего тарифа.

POST/v1/templates/validate

Проверить шаблоны

API-ключ проекта

Компилирует шаблоны и возвращает ошибки синтаксиса без запуска товаров.

POST/v1/templates/preview

Проверить один товар

API-ключ проекта

Формирует все outputs для одного JobItem и возвращает warnings, trace, channel_quality, hash применённого словаря и variation_combinations — теоретическое число вариантов каждого output. Необязательный channel выбирает контекст проверки; значение по умолчанию — website.

POST/v1/jobs/estimate

Оценить задание

API-ключ проекта

Возвращает объём обработки до постановки задания в очередь. Для strict и flexible AI-вызовы, токены и денежный резерв всегда равны нулю.

05 · Обработка

Создание и состояние задания

POST/v1/jobs

Создать асинхронное задание

API-ключ проекта
ПолеТипОбязательностьОписание
job_idUUID stringВсегдаУникальный ID принятого задания.
statusstringВсегдаНачальный статус queued или planning.
accepted_itemsintegerВсегдаКоличество принятых товаров.
status_urlstringВсегдаURL для polling состояния задания.
results_urlstringВсегдаURL постраничной выдачи результатов.
replayedbooleanИногдаtrue, если тот же Idempotency-Key и идентичное тело запроса уже создавали это задание.
202 Accepted
{
  "job_id": "4c64b96d-c3eb-4af4-8488-2b7be3a7df41",
  "status": "queued",
  "accepted_items": 1,
  "status_url": "/v1/jobs/4c64b96d-c3eb-4af4-8488-2b7be3a7df41",
  "results_url": "/v1/jobs/4c64b96d-c3eb-4af4-8488-2b7be3a7df41/results",
  "replayed": false
}
POST/v1/generate

Создать задание с коротким ожиданием

API-ключ проекта

Тело совпадает с POST /v1/jobs. Если первый результат готов примерно за 1,5 секунды, API вернёт 200; иначе вернёт обычный 202 и интеграция продолжит polling.

GET/v1/jobs/{job_id}

Получить статус и usage

API-ключ проекта

Статусы: planning, planning_ai, queued, processing, completed, completed_with_warnings, failed, cancelled и review. Продолжайте polling до финального статуса. review означает, что неизвестный исход AI-вызова проверяет администратор; повторять такой job автоматически нельзя. Поля processing_attempts и max_processing_attempts показывают серверный бюджет безопасных попыток.

POST/v1/jobs/{job_id}/cancel

Отменить ещё не начатое задание

API-ключ проекта

Отмена разрешена только до необратимого начала обработки или AI-вызова.

POST/v1/jobs/{job_id}/retry-failed

Повторить ошибочные товары

API-ключ проекта

Создаёт новое задание только для строк с ошибками; успешные результаты не пересчитываются.

1POST jobs202 + job_id
2GET statuspolling
3completedфинальный статус
4GET resultsвсе страницы
5external_idчитать позже
06 · Каталог

Результаты и версии товаров

GET/v1/jobs/{job_id}/results?cursor=0&limit=100

Получить страницу результатов

API-ключ проекта

Возвращает items, next_cursor и completed. Запрашивайте страницы, пока next_cursor не станет null.

GET/v1/products/{external_id}

Получить актуальный результат товара

API-ключ проекта

Возвращает последние успешные результаты в пределах проекта ключа. Необязательный channel сужает выборку. Поле duplicates пересчитывается по текущему каталогу проекта и не раскрывает совпадения из других проектов.

Предупреждение о дубле
{
  "warnings": ["duplicate_output"],
  "duplicates": [{
    "code": "duplicate_output",
    "severity": "warning",
    "field": "seo_title",
    "match_type": "normalized_duplicate",
    "duplicate_count": 2,
    "duplicate_external_ids": ["SKU-17", "SKU-42"]
  }]
}
POST/v1/products/lookup

Получить до 500 товаров

API-ключ проекта
Request
{
  "external_ids": ["SKU-1001", "SKU-1002"],
  "channel": "website"
}
GET/v1/products/{external_id}/history

Получить историю генераций

API-ключ проекта

Возвращает current, history и history_pending. Кратковременно пустая история не мешает чтению актуального результата.

GET/v1/products/{external_id}/versions/{version}

Получить конкретную версию

API-ключ проекта

Версия увеличивается после каждой успешной повторной генерации товара.

07 · Ошибки

Что должна обрабатывать интеграция

Любая ошибка возвращает один JSON-конверт. Для логики используйте HTTP-статус и error.code; error.message подходит для журнала или интерфейса, error.details содержит дополнительные поля. Поле detail временно сохранено для обратной совместимости.

Единый формат ошибки
{
  "detail": {
    "code": "idempotency_payload_mismatch",
    "message": "Idempotency-Key already belongs to another payload"
  },
  "error": {
    "code": "idempotency_payload_mismatch",
    "message": "Idempotency-Key already belongs to another payload",
    "request_id": "46cc6807-75d7-4218-8950-0a70c1ecddfb",
    "details": null
  }
}
HTTPКод / формаПричинаДействие
400invalid requestНекорректный параметр или формат запроса каталога.Исправить запрос.
401Valid service API key is requiredКлюч отсутствует, неверен или отозван.Проверить Authorization и активность ключа.
403generation_mode_not_in_planРежим не входит в текущий тариф.Изменить режим или тариф.
404Job/Product not foundОбъект отсутствует в проекте, определённом API-ключом.Проверить job_id/external_id и ключ проекта.
409idempotency_payload_mismatchIdempotency-Key уже использован с другим телом.Для нового тела создать новый ключ.
413request_body_too_largeJSON превышает 2 MiB.Разделить каталог на задания.
422validationОшибка полей, режима или синтаксиса шаблона.Исправить запрос по detail; автоматически не повторять.
422blocked_contentШаблон, инструкция, входное поле или готовый текст нарушает активную политику контента.Исправить указанное поле по безопасным metadata; запрещённый фрагмент API намеренно не повторяет.
429rate limit / operation_quota_exceededТранспортный лимит или месячная квота.Для rate limit учесть Retry-After; для квоты изменить тариф.
503history temporarily unavailableИстория версий временно недоступна.Повторить GET с ограниченным backoff.
08 · Ограничения

Основные лимиты запроса

2 MiBJSON body
500товаров в job
500external_id в lookup
9output-полей
4000символов шаблона
2000символов инструкций
240символов external_id
8–160Idempotency-Key

Runtime-пределы AI и доступные режимы всегда читайте через GET /v1/generation/capabilities. Тарифная квота может быть ниже системного hard cap.

09 · Примеры

Быстрый старт на популярных языках

Все примеры предназначены для backend-кода и читают секрет из переменной окружения.

cURL

bash
export ASM_API_URL="https://api.example.com"
# ASM_API_KEY задайте через secret manager или защищённую переменную окружения.

curl --request POST "$ASM_API_URL/v1/jobs" \
  --header "Authorization: Bearer $ASM_API_KEY" \
  --header "Idempotency-Key: catalog-2026-08-06-0001" \
  --header "Content-Type: application/json" \
  --data '{
  "channel": "website",
  "locale": "ru-RU",
  "generation_mode": "strict",
  "templates": {
    "seo_title": "Купить {{color}} {{product_name}} {{product_brand}} — {{price|amount:рубль,рубля,рублей}}",
    "meta_description": "{{product_name|title}} {{product_brand}} — характеристики и цена"
  },
  "items": [
    {
      "external_id": "SKU-1001",
      "product_name": "куртка Армани",
      "product_brand": "Армани",
      "color": "синий",
      "price": 12990
    }
  ]
}'

PHP 8

php
<?php
$baseUrl = getenv('ASM_API_URL') ?: 'https://api.example.com';
$apiKey = getenv('ASM_API_KEY');
if (!$apiKey) {
    throw new RuntimeException('ASM_API_KEY is not configured');
}

$payload = [
    'channel' => 'website',
    'locale' => 'ru-RU',
    'generation_mode' => 'strict',
    'templates' => [
        'seo_title' => 'Купить {{color}} {{product_name}} {{product_brand}} — {{price|amount:рубль,рубля,рублей}}',
        'meta_description' => '{{product_name|title}} {{product_brand}} — характеристики и цена',
    ],
    'items' => [[
        'external_id' => 'SKU-1001',
        'product_name' => 'куртка Армани',
        'product_brand' => 'Армани',
        'color' => 'синий',
        'price' => 12990,
    ]],
];

$curl = curl_init($baseUrl . '/v1/jobs');
curl_setopt_array($curl, [
    CURLOPT_POST => true,
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_HTTPHEADER => [
        'Authorization: Bearer ' . $apiKey,
        'Idempotency-Key: catalog-' . bin2hex(random_bytes(12)),
        'Content-Type: application/json',
    ],
    CURLOPT_POSTFIELDS => json_encode($payload, JSON_UNESCAPED_UNICODE),
]);
$body = curl_exec($curl);
$status = curl_getinfo($curl, CURLINFO_RESPONSE_CODE);
if ($body === false || $status >= 400) {
    throw new RuntimeException('API request failed: ' . curl_error($curl) . ' ' . $body);
}
print_r(json_decode($body, true, flags: JSON_THROW_ON_ERROR));

Python 3

python
import json
import os
import secrets
from urllib.request import Request, urlopen

api_url = os.environ.get("ASM_API_URL", "https://api.example.com")
api_key = os.environ["ASM_API_KEY"]
payload = {
    "channel": "website",
    "locale": "ru-RU",
    "generation_mode": "strict",
    "templates": {
        "seo_title": "Купить {{color}} {{product_name}} {{product_brand}} — {{price|amount:рубль,рубля,рублей}}",
        "meta_description": "{{product_name|title}} {{product_brand}} — характеристики и цена"
    },
    "items": [
        {
            "external_id": "SKU-1001",
            "product_name": "куртка Армани",
            "product_brand": "Армани",
            "color": "синий",
            "price": 12990
        }
    ]
}

request = Request(
    api_url + "/v1/jobs",
    data=json.dumps(payload, ensure_ascii=False).encode("utf-8"),
    method="POST",
    headers={
        "Authorization": "Bearer " + api_key,
        "Idempotency-Key": "catalog-" + secrets.token_hex(12),
        "Content-Type": "application/json",
    },
)
with urlopen(request, timeout=30) as response:
    accepted = json.load(response)
    print(accepted["job_id"], accepted["status_url"])

Go

go
package main

import (
    "bytes"
    "crypto/rand"
    "encoding/hex"
    "encoding/json"
    "fmt"
    "net/http"
    "os"
)

func main() {
    baseURL := os.Getenv("ASM_API_URL")
    if baseURL == "" { baseURL = "https://api.example.com" }
    apiKey := os.Getenv("ASM_API_KEY")
    if apiKey == "" { panic("ASM_API_KEY is not configured") }

    payload := []byte("{\n  \"channel\": \"website\",\n  \"locale\": \"ru-RU\",\n  \"generation_mode\": \"strict\",\n  \"templates\": {\n    \"seo_title\": \"Купить {{color}} {{product_name}} {{product_brand}} — {{price|amount:рубль,рубля,рублей}}\",\n    \"meta_description\": \"{{product_name|title}} {{product_brand}} — характеристики и цена\"\n  },\n  \"items\": [\n    {\n      \"external_id\": \"SKU-1001\",\n      \"product_name\": \"куртка Армани\",\n      \"product_brand\": \"Армани\",\n      \"color\": \"синий\",\n      \"price\": 12990\n    }\n  ]\n}")
    nonce := make([]byte, 12)
    if _, err := rand.Read(nonce); err != nil { panic(err) }

    req, err := http.NewRequest(http.MethodPost, baseURL+"/v1/jobs", bytes.NewReader(payload))
    if err != nil { panic(err) }
    req.Header.Set("Authorization", "Bearer "+apiKey)
    req.Header.Set("Idempotency-Key", "catalog-"+hex.EncodeToString(nonce))
    req.Header.Set("Content-Type", "application/json")

    response, err := http.DefaultClient.Do(req)
    if err != nil { panic(err) }
    defer response.Body.Close()
    if response.StatusCode >= 400 { panic(response.Status) }

    var accepted map[string]any
    if err := json.NewDecoder(response.Body).Decode(&accepted); err != nil { panic(err) }
    fmt.Println(accepted["job_id"], accepted["status_url"])
}

JavaScript / TypeScript

typescript
// Выполняйте этот код на сервере (Node.js/SSR), не раскрывайте API key в браузере.
import { randomUUID } from "node:crypto";

const apiUrl = process.env.ASM_API_URL ?? "https://api.example.com";
const apiKey = process.env.ASM_API_KEY;
if (!apiKey) throw new Error("ASM_API_KEY is not configured");

const payload = {
  "channel": "website",
  "locale": "ru-RU",
  "generation_mode": "strict",
  "templates": {
    "seo_title": "Купить {{color}} {{product_name}} {{product_brand}} — {{price|amount:рубль,рубля,рублей}}",
    "meta_description": "{{product_name|title}} {{product_brand}} — характеристики и цена"
  },
  "items": [
    {
      "external_id": "SKU-1001",
      "product_name": "куртка Армани",
      "product_brand": "Армани",
      "color": "синий",
      "price": 12990
    }
  ]
};
const response = await fetch(apiUrl + "/v1/jobs", {
  method: "POST",
  headers: {
    Authorization: "Bearer " + apiKey,
    "Idempotency-Key": "catalog-" + randomUUID(),
    "Content-Type": "application/json",
  },
  body: JSON.stringify(payload),
});
if (!response.ok) throw new Error(response.status + " " + await response.text());
const accepted = await response.json();
console.log(accepted.job_id, accepted.status_url);

1С:Предприятие

1c
// Получите ключ из защищённого хранилища вашей конфигурации, а не из кода.
APIКлюч = ПолучитьAPIКлючИзЗащищенногоХранилища();
Если ПустаяСтрока(APIКлюч) Тогда
    ВызватьИсключение "API-ключ не настроен";
КонецЕсли;

Шаблоны = Новый Структура;
Шаблоны.Вставить("seo_title", "Купить {{color}} {{product_name}} {{product_brand}}");
Шаблоны.Вставить("meta_description", "{{product_name|title}} {{product_brand}} — характеристики и цена");

Товар = Новый Структура;
Товар.Вставить("external_id", "SKU-1001");
Товар.Вставить("product_name", "куртка Армани");
Товар.Вставить("product_brand", "Армани");
Товар.Вставить("color", "синий");
Товары = Новый Массив;
Товары.Добавить(Товар);

Данные = Новый Структура;
Данные.Вставить("channel", "website");
Данные.Вставить("locale", "ru-RU");
Данные.Вставить("generation_mode", "strict");
Данные.Вставить("templates", Шаблоны);
Данные.Вставить("items", Товары);

Запись = Новый ЗаписьJSON;
Запись.УстановитьСтроку();
ЗаписатьJSON(Запись, Данные);
ТелоJSON = Запись.Закрыть();

SSL = Новый ЗащищенноеСоединениеOpenSSL;
Соединение = Новый HTTPСоединение("api.example.com", 443,,,, SSL);
Запрос = Новый HTTPЗапрос("/v1/jobs");
Запрос.Заголовки.Вставить("Authorization", "Bearer " + APIКлюч);
Запрос.Заголовки.Вставить("Idempotency-Key", "catalog-" + Строка(Новый УникальныйИдентификатор));
Запрос.Заголовки.Вставить("Content-Type", "application/json; charset=utf-8");
Запрос.УстановитьТелоИзСтроки(ТелоJSON, КодировкаТекста.UTF8);

Ответ = Соединение.ВызватьHTTPМетод("POST", Запрос);
Если Ответ.КодСостояния >= 400 Тогда
    ВызватьИсключение Ответ.ПолучитьТелоКакСтроку();
КонецЕсли;
Сообщить(Ответ.ПолучитьТелоКакСтроку());

Java 11+

java
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.util.UUID;

public final class Main {
    public static void main(String[] args) throws Exception {
        String baseUrl = System.getenv().getOrDefault("ASM_API_URL", "https://api.example.com");
        String apiKey = System.getenv("ASM_API_KEY");
        if (apiKey == null || apiKey.isBlank()) {
            throw new IllegalStateException("ASM_API_KEY is not configured");
        }

        String payload = "{\n  \"channel\": \"website\",\n  \"locale\": \"ru-RU\",\n  \"generation_mode\": \"strict\",\n  \"templates\": {\n    \"seo_title\": \"Купить {{color}} {{product_name}} {{product_brand}} — {{price|amount:рубль,рубля,рублей}}\",\n    \"meta_description\": \"{{product_name|title}} {{product_brand}} — характеристики и цена\"\n  },\n  \"items\": [\n    {\n      \"external_id\": \"SKU-1001\",\n      \"product_name\": \"куртка Армани\",\n      \"product_brand\": \"Армани\",\n      \"color\": \"синий\",\n      \"price\": 12990\n    }\n  ]\n}";
        HttpRequest request = HttpRequest.newBuilder(URI.create(baseUrl + "/v1/jobs"))
            .header("Authorization", "Bearer " + apiKey)
            .header("Idempotency-Key", "catalog-" + UUID.randomUUID())
            .header("Content-Type", "application/json")
            .POST(HttpRequest.BodyPublishers.ofString(payload))
            .build();
        HttpResponse<String> response = HttpClient.newHttpClient()
            .send(request, HttpResponse.BodyHandlers.ofString());
        if (response.statusCode() >= 400) throw new RuntimeException(response.body());
        System.out.println(response.body());
    }
}

C# / .NET

csharp
using System;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Text;

var baseUrl = Environment.GetEnvironmentVariable("ASM_API_URL") ?? "https://api.example.com";
var apiKey = Environment.GetEnvironmentVariable("ASM_API_KEY")
    ?? throw new InvalidOperationException("ASM_API_KEY is not configured");
var payload = "{\n  \"channel\": \"website\",\n  \"locale\": \"ru-RU\",\n  \"generation_mode\": \"strict\",\n  \"templates\": {\n    \"seo_title\": \"Купить {{color}} {{product_name}} {{product_brand}} — {{price|amount:рубль,рубля,рублей}}\",\n    \"meta_description\": \"{{product_name|title}} {{product_brand}} — характеристики и цена\"\n  },\n  \"items\": [\n    {\n      \"external_id\": \"SKU-1001\",\n      \"product_name\": \"куртка Армани\",\n      \"product_brand\": \"Армани\",\n      \"color\": \"синий\",\n      \"price\": 12990\n    }\n  ]\n}";

using var client = new HttpClient();
using var request = new HttpRequestMessage(HttpMethod.Post, baseUrl + "/v1/jobs");
request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", apiKey);
request.Headers.Add("Idempotency-Key", "catalog-" + Guid.NewGuid());
request.Content = new StringContent(payload, Encoding.UTF8, "application/json");

using var response = await client.SendAsync(request);
var body = await response.Content.ReadAsStringAsync();
if (!response.IsSuccessStatusCode) throw new HttpRequestException(body);
Console.WriteLine(body);
Готовы проверить интеграцию?Сначала validate и preview, затем estimate и job.