REST API · MCP-сервер

API векторизации изображений

Отправьте картинку — получите чистый SVG, PDF, EPS, DXF или PNG. Один HTTP-запрос с вашего сервера или один вызов инструмента вашим ИИ-агентом.

Войдите по почте или через Google — сразу откроется страница Ключи API.

Терминал
curl -sS https://shapesoda.com/v1/vectorize \
  -H "Authorization: Bearer $SHAPESODA_KEY" \
  --data-binary @logo.png \
  -o logo.svg
Ответ
HTTP/1.1 200 OK
Content-Type: image/svg+xml; charset=utf-8
X-Credits-Charged: 1
X-Mode: live
X-Credits-Remaining: 99
X-Request-Id: req_4f1c2a9e0b7d4c3a8e21
Содержание

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

Три шага до первого SVG. Базовый адрес — https://shapesoda.com, все запросы идут по HTTPS.

  1. Получите ключ API

    Откройте в аккаунте страницу Ключи API (если попросит, войдите по почте или через Google) и выберите Новый ключ. Ключ начинается с ssk_ и показывается только один раз — сразу сохраните его в хранилище секретов. Новым аккаунтам дарим бесплатный кредит, чтобы попробовать.

    Терминал
    export SHAPESODA_KEY="ssk_your_key_here"
  2. Отправьте картинку

    Передайте байты изображения телом запроса, а параметры — в строке запроса:

    Тело запроса
    curl -sS "https://shapesoda.com/v1/vectorize?colors=auto" \
      -H "Authorization: Bearer $SHAPESODA_KEY" \
      --data-binary @logo.png \
      -D - -o logo.svg

    Или отправьте форму multipart/form-data: файл в поле image, параметры — обычными полями:

    Multipart
    curl -sS https://shapesoda.com/v1/vectorize \
      -H "Authorization: Bearer $SHAPESODA_KEY" \
      -F [email protected] \
      -F colors=8 \
      -F image_type=artwork \
      -D - -o logo.svg
  3. Прочитайте ответ

    При успехе приходит 200, в теле — SVG (image/svg+xml). Ключ -D - выводит заголовки — по ним видно, сколько стоил запрос:

    Заголовки ответа
    HTTP/1.1 200 OK
    Content-Type: image/svg+xml; charset=utf-8
    Content-Disposition: inline; filename="vector.svg"
    X-Credits-Charged: 1
    X-Mode: live
    X-Credits-Remaining: 99
    X-Request-Id: req_4f1c2a9e0b7d4c3a8e21
    ЗаголовокЗначение
    X-Credits-ChargedСколько кредитов списал запрос: 1 за успешную векторизацию (сколько бы ни было форматов), 0 в тестовом режиме и за бесплатные повторы. Неудачные запросы не списываются.
    X-Modelive или test в тестовом режиме.
    X-Credits-RemainingСколько картинок можно обработать прямо сейчас (баланс плюс включённые кредиты, с учётом вашего лимита расходов).
    X-Request-IdУникальный номер запроса. Возвращается и при ошибках — укажите его, если пишете в поддержку.

Авторизация#

Запросы к аккаунту и векторизации (POST /v1/vectorize, GET /v1/account, /mcp) передают ключ API в заголовке:

Заголовок
Authorization: Bearer ssk_…
  • Создавайте, просматривайте и отзывайте ключи на странице Ключи API в аккаунте (до 10 действующих ключей на аккаунт). Отзыв срабатывает сразу.
  • Без ключа, с неверным или отозванным ключом приходит 401 unauthorized. Частые неудачные попытки с одного IP-адреса ограничиваются.
  • Храните ключ в секрете и только на сервере. API не отдаёт заголовков CORS и не предназначен для вызова со страницы в браузере: никогда не вызывайте API из браузера или мобильного приложения с секретным ключом, не встраивайте его во фронтенд-код и не коммитьте. Если ключ утёк, отзовите его.
  • Сохранённые результаты используют другой секрет. GET и DELETE /v1/results/{id} принимают токен результата, а не ключ API. Передавайте его как Authorization: Bearer <result_token> (рекомендуется). ?token= в адресе тоже принимается, но токен в URL может попасть в журналы доступа, историю браузера и аналитику — не используйте его там, где адреса логируются или пересылаются. См. Хранение результата.
  • Одноразовые ссылки загрузки MCP несут собственный секрет и другой авторизации не требуют. GET /v1/plans открыт для всех.
  • Все ключи аккаунта расходуют один баланс кредитов — тот же, что и на сайте.

Примеры кода#

Короткие примеры почти без зависимостей: превратить logo.png в logo.svg, получить несколько форматов с пресетом, показать ошибку API и вывести остаток кредитов. Выбранный язык запоминается для всей страницы.

Векторизация картинки#

# --fail-with-body: exit non-zero on HTTP errors (the JSON error is written to logo.svg)
curl -sS --fail-with-body "https://shapesoda.com/v1/vectorize?colors=auto&image_type=artwork" \
  -H "Authorization: Bearer $SHAPESODA_KEY" \
  --data-binary @logo.png \
  -D headers.txt -o logo.svg

grep -i x-credits-remaining headers.txt
// vectorize.mjs — Node.js 18+ (built-in fetch), no dependencies
import { readFile, writeFile } from "node:fs/promises";

const res = await fetch("https://shapesoda.com/v1/vectorize?colors=auto", {
  method: "POST",
  headers: { Authorization: `Bearer ${process.env.SHAPESODA_KEY}` },
  body: await readFile("logo.png"),
});

if (!res.ok) {
  const { error } = await res.json();
  throw new Error(`${res.status} ${error.code}: ${error.message} (${error.request_id})`);
}

await writeFile("logo.svg", await res.text());
console.log("Credits left:", res.headers.get("X-Credits-Remaining"));
# vectorize.py — pip install requests
import os
import requests

with open("logo.png", "rb") as f:
    r = requests.post(
        "https://shapesoda.com/v1/vectorize",
        headers={"Authorization": f"Bearer {os.environ['SHAPESODA_KEY']}"},
        files={"image": f},
        data={"colors": "auto"},
        timeout=120,
    )

if r.status_code != 200:
    e = r.json()["error"]
    raise SystemExit(f"{r.status_code} {e['code']}: {e['message']} ({e['request_id']})")

with open("logo.svg", "w", encoding="utf-8") as out:
    out.write(r.text)
print("Credits left:", r.headers["X-Credits-Remaining"])
<?php
// vectorize.php — PHP 8 with the curl extension
$remaining = null;
$ch = curl_init("https://shapesoda.com/v1/vectorize");
curl_setopt_array($ch, [
    CURLOPT_POST => true,
    CURLOPT_HTTPHEADER => ["Authorization: Bearer " . getenv("SHAPESODA_KEY")],
    CURLOPT_POSTFIELDS => ["image" => new CURLFile("logo.png"), "colors" => "auto"],
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_TIMEOUT => 120,
    CURLOPT_HEADERFUNCTION => function ($ch, $line) use (&$remaining) {
        if (stripos($line, "X-Credits-Remaining:") === 0) $remaining = trim(substr($line, 20));
        return strlen($line);
    },
]);
$body = curl_exec($ch);
if ($body === false) {
    fwrite(STDERR, "Network error: " . curl_error($ch) . PHP_EOL);
    exit(1);
}
$status = curl_getinfo($ch, CURLINFO_RESPONSE_CODE);

if ($status !== 200) {
    $e = json_decode($body, true)["error"];
    fwrite(STDERR, "$status {$e['code']}: {$e['message']} ({$e['request_id']})" . PHP_EOL);
    exit(1);
}

file_put_contents("logo.svg", $body);
echo "Credits left: $remaining", PHP_EOL;
// main.go — standard library only: go run main.go
package main

import (
	"bytes"
	"fmt"
	"io"
	"log"
	"net/http"
	"os"
	"time"
)

func main() {
	img, err := os.ReadFile("logo.png")
	if err != nil {
		log.Fatal(err)
	}
	req, err := http.NewRequest("POST", "https://shapesoda.com/v1/vectorize?colors=auto", bytes.NewReader(img))
	if err != nil {
		log.Fatal(err)
	}
	req.Header.Set("Authorization", "Bearer "+os.Getenv("SHAPESODA_KEY"))

	client := &http.Client{Timeout: 120 * time.Second}
	resp, err := client.Do(req)
	if err != nil {
		log.Fatal(err)
	}
	defer resp.Body.Close()
	body, err := io.ReadAll(resp.Body)
	if err != nil {
		log.Fatal(err)
	}
	if resp.StatusCode != http.StatusOK {
		log.Fatalf("%d: %s", resp.StatusCode, body) // {"error":{"code","message","request_id"}}
	}
	if err := os.WriteFile("logo.svg", body, 0o644); err != nil {
		log.Fatal(err)
	}
	fmt.Println("Credits left:", resp.Header.Get("X-Credits-Remaining"))
}

Векторизация с пресетом#

Пресет print: PDF и SVG шириной 210 мм за один вызов и 1 кредит. Несколько форматов приходят в JSON, каждый файл скачивается по своей ссылке с токеном результата.

# preset=print, two formats in one call (1 credit) → JSON with a link per format (needs jq)
curl -sS --fail-with-body "https://shapesoda.com/v1/vectorize?preset=print&format=pdf,svg&width=210&unit=mm" \
  -H "Authorization: Bearer $SHAPESODA_KEY" \
  --data-binary @logo.png -o result.json

# download each file with the result token (not the API key)
TOKEN=$(jq -r .result_token result.json)
for f in pdf svg; do
  curl -sS --fail-with-body "$(jq -r .files.$f result.json)" \
    -H "Authorization: Bearer $TOKEN" -o "logo.$f"
done
// preset.mjs — Node.js 18+ (built-in fetch), no dependencies
import { readFile, writeFile } from "node:fs/promises";

const url = "https://shapesoda.com/v1/vectorize?preset=print&format=pdf,svg&width=210&unit=mm";
const res = await fetch(url, {
  method: "POST",
  headers: { Authorization: `Bearer ${process.env.SHAPESODA_KEY}` },
  body: await readFile("logo.png"),
});
if (!res.ok) {
  const { error } = await res.json();
  throw new Error(`${res.status} ${error.code}: ${error.message} (${error.request_id})`);
}

// several formats → JSON with a link per format, stored for 24 hours
const result = await res.json();
for (const [format, link] of Object.entries(result.files)) {
  const file = await fetch(link, { headers: { Authorization: `Bearer ${result.result_token}` } });
  if (!file.ok) throw new Error(`download ${format}: ${file.status}`);
  await writeFile(`logo.${format}`, Buffer.from(await file.arrayBuffer()));
}
console.log("Credits left:", result.credits_remaining);
# preset.py — pip install requests
import os
import requests

with open("logo.png", "rb") as f:
    r = requests.post(
        "https://shapesoda.com/v1/vectorize",
        headers={"Authorization": f"Bearer {os.environ['SHAPESODA_KEY']}"},
        files={"image": f},
        data={"preset": "print", "format": "pdf,svg", "width": "210", "unit": "mm"},
        timeout=120,
    )

if r.status_code != 200:
    e = r.json()["error"]
    raise SystemExit(f"{r.status_code} {e['code']}: {e['message']} ({e['request_id']})")

# several formats → JSON with a link per format, stored for 24 hours
result = r.json()
for fmt, link in result["files"].items():
    file = requests.get(link, headers={"Authorization": f"Bearer {result['result_token']}"}, timeout=120)
    file.raise_for_status()
    with open(f"logo.{fmt}", "wb") as out:
        out.write(file.content)
print("Credits left:", result["credits_remaining"])
<?php
// preset.php — PHP 8 with the curl extension
function http(string $url, array $opts): array {
    $ch = curl_init($url);
    curl_setopt_array($ch, $opts + [CURLOPT_RETURNTRANSFER => true, CURLOPT_TIMEOUT => 120]);
    $body = curl_exec($ch);
    if ($body === false) throw new RuntimeException(curl_error($ch));
    return [curl_getinfo($ch, CURLINFO_RESPONSE_CODE), $body];
}

[$status, $body] = http("https://shapesoda.com/v1/vectorize", [
    CURLOPT_POST => true,
    CURLOPT_HTTPHEADER => ["Authorization: Bearer " . getenv("SHAPESODA_KEY")],
    CURLOPT_POSTFIELDS => ["image" => new CURLFile("logo.png"), "preset" => "print", "format" => "pdf,svg", "width" => "210", "unit" => "mm"],
]);
if ($status !== 200) {
    $e = json_decode($body, true)["error"];
    fwrite(STDERR, "$status {$e['code']}: {$e['message']} ({$e['request_id']})" . PHP_EOL);
    exit(1);
}

// several formats → JSON with a link per format, stored for 24 hours
$result = json_decode($body, true);
foreach ($result["files"] as $format => $link) {
    [$status, $file] = http($link, [CURLOPT_HTTPHEADER => ["Authorization: Bearer " . $result["result_token"]]]);
    if ($status !== 200) exit("download $format: $status" . PHP_EOL);
    file_put_contents("logo.$format", $file);
}
echo "Credits left: {$result['credits_remaining']}", PHP_EOL;
// main.go — standard library only: go run main.go
package main

import (
	"bytes"
	"encoding/json"
	"fmt"
	"io"
	"log"
	"net/http"
	"os"
	"time"
)

var client = &http.Client{Timeout: 120 * time.Second}

// do sends the request with a bearer token and returns the body of a 200 response.
func do(req *http.Request, token string) []byte {
	req.Header.Set("Authorization", "Bearer "+token)
	resp, err := client.Do(req)
	if err != nil {
		log.Fatal(err)
	}
	defer resp.Body.Close()
	body, err := io.ReadAll(resp.Body)
	if err != nil {
		log.Fatal(err)
	}
	if resp.StatusCode != http.StatusOK {
		log.Fatalf("%d: %s", resp.StatusCode, body) // {"error":{"code","message","request_id"}}
	}
	return body
}

func main() {
	img, err := os.ReadFile("logo.png")
	if err != nil {
		log.Fatal(err)
	}
	url := "https://shapesoda.com/v1/vectorize?preset=print&format=pdf,svg&width=210&unit=mm"
	req, err := http.NewRequest("POST", url, bytes.NewReader(img))
	if err != nil {
		log.Fatal(err)
	}

	// several formats → JSON with a link per format, stored for 24 hours
	var result struct {
		Token            string            `json:"result_token"`
		Files            map[string]string `json:"files"`
		CreditsRemaining int               `json:"credits_remaining"`
	}
	if err := json.Unmarshal(do(req, os.Getenv("SHAPESODA_KEY")), &result); err != nil {
		log.Fatal(err)
	}
	for format, link := range result.Files {
		get, err := http.NewRequest("GET", link, nil)
		if err != nil {
			log.Fatal(err)
		}
		if err := os.WriteFile("logo."+format, do(get, result.Token), 0o644); err != nil {
			log.Fatal(err)
		}
	}
	fmt.Println("Credits left:", result.CreditsRemaining)
}

Проверка баланса#

curl -sS https://shapesoda.com/v1/account \
  -H "Authorization: Bearer $SHAPESODA_KEY"
const res = await fetch("https://shapesoda.com/v1/account", {
  headers: { Authorization: `Bearer ${process.env.SHAPESODA_KEY}` },
});
const { credits } = await res.json();
console.log(`${credits.available} images available`);
import os
import requests

r = requests.get(
    "https://shapesoda.com/v1/account",
    headers={"Authorization": f"Bearer {os.environ['SHAPESODA_KEY']}"},
    timeout=30,
)
r.raise_for_status()
print(r.json()["credits"]["available"], "images available")
<?php
$ch = curl_init("https://shapesoda.com/v1/account");
curl_setopt_array($ch, [
    CURLOPT_HTTPHEADER => ["Authorization: Bearer " . getenv("SHAPESODA_KEY")],
    CURLOPT_RETURNTRANSFER => true,
]);
$data = json_decode(curl_exec($ch), true);
echo $data["credits"]["available"], " images available", PHP_EOL;
package main

import (
	"encoding/json"
	"fmt"
	"log"
	"net/http"
	"os"
)

func main() {
	req, _ := http.NewRequest("GET", "https://shapesoda.com/v1/account", nil)
	req.Header.Set("Authorization", "Bearer "+os.Getenv("SHAPESODA_KEY"))
	resp, err := http.DefaultClient.Do(req)
	if err != nil {
		log.Fatal(err)
	}
	defer resp.Body.Close()
	var out struct {
		Credits struct {
			Available int `json:"available"`
		} `json:"credits"`
	}
	if err := json.NewDecoder(resp.Body).Decode(&out); err != nil {
		log.Fatal(err)
	}
	fmt.Println(out.Credits.Available, "images available")
}

Векторизация#

POST/v1/vectorize1 кредит за успешную картинку

Превращает одно растровое изображение в SVG, PDF, EPS, DXF или PNG — один формат или несколько за 1 кредит. Запрос синхронный: ответ приходит, когда вектор готов, — обычно за несколько секунд (для самых больших картинок — до минуты).

Тело запроса#

Картинку можно передать тремя способами:

  • Байтами — сам файл и есть тело запроса, параметры — в строке запроса. Заголовок Content-Type не учитывается.
  • Формой — Content-Type: multipart/form-data, файл в поле image; параметры — полями формы или в строке запроса (поля формы важнее). Файл в любом другом поле отклоняется.
  • Ссылкой — image_url с пустым телом (включаем постепенно).

Формат определяется по содержимому файла (сигнатуре), а не по имени или Content-Type.

Параметры#

Все параметры необязательны. Регистр важен; неизвестный параметр или значение и несовместимые параметры (см. Сочетания) — 400 bad_request с указанием причины.

Тонкая настройка и пресеты. Пресет лишь задаёт значения по умолчанию. Любой параметр ниже применяется поверх него и всегда важнее: preset=cut&max_colors=4&stroke_width=0.2 — это пресет cut на 4 цвета. Без пресета исходная точка — web.

ПараметрЗначенияОписание
Основные
presetweb print cut laser embroideryГотовые настройки под задачу, по умолчанию web. См. Пресеты.
formatsvg pdf eps dxf pngФормат результата или список через запятую (svg,pdf) за 1 кредит. По умолчанию — формат пресета. См. Форматы результата.
image_typeauto artwork artwork_sharp photoТип картинки. artwork — логотипы, иконки, иллюстрации со сглаженными краями; artwork_sharp — жёсткие пиксельные края без сглаживания (пиксель-арт, скриншоты); photo — фотографии и сложные изображения.
qualityauto high medium lowКачество исходника. low — для размытых, шумных или сильно сжатых JPEG.
detailauto low medium highДетализация результата. Для фотографий.
modelive test previewtest — бесплатный результат с водяным знаком для отладки интеграции (Тестовый режим). preview — бесплатный PNG-предпросмотр до 1 Мп, векторы потом открываются за 1 кредит (Предпросмотр).
retainfalse truetrue — сохранить и результат на один формат на 24 часа. См. Хранение результата.
retention_days1–30Хранить результат столько дней вместо 24 часов; бесплатно. См. Срок хранения.
image_urlURLАдрес публичной картинки вместо файла. См. Картинка по ссылке (включаем постепенно).
Обработка
colorsauto unlimited 2–32Число цветов (прежний параметр). Число ограничивает палитру (2 — двухцветный результат); unlimited сохраняет все найденные цвета. По умолчанию auto. Для более тонкого предела — max_colors.
max_colors2–256Максимум цветов; движок может взять меньше. Не сочетается с colors и palette.
palette2–256 × #rrggbbСвоя палитра через запятую — в результате только эти цвета. В строке запроса пишите # как %23. Не сочетается с colors и max_colors.
palette_tolerance0–100С palette: насколько цвет картинки (CIE76 ΔE) может отличаться от цвета палитры, чтобы притянуться к нему; более далёкие цвета остаются своими.
min_area0–10000Наименьшая оставляемая фигура, в px²; мельче — отбрасываются. Перекрывает значение пресета, в том числе для фотографий.
max_pixels10000–4200000Уменьшить исходник до такого числа пикселей перед трассировкой: быстрее и меньше узлов. Коэффициент придёт в X-Input-Scale.
curve_tolerance0.01–10Допуск подгонки кривых в пикселях исходника (по умолчанию около 0.5). Меньше — контур точнее, узлов больше; больше — узлов меньше. Также задаёт smoothness, если вы его не передали.
smoothness1–10Сглаживание контуров: больше — глаже и меньше узлов.
cornerssharp smoothОставлять углы острыми или скруглять. По умолчанию — по автоматическому анализу.
denoise0–3Шумоподавление перед трассировкой (артефакты JPEG, зерно): 0 — выключено, 3 — сильнейшее.
Рисование
drawfill stroke stroke_edgesfill — залитые фигуры; stroke — контур каждой фигуры без заливки (лазер, гравировка); stroke_edges — только границы между фигурами, каждая один раз, поэтому общий край не режется дважды. По умолчанию — из пресета.
layersstacked cutouts cutouts_groupedФигуры лежат друг на друге, вырезаны друг из друга (без наложений) или вырезаны и сгруппированы по цвету. По умолчанию — из пресета.
group_bynone color layerГруппы в результате: color — группа на каждый цвет (фигуры вырезаны друг из друга); layer — по глубине вложенности (layer_0 — фон). SVG <g id>, слои PDF, блоки EPS, слои DXF. По умолчанию SVG группирует по цвету только для cutouts_grouped, DXF всегда кладёт фигуры на слои по цвету, у PDF и EPS групп нет.
Кривые и обводка
curvesall lines lines_arcsДопустимые сегменты: all — кривые и отрезки; lines — только отрезки (для станков без кривых); lines_arcs — в DXF отрезки и дуги, в остальных форматах как lines.
stroke_width0.01–100Толщина обводки; нужен draw=stroke или stroke_edges. В единицах результата или, при stroke_scaling=scale, в пикселях исходника. По умолчанию — волосяная линия.
stroke_color#rrggbbЦвет обводки вместо цвета каждой фигуры. Условие то же, что у stroke_width.
stroke_scalingfixed scalefixed (по умолчанию) — толщина остаётся в единицах результата при изменении размера; scale — толщина в пикселях исходника и масштабируется вместе с рисунком.
Заполнение щелей
gap_filleroff onon — подложка под заливками: каждая фигура обведена своим цветом, поэтому между соседями не просвечивают щели. Нужен draw=fill. По умолчанию off.
gap_filler_width0–10Толщина обводки подложки в пикселях исходника (по умолчанию 1). Нужен gap_filler=on.
Размер
width, heightчисло > 0Размер результата в unit. Одна сторона — вторая по пропорциям; обе — лист такого размера, рисунок размещается по aspect. До 10 м.
unitpx pt mm cm inЕдиницы width/height, по умолчанию px.
aspectfit fill stretchПри заданных width и height: fit (по умолчанию) — рисунок целиком внутри листа; fill — закрывает лист, лишнее обрезается; stretch — растягивается по осям до точного размера.
align_x, align_y0–1Положение рисунка на листе при aspect=fit или fill; по умолчанию 0.5 (по центру).
scale>0–100Размер результата как кратное размера исходника. Не сочетается с width/height.
dpi1–2400Пикселей на дюйм в PNG, когда размер в pt, mm, cm или in; по умолчанию 300.
Для отдельных форматов
svg_version1.1 1.0 tiny1.2Вариант SVG. tiny1.2 — профиль SVG Tiny для плоттеров и встраиваемых просмотрщиков: без прозрачности и трансформаций градиентов.
svg_fixed_sizetrue falsetrue (по умолчанию) — width и height у корневого элемента; false — только viewBox, SVG подстраивается под контейнер.
adobe_compatfalse truetrue — SVG, удобный для Adobe Illustrator: размер всегда задан, толщина обводки в координатах рисунка.
dxf_curveslines arcs splinesКривые в DXF: ломаные (по умолчанию), ломаные с дугами или SPLINE. curves=lines и lines_arcs важнее.
png_antialiason offСглаживание в PNG; по умолчанию on.

Параметры проверяются друг с другом; конфликт — 400 bad_request:

  • palette не сочетается с colors и max_colors, а max_colors — с colors. palette_tolerance требует palette.
  • stroke_width, stroke_color, stroke_scaling требуют draw=stroke или stroke_edges (в том числе если так задано пресетом, например laser). gap_filler=on требует draw=fill; gap_filler_width — gap_filler=on.
  • unit требует width или height. scale не сочетается с width/height. aspect, align_x, align_y требуют и width, и height (выравнивание при aspect=stretch ни на что не влияет). dpi требует размер в pt, mm, cm или in.
  • retention_days работает только с mode=live и не сочетается с retain=false. mode=preview отдаёт только png.

Ограничения входа#

ОграничениеЗначениеОшибка
ФорматыPNG, JPEG, WebP, BMP (без сжатия, 24/32 бит), GIF (первый кадр), TIFF (первая страница)415 unsupported_format
Размер файла10 МБ413 too_large
РазрешениеДо 40 мегапикселей. Картинки больше 4,2 Мп или 4096 px по стороне уменьшаются автоматически, коэффициент — в X-Input-Scale422 too_many_pixels

Уменьшение не меняет физический размер, заданный в width/height. Большое разрешение не делает вектор лучше: быстрее всего обрабатываются картинки около 2000 px по длинной стороне.

Ответ#

Что придёт, зависит от запрошенных форматов:

  • Один формат (по умолчанию) — 200 OK, в теле сам файл с Content-Type формата (см. Форматы результата). SVG отдаётся inline, остальные форматы — вложением vector.<формат>. Ничего не хранится, если не передан retain=true или retention_days.
  • Несколько форматов или Accept: application/json — JSON со ссылкой на каждый формат; результат хранится 24 часа. См. Хранение результата.
ЗаголовокЗначение
X-Credits-Charged, X-Credits-RemainingСколько стоил запрос и сколько осталось (у ответа-файла; в JSON — то же в полях).
X-Modelive, test или preview.
X-Input-ScaleТолько если исходник уменьшен: коэффициент по каждой стороне, например 0.5.
X-Result-Id, X-Result-Url, X-Result-Token, X-Result-ExpiresТолько если результат сохранён (retain=true, retention_days, mode=preview): сам результат, его секретный токен и срок удаления.
X-Request-IdУникальный номер запроса.

При любой ошибке в теле — JSON, см. Ошибки.

Пресеты#

Пресет — готовый набор настроек под задачу. Передайте preset и при необходимости уточните его явными параметрами: они всегда важнее пресета. Без preset действует web — ровно тот SVG, что API отдавал всегда.

ПресетФормат по умолчаниюЧто делаетДля чего
webSVGЦвета подбираются автоматически, фигуры лежат друг на друге.Сайты, приложения, Figma
printPDFФигуры вырезаны друг из друга, без наложений.Типографии, плакаты, документы
cutSVG8 цветов; фигуры вырезаны и сгруппированы по цвету — по группе на цвет плёнки.Плоттеры, наклейки, Cricut, Silhouette
laserSVG2 цвета, только контуры (draw=stroke).Лазерная резка и гравировка; для CAD добавьте format=dxf
embroiderySVG12 цветов, фигуры вырезаны друг из друга, мелкие точки убраны.Оцифровка вышивки
Терминал · по вызову на пресет
API="https://shapesoda.com/v1/vectorize"
AUTH="Authorization: Bearer $SHAPESODA_KEY"

# web (default): SVG for sites and apps, stacked shapes — the classic result
curl -sS "$API?preset=web" -H "$AUTH" --data-binary @logo.png -o logo.svg

# print: PDF, shapes cut out of each other; here 210 mm wide (A4)
curl -sS "$API?preset=print&width=210&unit=mm" -H "$AUTH" --data-binary @poster.png -o poster.pdf

# cut: SVG, 8 colors cut out and grouped by color — vinyl, stickers, Cricut
curl -sS "$API?preset=cut&width=4&unit=in" -H "$AUTH" --data-binary @sticker.png -o sticker.svg

# laser: 2-color outlines; DXF 100 mm wide for the laser software
curl -sS "$API?preset=laser&format=dxf&width=100&unit=mm" -H "$AUTH" --data-binary @sign.png -o sign.dxf

# embroidery: 12 colors, no tiny shapes; a fixed thread palette instead of 12 colors
curl -sS "$API?preset=embroidery&palette=%23000000,%23ffffff,%23d62828,%23003049" \
  -H "$AUTH" --data-binary @patch.png -o patch.svg

Настройки print, cut, laser и embroidery могут уточняться; web не меняется. Тот же вызов на пяти языках — в примере Векторизация с пресетом.

Форматы результата#

Формат выбирается параметром format. Одно значение — в ответе сам файл; список через запятую (svg,pdf,png) — JSON со ссылкой на каждый формат, все вместе за 1 кредит.

ФорматContent-TypeС width/heightДля чего
svgimage/svg+xmlАтрибуты width/height в единицах, тот же viewBoxВеб, Figma, Illustrator, Cricut Design Space
pdfapplication/pdfРазмер страницы (MediaBox)Печать, документы, Illustrator
epsapplication/postscriptBoundingBoxПечатные RIP, CorelDRAW, старые редакторы
dxfapplication/dxfКоординаты в единицах и $INSUNITSCAD, лазер и ЧПУ (кривые становятся ломаными)
pngimage/pngПиксели: mm и in — по 300 DPI, не больше 4,2 МпЧистая растеризация вектора в любом размере
Терминал · по вызову на формат
API="https://shapesoda.com/v1/vectorize"
AUTH="Authorization: Bearer $SHAPESODA_KEY"

curl -sS "$API?format=svg" -H "$AUTH" --data-binary @logo.png -o logo.svg   # image/svg+xml
curl -sS "$API?format=pdf" -H "$AUTH" --data-binary @logo.png -o logo.pdf   # application/pdf
curl -sS "$API?format=eps" -H "$AUTH" --data-binary @logo.png -o logo.eps   # application/postscript
curl -sS "$API?format=dxf" -H "$AUTH" --data-binary @logo.png -o logo.dxf   # application/dxf
curl -sS "$API?format=png&width=2000" -H "$AUTH" --data-binary @logo.png -o logo.png   # image/png

# several at once for 1 credit → JSON with links (see Result storage)
curl -sS "$API?format=svg,pdf,eps,dxf,png" -H "$AUTH" --data-binary @logo.png

Физический размер: задайте width или height и unit — px (по умолчанию), pt, mm, cm или in. С одной стороной вторая идёт по пропорциям; с обеими получится лист ровно такого размера, а aspect (fit, fill, stretch) и align_x/align_y решают, как рисунок лежит на нём. Максимум 10 м. Без размера результат в пикселях исходника (после уменьшения, если оно было), умноженных на scale, если он задан. PNG получает пиксели: физические единицы — по dpi (по умолчанию 300).

Тестовый режим#

Собирайте и проверяйте интеграцию бесплатно с mode=test. Ответ того же формата, с теми же заголовками и структурой, что в боевом режиме, но на результате водяной знак: плотный узор, вплетённый в сами фигуры, — удалением слоя его не убрать.

  • Не нужны ни кредиты, ни подписка: X-Credits-Charged: 0 и X-Mode: test.
  • Пресеты, форматы и параметры работают как в боевом режиме, с двумя исключениями: retention_days и image_url отклоняются с 400 bad_request (тестируйте с файлом и без retention_days).
  • Тестовые результаты можно сохранять (несколько форматов, retain=true или Accept: application/json), но только на 1 час вместо 24, и в любом формате они остаются с водяным знаком. Они занимают место в хранилище аккаунта и вытесняются первыми, когда место кончается.
  • Лимит: 100 тестовых вызовов на ключ и 100 на аккаунт за сутки (UTC), дальше — 429 test_limit с Retry-After до 00:00 UTC. Тестовые вызовы не входят в суточный лимит картинок.
Терминал
curl -sS "https://shapesoda.com/v1/vectorize?mode=test&preset=cut" \
  -H "Authorization: Bearer $SHAPESODA_KEY" \
  --data-binary @logo.png \
  -D - -o test.svg
Заголовки ответа
HTTP/1.1 200 OK
Content-Type: image/svg+xml; charset=utf-8
X-Credits-Charged: 0
X-Mode: test
X-Credits-Remaining: 99
X-Request-Id: req_4f1c2a9e0b7d4c3a8e21

Предпросмотр#

Покажите пользователю результат до оплаты. mode=preview бесплатен и возвращает растровый PNG до 1 мегапикселя (крупнее — уменьшается), без векторов. Когда результат устроил, откройте векторы по той же ссылке за 1 кредит, не загружая картинку заново.

  • Бесплатно, без водяного знака: X-Credits-Charged: 0 и X-Mode: preview. Только format=png; не сочетается с retention_days и image_url. Остальные параметры действуют, поэтому предпросмотр показывает настоящие цвета и фигуры.
  • Лимит: 200 предпросмотров на аккаунт за сутки (UTC), дальше — 429 preview_limit с Retry-After до 00:00 UTC. Предпросмотры не входят ни в суточный лимит картинок, ни в тестовый лимит.
  • Предпросмотр всегда хранится 24 часа. Ссылка приходит в заголовках X-Result-* (или в JSON с Accept: application/json).
  • Открыть векторы: GET /v1/results/{id}?format=svg (или pdf, eps, dxf) с токеном результата. Списывает 1 кредит один раз (0, если эту картинку оплатили за последние 30 дней, см. Бесплатные повторы). Полноразмерный PNG вместо предпросмотра в 1 Мп — с upgrade=true: ?format=png&upgrade=true. Просто ?format=png снова отдаёт бесплатный предпросмотр.
  • После этого результат — обычный live (X-Mode: live): все остальные форматы бесплатны, действуют правила хранения результата. Если сборка не удалась, кредит возвращается и результат остаётся предпросмотром. Без кредитов — 402 no_credits.
Терминал · предпросмотр, затем оплата
# 1. бесплатный предпросмотр: PNG в теле, ссылка на результат — в заголовках
curl -sS "https://shapesoda.com/v1/vectorize?mode=preview&preset=cut" \
  -H "Authorization: Bearer $SHAPESODA_KEY" \
  --data-binary @logo.png -D headers.txt -o preview.png
RES=$(grep -i '^x-result-url:' headers.txt | cut -d' ' -f2 | tr -d '\r')
TOKEN=$(grep -i '^x-result-token:' headers.txt | cut -d' ' -f2 | tr -d '\r')

# 2. пользователя устроило: открыть SVG за 1 кредит (токен результата, не ключ API)
curl -sS "$RES?format=svg" -H "Authorization: Bearer $TOKEN" -o logo.svg

# 3. дальше все остальные форматы бесплатны
curl -sS "$RES?format=pdf" -H "Authorization: Bearer $TOKEN" -o logo.pdf
curl -sS "$RES?format=png&upgrade=true" -H "Authorization: Bearer $TOKEN" -o logo-full.png
Заголовки ответа на предпросмотр
HTTP/1.1 200 OK
Content-Type: image/png
X-Credits-Charged: 0
X-Mode: preview
X-Result-Id: res_9b2e41c07f3a58d61e0c4b7a
X-Result-Url: https://shapesoda.com/v1/results/res_9b2e41c07f3a58d61e0c4b7a
X-Result-Token: q3v6X0cQ1l2mYp8rT4wZkH9sNfB7aJdE5uGiLoVxCyA
X-Result-Expires: 2026-09-30T12:00:00.000Z

Бесплатные повторы#

За картинку вы платите один раз. Повтор бесплатен в двух случаях, и в ответе будет X-Credits-Charged: 0:

ПовторБесплатноОграничение
Точно тот же запрос: те же байты картинки, те же параметры24 часаНет — безопасные ретраи
Та же картинка с другими параметрами или форматами30 дней после оплаты20 раз в сутки на картинку; сверх — 1 кредит за повтор
  • Картинка узнаётся по хешу SHA-256 её байтов — тому же, что на сайте: картинку, оплаченную кредитом на shapesoda.com, можно бесплатно повторить через API, и наоборот. Уменьшенный или пересохранённый файл — уже другая картинка.
  • Повторы с другими параметрами входят в суточный лимит картинок ключа.
  • Тестовые вызовы и картинки, сделанные по безлимитной подписке сайта, 30-дневное окно не открывают.
  • Предпросмотры (mode=preview) бесплатны и 30-дневное окно не открывают; открытие векторов из предпросмотра — обычный платный вызов (1 кредит или бесплатный повтор, если картинку оплатили за последние 30 дней).

Хранение результата#

GET/v1/results/{id}бесплатно · токен результата
DELETE/v1/results/{id}токен результата

По умолчанию ничего не хранится: запрос на один формат возвращает файл и забывает картинку. Результат хранится 24 часа, только когда он понадобится позже:

  • вы просите несколько форматов или шлёте Accept: application/json — ровно это значение; клиенты с application/json, */* по-прежнему получают файл;
  • вы передаёте retain=true или retention_days — файл приходит как обычно, а ссылка — в заголовках X-Result-*;
  • вы используете mode=preview (хранится всегда, см. Предпросмотр);
  • вызов пришёл через MCP.
Терминал
# several formats → JSON (or add -H "Accept: application/json" for a single format)
curl -sS "https://shapesoda.com/v1/vectorize?format=svg,pdf" \
  -H "Authorization: Bearer $SHAPESODA_KEY" \
  --data-binary @logo.png
200 OK · application/json
{
  "result_id": "res_9b2e41c07f3a58d61e0c4b7a",
  "result_url": "https://shapesoda.com/v1/results/res_9b2e41c07f3a58d61e0c4b7a",
  "result_token": "q3v6X0cQ1l2mYp8rT4wZkH9sNfB7aJdE5uGiLoVxCyA",
  "width": 1200,
  "height": 800,
  "input_scale": 1,
  "credits_charged": 1,
  "credits_remaining": 99,
  "mode": "live",
  "expires_at": "2026-09-30T12:00:00.000Z",
  "files": {
    "svg": "https://shapesoda.com/v1/results/res_9b2e41c07f3a58d61e0c4b7a?format=svg",
    "pdf": "https://shapesoda.com/v1/results/res_9b2e41c07f3a58d61e0c4b7a?format=pdf"
  }
}
ПолеЗначение
result_id, result_urlСохранённый результат и его адрес.
result_tokenСекретный токен для скачивания, показывается один раз. Он же ключ расшифровки — восстановить его мы не можем.
filesСсылка на каждый запрошенный формат.
expires_atКогда результат удалится (через 24 часа или retention_days суток).
width, height, input_scaleРазмер векторизованной картинки в пикселях и коэффициент уменьшения (1 — без уменьшения).
credits_charged, credits_remaining, modeТо же, что заголовки X-Credits-* и X-Mode у ответа-файла.

Скачивайте с токеном результата, а не с ключом API: Authorization: Bearer <result_token> (рекомендуется). ?token=<result_token> тоже принимается, но токен в адресе может попасть в журналы доступа, историю браузера и аналитику. Скачать результат может любой, у кого есть токен, поэтому обращайтесь со ссылкой как с самим файлом. Без format придёт первый формат исходного запроса: первый в вашем списке format, в том порядке, как вы его записали (повторы отбрасываются), а если format не передавали — формат пресета. Объект files в JSON-ответе перечисляет форматы в том же порядке. Формат, который вы не просили, собирается один раз из сохранённого исходника строго с теми же пресетом, параметрами и режимом — бесплатно и без новой загрузки. Сборку ограничивает частота запросов аккаунта-владельца результата (60 в минуту, 429 rate_limited с Retry-After); уже сохранённые форматы — нет.

Терминал
RES="https://shapesoda.com/v1/results/res_9b2e41c07f3a58d61e0c4b7a"
TOKEN="q3v6X0cQ1l2mYp8rT4wZkH9sNfB7aJdE5uGiLoVxCyA"   # result_token, not the API key

curl -sS "$RES?format=pdf" -H "Authorization: Bearer $TOKEN" -o logo.pdf
# a format you did not ask for is built from the stored source, free of charge
curl -sS "$RES?format=dxf" -H "Authorization: Bearer $TOKEN" -o logo.dxf
# the same with the token in the link (less safe: URLs end up in logs)
curl -sS "$RES?format=eps&token=$TOKEN" -o logo.eps
# delete now instead of waiting 24 hours
curl -sS -X DELETE "$RES" -H "Authorization: Bearer $TOKEN"

С retain=true (или retention_days) и одним форматом файл приходит как обычно, плюс ссылка:

Заголовки ответа
HTTP/1.1 200 OK
Content-Type: image/svg+xml; charset=utf-8
X-Credits-Charged: 1
X-Mode: live
X-Result-Id: res_9b2e41c07f3a58d61e0c4b7a
X-Result-Url: https://shapesoda.com/v1/results/res_9b2e41c07f3a58d61e0c4b7a
X-Result-Token: q3v6X0cQ1l2mYp8rT4wZkH9sNfB7aJdE5uGiLoVxCyA
X-Result-Expires: 2026-09-30T12:00:00.000Z
  • Зашифровано. Исходник и каждый формат шифруются AES-256-GCM ключом, выведенным из токена. Мы храним только хеш токена, поэтому без ссылки сохранённый результат не прочитать.
  • Удаляется через 24 часа (предпросмотр тоже), тестовый — через 1 час, результат с retention_days — через столько дней (см. expires_at). DELETE удаляет сразу и возвращает 204.
  • Неизвестный id, неверный токен и истёкший результат одинаково дают 404 not_found.
  • Если хранилище заполнено, запросы с JSON получают 503 storage_full (кредит не списывается; повторите через Retry-After или попросите один формат), а retain=true и retention_days возвращают файл без заголовков X-Result-*.

Срок хранения#

По умолчанию сохранённый результат живёт 24 часа. Если пользователи возвращаются за файлами, попросите больше через retention_days — от 1 до 30 суток, бесплатно. Параметр сохраняет результат даже на один формат (как retain=true) и отдаёт ссылку в заголовках X-Result-*; срок — в expires_at / X-Result-Expires.

  • Только для mode=live и не вместе с retain=false. Тестовые результаты живут 1 час, предпросмотры — 24 часа.
  • Сохранённые результаты входят в квоту хранилища аккаунта. Когда место кончается, вытесняются самые старые в таком порядке: тестовые, предпросмотры, боевые без retention_days и в последнюю очередь — с retention_days. Если места всё равно нет — 503 storage_full (кредит не списывается).
  • Форматы, дособранные из сохранённого результата, бесплатны весь срок. DELETE удаляет результат в любой момент.
Терминал · хранить 30 суток
curl -sS "https://shapesoda.com/v1/vectorize?retention_days=30" \
  -H "Authorization: Bearer $SHAPESODA_KEY" \
  --data-binary @logo.png -D - -o logo.svg   # X-Result-Expires: через 30 суток

Картинка по ссылкеСкоро#

Включаем постепенно. Пока вход по ссылке не включён, запрос с image_url получает 503 url_unavailable — загрузите файл.

Вместо файла передайте адрес публичной картинки в image_url (параметром запроса или полем формы) с пустым телом. Всё остальное работает как с файлом.

Терминал
curl -sS https://shapesoda.com/v1/vectorize \
  -H "Authorization: Bearer $SHAPESODA_KEY" \
  -F image_url=https://example.com/logo.png \
  -F preset=cut \
  -o logo.svg
  • http или https, порт 80 или 443, без логина и пароля, до 2048 символов — иначе 400 url_invalid.
  • Картинка скачивается через изолированный egress-прокси: частные, локальные, link-local адреса и адреса метаданных облаков закрыты, в том числе после перенаправлений (422 url_blocked).
  • Не больше 3 перенаправлений, 10 секунд (504 url_timeout) и 10 МБ (413 too_large). Сервер не ответил или вернул ошибку — 422 url_unreachable.
  • Передавайте либо файл, либо image_url, не оба сразу. В тестовом режиме не работает.

Аккаунт и кредиты#

GET/v1/accountбесплатно

Возвращает аккаунт, которому принадлежит ключ, и его кредиты. Кредиты считаются по календарным месяцам (UTC).

200 OK · application/json
{
  "account": { "id": "acc_3f9c1e7a2b4d6e80", "name": "[email protected]" },
  "credits": {
    "available": 97,
    "balance": 97,
    "quota": 0,
    "quota_used": 0,
    "quota_left": 0,
    "spent_month": 3,
    "spend_limit": null
  }
}
ПолеЗначение
availableСколько картинок можно обработать прямо сейчас: quota_left + balance, но не больше остатка лимита расходов.
balanceКупленные кредиты, общие с сайтом. Включают subscription_balance.
subscription_balanceКредиты API-подписки (тратятся первыми). Остаток переходит дальше, но не больше 5 месячных объёмов, и сгорает, когда подписка закончилась.
quota, quota_used, quota_leftКредиты, включённые в тариф на месяц, если они есть (тратятся первыми). Для оплаты по факту — 0.
spent_monthКредиты, потраченные в этом календарном месяце (UTC).
spend_limitВаш лимит расходов на месяц в кредитах или null, если лимита нет. Задаётся в меню аккаунта.

Прайс-лист#

GET/v1/plansоткрыт · без ключа

Те же цены, что в разделе Цены, в виде JSON и без авторизации. В ответе: api_plans (месячные планы с кредитами: credits, price, per_image), subscriptions (подписки для сайта), credits_never_expire, redownload_free_days, result_retention_hours, signup_credits, enterprise_contact и payments_enabled / billing (данные для оплаты, если для посетителя включены платежи).

Ошибки#

Ошибки приходят со стандартными кодами HTTP и JSON-телом с постоянным машинным кодом code. Обрабатывайте code, а не message: сообщение — подсказка для человека, оно может меняться.

application/json
{
  "error": {
    "code": "too_many_pixels",
    "message": "Human-readable description",
    "request_id": "req_4f1c2a9e0b7d4c3a8e21"
  }
}
HTTPКодЧто случилосьЧто делать
400bad_requestНеизвестный параметр или значение, несовместимые параметры, испорченная форма, файл не в поле image или одновременно файл и image_url.Исправьте запрос. Повторять как есть бесполезно.
400emptyВ запросе нет картинки.Передайте файл телом запроса или в поле image.
400url_invalidimage_url — не http(s)-адрес на порту 80 или 443, содержит логин и пароль или длиннее 2048 символов.Исправьте адрес.
401unauthorizedНет ключа, ключ неверный или отозван.Проверьте заголовок Authorization: Bearer; при необходимости создайте новый ключ.
402no_creditsКредиты закончились (в том числе при открытии предпросмотра).Купите пакет кредитов. Не повторяйте.
402spend_limitДостигнут ваш лимит расходов на месяц.Поднимите или снимите лимит в меню аккаунта либо дождитесь следующего месяца.
404not_foundНет такого адреса, или ссылка загрузки/результата неизвестна, устарела или с неверным токеном.Проверьте адрес.
405method_not_allowedНеверный HTTP-метод для этого адреса.Используйте метод из справочника.
413too_largeФайл (или картинка по image_url) больше 10 МБ.Уменьшите или пересожмите картинку.
415unsupported_formatНе PNG, JPEG, WebP, BMP без сжатия, GIF или TIFF (по содержимому).Сначала сконвертируйте в PNG.
422invalid_imageФайл повреждён, обрезан или не читается.Пересохраните картинку.
422too_many_pixelsБольше 40 Мп или картинке нужно слишком много памяти.Уменьшите картинку.
422url_blockedimage_url ведёт на частный, локальный или внутренний адрес.Используйте публичный адрес. Не повторяйте.
422url_unreachableКартинку по image_url не удалось скачать.Проверьте адрес или загрузите файл.
429rate_limitedСлишком много запросов с этого ключа (или неудачных попыток входа с вашего IP).Подождите Retry-After секунд и повторите.
429daily_limitКлюч исчерпал суточный лимит.Дождитесь 00:00 UTC или используйте другой ключ.
429preview_limitАккаунт сделал 200 предпросмотров (mode=preview) за сутки.Подождите до 00:00 UTC (Retry-After) или используйте mode=live.
429test_limitИсчерпан суточный лимит тестового режима ключа или аккаунта.Дождитесь 00:00 UTC (Retry-After) или используйте mode=live.
500internalСбой на нашей стороне. Кредит не списывается.Повторите один раз после паузы; если не помогло, пришлите нам request_id.
503busyВсе обработчики заняты, очередь заполнена.Повторите через Retry-After секунд (обычно 5).
503timeoutОбработка заняла больше 60 секунд. Кредит не списывается.Попробуйте картинку поменьше или меньше цветов.
503storage_fullХранилище результатов заполнено. Кредит не списывается.Повторите через Retry-After секунд или попросите один формат.
503url_unavailableВход по ссылке выключен или временно недоступен.Загрузите файл.
504url_timeoutСкачивание image_url заняло больше 10 секунд. Кредит не списывается.Загрузите файл.

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

Ограничения частоты#

ОграничениеПо умолчаниюОшибка
Запросов на ключ60 в минуту, пачкой — до 10 сразу429 rate_limited + Retry-After
Одновременных запросов на аккаунт5 сразу (в обработке и в очереди); шлите параллельно не больше 5429 rate_limited + Retry-After: 2, без списания
Картинок на ключ в сутки5000, включая бесплатные повторы с другими параметрами (сброс в 00:00 UTC)429 daily_limit + Retry-After
Неудачных авторизаций с IP30 в минуту429 rate_limited + Retry-After
Предпросмотров (mode=preview)200 на аккаунт в сутки429 preview_limit + Retry-After
Тестовых вызовов100 на ключ и 100 на аккаунт в сутки429 test_limit + Retry-After
Бесплатных повторов оплаченной картинки с другими параметрами20 в сутки на картинку, дальше — по 1 кредиту—

Бюджет запросов ключа общий для REST API и MCP-сервера. GET /v1/account в него не входит. Нужно больше — напишите на [email protected].

Повторные запросы#

  • Повторяйте только rate_limited и busy (через время из Retry-After) и один раз internal с короткой паузой. Остальные ошибки 4xx повторятся.
  • Retry-After — целое число секунд, а не дата HTTP. Он приходит с rate_limited (время до следующего разрешённого запроса), busy и timeout (5), storage_full (60) и суточными лимитами (секунды до 00:00 UTC).
  • Отклонённый запрос не расходует лимит. Лимит в минуту устроен как «ведро с токенами»: запрос, получивший 429 rate_limited, токен не тратит, поэтому достаточно подождать Retry-After.
  • Повторы бесплатны: тот же запрос (та же картинка и те же параметры) в течение 24 часов повторно не оплачивается — в ответе X-Credits-Charged: 0. Та же картинка с другими параметрами тоже бесплатна 30 дней в пределах суточного лимита — см. Бесплатные повторы.
  • Ставьте щедрый таймаут клиента — не меньше 120 секунд, чтобы не оборвать медленный запрос, когда картинка уже обработана и оплачена.
  • Для пакетной обработки запускайте несколько запросов параллельно (2–4), а не десятки: всё сверх пачки получит 429.
JavaScript · повтор по Retry-After
async function vectorize(image, attempt = 0, retriedInternal = false) {
  const res = await fetch("https://shapesoda.com/v1/vectorize", {
    method: "POST",
    headers: { Authorization: `Bearer ${process.env.SHAPESODA_KEY}` },
    body: image,
    signal: AbortSignal.timeout(120_000),
  });
  if (res.ok) return res.text();
  const { error } = await res.json();
  if (["rate_limited", "busy"].includes(error.code) && attempt < 3) {
    const wait = Number(res.headers.get("Retry-After") ?? 2 ** attempt);
    await new Promise((r) => setTimeout(r, wait * 1000));
    return vectorize(image, attempt + 1, retriedInternal);
  }
  if (error.code === "internal" && !retriedInternal) { // один повтор с короткой паузой
    await new Promise((r) => setTimeout(r, 1000));
    return vectorize(image, attempt, true);
  }
  throw new Error(`${error.code}: ${error.message} (${error.request_id})`);
}

Чек-лист для продакшена#

  • Таймаут: поставьте таймаут клиента не меньше 120 секунд.
  • Проверяйте до сохранения: пишите тело в файл, только если статус 200, а Content-Type — тот, что вы просили (image/svg+xml, application/pdf, …, а для нескольких форматов — application/json). Ошибки приходят в JSON — не сохраняйте их как .svg.
  • Учитывайте Retry-After (секунды); повторяйте только rate_limited, busy и один раз internal. См. Повторные запросы.
  • Держите ключ API в секрете и только на сервере: не в браузере, не в мобильном приложении и не в репозитории. Утёкший ключ отзовите.
  • Токены результатов: передавайте их в заголовке Authorization, а не в ?token=, и не пускайте ссылки на результаты в журналы и аналитику.
  • Разработка: пока собираете интеграцию, используйте mode=test (бесплатно, с водяным знаком, 100 вызовов в сутки); в релизе переключитесь на live.
  • Поддержка: сохраняйте X-Request-Id при каждой ошибке.

Безопасность и приватность#

  • Ключи хранятся в виде хешей. Мы храним только хеш с секретом (HMAC-SHA-256) и короткий префикс ключа, поэтому ключ показывается один раз и не восстанавливается — его можно только отозвать и заменить.
  • Лимит расходов задаёте вы. Месячный лимит в меню аккаунта действует для всех ключей, MCP-сервера и сайта: ни агент, ни утёкший ключ не потратят больше, чем вы разрешили.
  • Картинки хранятся, только когда это нужно вам. Запрос на один формат обрабатывается в памяти в изолированном процессе и сразу забывается — на диск и в базу ничего не пишется. Несколько форматов, retain=true, retention_days, предпросмотры и результаты MCP хранятся 24 часа (с retention_days — до 30 суток) в зашифрованном виде — ключ есть только у владельца токена результата, — а потом удаляются; скачать их может любой, у кого есть ссылка с токеном. Загрузки MCP живут в памяти не дольше 10 минут. Для обучения моделей картинки не используются. Подробнее — в разделе Хранение результата и в Политике конфиденциальности.
  • SVG проверяется. Каждый результат перед отдачей сверяется со строгим белым списком (контуры, группы и градиенты с простыми цветами) — никаких скриптов, обработчиков событий, внешних ссылок и встроенного содержимого. PDF, EPS и DXF собираются только из геометрии и цветов вектора.
  • Вход проверяется. Формат определяется по содержимому, разрешение — до декодирования (защита от «бомб»), каждая задача выполняется в отдельном процессе с ограничением по времени. Картинки по ссылке скачиваются в отдельном процессе через egress-прокси, который закрывает внутренние адреса.

SDK и CLI#

Официальные библиотеки оборачивают тот же API: типизированные опции, автоматические повторы после 429/503, скачивание результатов. Они сгенерированы из спецификации OpenAPI, поэтому доступен каждый параметр с этой страницы.

  • Python: pip install shapesoda (PyPI). Shapesoda(key).vectorize_file("logo.png", formats=["svg","pdf"], preset="print"), а также preview_file() и upgrade().
  • JavaScript / TypeScript: npm install @shapesoda/sdk (npm). new Shapesoda(key).vectorizeFile("logo.png", { formats: ["svg","pdf"], preset: "print" }), а также previewFile() и upgrade(). Работает через import и require, Node 18+.
  • Go, PHP, Ruby, Java, C#: сгенерированные клиенты лежат в папке clients/sdk репозитория; публикация в pkg.go.dev, Packagist, RubyGems, Maven Central и NuGet идёт. Пока их нет в реестрах, сгенерируйте клиент из спецификации (см. ниже) или используйте примеры на HTTP выше.
  • CLI: npx shapesoda ./in ./out --preset cut --format svg,dxf (npm, Node 18+). Папка на вход, папка на выход, повторы и пропуск готовых файлов встроены; --preview, --test, shapesoda account.

Спецификация OpenAPI#

Машиночитаемое описание API в формате OpenAPI 3.1 — импортируйте в Postman, Insomnia или Bruno либо сгенерируйте клиент:

Терминал
npx @openapitools/openapi-generator-cli generate \
  -i https://shapesoda.com/openapi.json -g typescript-fetch -o ./shapesoda-client

MCP для ИИ-приложений и агентов#

Model Context Protocol позволяет ИИ-приложениям и агентам вызывать внешние инструменты. MCP-сервер Shapesoda превращает картинки в SVG, PDF, EPS, DXF или PNG прямо в ChatGPT, Claude, Cursor, Claude Code и любом другом MCP-клиенте. Аккаунт Shapesoda подключается входом (OAuth); агенты и скрипты по-прежнему могут работать с ключом API — с теми же кредитами и лимитами, что и REST API.

POSThttps://shapesoda.com/mcpStreamable HTTP · OAuth или ключ Bearer

Бесплатно: 3 векторизации в месяц без аккаунта, 10 в месяц с подключённым аккаунтом (OAuth или ключ), дальше кредиты. REST и сайт это не меняет. Личный план с сайта в чатах без лимита, если аккаунт подключён входом; вызовы с ключом API тратят кредиты. Смена настроек той же картинки в окне Shapesoda бесплатна 24 часа — за одну картинку платите один раз. Тестовому режиму, предпросмотру, retention_days и vectorize_url нужен аккаунт. Бесплатные обновляются 1-го числа каждого месяца (UTC); остаток показывают get_usage и free_remaining_today в каждом результате. Внутри чатов ничего не продаём: цены и оплата — на сайте.

Подключить в ChatGPT, Claude, Cursor или Claude Code#

Адрес сервера: https://shapesoda.com/mcp. Когда бесплатные закончатся, чат предложит подключить аккаунт Shapesoda: войдите через Google или по коду из письма и нажмите «Разрешить». Подключённые приложения видны в аккаунте, там же их можно отключить.

ChatGPT#

Найдите Shapesoda в каталоге приложений ChatGPT или добавьте адрес сервера как своё приложение в режиме разработчика. Приложите картинку и напишите @Shapesoda или «сделай SVG»: ChatGPT один раз спросит разрешение передать файл (вход image у vectorize), и окно Shapesoda покажет шторку «до/после», настройки, скачивание во всех форматах и «Копировать SVG».

Claude#

В claude.ai или Claude Desktop откройте «Настройки» → «Коннекторы» и добавьте Shapesoda из каталога или через «Добавить свой коннектор» с адресом сервера. Claude не передаёт приложениям приложенные файлы, поэтому просто попросите векторизовать картинку: он вызовет open_editor, и вы выберете файл в окне Shapesoda.

Claude Code#

Добавьте сервер, затем выполните /mcp, выберите shapesoda и Authenticate — аккаунт подключится в браузере:

Терминал
claude mcp add --transport http shapesoda https://shapesoda.com/mcp

Или с ключом API вместо входа:

Терминал
claude mcp add --transport http shapesoda https://shapesoda.com/mcp \
  --header "Authorization: Bearer $SHAPESODA_KEY"

Cursor#

Установите плагин Shapesoda из маркетплейса Cursor или добавьте сервер в ~/.cursor/mcp.json (или в .cursor/mcp.json проекта). При первом вызове Cursor предложит подключить аккаунт.

mcp.json
{
  "mcpServers": {
    "shapesoda": { "url": "https://shapesoda.com/mcp" }
  }
}

С ключом API Cursor подставит ${env:SHAPESODA_KEY} из переменных окружения, и ключ не окажется в файле:

mcp.json
{
  "mcpServers": {
    "shapesoda": {
      "url": "https://shapesoda.com/mcp",
      "headers": { "Authorization": "Bearer ${env:SHAPESODA_KEY}" }
    }
  }
}

Другие клиенты, которые поддерживают удалённые MCP-серверы (Streamable HTTP), подключаются так же: адрес https://shapesoda.com/mcp, вход OAuth 2.1 (клиент находит его по /.well-known/oauth-protected-resource) или заголовок Authorization: Bearer <ключ>. Сервер отвечает JSON (без потока SSE) и поддерживает версии протокола 2025-11-25, 2025-06-18 и 2025-03-26.

Инструменты#

vectorize

Превращает картинку в SVG, PDF, EPS, DXF или PNG. Картинка приходит ровно одним способом: image (файл, приложенный к сообщению в ChatGPT), upload_id (из create_upload) или небольшой image_base64. Принимает те же параметры, что REST API: preset, format списком, draw, layers, palette, transparency, width/height/unit, mode=test, mode=preview (бесплатный PNG-предпросмотр; чтобы открыть векторы за 1 кредит, откройте его ссылку с format=svg вместо format=png), retention_days и остальные — см. Параметры. Возвращает ссылку на каждый формат на 24 часа, а в чатах, которые это умеют, показывает окно Shapesoda. Списывает 1 кредит за все форматы.

open_editor

Открывает окно Shapesoda без картинки: человек выбирает файл в нём. Для Claude, который не передаёт приложениям вложения. Кредиты не тратит.

create_upload

Создаёт одноразовую ссылку для загрузки локального файла (действует 10 минут, до 10 МБ) и возвращает команду curl для загрузки. Для агентов с терминалом.

revectorize

Только для окна Shapesoda (модели не виден): векторизует ту же картинку с другими настройками, бесплатно 24 часа после первой векторизации, до 30 изменений в сутки.

vectorize_url

Скачивает публичную картинку по ссылке и векторизует её как vectorize. Появляется в списке, только когда включён вход по ссылке (включаем постепенно); без тестового режима.

get_usage

Показывает остаток бесплатных на этот месяц, доступные кредиты, баланс, расход за месяц и лимит расходов. Бесплатно.

Как агент передаёт файл#

Агенты обычно не умеют прикладывать двоичные файлы к вызову инструмента, поэтому большие картинки идут через короткоживущую ссылку загрузки:

  1. create_upload

    возвращает upload_id и секретную ссылку upload_url.

  2. Загрузка через curl

    Агент выполняет полученную команду: curl -sS -X PUT --data-binary @image.png "<upload_url>". Ссылка принимает один файл PNG, JPEG, WebP, BMP, GIF или TIFF и не требует другой авторизации.

  3. vectorize с upload_id

    В результате — ссылка на каждый запрошенный формат в files (на 24 часа, токен уже в ссылке), download_url на первый из них, SVG прямо в ответе (если он меньше 60 КБ), размеры картинки, credits_charged и credits_remaining.

Небольшую картинку (до 2 МБ) агент может передать сразу в image_base64, без загрузки.

Ссылку загрузки обслуживает PUT /v1/uploads/{upload_id}. Это часть потока загрузки MCP: он есть в спецификации OpenAPI для полноты, но в REST-интеграции вы его не вызываете.

Цены#

Ежемесячная подписка на выбранное число кредитов. Один кредит — одна успешно векторизованная картинка во всех форматах, запрошенных в этом вызове, через API, MCP или сайт. Отмена в любой момент.

  • Неиспользованные кредиты переходят дальше (до 5 месяцев)
  • Отмена в любой момент, кредиты действуют до конца тарифа
  • Общий баланс с сайтом
  • Неудачные запросы бесплатны
50
кредитов в месяц
$3.99 / мес
$0.08 за картинку
200
кредитов в месяц
$9.99 / мес
$0.05 за картинку
5,000
кредитов в месяц
$74.99 / мес
$0.015 за картинку
20,000
кредитов в месяц
$199.99 / мес
$0.01 за картинку
100,000
кредитов в месяц
$499.99 / мес
$0.005 за картинку

Нужен корпоративный тариф или больше 100 тысяч картинок в месяц? Напишите на [email protected] — обсудим цену за объём и лимиты.