Shapesoda API

REST API · MCP-сервер

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

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

Войдите на shapesoda.com, откройте меню аккаунта и выберите Ключи 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-Credits-Remaining: 99
X-Request-Id: req_4f1c2a9e0b7d4c3a8e21
Содержание

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

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

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

    Войдите на shapesoda.com по почте или через Google, откройте меню аккаунта и выберите Ключи API → Новый ключ. Ключ начинается с 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-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")
}

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

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

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

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

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

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

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

Параметры#

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

ПараметрЗначенияОписание
image_typeauto artwork artwork_sharp photoТип картинки. artwork — логотипы, иконки, иллюстрации со сглаженными краями; artwork_sharp — жёсткие пиксельные края без сглаживания (пиксель-арт, скриншоты); photo — фотографии и сложные изображения.
qualityauto high medium lowКачество исходника. low — для размытых, шумных или сильно сжатых JPEG.
colorsauto unlimited 2–32Число цветов в результате. Число ограничивает палитру (2 — двухцветный результат); unlimited сохраняет все найденные цвета.
detailauto 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, см. Ошибки.

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

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Купленные кредиты. Не сгорают и общие с сайтом.
quota, quota_used, quota_leftКредиты, включённые в тариф на месяц, если они есть (тратятся первыми). Для оплаты по факту — 0.
spent_monthКредиты, потраченные в этом календарном месяце (UTC).
spend_limitВаш лимит расходов на месяц в кредитах или null, если лимита нет. Задаётся в меню аккаунта.

Ошибки#

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

application/json
{
  "error": {
    "code": "too_many_pixels",
    "message": "Human-readable description",
    "request_id": "req_4f1c2a9e0b7d4c3a8e21"
  }
}
HTTPКодЧто случилосьЧто делать
400bad_requestНеизвестный параметр или значение, испорченная форма или файл не в поле image.Исправьте запрос. Повторять как есть бесполезно.
400emptyВ запросе нет картинки.Передайте файл телом запроса или в поле image.
401unauthorizedНет ключа, ключ неверный или отозван.Проверьте заголовок Authorization: Bearer; при необходимости создайте новый ключ.
402no_creditsКредиты закончились.Купите пакет кредитов. Не повторяйте.
402spend_limitДостигнут ваш лимит расходов на месяц.Поднимите или снимите лимит в меню аккаунта либо дождитесь следующего месяца.
404not_foundНет такого адреса, или ссылка загрузки/результата устарела.Проверьте адрес.
405method_not_allowedНеверный HTTP-метод для этого адреса.Используйте метод из справочника.
413too_largeФайл больше 10 МБ.Уменьшите или пересожмите картинку.
415unsupported_formatНе PNG, JPEG и не WebP (по содержимому).Сначала сконвертируйте в PNG.
422invalid_imageФайл повреждён, обрезан, анимирован или не читается.Пересохраните картинку.
422too_many_pixelsБольше 4,2 Мп или сторона длиннее 4096 px.Уменьшите картинку.
429rate_limitedСлишком много запросов с этого ключа (или неудачных попыток входа с вашего IP).Подождите Retry-After секунд и повторите.
429daily_limitКлюч исчерпал суточный лимит.Дождитесь 00:00 UTC или используйте другой ключ.
500internalСбой на нашей стороне. Кредит не списывается.Повторите один раз после паузы; если не помогло, пришлите нам request_id.
503busyВсе обработчики заняты, очередь заполнена.Повторите через Retry-After секунд (обычно 5).
503timeoutОбработка заняла больше 60 секунд. Кредит не списывается.Попробуйте картинку поменьше или меньше цветов.

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

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

ОграничениеПо умолчаниюОшибка
Запросов на ключ60 в минуту, пачкой — до 10 сразу429 rate_limited + Retry-After
Картинок на ключ в сутки5000 (сброс в 00:00 UTC)429 daily_limit + Retry-After
Неудачных авторизаций с IP30 в минуту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.
JavaScript · повтор по Retry-After
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.

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

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} из переменных окружения, и ключ не окажется в файле.

mcp.json
{
  "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

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

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

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

  1. create_upload

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

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

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

  3. vectorize с upload_id

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

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

Цены#

Пакеты кредитов с предоплатой. Один кредит — одна успешно векторизованная картинка через API, MCP или сайт.

  • Кредиты не сгорают
  • Без подписки
  • Общий баланс с сайтом
  • Неудачные запросы бесплатны
100
кредитов
$12
$0.12 за картинку
500
кредитов
$49
$0.098 за картинку
10,000
кредитов
$599
$0.06 за картинку
50,000
кредитов
$2,250
$0.045 за картинку

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