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

Единый асинхронный HTTP API: загрузите изображение, получите Full HD. Опциональный long-polling — одна интеграция без цикла опроса.

Обзор

API версионирован под /v1/. Все запросы и ответы — JSON, кроме загрузки изображения (multipart/form-data) и скачивания результата (бинарник).

Три шага:

  1. Создать задачуPOST /v1/jobs с файлом.
  2. Дождаться — long-poll ?wait=60 либо опрос GET /v1/jobs/{id}.
  3. СкачатьGET /v1/jobs/{id}/result (или по result_url из ответа).

Результаты хранятся 30 минут после завершения, затем удаляются. Базовый URL обозначается как BASE (например https://api.example.com).

Аутентификация

Необязательная. Если сервер запущен с переменной API_TOKEN, каждый запрос к /v1/* должен содержать:

Authorization: Bearer YOUR_TOKEN

Без токена — 401 unauthorized. /health всегда публичный.

Лимиты

ПараметрЗначение
Максимальный размер файла3 MiB
Максимальное разрешение входадолжно уместиться в 1920×1080 либо 1080×1920
Разрешение результатавсегда Full HD (1920×1080 в границах)
Таймаут обработки2 минуты на задачу
TTL результата30 минут после succeeded
Переполнение очереди503 queue_full с Retry-After

Формат ошибок

Все ошибки приходят в одном формате:

{
  "error": {
    "code": "invalid_image",
    "message": "resolution 4000x3000 exceeds Full HD (1920x1080 or 1080x1920)"
  }
}
HTTPcodeКогда
400invalid_image, invalid_parameter, image_requiredбитый файл, слишком большой/крупный, неверный параметр
401unauthorizedнет/неверный Bearer
404job_not_foundзадача удалена по TTL
410job_failedобработка упала
503queue_fullповторить через Retry-After секунд

Каждый ответ содержит заголовок X-Request-Id — прикладывайте его в баг-репортах.

POST /v1/jobs — создать задачу

Content-Type: multipart/form-data

ПолеГдеОбязательноОписание
imageformдафайл (JPEG, PNG, GIF, BMP, TIFF, WebP)
taskform / queryнетupscale (по умолчанию), remove-bg или restore
modeform / queryнеттолько для upscale: fit (по умолчанию), fill, stretch
formatform / queryнеттолько для upscale: jpeg (по умолчанию) или png. remove-bg всегда возвращает PNG.
qualityform / queryнетJPEG quality 1–100 (92) — только для upscale
waitqueryнетlong-poll: ждать до N секунд (макс. 60)

Задачи task

Режимы mode

Ответ

202 Accepted (или 200 OK, если wait успел). Заголовок Location указывает на запись задачи.

{
  "id": "7b1f…",
  "status": "queued",
  "mode": "fit",
  "format": "jpeg",
  "created_at": "2026-04-22T10:20:30Z",
  "original_width": 1280,
  "original_height": 720
}

GET /v1/jobs/{id} — статус задачи

QueryОписание
waitlong-poll: держит коннект до терминального статуса (макс. 60 с)

Статусы: queued, processing, succeeded, failed.

Ответ при succeeded:

{
  "id": "7b1f…",
  "status": "succeeded",
  "mode": "fit",
  "format": "jpeg",
  "created_at": "2026-04-22T10:20:30Z",
  "started_at":  "2026-04-22T10:20:30Z",
  "finished_at": "2026-04-22T10:20:42Z",
  "result_url":  "https://api.example.com/v1/jobs/7b1f…/result",
  "expires_at":  "2026-04-22T10:50:42Z",
  "original_width": 1280, "original_height": 720,
  "output_width": 1920,  "output_height": 1080
}

GET /v1/jobs/{id}/result — скачать результат

Возвращает байты изображения (image/jpeg или image/png).

POST /v1/cull — отбор лучшего кадра из серии

Загрузите 2–20 изображений одним multipart-запросом. Сервис группирует похожие кадры через perceptual hash, оценивает резкость (Laplacian variance), экспозицию и насыщенность, возвращает JSON с лучшим кадром в каждой группе и причинами отклонения для остальных.

Content-Type: multipart/form-data. Поле images повторяется для каждого файла.

ЛимитЗначение
Файлов за запрос2–20
Размер одного файла3 MiB
Суммарный upload60 MiB
Rate limitсчитается как 1 запрос из квоты 30/час

Пример:

curl -X POST https://api.printgpt.ru/v1/cull \
  -F "images=@IMG_001.jpg" \
  -F "images=@IMG_002.jpg" \
  -F "images=@IMG_003.jpg"

Ответ (сокращённо):

{
  "images": [
    { "index": 0, "filename": "IMG_001.jpg", "group_id": 0, "is_best": true,
      "metrics": { "score": 9.15, "blur": 1.0, "exposure": 1.0,
                   "saturation": 0.43, "blur_raw": 333.8,
                   "width": 800, "height": 600 } },
    { "index": 1, "filename": "IMG_002.jpg", "group_id": 0, "is_best": false,
      "metrics": { "score": 3.18, "blur": 0.01, "exposure": 1.0,
                   "saturation": 0.42, "blur_raw": 5.7,
                   "width": 800, "height": 600,
                   "reasons": ["blurry"] } }
  ],
  "groups": [ { "group_id": 0, "best_index": 0, "members": [0, 1] } ]
}

Поля:

GET /health

{ "status": "ok", "version": "2.0.0", "api": "v1" }

Быстрый старт — curl

curl -X POST "$BASE/v1/jobs?wait=45" \
  -H "Authorization: Bearer $TOKEN" \
  -F "image=@photo.jpg" -F "mode=fit" -F "format=jpeg"

Если wait успел — статус уже succeeded, в ответе есть result_url:

curl -L -H "Authorization: Bearer $TOKEN" \
  "$BASE/v1/jobs/$ID/result" -o result.jpg

Long-polling цикл

# Создаём задачу
ID=$(curl -sS -X POST "$BASE/v1/jobs" -F "image=@photo.jpg" | jq -r .id)

# Ждём до 30 секунд на запрос; повторяем до терминального статуса
while :; do
  RESP=$(curl -sS "$BASE/v1/jobs/$ID?wait=30")
  STATUS=$(echo "$RESP" | jq -r .status)
  [ "$STATUS" = "succeeded" ] && break
  [ "$STATUS" = "failed" ]    && { echo "$RESP"; exit 1; }
done

curl -sSL "$BASE/v1/jobs/$ID/result" -o result.jpg

PHP

<?php
$base  = 'https://api.example.com';
$token = getenv('API_TOKEN');

$ch = curl_init("$base/v1/jobs?wait=45");
curl_setopt_array($ch, [
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_POST           => true,
    CURLOPT_HTTPHEADER     => ["Authorization: Bearer $token"],
    CURLOPT_POSTFIELDS     => [
        'image'  => new CURLFile('/path/to/photo.jpg'),
        'mode'   => 'fit',
        'format' => 'jpeg',
    ],
]);
$job = json_decode(curl_exec($ch), true);
curl_close($ch);

if (($job['status'] ?? '') !== 'succeeded') {
    throw new RuntimeException('job not ready: ' . json_encode($job));
}

file_put_contents('/tmp/result.jpg',
    file_get_contents($job['result_url'], false, stream_context_create([
        'http' => ['header' => "Authorization: Bearer $token"]
    ]))
);

Node.js (fetch, Node 18+)

import fs from 'node:fs';

const base  = 'https://api.example.com';
const token = process.env.API_TOKEN;

const form = new FormData();
form.set('image', new Blob([fs.readFileSync('photo.jpg')]), 'photo.jpg');
form.set('mode', 'fit');

const r = await fetch(`${base}/v1/jobs?wait=45`, {
    method: 'POST',
    headers: { Authorization: `Bearer ${token}` },
    body: form,
});
const job = await r.json();
if (job.status !== 'succeeded') throw new Error(JSON.stringify(job));

const img = await fetch(job.result_url, {
    headers: { Authorization: `Bearer ${token}` },
}).then(x => x.arrayBuffer());
fs.writeFileSync('result.jpg', Buffer.from(img));