Что входит в публичный API
API принимает подготовленные поля товаров из CMS/PIM, формирует несколько текстовых выходов и сохраняет результат по клиентскому external_id. Обход сайтов и методы личного кабинета сюда не входят.
Один JSON содержит шаблоны и до 500 товаров.
Получите job_id и опрашивайте status_url с backoff.
Читайте страницы задания или актуальную версию по external_id.
API-ключ проекта
Каждый ключ относится к одному проекту. Клиент не передаёт project_id в запросах.
Authorization: Bearer <API_KEY>- Храните ключ на backend клиента или в secret manager.
- Не вставляйте ключ в браузерный JavaScript и публичный репозиторий.
- Для замены выпустите новый ключ, проверьте его и только затем отзовите старый.
Задание и товарные поля
| Поле | Тип | Обязательность | Описание |
|---|---|---|---|
channel | string | Нет · website | Канал результата: website, ozon, wildberries, telegram или собственная стабильная метка. 2–48 символов. |
locale | string | Нет · ru-RU | Локаль обработки и правил, 2–16 символов. |
generation_mode | strict | flexible | Нет · strict | strict применяет один шаблон; flexible стабильно выбирает варианты из блоков [[Купить|Приобрести]]. Оба режима работают без AI. |
templates | object<string,string> | Да | От 1 до 12 шаблонов, каждый до 4000 символов. Публично поддерживаются девять output-полей из списка ниже. |
items | JobItem[] | Да | От 1 до 500 товаров в одном JSON-задании. |
| Поле | Тип | Обязательность | Описание |
|---|---|---|---|
external_id | string | Да | Единственное служебное поле товара: 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}} внутри блока вариантов, пустые, повторяющиеся и вложенные варианты запрещены. Изменение самого шаблона может изменить распределение.
{
"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
}
]
}Проверка шаблона и объёма
/v1/generation/capabilitiesДоступные режимы
API-ключ проектаВозвращает доступность публичных режимов strict/flexible для текущего тарифа.
/v1/templates/validateПроверить шаблоны
API-ключ проектаКомпилирует шаблоны и возвращает ошибки синтаксиса без запуска товаров.
/v1/templates/previewПроверить один товар
API-ключ проектаФормирует все outputs для одного JobItem и возвращает warnings, trace, channel_quality, hash применённого словаря и variation_combinations — теоретическое число вариантов каждого output. Необязательный channel выбирает контекст проверки; значение по умолчанию — website.
/v1/jobs/estimateОценить задание
API-ключ проектаВозвращает объём обработки до постановки задания в очередь. Для strict и flexible AI-вызовы, токены и денежный резерв всегда равны нулю.
Создание и состояние задания
/v1/jobsСоздать асинхронное задание
API-ключ проекта| Поле | Тип | Обязательность | Описание |
|---|---|---|---|
job_id | UUID string | Всегда | Уникальный ID принятого задания. |
status | string | Всегда | Начальный статус queued или planning. |
accepted_items | integer | Всегда | Количество принятых товаров. |
status_url | string | Всегда | URL для polling состояния задания. |
results_url | string | Всегда | URL постраничной выдачи результатов. |
replayed | boolean | Иногда | true, если тот же Idempotency-Key и идентичное тело запроса уже создавали это задание. |
{
"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
}/v1/generateСоздать задание с коротким ожиданием
API-ключ проектаТело совпадает с POST /v1/jobs. Если первый результат готов примерно за 1,5 секунды, API вернёт 200; иначе вернёт обычный 202 и интеграция продолжит polling.
/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 показывают серверный бюджет безопасных попыток.
/v1/jobs/{job_id}/cancelОтменить ещё не начатое задание
API-ключ проектаОтмена разрешена только до необратимого начала обработки или AI-вызова.
/v1/jobs/{job_id}/retry-failedПовторить ошибочные товары
API-ключ проектаСоздаёт новое задание только для строк с ошибками; успешные результаты не пересчитываются.
Результаты и версии товаров
/v1/jobs/{job_id}/results?cursor=0&limit=100Получить страницу результатов
API-ключ проектаВозвращает items, next_cursor и completed. Запрашивайте страницы, пока next_cursor не станет null.
/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"]
}]
}/v1/products/lookupПолучить до 500 товаров
API-ключ проекта{
"external_ids": ["SKU-1001", "SKU-1002"],
"channel": "website"
}/v1/products/{external_id}/historyПолучить историю генераций
API-ключ проектаВозвращает current, history и history_pending. Кратковременно пустая история не мешает чтению актуального результата.
/v1/products/{external_id}/versions/{version}Получить конкретную версию
API-ключ проектаВерсия увеличивается после каждой успешной повторной генерации товара.
Что должна обрабатывать интеграция
Любая ошибка возвращает один 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 | Код / форма | Причина | Действие |
|---|---|---|---|
400 | invalid request | Некорректный параметр или формат запроса каталога. | Исправить запрос. |
401 | Valid service API key is required | Ключ отсутствует, неверен или отозван. | Проверить Authorization и активность ключа. |
403 | generation_mode_not_in_plan | Режим не входит в текущий тариф. | Изменить режим или тариф. |
404 | Job/Product not found | Объект отсутствует в проекте, определённом API-ключом. | Проверить job_id/external_id и ключ проекта. |
409 | idempotency_payload_mismatch | Idempotency-Key уже использован с другим телом. | Для нового тела создать новый ключ. |
413 | request_body_too_large | JSON превышает 2 MiB. | Разделить каталог на задания. |
422 | validation | Ошибка полей, режима или синтаксиса шаблона. | Исправить запрос по detail; автоматически не повторять. |
422 | blocked_content | Шаблон, инструкция, входное поле или готовый текст нарушает активную политику контента. | Исправить указанное поле по безопасным metadata; запрещённый фрагмент API намеренно не повторяет. |
429 | rate limit / operation_quota_exceeded | Транспортный лимит или месячная квота. | Для rate limit учесть Retry-After; для квоты изменить тариф. |
503 | history temporarily unavailable | История версий временно недоступна. | Повторить GET с ограниченным backoff. |
Основные лимиты запроса
Runtime-пределы AI и доступные режимы всегда читайте через GET /v1/generation/capabilities. Тарифная квота может быть ниже системного hard cap.
Быстрый старт на популярных языках
Все примеры предназначены для backend-кода и читают секрет из переменной окружения.
cURL
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
$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
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
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
// Выполняйте этот код на сервере (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С:Предприятие
// Получите ключ из защищённого хранилища вашей конфигурации, а не из кода.
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+
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
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);