Быстрый старт#
Три шага до первого SVG. Базовый адрес — https://shapesoda.com, все запросы идут по HTTPS.
-
Получите ключ API
Войдите на shapesoda.com по почте или через Google, откройте меню аккаунта и выберите Ключи API → Новый ключ. Ключ начинается с
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-Credits-Remaining: 99 X-Request-Id: req_4f1c2a9e0b7d4c3a8e21Заголовок Значение X-Credits-ChargedСколько кредитов списал запрос. За успешную векторизацию — всегда 1; неудачные запросы не списываются.X-Credits-RemainingСколько картинок можно обработать прямо сейчас (баланс плюс включённые кредиты, с учётом вашего лимита расходов). X-Request-IdУникальный номер запроса. Возвращается и при ошибках — укажите его, если пишете в поддержку.
Авторизация#
В каждом запросе к API передавайте ключ в заголовке:
Authorization: Bearer ssk_…
- Создавайте, просматривайте и отзывайте ключи в меню аккаунта на shapesoda.com (до 10 действующих ключей на аккаунт). Отзыв срабатывает сразу.
- Без ключа, с неверным или отозванным ключом приходит
401 unauthorized. Частые неудачные попытки с одного IP-адреса ограничиваются. - Храните ключи на сервере. API не отдаёт заголовков CORS и не предназначен для вызова со страницы в браузере; не встраивайте ключ в код сайта или мобильного приложения.
- Все ключи аккаунта расходуют один баланс кредитов — тот же, что и на сайте.
Примеры кода#
Короткие примеры почти без зависимостей: превратить 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"))
}
Проверка баланса#
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. Запрос синхронный: ответ приходит, когда вектор готов, — обычно за несколько секунд (для самых больших картинок — до минуты).
Тело запроса#
Картинку можно передать двумя способами:
- Байтами — сам файл и есть тело запроса, параметры — в строке запроса. Заголовок
Content-Typeне учитывается. - Формой —
Content-Type: multipart/form-data, файл в полеimage; параметры — полями формы или в строке запроса (поля формы важнее). Файл в любом другом поле отклоняется.
Формат определяется по содержимому файла (сигнатуре), а не по имени или Content-Type.
Параметры#
Все параметры необязательны, по умолчанию — auto. Регистр важен; неизвестный параметр или значение — 400 bad_request.
| Параметр | Значения | Описание |
|---|---|---|
image_type | auto artwork artwork_sharp photo | Тип картинки. artwork — логотипы, иконки, иллюстрации со сглаженными краями; artwork_sharp — жёсткие пиксельные края без сглаживания (пиксель-арт, скриншоты); photo — фотографии и сложные изображения. |
quality | auto high medium low | Качество исходника. low — для размытых, шумных или сильно сжатых JPEG. |
colors | auto unlimited 2–32 | Число цветов в результате. Число ограничивает палитру (2 — двухцветный результат); unlimited сохраняет все найденные цвета. |
detail | auto low medium high | Детализация результата. Для фотографий. |
Ограничения входа#
| Ограничение | Значение | Ошибка |
|---|---|---|
| Форматы | PNG, JPEG, WebP (статичные; анимированные и многостраничные файлы отклоняются) | 415 unsupported_format |
| Размер файла | 10 МБ | 413 too_large |
| Разрешение | 4,2 мегапикселя и не больше 4096 px по каждой стороне | 422 too_many_pixels |
Большое разрешение не делает вектор лучше: для самой быстрой обработки уменьшайте картинку примерно до 2000 px по длинной стороне.
Ответ#
200 OK, в теле — SVG-документ, Content-Type: image/svg+xml; charset=utf-8 и заголовки X-Credits-Charged, X-Credits-Remaining и X-Request-Id (см. Быстрый старт). При любой ошибке в теле — JSON, см. Ошибки.
Аккаунт и кредиты#
Возвращает аккаунт, которому принадлежит ключ, и его кредиты. Кредиты считаются по календарным месяцам (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 | Купленные кредиты. Не сгорают и общие с сайтом. |
quota, quota_used, quota_left | Кредиты, включённые в тариф на месяц, если они есть (тратятся первыми). Для оплаты по факту — 0. |
spent_month | Кредиты, потраченные в этом календарном месяце (UTC). |
spend_limit | Ваш лимит расходов на месяц в кредитах или null, если лимита нет. Задаётся в меню аккаунта. |
Ошибки#
Ошибки приходят со стандартными кодами HTTP и JSON-телом с постоянным машинным кодом code. Обрабатывайте code, а не message: сообщение — подсказка для человека, оно может меняться.
{
"error": {
"code": "too_many_pixels",
"message": "Human-readable description",
"request_id": "req_4f1c2a9e0b7d4c3a8e21"
}
}
| HTTP | Код | Что случилось | Что делать |
|---|---|---|---|
| 400 | bad_request | Неизвестный параметр или значение, испорченная форма или файл не в поле image. | Исправьте запрос. Повторять как есть бесполезно. |
| 400 | empty | В запросе нет картинки. | Передайте файл телом запроса или в поле image. |
| 401 | unauthorized | Нет ключа, ключ неверный или отозван. | Проверьте заголовок Authorization: Bearer; при необходимости создайте новый ключ. |
| 402 | no_credits | Кредиты закончились. | Купите пакет кредитов. Не повторяйте. |
| 402 | spend_limit | Достигнут ваш лимит расходов на месяц. | Поднимите или снимите лимит в меню аккаунта либо дождитесь следующего месяца. |
| 404 | not_found | Нет такого адреса, или ссылка загрузки/результата устарела. | Проверьте адрес. |
| 405 | method_not_allowed | Неверный HTTP-метод для этого адреса. | Используйте метод из справочника. |
| 413 | too_large | Файл больше 10 МБ. | Уменьшите или пересожмите картинку. |
| 415 | unsupported_format | Не PNG, JPEG и не WebP (по содержимому). | Сначала сконвертируйте в PNG. |
| 422 | invalid_image | Файл повреждён, обрезан, анимирован или не читается. | Пересохраните картинку. |
| 422 | too_many_pixels | Больше 4,2 Мп или сторона длиннее 4096 px. | Уменьшите картинку. |
| 429 | rate_limited | Слишком много запросов с этого ключа (или неудачных попыток входа с вашего IP). | Подождите Retry-After секунд и повторите. |
| 429 | daily_limit | Ключ исчерпал суточный лимит. | Дождитесь 00:00 UTC или используйте другой ключ. |
| 500 | internal | Сбой на нашей стороне. Кредит не списывается. | Повторите один раз после паузы; если не помогло, пришлите нам request_id. |
| 503 | busy | Все обработчики заняты, очередь заполнена. | Повторите через Retry-After секунд (обычно 5). |
| 503 | timeout | Обработка заняла больше 60 секунд. Кредит не списывается. | Попробуйте картинку поменьше или меньше цветов. |
Кредит резервируется в начале обработки и автоматически возвращается при ошибке — вы платите только за полученные SVG.
Ограничения частоты#
| Ограничение | По умолчанию | Ошибка |
|---|---|---|
| Запросов на ключ | 60 в минуту, пачкой — до 10 сразу | 429 rate_limited + Retry-After |
| Картинок на ключ в сутки | 5000 (сброс в 00:00 UTC) | 429 daily_limit + Retry-After |
| Неудачных авторизаций с IP | 30 в минуту | 429 rate_limited + Retry-After |
Бюджет запросов ключа общий для REST API и MCP-сервера. GET /v1/account в него не входит. Нужно больше — напишите на [email protected].
Повторные запросы#
- Повторяйте только
rate_limitedиbusy(через время изRetry-After) и один разinternalс паузой. Остальные ошибки 4xx повторятся. - Повторы бесплатны: тот же запрос (та же картинка и те же параметры) в течение 24 часов повторно не оплачивается — в ответе
X-Credits-Charged: 0. Другая картинка или другие параметры — новый запрос. - Ставьте щедрый таймаут клиента — не меньше 90–120 секунд, чтобы не оборвать медленный запрос, когда картинка уже обработана и оплачена.
- Для пакетной обработки запускайте несколько запросов параллельно (2–4), а не десятки: всё сверх пачки получит
429.
async function vectorize(image, attempt = 0) {
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);
}
throw new Error(`${error.code}: ${error.message} (${error.request_id})`);
}
Безопасность и приватность#
- Ключи хранятся в виде хешей. Мы храним только хеш с секретом (HMAC-SHA-256) и короткий префикс ключа, поэтому ключ показывается один раз и не восстанавливается — его можно только отозвать и заменить.
- Лимит расходов задаёте вы. Месячный лимит в меню аккаунта действует для всех ключей, MCP-сервера и сайта: ни агент, ни утёкший ключ не потратят больше, чем вы разрешили.
- Картинки не хранятся. Запросы REST обрабатываются в памяти в изолированном процессе и сразу забываются; картинки не пишутся на диск и в базу и не используются для обучения моделей. Загрузки MCP живут в памяти не дольше 10 минут, результаты MCP — не дольше часа. Подробнее — в Политике конфиденциальности.
- SVG проверяется. Каждый результат перед отдачей сверяется со строгим белым списком (контуры, группы и градиенты с простыми цветами) — никаких скриптов, обработчиков событий, внешних ссылок и встроенного содержимого.
- Вход проверяется. Формат определяется по содержимому, разрешение — до декодирования (защита от «бомб»), каждая задача выполняется в отдельном процессе с ограничением по времени.
Спецификация 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 — с тем же ключом API, кредитами и лимитами, что и REST API.
Claude Code#
claude mcp add --transport http shapesoda https://shapesoda.com/mcp \
--header "Authorization: Bearer $SHAPESODA_KEY"
Cursor#
Добавьте сервер в ~/.cursor/mcp.json (или в .cursor/mcp.json проекта). Cursor подставит ${env:SHAPESODA_KEY} из переменных окружения, и ключ не окажется в файле.
{
"mcpServers": {
"shapesoda": {
"url": "https://shapesoda.com/mcp",
"headers": { "Authorization": "Bearer ${env:SHAPESODA_KEY}" }
}
}
}
Другие клиенты, которые поддерживают удалённые MCP-серверы (Streamable HTTP) со своими заголовками, подключаются так же: адрес https://shapesoda.com/mcp, заголовок Authorization: Bearer <ключ>. Сервер без состояния, отвечает JSON (без потока SSE) и поддерживает версии протокола 2025-11-25, 2025-06-18 и 2025-03-26.
Коннекторы для ChatGPT и Claude.ai (вход через OAuth вместо ключа) — скоро.
Инструменты#
create_uploadСоздаёт одноразовую ссылку для загрузки локального файла (действует 10 минут, до 10 МБ) и возвращает команду curl для загрузки.
vectorizeПревращает загруженный файл (upload_id) или небольшой image_base64 в SVG. Принимает те же параметры, что REST API. Списывает 1 кредит.
get_usageПоказывает доступные кредиты, баланс, расход за месяц и лимит расходов. Бесплатно.
Как агент передаёт файл#
Агенты обычно не умеют прикладывать двоичные файлы к вызову инструмента, поэтому большие картинки идут через короткоживущую ссылку загрузки:
- create_upload
возвращает
upload_idи секретную ссылкуupload_url. - Загрузка через curl
Агент выполняет полученную команду:
curl -sS -X PUT --data-binary @image.png "<upload_url>". Ссылка принимает один файл PNG, JPEG или WebP и не требует другой авторизации. - vectorize с upload_id
В результате — SVG прямо в ответе (если он меньше 60 КБ), ссылка
download_urlна час, размеры картинки иcredits_remaining.
Небольшую картинку (до 2 МБ) агент может передать сразу в image_base64, без загрузки.
Цены#
Пакеты кредитов с предоплатой. Один кредит — одна успешно векторизованная картинка через API, MCP или сайт.
- Кредиты не сгорают
- Без подписки
- Общий баланс с сайтом
- Неудачные запросы бесплатны
Онлайн-оплату сейчас подключаем. Нужен корпоративный тариф или больше 100 тысяч картинок? Напишите на [email protected] — обсудим цену за объём и лимиты.