Документация API

Справочник на одной странице: ключи, методы, ошибки, вебхуки, фид Авито и сцены.

Обновлено: 24 сентября 2026 · Описание для генераторов клиентов — openapi.yaml

  1. Быстрый старт
  2. Ключи и авторизация
  3. Тестовый ключ
  4. Формат ответов
  5. Методы
  6. Баланс
  7. POST /jobs
  8. Объект задачи
  9. Идемпотентность
  10. Опрос статуса
  11. Вебхуки и подпись
  12. Фид Авито
  13. Ссылки на кадры
  14. Ошибки
  15. Лимиты
  16. Цены
  17. Сцены
  18. Файлы для машин

Быстрый старт

  1. Выпустите тестовый ключ в студии: «Настройки» → «API для интеграции». Он бесплатный и сразу отвечает тестовым кадром.
  2. Создайте задачу:
curl -X POST https://banan.wtf/api/integration/v1/jobs \
  -H "Authorization: Bearer $BANAN_API_KEY" \
  -H "Idempotency-Key: demo-0001" \
  -H "Content-Type: application/json" \
  -d '{ "imageUrl": "https://cdn.example.com/items/123.jpg", "sceneId": "white_main" }'
  1. Заберите результат: GET /jobs/{id} или вебхук. Готовые кадры — в job.frames[].url.
  2. Для боевых кадров выпустите ключ bnk_live_… и пополните баланс API. Цены — на странице цен.

Ключи и авторизация

Каждый запрос несёт заголовок:

Authorization: Bearer bnk_live_…   # боевой ключ
Authorization: Bearer bnk_test_…   # тестовый ключ

Тестовый ключ

Тестовый кадр banan.wtf: «Интеграция работает» и Idempotency-Key запроса
Кадр тестового ключа

Формат ответов

Базовый адрес: https://banan.wtf/api/integration/v1. Тела запросов и ответов — JSON в UTF-8.

// успех
{ "success": true, "job": { … } }

// ошибка: code всегда на верхнем уровне
{ "success": false, "code": "scene_unknown", "message": "Неизвестная сцена." }

Коды ошибок — в таблице ниже. Текст message — для человека, решения принимайте по code.

Методы

МетодПутьЧто делает
GET/pricingцены и правила списания, без ключа
GET/meаккаунт, режим ключа, баланс, цены
GET/balanceостаток баланса и сколько кадров на него выйдет
GET/scenesсцены каталога; группы вашей аудитории первыми
POST/jobsсоздать задачу: 202, повтор с тем же ключом идемпотентности — 200
GET/jobsсписок задач режима ключа
GET/jobs/{id}статус задачи и кадры
GET/feeds/avito.xml?token=…фид Авито Автозагрузки, токен только для чтения

GET /pricing

Цены за кадр, пороги пополнения, бонусы, условия тестового ключа и правила списания. Ключ не нужен. Содержимое совпадает с pricing.json.

curl https://banan.wtf/api/integration/v1/pricing

GET /me

Кто вы для API: аккаунт и аудитория, режим ключа (live или test), остаток баланса API, сколько тестовых задач уже было сегодня, текущие цены. Удобно для проверки ключа при подключении.

curl https://banan.wtf/api/integration/v1/me -H "Authorization: Bearer $BANAN_API_KEY"

{
  "success": true,
  "account": { "userId": 123456, "audience": { "id": "resale", "label": "…" } },
  "key": { "id": "66f0a1…", "prefix": "bnk_test_a1b2c3", "label": "Учётная программа", "mode": "test" },
  "balance": { "rub": 0, "kopecks": 0 },
  "framesLeft": { "standard": 0, "high": 0 },
  "test": { "static": true, "jobsPerDay": 200, "usedToday": 3 },
  "pricing": { "currency": "RUB", "defaultTier": "standard", "tiers": [ … ] },
  "topupUrl": "https://banan.wtf/profile/settings?section=api"
}

GET /balance

Остаток баланса API и сколько кадров на него выйдет в каждом качестве. Вызывайте перед ночной пачкой и по расписанию, чтобы заранее узнать о низком остатке.

curl https://banan.wtf/api/integration/v1/balance -H "Authorization: Bearer $BANAN_API_KEY"

{
  "success": true,
  "balance": { "rub": 5000, "kopecks": 500000 },
  "framesLeft": { "standard": 263, "high": 102 },
  "topupUrl": "https://banan.wtf/profile/settings?section=api"
}

Остаток после каждой задачи приходит и в ответе POST /jobs, поле balance.

GET /scenes

Группы сцен каталога с полями inputs, вариантами и форматом. Группы аудитории вашего аккаунта идут первыми и помечены "audience": true. Внутренние инструкции модели наружу не отдаются. Таблица всех сцен — ниже.

{
  "success": true,
  "audience": "resale",
  "groups": [
    {
      "id": "resale",
      "title": "Ресейл и ломбарды",
      "audience": true,
      "scenes": [
        {
          "id": "resale_condition_card",
          "groupId": "resale",
          "title": "Карточка состояния",
          "subtitle": "Проба, вес, состояние, комплектность подписями",
          "aspectRatio": "4:3",
          "fidelity": null,
          "inputs": [{ "id": "facts", "label": "Что написать на карточке", "hint": "…", "maxLength": 240, "required": true }],
          "variants": []
        }
      ]
    }
  ]
}

POST /jobs

Создаёт задачу: одно исходное фото → один кадр. Ответ 202 с задачей в статусе queued, без кадров, и остатком баланса после резерва. У тестового ключа задача сразу completed.

curl -X POST https://banan.wtf/api/integration/v1/jobs \
  -H "Authorization: Bearer $BANAN_API_KEY" \
  -H "Idempotency-Key: INV-2026-000123-hallmark" \
  -H "Content-Type: application/json" \
  -d '{
    "imageUrl": "https://cdn.example.com/items/123.jpg",
    "sceneId": "resale_condition_card",
    "inputs": { "facts": "Золото 585, 3,2 г, состояние хорошее" },
    "quality": "standard",
    "fidelity": "preserve_condition",
    "aspectRatio": "4:3",
    "branch": "Ленина, 12",
    "externalId": "INV-2026-000123",
    "webhookUrl": "https://erp.example.com/hooks/banan",
    "listing": {
      "Title": "Кольцо 585, 3,2 г",
      "Price": 12000,
      "Category": "Часы и украшения",
      "Description": "Золото 585, гранат. Состояние хорошее."
    }
  }'
ПолеТипЧто
imageUrlstringпубличная ссылка на исходное фото: до 25 МБ, JPEG, PNG, WebP или HEIC. Скачивается с защитой от SSRF, общий срок 20 с.
imageBase64stringвместо imageUrl: data URL или чистый base64. Тело запроса — до 20 МБ.
sceneIdstringid сцены из каталога. Нужен sceneId или prompt.
promptstringсвоё описание задачи, до 2000 символов
variantIdstringвариант сцены, например hand у resale_scale
inputsobjectполя сцены: { "facts": "…" }. Какие поля есть у сцены — в таблице.
qualitystringstandard (по умолчанию) или high. Модель и цена заданы тарифной сеткой API и не зависят от настроек студии.
fidelitystringpreserve_condition — вещь как есть, износ и клеймо сохраняются; enhance_allowed — допустима косметическая чистка. Без поля — умолчание сцены, затем аудитории.
aspectRatiostringформат кадра: 3:4, 4:3, 1:1 и другие. Без поля — формат сцены.
branchstringметка точки, до 40 символов. Попадает в отчёт по филиалам.
externalIdstringваш номер позиции, до 120 символов. Это метка, а не ключ идемпотентности: несколько задач с одним externalId — серия кадров одной вещи, в фиде это одно объявление.
webhookUrlstringкуда прислать вебхук. Принимаются только публичные адреса.
listingobjectполя объявления для фида Авито. Ключ — имя тега Avito XML (^[A-Z][A-Za-z0-9]{1,40}$, кроме Id, Images, Image), значение — строка до 4000 символов. До 40 полей.

Порядок проверок. Ключ → тело запроса → идемпотентность → деньги или дневной лимит тестового ключа → скачивание фото → резерв и постановка в очередь. Ошибка в теле не тратит ни денег, ни времени на скачивание.

HTTP/1.1 202 Accepted

{
  "success": true,
  "job": {
    "id": "66f2c1…",
    "status": "queued",
    "mode": "live",
    "quality": "standard",
    "priceRub": 19,
    "refunded": false,
    "scene": { "id": "resale_condition_card", "variantId": null },
    "fidelity": "preserve_condition",
    "branch": "Ленина, 12",
    "externalId": "INV-2026-000123",
    "createdAt": "2026-09-24T21:04:03.000Z",
    "completedAt": null,
    "error": null,
    "frames": []
  },
  "balance": { "rub": 1462, "kopecks": 146200 }
}

GET /jobs

Задачи API этого аккаунта в режиме ключа, новые первыми. Параметры, все необязательные:

Ответ — { "success": true, "jobs": [ … ], "nextCursor": "…" }. Когда задач больше нет, nextCursor равен null.

curl "https://banan.wtf/api/integration/v1/jobs?externalId=INV-2026-000123&status=completed" \
  -H "Authorization: Bearer $BANAN_API_KEY"

GET /jobs/{id}

Статус задачи и кадры. Видны только задачи API этого аккаунта в режиме ключа: работы из студии через API не читаются. Чужой или несуществующий id — 404 job_not_found.

Объект задачи

Один и тот же объект отдают POST /jobs, GET /jobs, GET /jobs/{id} и вебхук.

ПолеЧто
idid задачи
statusqueued → processing → completed или failed
modelive или test — режим ключа, которым создана задача
quality, priceRubуровень качества и цена кадра в рублях
refundedtrue, если задача упала и цена вернулась на баланс
scene{ id, variantId } или null для задачи по prompt
fidelity, branch, externalIdкак в запросе
createdAt, completedAtвремя в ISO 8601
errorтекст причины у failed, иначе null
framesу completed: [{ index, url }], постоянные ссылки на кадры
{
  "id": "66f2c1…",
  "status": "completed",
  "mode": "live",
  "quality": "standard",
  "priceRub": 19,
  "refunded": false,
  "scene": { "id": "resale_condition_card", "variantId": null },
  "fidelity": "preserve_condition",
  "branch": "Ленина, 12",
  "externalId": "INV-2026-000123",
  "createdAt": "2026-09-24T21:04:03.000Z",
  "completedAt": "2026-09-24T21:05:11.000Z",
  "error": null,
  "frames": [{ "index": 0, "url": "https://banan.wtf/f/Qm9v….jpg" }]
}

Идемпотентность

Сеть оборвалась, ERP повторила запрос — второй задачи и второго списания не будет, если передан заголовок Idempotency-Key.

Опрос статуса

Если вебхук не подходит, опрашивайте GET /jobs/{id} раз в несколько секунд. Для пачки удобнее GET /jobs?status=completed&since=… — один запрос вместо сотни.

const API = 'https://banan.wtf/api/integration/v1';
const headers = { Authorization: `Bearer ${process.env.BANAN_API_KEY}` };
const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));

async function waitForJob(id) {
  for (let attempt = 0; attempt < 120; attempt += 1) {
    const res = await fetch(`${API}/jobs/${id}`, { headers });
    if (res.status === 429) {
      await sleep(Number(res.headers.get('Retry-After') || 5) * 1000);
      continue;
    }
    const body = await res.json();
    if (!body.success) throw new Error(body.code);
    if (body.job.status === 'completed' || body.job.status === 'failed') return body.job;
    await sleep(5000);
  }
  throw new Error('job_timeout');
}

Вебхуки

Когда задача завершилась или упала, на её webhookUrl уходит POST:

POST /hooks/banan HTTP/1.1
Content-Type: application/json
X-Banan-Event: job.completed
X-Banan-Signature: sha256=5f1c…e9

{ "event": "job.completed", "occurredAt": "2026-09-24T21:05:12.000Z", "attempt": 1, "job": { … } }

Проверка подписи

Заголовок X-Banan-Signature — это sha256= и HMAC-SHA256 от сырого тела запроса в hex. Ключ HMAC — секрет подписи вашего API-ключа. Считайте подпись по байтам тела до разбора JSON и сравнивайте за постоянное время.

Node.js (Express)

const crypto = require('node:crypto');
const express = require('express');

const app = express();
const SECRET = process.env.BANAN_WEBHOOK_SECRET;

// express.raw: подпись считается по сырому телу, а не по разобранному JSON
app.post('/hooks/banan', express.raw({ type: 'application/json' }), (req, res) => {
  const expected = 'sha256=' + crypto.createHmac('sha256', SECRET).update(req.body).digest('hex');
  const received = req.get('X-Banan-Signature') || '';
  const valid = received.length === expected.length
    && crypto.timingSafeEqual(Buffer.from(received), Buffer.from(expected));
  if (!valid) return res.status(401).end();

  const { event, job } = JSON.parse(req.body.toString('utf8'));
  // event: job.completed | job.failed; кадры — job.frames[].url
  res.status(204).end();
});

Python (Flask)

import hashlib
import hmac
import os

from flask import Flask, abort, request

app = Flask(__name__)
SECRET = os.environ["BANAN_WEBHOOK_SECRET"].encode()

@app.post("/hooks/banan")
def banan_hook():
    body = request.get_data()  # сырые байты тела
    expected = "sha256=" + hmac.new(SECRET, body, hashlib.sha256).hexdigest()
    received = request.headers.get("X-Banan-Signature", "")
    if not hmac.compare_digest(expected, received):
        abort(401)
    payload = request.get_json()
    # payload["event"]: job.completed | job.failed
    return "", 204

PHP

<?php
$secret = getenv('BANAN_WEBHOOK_SECRET');
$body = file_get_contents('php://input'); // сырое тело
$expected = 'sha256=' . hash_hmac('sha256', $body, $secret);
$received = $_SERVER['HTTP_X_BANAN_SIGNATURE'] ?? '';

if (!hash_equals($expected, $received)) {
    http_response_code(401);
    exit;
}

$payload = json_decode($body, true);
// $payload['event']: job.completed | job.failed
http_response_code(204);

Фид Авито Автозагрузки

GET /feeds/avito.xml?token=<токен фида> — готовые задачи с полями listing в формате Авито Автозагрузки formatVersion="3". Адрес вставляется в кабинет Авито: «Автозагрузка» → ссылка на файл.

<?xml version="1.0" encoding="UTF-8"?>
<Ads formatVersion="3" target="Avito.ru">
  <Ad>
    <Id>INV-2026-000123</Id>
    <Title>Кольцо 585, 3,2 г</Title>
    <Price>12000</Price>
    <Category>Часы и украшения</Category>
    <Description><![CDATA[Золото 585, гранат. Состояние хорошее.]]></Description>
    <Images>
      <Image url="https://banan.wtf/f/Qm9v…1.jpg"/>
      <Image url="https://banan.wtf/f/Qm9v…2.jpg"/>
      <Image url="https://banan.wtf/f/Qm9v…3.jpg"/>
    </Images>
  </Ad>
</Ads>

Какие поля обязательны для вашей категории, смотрите в справочнике Авито Автозагрузки. Мы передаём listing как есть. Пошагово — фид Авито с готовыми фото.

Ссылки на кадры

Ошибки

HTTPcodeКогда
400scene_unknownтакой сцены нет в каталоге
400prompt_requiredнет ни sceneId, ни prompt
400image_requiredнет ни imageUrl, ни imageBase64
400quality_invalidquality не standard и не high
400idempotency_key_invalidдлина или символы Idempotency-Key
400webhook_url_invalidадрес вебхука не публичный или не http(s)
400remote_image_*фото по ссылке не скачалось: remote_image_url_invalid, remote_image_url_private, remote_image_dns, remote_image_fetch_failed, remote_image_timeout, remote_image_too_large, remote_image_unsupported
400status_invalid, since_invalid, limit_invalid, cursor_invalidпараметры GET /jobs
400body_invalidтело запроса — не JSON
413body_too_largeтело запроса больше 20 МБ
401api_key_requiredнет заголовка Authorization
401api_key_invalidключ неверный или отозван
402insufficient_balanceна балансе меньше цены кадра. В ответе balanceRub, priceRub, topupUrl
403api_key_member_forbiddenключ принадлежит участнику организации
403account_deleted, account_restrictedаккаунт удалён или ограничен
404job_not_foundзадачи нет или она не из этого режима
404feed_not_foundтокен фида неверный или перевыпущен
404route_not_foundтакого метода нет
422idempotency_key_reusedтот же Idempotency-Key с другим телом
429rate_limitedпревышен лимит, ждите Retry-After секунд
429test_daily_limitбольше 200 тестовых задач за сутки, ждите Retry-After секунд
503retry_laterвременный конфликт записи, "retryable": true — повторите запрос
HTTP/1.1 402 Payment Required

{
  "success": false,
  "code": "insufficient_balance",
  "message": "На балансе API недостаточно денег для этого кадра.",
  "balanceRub": 12,
  "priceRub": 19,
  "topupUrl": "https://banan.wtf/profile/settings?section=api"
}

Лимиты

ЧтоЛимитСчётчик
Чтение (GET)600 в минутуключ
Создание задач60 в минутуключ
Запросы без действующего ключа120 в минутуIP
Исходное фотодо 25 МБзадача
Активные ключидо 10аккаунт

При превышении — 429 rate_limited и заголовок Retry-After в секундах. Ночную пачку отправляйте ровным потоком, не залпом.

Цены

Боевой ключ списывает рублёвый баланс API по цене за кадр. Баланс API отдельный от тарифа студии.

КачествоЦена за кадрКачество моделиДлинная сторонаДля чего
Стандарт
quality: "standard" по умолчанию
19 ₽ среднее 1536 px Каталожный кадр для карточки и объявления
Высокое
quality: "high"
49 ₽ высокое 2160 px Крупный план, мелкий текст на этикетке, печать

Пополнение картой или СБП — от 500 ₽, по счёту — от 10 000 ₽. Бонусы и примеры расчёта — на странице цен.

Сцены

Всего 26 сцен в 5 группах. Группа ведёт в свой раздел галереи с примерами. Машиночитаемый список — scenes.json, живой — GET /scenes.

До → послеsceneIdСценаФорматinputsvariantId
Одежда на модели
Исходное фото: Одежда на моделиНа модели в студии clothing_model_studio На модели в студии
Полный рост, светлый фон
3:4 — —
Исходное фото: Одежда на моделиНа модели на улице clothing_model_street На модели на улице
Городская улица, мягкий свет
3:4 — —
Исходное фото: Одежда на моделиСо спины clothing_model_back Со спины
Спинка, плечи, длина
3:4 — —
Исходное фото: Одежда на моделиВ движении clothing_model_walk В движении
Кадр в шаге, ткань в движении
3:4 — —
Исходное фото: Одежда на моделиФактура крупно clothing_detail Фактура крупно
Ткань, швы, фурнитура
3:4 — —
Другой типаж модели clothing_model_other Другой типаж модели
Размер, возраст, внешность
3:4 — plus, asian, mature, man, woman
Инфографика
Исходное фото: ИнфографикаПреимущества infographic_benefits Преимущества
Три факта подписями на слайде
3:4 facts —
Размеры infographic_size Размеры
Стрелки с габаритами
3:4 dims —
Комплектация infographic_inbox Комплектация
Что в коробке, с подписями
3:4 items —
Предметная съёмка
Исходное фото: Предметная съёмкаЧёрная студия studio_dark Чёрная студия
Контровой свет, отражение
3:4 — —
Исходное фото: Предметная съёмкаМрамор studio_marble Мрамор
Капли воды, холодный свет
3:4 — —
Исходное фото: Предметная съёмкаВ руке studio_hand В руке
Масштаб и живой контекст
3:4 — —
Исходное фото: Предметная съёмкаПесок studio_sand Песок
Закат, длинная тень
3:4 — —
Исходное фото: Предметная съёмкаЛес studio_forest Лес
Туман, мягкий свет
3:4 — —
Исходное фото: Предметная съёмкаВсплеск studio_splash Всплеск
Замороженное движение воды
3:4 — —
Исходное фото: Предметная съёмкаМакро studio_macro Макро
Фактура и материал крупно
3:4 — —
В применении lifestyle_in_use В применении
Товар в живой обстановке
3:4 — —
Белый фон
Исходное фото: Белый фонГлавное фото на белом white_main Главное фото на белом
Чистый белый фон, без ореолов
3:4 — —
Ракурс три четверти white_angle Ракурс три четверти
Объём и боковая сторона
3:4 — —
Ресейл и ломбарды
Исходное фото: Ресейл и ломбардыВитринный кадр resale_clean_asis Витринный кадр
Современно и легко, вещь как есть
4:3 — —
Исходное фото: Ресейл и ломбардыБелый фон для сайта resale_white Белый фон для сайта
Чистый белый, без ореолов
1:1 — —
Исходное фото: Ресейл и ломбардыКлеймо и проба крупно resale_hallmark Клеймо и проба крупно
Макро: проба, клеймо, бирка, номер
4:3 — —
Исходное фото: Ресейл и ломбардыДругой ракурс resale_angle Другой ракурс
Три четверти в том же лёгком стиле, для серии
4:3 — —
Исходное фото: Ресейл и ломбардыМасштаб resale_scale Масштаб
На руке, на манекене или рядом с монетой
4:3 — hand, mannequin, coin
Исходное фото: Ресейл и ломбардыКарточка состояния resale_condition_card Карточка состояния
Проба, вес, состояние, комплектность подписями
4:3 facts —
Исходное фото: Ресейл и ломбардыБаннер акции resale_promo_banner Баннер акции
Для соцсетей и витрины, подпись по желанию
4:3 headline, disclaimer (необяз.) —

Поля inputs с пометкой «необяз.» можно не передавать.

Файлы для машин

Вопросы по подключению — support@banan.wtf. Интеграторам, которые подключают своих клиентов, платит партнёрская программа.

API для интеграции · Цены API · Фид Авито · Готовые сцены · Оферта · Конфиденциальность