Быстрый старт#
Три шага до первого SVG. Базовый адрес — https://shapesoda.com, все запросы идут по HTTPS.
-
Получите ключ API
Откройте в аккаунте страницу Ключи API (если попросит, войдите по почте или через Google) и выберите Новый ключ. Ключ начинается с
ssk_и показывается только один раз — сразу сохраните его в хранилище секретов. Новым аккаунтам дарим бесплатный кредит, чтобы попробовать.Терминалexport SHAPESODA_KEY="ssk_your_key_here" -
Отправьте картинку
Передайте байты изображения телом запроса, а параметры — в строке запроса:
Тело запроса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, параметры — обычными полями:Multipartcurl -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 -
Прочитайте ответ
При успехе приходит
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")
}
Векторизация#
Превращает одно растровое изображение в 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.
| Параметр | Значения | Описание |
|---|---|---|
| Основные | ||
preset | web print cut laser embroidery | Готовые настройки под задачу, по умолчанию web. См. Пресеты. |
format | svg pdf eps dxf png | Формат результата или список через запятую (svg,pdf) за 1 кредит. По умолчанию — формат пресета. См. Форматы результата. |
image_type | auto artwork artwork_sharp photo | Тип картинки. artwork — логотипы, иконки, иллюстрации со сглаженными краями; artwork_sharp — жёсткие пиксельные края без сглаживания (пиксель-арт, скриншоты); photo — фотографии и сложные изображения. |
quality | auto high medium low | Качество исходника. low — для размытых, шумных или сильно сжатых JPEG. |
detail | auto low medium high | Детализация результата. Для фотографий. |
mode | live test preview | test — бесплатный результат с водяным знаком для отладки интеграции (Тестовый режим). preview — бесплатный PNG-предпросмотр до 1 Мп, векторы потом открываются за 1 кредит (Предпросмотр). |
retain | false true | true — сохранить и результат на один формат на 24 часа. См. Хранение результата. |
retention_days | 1–30 | Хранить результат столько дней вместо 24 часов; бесплатно. См. Срок хранения. |
image_url | URL | Адрес публичной картинки вместо файла. См. Картинка по ссылке (включаем постепенно). |
| Обработка | ||
colors | auto unlimited 2–32 | Число цветов (прежний параметр). Число ограничивает палитру (2 — двухцветный результат); unlimited сохраняет все найденные цвета. По умолчанию auto. Для более тонкого предела — max_colors. |
max_colors | 2–256 | Максимум цветов; движок может взять меньше. Не сочетается с colors и palette. |
palette | 2–256 × #rrggbb | Своя палитра через запятую — в результате только эти цвета. В строке запроса пишите # как %23. Не сочетается с colors и max_colors. |
palette_tolerance | 0–100 | С palette: насколько цвет картинки (CIE76 ΔE) может отличаться от цвета палитры, чтобы притянуться к нему; более далёкие цвета остаются своими. |
min_area | 0–10000 | Наименьшая оставляемая фигура, в px²; мельче — отбрасываются. Перекрывает значение пресета, в том числе для фотографий. |
max_pixels | 10000–4200000 | Уменьшить исходник до такого числа пикселей перед трассировкой: быстрее и меньше узлов. Коэффициент придёт в X-Input-Scale. |
curve_tolerance | 0.01–10 | Допуск подгонки кривых в пикселях исходника (по умолчанию около 0.5). Меньше — контур точнее, узлов больше; больше — узлов меньше. Также задаёт smoothness, если вы его не передали. |
smoothness | 1–10 | Сглаживание контуров: больше — глаже и меньше узлов. |
corners | sharp smooth | Оставлять углы острыми или скруглять. По умолчанию — по автоматическому анализу. |
denoise | 0–3 | Шумоподавление перед трассировкой (артефакты JPEG, зерно): 0 — выключено, 3 — сильнейшее. |
| Рисование | ||
draw | fill stroke stroke_edges | fill — залитые фигуры; stroke — контур каждой фигуры без заливки (лазер, гравировка); stroke_edges — только границы между фигурами, каждая один раз, поэтому общий край не режется дважды. По умолчанию — из пресета. |
layers | stacked cutouts cutouts_grouped | Фигуры лежат друг на друге, вырезаны друг из друга (без наложений) или вырезаны и сгруппированы по цвету. По умолчанию — из пресета. |
group_by | none color layer | Группы в результате: color — группа на каждый цвет (фигуры вырезаны друг из друга); layer — по глубине вложенности (layer_0 — фон). SVG <g id>, слои PDF, блоки EPS, слои DXF. По умолчанию SVG группирует по цвету только для cutouts_grouped, DXF всегда кладёт фигуры на слои по цвету, у PDF и EPS групп нет. |
| Кривые и обводка | ||
curves | all lines lines_arcs | Допустимые сегменты: all — кривые и отрезки; lines — только отрезки (для станков без кривых); lines_arcs — в DXF отрезки и дуги, в остальных форматах как lines. |
stroke_width | 0.01–100 | Толщина обводки; нужен draw=stroke или stroke_edges. В единицах результата или, при stroke_scaling=scale, в пикселях исходника. По умолчанию — волосяная линия. |
stroke_color | #rrggbb | Цвет обводки вместо цвета каждой фигуры. Условие то же, что у stroke_width. |
stroke_scaling | fixed scale | fixed (по умолчанию) — толщина остаётся в единицах результата при изменении размера; scale — толщина в пикселях исходника и масштабируется вместе с рисунком. |
| Заполнение щелей | ||
gap_filler | off on | on — подложка под заливками: каждая фигура обведена своим цветом, поэтому между соседями не просвечивают щели. Нужен draw=fill. По умолчанию off. |
gap_filler_width | 0–10 | Толщина обводки подложки в пикселях исходника (по умолчанию 1). Нужен gap_filler=on. |
| Размер | ||
width, height | число > 0 | Размер результата в unit. Одна сторона — вторая по пропорциям; обе — лист такого размера, рисунок размещается по aspect. До 10 м. |
unit | px pt mm cm in | Единицы width/height, по умолчанию px. |
aspect | fit fill stretch | При заданных width и height: fit (по умолчанию) — рисунок целиком внутри листа; fill — закрывает лист, лишнее обрезается; stretch — растягивается по осям до точного размера. |
align_x, align_y | 0–1 | Положение рисунка на листе при aspect=fit или fill; по умолчанию 0.5 (по центру). |
scale | >0–100 | Размер результата как кратное размера исходника. Не сочетается с width/height. |
dpi | 1–2400 | Пикселей на дюйм в PNG, когда размер в pt, mm, cm или in; по умолчанию 300. |
| Для отдельных форматов | ||
svg_version | 1.1 1.0 tiny1.2 | Вариант SVG. tiny1.2 — профиль SVG Tiny для плоттеров и встраиваемых просмотрщиков: без прозрачности и трансформаций градиентов. |
svg_fixed_size | true false | true (по умолчанию) — width и height у корневого элемента; false — только viewBox, SVG подстраивается под контейнер. |
adobe_compat | false true | true — SVG, удобный для Adobe Illustrator: размер всегда задан, толщина обводки в координатах рисунка. |
dxf_curves | lines arcs splines | Кривые в DXF: ломаные (по умолчанию), ломаные с дугами или SPLINE. curves=lines и lines_arcs важнее. |
png_antialias | on 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-Scale | 422 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-Mode | live, 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 отдавал всегда.
| Пресет | Формат по умолчанию | Что делает | Для чего |
|---|---|---|---|
web | SVG | Цвета подбираются автоматически, фигуры лежат друг на друге. | Сайты, приложения, Figma |
print | Фигуры вырезаны друг из друга, без наложений. | Типографии, плакаты, документы | |
cut | SVG | 8 цветов; фигуры вырезаны и сгруппированы по цвету — по группе на цвет плёнки. | Плоттеры, наклейки, Cricut, Silhouette |
laser | SVG | 2 цвета, только контуры (draw=stroke). | Лазерная резка и гравировка; для CAD добавьте format=dxf |
embroidery | SVG | 12 цветов, фигуры вырезаны друг из друга, мелкие точки убраны. | Оцифровка вышивки |
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 | Для чего |
|---|---|---|---|
svg | image/svg+xml | Атрибуты width/height в единицах, тот же viewBox | Веб, Figma, Illustrator, Cricut Design Space |
pdf | application/pdf | Размер страницы (MediaBox) | Печать, документы, Illustrator |
eps | application/postscript | BoundingBox | Печатные RIP, CorelDRAW, старые редакторы |
dxf | application/dxf | Координаты в единицах и $INSUNITS | CAD, лазер и ЧПУ (кривые становятся ломаными) |
png | image/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 дней).
Хранение результата#
По умолчанию ничего не хранится: запрос на один формат возвращает файл и забывает картинку. Результат хранится 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
{
"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удаляет результат в любой момент.
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, не оба сразу. В тестовом режиме не работает.
Аккаунт и кредиты#
Возвращает аккаунт, которому принадлежит ключ, и его кредиты. Кредиты считаются по календарным месяцам (UTC).
{
"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, если лимита нет. Задаётся в меню аккаунта. |
Прайс-лист#
Те же цены, что в разделе Цены, в виде 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: сообщение — подсказка для человека, оно может меняться.
{
"error": {
"code": "too_many_pixels",
"message": "Human-readable description",
"request_id": "req_4f1c2a9e0b7d4c3a8e21"
}
}
| HTTP | Код | Что случилось | Что делать |
|---|---|---|---|
| 400 | bad_request | Неизвестный параметр или значение, несовместимые параметры, испорченная форма, файл не в поле image или одновременно файл и image_url. | Исправьте запрос. Повторять как есть бесполезно. |
| 400 | empty | В запросе нет картинки. | Передайте файл телом запроса или в поле image. |
| 400 | url_invalid | image_url — не http(s)-адрес на порту 80 или 443, содержит логин и пароль или длиннее 2048 символов. | Исправьте адрес. |
| 401 | unauthorized | Нет ключа, ключ неверный или отозван. | Проверьте заголовок Authorization: Bearer; при необходимости создайте новый ключ. |
| 402 | no_credits | Кредиты закончились (в том числе при открытии предпросмотра). | Купите пакет кредитов. Не повторяйте. |
| 402 | spend_limit | Достигнут ваш лимит расходов на месяц. | Поднимите или снимите лимит в меню аккаунта либо дождитесь следующего месяца. |
| 404 | not_found | Нет такого адреса, или ссылка загрузки/результата неизвестна, устарела или с неверным токеном. | Проверьте адрес. |
| 405 | method_not_allowed | Неверный HTTP-метод для этого адреса. | Используйте метод из справочника. |
| 413 | too_large | Файл (или картинка по image_url) больше 10 МБ. | Уменьшите или пересожмите картинку. |
| 415 | unsupported_format | Не PNG, JPEG, WebP, BMP без сжатия, GIF или TIFF (по содержимому). | Сначала сконвертируйте в PNG. |
| 422 | invalid_image | Файл повреждён, обрезан или не читается. | Пересохраните картинку. |
| 422 | too_many_pixels | Больше 40 Мп или картинке нужно слишком много памяти. | Уменьшите картинку. |
| 422 | url_blocked | image_url ведёт на частный, локальный или внутренний адрес. | Используйте публичный адрес. Не повторяйте. |
| 422 | url_unreachable | Картинку по image_url не удалось скачать. | Проверьте адрес или загрузите файл. |
| 429 | rate_limited | Слишком много запросов с этого ключа (или неудачных попыток входа с вашего IP). | Подождите Retry-After секунд и повторите. |
| 429 | daily_limit | Ключ исчерпал суточный лимит. | Дождитесь 00:00 UTC или используйте другой ключ. |
| 429 | preview_limit | Аккаунт сделал 200 предпросмотров (mode=preview) за сутки. | Подождите до 00:00 UTC (Retry-After) или используйте mode=live. |
| 429 | test_limit | Исчерпан суточный лимит тестового режима ключа или аккаунта. | Дождитесь 00:00 UTC (Retry-After) или используйте mode=live. |
| 500 | internal | Сбой на нашей стороне. Кредит не списывается. | Повторите один раз после паузы; если не помогло, пришлите нам request_id. |
| 503 | busy | Все обработчики заняты, очередь заполнена. | Повторите через Retry-After секунд (обычно 5). |
| 503 | timeout | Обработка заняла больше 60 секунд. Кредит не списывается. | Попробуйте картинку поменьше или меньше цветов. |
| 503 | storage_full | Хранилище результатов заполнено. Кредит не списывается. | Повторите через Retry-After секунд или попросите один формат. |
| 503 | url_unavailable | Вход по ссылке выключен или временно недоступен. | Загрузите файл. |
| 504 | url_timeout | Скачивание image_url заняло больше 10 секунд. Кредит не списывается. | Загрузите файл. |
Кредит резервируется в начале обработки и автоматически возвращается при ошибке — вы платите только за полученные результаты.
Ограничения частоты#
| Ограничение | По умолчанию | Ошибка |
|---|---|---|
| Запросов на ключ | 60 в минуту, пачкой — до 10 сразу | 429 rate_limited + Retry-After |
| Одновременных запросов на аккаунт | 5 сразу (в обработке и в очереди); шлите параллельно не больше 5 | 429 rate_limited + Retry-After: 2, без списания |
| Картинок на ключ в сутки | 5000, включая бесплатные повторы с другими параметрами (сброс в 00:00 UTC) | 429 daily_limit + Retry-After |
| Неудачных авторизаций с IP | 30 в минуту | 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.
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.
Бесплатно: 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 предложит подключить аккаунт.
{
"mcpServers": {
"shapesoda": { "url": "https://shapesoda.com/mcp" }
}
}
С ключом API Cursor подставит ${env:SHAPESODA_KEY} из переменных окружения, и ключ не окажется в файле:
{
"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Показывает остаток бесплатных на этот месяц, доступные кредиты, баланс, расход за месяц и лимит расходов. Бесплатно.
Как агент передаёт файл#
Агенты обычно не умеют прикладывать двоичные файлы к вызову инструмента, поэтому большие картинки идут через короткоживущую ссылку загрузки:
- create_upload
возвращает
upload_idи секретную ссылкуupload_url. - Загрузка через curl
Агент выполняет полученную команду:
curl -sS -X PUT --data-binary @image.png "<upload_url>". Ссылка принимает один файл PNG, JPEG, WebP, BMP, GIF или TIFF и не требует другой авторизации. - 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 месяцев)
- Отмена в любой момент, кредиты действуют до конца тарифа
- Общий баланс с сайтом
- Неудачные запросы бесплатны
Нужен корпоративный тариф или больше 100 тысяч картинок в месяц? Напишите на [email protected] — обсудим цену за объём и лимиты.