Photo Studio Service — API Contract
Этот документ является единственным источником правды для API Photo Studio Service.
Бэкенд реализует контракт. Фронтенд потребляет контракт. Отклонения запрещены.
Все пути ниже — через API Gateway: {gateway}/api/v1/gensvc/.... Внутренние пути сервиса без /api/ и /gensvc/ сегментов (/v1/jobs/...). При прямом обращении к сервису использовать внутренние пути. Подробнее — Overview .
Auth
Все endpoints (кроме /healthz, /readyz) требуют:
Authorization: Bearer <JWT>
JWT валидируется через POST /internal/auth/validate Auth Service. Permissions извлекаются из ответа introspection, кэшируются в Redis (TTL 60 сек).
Ошибки авторизации возвращаются в формате RFC 7807 Problem Details:
{
"type" : "about:blank" ,
"title" : "Unauthorized" ,
"status" : 401 ,
"detail" : "missing or invalid token"
}
Содержание
Jobs
Presets
User Presets
Admin Presets
POST /api/v1/gensvc/jobs/photo
Создаёт задание на генерацию фото в выбранном стиле (style transfer).
Параметр Значение Auth Bearer JWT Permission gensvc.photo.createContent-Type multipart/form-data
Request Body (multipart)
Поле Тип Required Описание imagebinary yes Фото блюда — JPEG, PNG или WebP, макс. 10 MB metastring (JSON) yes JSON-строка с параметрами задания
Структура meta (JSON):
{
"preset_id" : "string, optional — ID системного пресета" ,
"user_preset_id" : "string, optional — ID личного пресета" ,
"extra_prompt" : "string, optional, макс. 500 символов — дополнительная инструкция" ,
"size" : "string, optional — один из: delivery-1x1, story-9x16, banner-16x9, menu-4x3" ,
"count" : "integer, optional, 1-4 — количество вариантов (default: 1)"
}
Ровно одно из preset_id или user_preset_id обязательно.
Заголовок Описание Idempotency-KeyUUID, опциональный. Защита от дублирования при ретраях
Response 202
{
"job_id" : "string — ULID с префиксом job_" ,
"status_url" : "string — URL для polling статуса" ,
"stream_url" : "string — URL SSE-потока"
}
Errors
Code HTTP Когда UNAUTHORIZED401 Нет токена или невалидный JWT FORBIDDEN403 Нет permission gensvc.photo.create VALIDATION_ERROR400 Не указан ни preset_id, ни user_preset_id; оба указаны одновременно; extra_prompt > 500 символов PRESET_NOT_FOUND404 Пресет с указанным ID не найден или выключен IMAGE_TOO_LARGE413 Изображение превышает 10 MB UNSUPPORTED_MEDIA_TYPE415 Формат файла не JPEG/PNG/WebP INTERNAL_ERROR500 Внутренняя ошибка
POST /api/v1/gensvc/jobs/enhance
Создаёт задание на улучшение фото — AI сохраняет блюдо, пересоздаёт сцену по текстовому описанию.
Параметр Значение Auth Bearer JWT Permission gensvc.photo.createContent-Type multipart/form-data
Request Body (multipart)
Поле Тип Required Описание imagebinary yes Фото блюда — JPEG, PNG или WebP, макс. 10 MB metastring (JSON) yes JSON-строка с параметрами
Структура meta (JSON):
{
"extra_prompt" : "string, required, 1-1000 символов — описание желаемой сцены" ,
"size" : "string, optional — один из: delivery-1x1, story-9x16, banner-16x9, menu-4x3" ,
"count" : "integer, optional, 1-4 — количество вариантов (default: 1)"
}
Response 202
Та же структура что и у POST /jobs/photo (job_id, status_url, stream_url).
Errors
Code HTTP Когда UNAUTHORIZED401 Нет токена или невалидный FORBIDDEN403 Нет permission gensvc.photo.create VALIDATION_ERROR400 extra_prompt пустой или > 1000 символовIMAGE_TOO_LARGE413 Изображение > 10 MB UNSUPPORTED_MEDIA_TYPE415 Не JPEG/PNG/WebP INTERNAL_ERROR500 Внутренняя ошибка
GET /api/v1/gensvc/jobs
Список заданий с keyset-пагинацией.
Параметр Значение Auth Bearer JWT Permission gensvc.history.read (свои) или gensvc.history.read.all (все франшизы)
Query Parameters
Param Type Required Description cursorstring no Непрозрачный cursor из поля next_cursor предыдущего ответа limitinteger no 1–100, default: 20 statusstring no Фильтр по статусу: pending, running, succeeded, failed, cancelled
Response 200
{
"items" : [
{
"id" : "string — ULID job_..." ,
"kind" : "string — photo-studio | enhance" ,
"status" : "string — pending | running | succeeded | failed | cancelled" ,
"user_id" : "string" ,
"franchise_id" : "string" ,
"preset_id" : "string | null" ,
"user_preset_id" : "string | null" ,
"extra_prompt" : "string | null" ,
"input_asset_id" : "string | null" ,
"output_asset_id" : "string | null" ,
"size" : "string | null" ,
"count" : "integer" ,
"retry_of" : "string | null" ,
"error_code" : "string | null" ,
"error_message" : "string | null" ,
"created_at" : "datetime" ,
"started_at" : "datetime | null" ,
"finished_at" : "datetime | null"
}
],
"next_cursor" : "string — пустая строка если страниц больше нет"
}
Без gensvc.history.read.all возвращаются только задания текущего пользователя (user_id = me). С gensvc.history.read.all — все задания франшизы (franchise_id = my_franchise).
Errors
Code HTTP Когда UNAUTHORIZED401 Нет токена FORBIDDEN403 Нет ни gensvc.history.read, ни gensvc.history.read.all
GET /api/v1/gensvc/jobs/{id}
Детали конкретного задания.
Параметр Значение Auth Bearer JWT Permission gensvc.history.read (только своё) или gensvc.history.read.all (любое в рамках франшизы)
Path Parameters
Param Type Description idstring ULID задания (job_...)
Response 200
Полный объект Job (та же структура что и в items[] списка).
Errors
Code HTTP Когда UNAUTHORIZED401 Нет токена FORBIDDEN403 Задание чужое, а gensvc.history.read.all нет NOT_FOUND404 Задание не найдено или принадлежит другой франшизе
GET /api/v1/gensvc/jobs/{id}/stream
SSE-поток статусных событий задания. Клиент подключается и получает обновления в реальном времени.
Параметр Значение Auth Bearer JWT Content-Type ответа text/event-stream
Path Parameters
Param Type Description idstring ULID задания
SSE Events
Каждое событие — JSON в поле data::
data: {"status":"running"}\n\n
data: {"status":"succeeded","output_asset_id":"ast_01HXYZ..."}\n\n
data: {"status":"failed","error":"timeout from AI provider"}\n\n
Поток закрывается при переходе в терминальный статус (succeeded, failed, cancelled).
Errors
Code HTTP Когда UNAUTHORIZED401 Нет токена NOT_FOUND404 Задание не найдено
POST /api/v1/gensvc/jobs/{id}/retry
Создаёт новое задание с теми же параметрами. Новый Job содержит retry_of = id.
Параметр Значение Auth Bearer JWT Permission gensvc.photo.create
Path Parameters
Param Type Description idstring ULID исходного задания
Response 202
{
"job_id" : "string" ,
"status_url" : "string" ,
"stream_url" : "string"
}
Errors
Code HTTP Когда UNAUTHORIZED401 Нет токена FORBIDDEN403 Нет gensvc.photo.create или задание чужое NOT_FOUND404 Исходное задание не найдено BUSINESS_RULE_VIOLATION422 Исходное задание ещё не завершено (статус не терминальный)
GET /api/v1/gensvc/presets
Каталог системных пресетов (только включённые).
Параметр Значение Auth Bearer JWT Permission gensvc.access
Query Parameters
Param Type Required Description categorystring no Фильтр по категории: bar-lounge, delivery-and-takeout, fine-dining, lifestyle, menu, pastry-bakery, studio qstring no Полнотекстовый поиск по name_en и slug limitinteger no 1–500, default: 50 offsetinteger no default: 0
Response 200
{
"items" : [
{
"id" : "string" ,
"slug" : "string" ,
"name_en" : "string" ,
"name_ru" : "string | null" ,
"category" : "string — основная категория (legacy)" ,
"reference_object_key" : "string" ,
"enabled" : true ,
"created_at" : "datetime"
}
],
"limit" : 50 ,
"offset" : 0
}
Errors
Code HTTP Когда UNAUTHORIZED401 Нет токена FORBIDDEN403 Нет gensvc.access
GET /api/v1/gensvc/presets/{id}
Детали пресета с presigned URL на reference-изображение.
Параметр Значение Auth Bearer JWT Permission gensvc.access
Path Parameters
Param Type Description idstring ID пресета (prs_...)
Response 200
{
"id" : "string" ,
"slug" : "string" ,
"name_en" : "string" ,
"name_ru" : "string | null" ,
"category" : "string" ,
"reference_object_key" : "string" ,
"reference_url" : "string — presigned URL, действителен 24 часа" ,
"enabled" : true ,
"created_at" : "datetime"
}
Errors
Code HTTP Когда UNAUTHORIZED401 Нет токена FORBIDDEN403 Нет gensvc.access NOT_FOUND404 Пресет не найден или выключен
GET /api/v1/gensvc/user-presets
Список личных пресетов текущего пользователя.
Параметр Значение Auth Bearer JWT Permission gensvc.access
Query Parameters
Param Type Required Description limitinteger no 1–50, default: 50 offsetinteger no default: 0
Response 200
{
"items" : [
{
"id" : "string — ULID upr_..." ,
"name" : "string" ,
"reference_url" : "string — presigned URL, действителен 1 час" ,
"created_at" : "datetime"
}
],
"total" : "integer — всего личных пресетов у пользователя" ,
"quota" : 50
}
Errors
Code HTTP Когда UNAUTHORIZED401 Нет токена FORBIDDEN403 Нет gensvc.access
POST /api/v1/gensvc/user-presets
Загружает reference-изображение как личный пресет.
Параметр Значение Auth Bearer JWT Permission gensvc.accessContent-Type multipart/form-data
Request Body (multipart)
Поле Тип Required Описание imagebinary yes Reference-изображение — JPEG, PNG или WebP, макс. 10 MB namestring yes Название (1–100 символов)
Response 201
{
"id" : "string" ,
"name" : "string" ,
"reference_url" : "string" ,
"created_at" : "datetime"
}
Errors
Code HTTP Когда UNAUTHORIZED401 Нет токена FORBIDDEN403 Нет gensvc.access VALIDATION_ERROR400 Пустое имя или > 100 символов QUOTA_EXCEEDED422 Превышена квота 50 личных пресетов IMAGE_TOO_LARGE413 > 10 MB UNSUPPORTED_MEDIA_TYPE415 Не JPEG/PNG/WebP
DELETE /api/v1/gensvc/user-presets/{id}
Удаляет личный пресет.
Параметр Значение Auth Bearer JWT Permission gensvc.access
Path Parameters
Param Type Description idstring ULID личного пресета (upr_...)
Response 204
Нет тела ответа.
Errors
Code HTTP Когда UNAUTHORIZED401 Нет токена NOT_FOUND404 Пресет не найден или не принадлежит текущему пользователю
GET /api/v1/gensvc/admin/presets
Список всех системных пресетов (включая выключенные). Admin view.
Параметр Значение Auth Bearer JWT Permission gensvc.preset.admin
Query Parameters
Param Type Required Description limitinteger no 1–200, default: 50 offsetinteger no default: 0 enabledboolean no Фильтр по флагу enabled
Response 200
{
"items" : [
{
"id" : "string" ,
"slug" : "string" ,
"name_en" : "string" ,
"name_ru" : "string | null" ,
"category" : "string — legacy основная категория" ,
"categories" : [ "string — полный список категорий" ],
"reference_object_key" : "string" ,
"reference_url" : "string — presigned URL 24ч" ,
"enabled" : true ,
"created_at" : "datetime" ,
"updated_at" : "datetime"
}
],
"limit" : 50 ,
"offset" : 0 ,
"total" : 282
}
Errors
Code HTTP Когда UNAUTHORIZED401 Нет токена FORBIDDEN403 Нет gensvc.preset.admin
POST /api/v1/gensvc/admin/presets
Создаёт новый системный пресет. Изображение загружается отдельно через PUT .../reference.
Параметр Значение Auth Bearer JWT Permission gensvc.preset.adminContent-Type application/json
Request Body
{
"slug" : "string, required — уникальный kebab-case идентификатор" ,
"name_en" : "string, required, макс. 100 символов" ,
"name_ru" : "string, optional, макс. 100 символов" ,
"categories" : [ "string, required, минимум 1 — список категорий" ],
"enabled" : "boolean, optional, default: true"
}
Response 201
Объект AdminPreset (полная структура, как в списке).
Errors
Code HTTP Когда UNAUTHORIZED401 Нет токена FORBIDDEN403 Нет gensvc.preset.admin VALIDATION_ERROR400 Пустой slug, пустой name_en, пустой список categories CONFLICT409 Пресет с таким slug уже существует
GET /api/v1/gensvc/admin/presets/{id}
Admin-детали пресета.
Параметр Значение Auth Bearer JWT Permission gensvc.preset.admin
Path Parameters
Param Type Description idstring ID пресета
Response 200
Объект AdminPreset с полем reference_url.
Errors
Code HTTP Когда UNAUTHORIZED401 Нет токена FORBIDDEN403 Нет gensvc.preset.admin NOT_FOUND404 Пресет не найден
PATCH /api/v1/gensvc/admin/presets/{id}
Частичное обновление метаданных пресета (PATCH semantics — только переданные поля).
Параметр Значение Auth Bearer JWT Permission gensvc.preset.adminContent-Type application/json
Path Parameters
Param Type Description idstring ID пресета
Request Body
{
"name_en" : "string, optional" ,
"name_ru" : "string | null, optional" ,
"categories" : [ "string, optional — минимум 1 элемент если передан" ],
"enabled" : "boolean, optional"
}
Response 200
Обновлённый объект AdminPreset.
Errors
Code HTTP Когда UNAUTHORIZED401 Нет токена FORBIDDEN403 Нет gensvc.preset.admin NOT_FOUND404 Пресет не найден VALIDATION_ERROR400 Передан пустой массив categories
PUT /api/v1/gensvc/admin/presets/{id}/reference
Заменяет reference-изображение пресета.
Параметр Значение Auth Bearer JWT Permission gensvc.preset.adminContent-Type multipart/form-data
Path Parameters
Param Type Description idstring ID пресета
Request Body (multipart)
Поле Тип Required Описание imagebinary yes Новое reference-изображение — WebP рекомендован, макс. 10 MB
Response 200
Обновлённый объект AdminPreset с новым reference_object_key.
Errors
Code HTTP Когда UNAUTHORIZED401 Нет токена FORBIDDEN403 Нет gensvc.preset.admin NOT_FOUND404 Пресет не найден IMAGE_TOO_LARGE413 > 10 MB
DELETE /api/v1/gensvc/admin/presets/{id}
Soft delete системного пресета (флаг enabled = false, запись сохраняется).
Параметр Значение Auth Bearer JWT Permission gensvc.preset.admin
Path Parameters
Param Type Description idstring ID пресета
Response 204
Нет тела ответа.
Errors
Code HTTP Когда UNAUTHORIZED401 Нет токена FORBIDDEN403 Нет gensvc.preset.admin NOT_FOUND404 Пресет не найден
Общий формат ошибок
Photo Studio Service возвращает ошибки в формате RFC 7807 Problem Details :
{
"type" : "about:blank" ,
"title" : "string — HTTP reason phrase" ,
"status" : 400 ,
"detail" : "string — человекочитаемое описание" ,
"instance" : "string — путь запроса (optional)"
}
Остальные ERP-сервисы используют { "error": { "code", "message", "details" } }. Photo Studio Service (Go-прототип) использует RFC 7807. При интеграции через API Gateway возможна нормализация формата — см. декомпозицию .