{
  "openapi": "3.1.0",
  "info": {
    "title": "Shapesoda API",
    "version": "1.0.0",
    "summary": "Convert raster images (PNG, JPEG, WebP) into SVG vectors.",
    "description": "REST API and MCP server of Shapesoda (https://shapesoda.com). Every request is authenticated with an API key sent as `Authorization: Bearer <key>`; create keys in the account menu on shapesoda.com (a key is shown once). One successfully vectorized image costs 1 credit; failed requests are refunded automatically. Errors are JSON objects `{\"error\": {\"code\", \"message\", \"request_id\"}}` — branch on `code`, the `message` is a human-readable hint. Rate limits (defaults): 60 requests per minute per key with bursts of 10, 5,000 images per key per UTC day. Documentation: https://shapesoda.com/api",
    "termsOfService": "https://shapesoda.com/terms",
    "license": { "name": "Proprietary", "url": "https://shapesoda.com/terms" },
    "contact": { "name": "Shapesoda support", "email": "support@shapesoda.com", "url": "https://shapesoda.com/api" }
  },
  "externalDocs": { "description": "Developer documentation", "url": "https://shapesoda.com/api" },
  "servers": [{ "url": "https://shapesoda.com", "description": "Production" }],
  "security": [{ "bearerAuth": [] }],
  "tags": [
    { "name": "Vectorize", "description": "Raster to vector conversion." },
    { "name": "Account", "description": "Credits and usage of the account that owns the key." },
    { "name": "Plans", "description": "Public price list." },
    { "name": "MCP", "description": "Model Context Protocol server for AI agents and the short-lived upload/result links it hands out." }
  ],
  "paths": {
    "/v1/vectorize": {
      "post": {
        "operationId": "vectorize",
        "tags": ["Vectorize"],
        "summary": "Vectorize an image",
        "description": "Converts one PNG, JPEG or WebP image into SVG. Send either the raw image bytes as the body (parameters in the query string; `Content-Type` is ignored) or `multipart/form-data` with the file in the `image` field (parameters as form fields or in the query; form fields win). The format is detected from the file content. Limits: 10 MB, 4.2 megapixels, 4096 px per side; still images only. The call is synchronous and charges 1 credit on success.",
        "parameters": [
          { "$ref": "#/components/parameters/image_type" },
          { "$ref": "#/components/parameters/quality" },
          { "$ref": "#/components/parameters/colors" },
          { "$ref": "#/components/parameters/detail" }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "image/png": { "schema": { "type": "string", "contentMediaType": "image/png" } },
            "image/jpeg": { "schema": { "type": "string", "contentMediaType": "image/jpeg" } },
            "image/webp": { "schema": { "type": "string", "contentMediaType": "image/webp" } },
            "application/octet-stream": { "schema": { "type": "string", "contentMediaType": "application/octet-stream" } },
            "multipart/form-data": {
              "schema": {
                "type": "object",
                "required": ["image"],
                "properties": {
                  "image": { "type": "string", "contentMediaType": "application/octet-stream", "description": "The image file (PNG, JPEG or WebP, up to 10 MB)." },
                  "image_type": { "$ref": "#/components/schemas/ImageType" },
                  "quality": { "$ref": "#/components/schemas/Quality" },
                  "colors": { "$ref": "#/components/schemas/Colors" },
                  "detail": { "$ref": "#/components/schemas/Detail" }
                },
                "additionalProperties": false
              },
              "encoding": { "image": { "contentType": "image/png, image/jpeg, image/webp, application/octet-stream" } }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The SVG document.",
            "headers": {
              "X-Credits-Charged": { "$ref": "#/components/headers/X-Credits-Charged" },
              "X-Credits-Remaining": { "$ref": "#/components/headers/X-Credits-Remaining" },
              "X-Request-Id": { "$ref": "#/components/headers/X-Request-Id" }
            },
            "content": { "image/svg+xml": { "schema": { "type": "string" } } }
          },
          "400": { "$ref": "#/components/responses/BadRequest" },
          "401": { "$ref": "#/components/responses/Unauthorized" },
          "402": { "$ref": "#/components/responses/PaymentRequired" },
          "405": { "$ref": "#/components/responses/MethodNotAllowed" },
          "413": { "$ref": "#/components/responses/TooLarge" },
          "415": { "$ref": "#/components/responses/UnsupportedFormat" },
          "422": { "$ref": "#/components/responses/Unprocessable" },
          "429": { "$ref": "#/components/responses/TooManyRequests" },
          "500": { "$ref": "#/components/responses/Internal" },
          "503": { "$ref": "#/components/responses/Unavailable" }
        }
      }
    },
    "/v1/account": {
      "get": {
        "operationId": "getAccount",
        "tags": ["Account"],
        "summary": "Get account and credits",
        "description": "Returns the account the key belongs to and its credits. Credits are counted per calendar month (UTC). Free and not counted against the per-key rate limit.",
        "responses": {
          "200": {
            "description": "Account and credits.",
            "headers": { "X-Request-Id": { "$ref": "#/components/headers/X-Request-Id" } },
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/AccountResponse" },
                "example": { "account": { "id": "acc_3f9c1e7a2b4d6e80", "name": "you@example.com" }, "credits": { "balance": 97, "quota": 0, "quota_used": 0, "quota_left": 0, "spent_month": 3, "spend_limit": null, "available": 97 } }
              }
            }
          },
          "401": { "$ref": "#/components/responses/Unauthorized" },
          "405": { "$ref": "#/components/responses/MethodNotAllowed" },
          "429": { "$ref": "#/components/responses/TooManyRequests" }
        }
      }
    },
    "/v1/plans": {
      "get": {
        "operationId": "getPlans",
        "tags": ["Plans"],
        "summary": "List prices",
        "description": "Public price list: website packs and subscriptions, and API credit packs (`api_packs`). No authentication.",
        "security": [],
        "responses": {
          "200": {
            "description": "Price list.",
            "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Plans" } } }
          }
        }
      }
    },
    "/mcp": {
      "post": {
        "operationId": "mcp",
        "tags": ["MCP"],
        "summary": "MCP server (Streamable HTTP)",
        "description": "Model Context Protocol endpoint for AI agents (Claude Code, Cursor and other clients with remote MCP support). Stateless Streamable HTTP: each POST carries one JSON-RPC 2.0 message and gets a JSON response (no SSE stream; notifications get `202`). Authenticated with the same API key; shares credits and rate limits with the REST API. Supported protocol versions: 2025-11-25, 2025-06-18, 2025-03-26. Requests with a browser `Origin` that is not allowed get `403 forbidden`.\n\nTools:\n- `create_upload` — returns `upload_id` and a one-time `upload_url` (valid 10 minutes) to PUT a local file to.\n- `vectorize` — `upload_id` or `image_base64` (≤ 2 MB) plus the same options as `/v1/vectorize`; returns the SVG inline when it is under 60 KB, a `download_url` valid for 1 hour, `width`, `height`, `svg_bytes` and `credits_remaining`. Uses 1 credit.\n- `get_usage` — credits available, balance, monthly quota and spending limit.\n\nSetup: `claude mcp add --transport http shapesoda https://shapesoda.com/mcp --header \"Authorization: Bearer $SHAPESODA_KEY\"`.",
        "externalDocs": { "url": "https://shapesoda.com/api#mcp" },
        "parameters": [
          { "name": "MCP-Protocol-Version", "in": "header", "required": false, "schema": { "type": "string", "enum": ["2025-11-25", "2025-06-18", "2025-03-26"] } }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": { "$ref": "#/components/schemas/JsonRpcRequest" },
              "example": { "jsonrpc": "2.0", "id": 1, "method": "tools/call", "params": { "name": "get_usage", "arguments": {} } }
            }
          }
        },
        "responses": {
          "200": { "description": "JSON-RPC 2.0 response (tool errors are reported inside `result` with `isError: true`).", "content": { "application/json": { "schema": { "type": "object", "required": ["jsonrpc"], "properties": { "jsonrpc": { "const": "2.0" }, "id": { "type": ["string", "integer", "null"] }, "result": {}, "error": { "type": "object", "properties": { "code": { "type": "integer" }, "message": { "type": "string" } } } } } } } },
          "202": { "description": "Notification accepted (no body)." },
          "400": { "$ref": "#/components/responses/BadRequest" },
          "401": { "$ref": "#/components/responses/Unauthorized" },
          "403": { "$ref": "#/components/responses/Forbidden" },
          "405": { "$ref": "#/components/responses/MethodNotAllowed" },
          "413": { "$ref": "#/components/responses/TooLarge" },
          "429": { "$ref": "#/components/responses/TooManyRequests" }
        }
      }
    },
    "/v1/uploads/{upload_id}": {
      "put": {
        "operationId": "putUpload",
        "tags": ["MCP"],
        "summary": "Upload a file to an MCP upload link",
        "description": "Capability URL returned by the MCP tool `create_upload` (as `upload_url`). The secret `token` in the query string is the only credential — no Authorization header is needed. Accepts one PNG, JPEG or WebP file up to 10 MB as the raw body, once; the link expires after 10 minutes. Then call the `vectorize` tool with the `upload_id`.",
        "security": [],
        "parameters": [
          { "name": "upload_id", "in": "path", "required": true, "schema": { "type": "string", "pattern": "^upl_[a-f0-9]{24}$" } },
          { "name": "token", "in": "query", "required": true, "description": "Secret token from `upload_url`.", "schema": { "type": "string" } }
        ],
        "requestBody": {
          "required": true,
          "content": { "application/octet-stream": { "schema": { "type": "string", "contentMediaType": "application/octet-stream" } } }
        },
        "responses": {
          "200": {
            "description": "Upload stored.",
            "content": { "application/json": { "schema": { "type": "object", "required": ["upload_id", "bytes"], "properties": { "upload_id": { "type": "string" }, "bytes": { "type": "integer" } } } } }
          },
          "400": { "$ref": "#/components/responses/BadRequest" },
          "404": { "$ref": "#/components/responses/NotFound" },
          "405": { "$ref": "#/components/responses/MethodNotAllowed" },
          "413": { "$ref": "#/components/responses/TooLarge" },
          "415": { "$ref": "#/components/responses/UnsupportedFormat" },
          "503": { "$ref": "#/components/responses/Unavailable" }
        }
      }
    },
    "/v1/results/{result_id}": {
      "get": {
        "operationId": "getResult",
        "tags": ["MCP"],
        "summary": "Download an MCP result",
        "description": "Capability URL returned by the MCP tool `vectorize` (as `download_url`). The secret `token` in the query string is the only credential. Valid for 1 hour; served as an attachment `vector.svg`.",
        "security": [],
        "parameters": [
          { "name": "result_id", "in": "path", "required": true, "schema": { "type": "string", "pattern": "^res_[a-f0-9]{24}$" } },
          { "name": "token", "in": "query", "required": true, "description": "Secret token from `download_url`.", "schema": { "type": "string" } }
        ],
        "responses": {
          "200": { "description": "The SVG document.", "content": { "image/svg+xml": { "schema": { "type": "string" } } } },
          "404": { "$ref": "#/components/responses/NotFound" },
          "405": { "$ref": "#/components/responses/MethodNotAllowed" }
        }
      }
    }
  },
  "components": {
    "securitySchemes": {
      "bearerAuth": { "type": "http", "scheme": "bearer", "description": "API key from the account menu on shapesoda.com, e.g. `Authorization: Bearer ssk_…`." }
    },
    "parameters": {
      "image_type": { "name": "image_type", "in": "query", "required": false, "description": "Kind of image: `artwork` (smooth, anti-aliased edges), `artwork_sharp` (hard pixel edges), `photo`.", "schema": { "$ref": "#/components/schemas/ImageType" } },
      "quality": { "name": "quality", "in": "query", "required": false, "description": "Input quality: `low` for blurry, noisy or heavily compressed images.", "schema": { "$ref": "#/components/schemas/Quality" } },
      "colors": { "name": "colors", "in": "query", "required": false, "description": "Number of colors: `auto`, `unlimited` or an integer from 2 to 32.", "schema": { "$ref": "#/components/schemas/Colors" } },
      "detail": { "name": "detail", "in": "query", "required": false, "description": "Output detail (applies to photos).", "schema": { "$ref": "#/components/schemas/Detail" } }
    },
    "headers": {
      "X-Request-Id": { "description": "Unique request ID; also returned in error bodies.", "schema": { "type": "string", "pattern": "^req_[a-f0-9]{20}$" } },
      "X-Credits-Charged": { "description": "Credits charged by this request (1).", "schema": { "type": "integer" } },
      "X-Credits-Remaining": { "description": "Images that can still be processed right now.", "schema": { "type": "integer" } },
      "Retry-After": { "description": "Seconds to wait before retrying.", "schema": { "type": "integer" } }
    },
    "schemas": {
      "ImageType": { "type": "string", "enum": ["auto", "artwork", "artwork_sharp", "photo"], "default": "auto" },
      "Quality": { "type": "string", "enum": ["auto", "high", "medium", "low"], "default": "auto" },
      "Colors": {
        "description": "`auto`, `unlimited`, or an integer 2–32.",
        "default": "auto",
        "oneOf": [
          { "type": "string", "enum": ["auto", "unlimited"] },
          { "type": "integer", "minimum": 2, "maximum": 32 }
        ]
      },
      "Detail": { "type": "string", "enum": ["auto", "low", "medium", "high"], "default": "auto" },
      "Error": {
        "type": "object",
        "required": ["error"],
        "properties": {
          "error": {
            "type": "object",
            "required": ["code", "message", "request_id"],
            "properties": {
              "code": {
                "type": "string",
                "description": "Stable machine-readable error code.",
                "enum": ["bad_request", "empty", "unauthorized", "no_credits", "spend_limit", "forbidden", "not_found", "method_not_allowed", "too_large", "unsupported_format", "invalid_image", "too_many_pixels", "rate_limited", "daily_limit", "internal", "busy", "timeout"]
              },
              "message": { "type": "string", "description": "Human-readable description; may change or be localized." },
              "request_id": { "type": "string" }
            }
          }
        },
        "example": { "error": { "code": "too_many_pixels", "message": "Human-readable description", "request_id": "req_4f1c2a9e0b7d4c3a8e21" } }
      },
      "Credits": {
        "type": "object",
        "required": ["balance", "quota", "quota_used", "quota_left", "spent_month", "spend_limit", "available"],
        "properties": {
          "available": { "type": "integer", "description": "Images that can be processed right now: quota_left + balance, capped by what is left of spend_limit." },
          "balance": { "type": "integer", "description": "Purchased credits; never expire; shared with the website." },
          "quota": { "type": "integer", "description": "Monthly included credits (0 for pay-as-you-go accounts)." },
          "quota_used": { "type": "integer" },
          "quota_left": { "type": "integer" },
          "spent_month": { "type": "integer", "description": "Credits used this calendar month (UTC)." },
          "spend_limit": { "type": ["integer", "null"], "description": "User-set monthly cap in credits; null means no cap." }
        }
      },
      "AccountResponse": {
        "type": "object",
        "required": ["account", "credits"],
        "properties": {
          "account": { "type": "object", "required": ["id", "name"], "properties": { "id": { "type": "string" }, "name": { "type": "string" } } },
          "credits": { "$ref": "#/components/schemas/Credits" }
        }
      },
      "Pack": {
        "type": "object",
        "required": ["id", "credits", "price", "per_image"],
        "properties": {
          "id": { "type": "string" },
          "credits": { "type": "integer" },
          "price": { "type": "string", "examples": ["$12"] },
          "per_image": { "type": "string", "examples": ["$0.12"] },
          "popular": { "type": "boolean" },
          "best_value": { "type": "boolean" }
        }
      },
      "Plans": {
        "type": "object",
        "required": ["packs", "api_packs", "payments_enabled"],
        "properties": {
          "packs": { "type": "array", "items": { "$ref": "#/components/schemas/Pack" } },
          "subscriptions": { "type": "array", "items": { "type": "object" } },
          "api_packs": { "type": "array", "items": { "$ref": "#/components/schemas/Pack" } },
          "credits_never_expire": { "type": "boolean" },
          "redownload_free_days": { "type": "integer" },
          "payments_enabled": { "type": "boolean" },
          "enterprise_contact": { "type": "string" },
          "signup_credits": { "type": "integer" }
        }
      },
      "JsonRpcRequest": {
        "type": "object",
        "required": ["jsonrpc", "method"],
        "properties": {
          "jsonrpc": { "const": "2.0" },
          "id": { "type": ["string", "integer"] },
          "method": { "type": "string", "examples": ["initialize", "tools/list", "tools/call", "ping"] },
          "params": { "type": "object" }
        }
      }
    },
    "responses": {
      "BadRequest": { "description": "`bad_request` (unknown parameter or value, malformed form) or `empty` (no image).", "headers": { "X-Request-Id": { "$ref": "#/components/headers/X-Request-Id" } }, "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } },
      "Unauthorized": { "description": "`unauthorized`: missing, malformed or revoked API key.", "headers": { "X-Request-Id": { "$ref": "#/components/headers/X-Request-Id" }, "WWW-Authenticate": { "schema": { "type": "string" } } }, "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } },
      "PaymentRequired": { "description": "`no_credits` (no credits left) or `spend_limit` (your monthly spending limit is reached).", "headers": { "X-Request-Id": { "$ref": "#/components/headers/X-Request-Id" } }, "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } },
      "Forbidden": { "description": "`forbidden`: browser request from an Origin that is not allowed.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } },
      "NotFound": { "description": "`not_found`: unknown path, or an expired or invalid link.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } },
      "MethodNotAllowed": { "description": "`method_not_allowed`.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } },
      "TooLarge": { "description": "`too_large`: the file is larger than 10 MB.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } },
      "UnsupportedFormat": { "description": "`unsupported_format`: not a PNG, JPEG or WebP file.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } },
      "Unprocessable": { "description": "`invalid_image` (damaged, animated or undecodable file) or `too_many_pixels` (over 4.2 MP or 4096 px per side).", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } },
      "TooManyRequests": { "description": "`rate_limited` (retry after `Retry-After` seconds) or `daily_limit` (daily cap per key; resets at 00:00 UTC).", "headers": { "Retry-After": { "$ref": "#/components/headers/Retry-After" } }, "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } },
      "Internal": { "description": "`internal`: unexpected error; not charged.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } },
      "Unavailable": { "description": "`busy` (queue full; retry after `Retry-After` seconds) or `timeout` (processing took longer than 60 s; not charged).", "headers": { "Retry-After": { "$ref": "#/components/headers/Retry-After" } }, "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } }
    }
  }
}
