# PerfectVector API for AI agents

Keys, endpoints, prices, limits, errors and a full example, on one page for coding agents.

This page is for AI coding agents and the people who use them. It puts what you need to write an
integration in one place, and each section links to the guide with the details.

The PerfectVector API converts PNG, JPEG and WebP images into SVG files. You send an image with an
API key, and you get the SVG back.

Using an AI assistant instead? See the [MCP docs](/docs/mcp).

## Before you start

- **Base URL:** `https://api.perfectvector.com/v1`
- **An API key.** Someone signed in to perfectvector.com creates it on the
  [Developer API page of their account](/account/developers). An API key cannot manage keys.
- **A server or a script.** The API sends no CORS headers, so browsers block calls from web pages,
  and an API key in a web page would be public.

## API keys

| Prefix | Mode | What it does |
|---|---|---|
| `pv_test_` | test | Returns a fixed sample SVG and never uses credits. |
| `pv_live_` | live | Converts your image and uses API credits when the conversion succeeds. |

- **Start with a test key.** It runs the whole request at no cost: the key check, the file checks,
  the rate limits and the response shape work as they do in live mode.
- **Send the key only in the `Authorization` header:** `Authorization: Bearer <key>`. A key in the
  URL is ignored, and the request fails with `401 invalid_api_key`.
- **Keep the key out of your code.** The examples read it from the `PERFECTVECTOR_API_KEY`
  environment variable.
- We show a key once, when it is created. An account can have up to 10 active keys, live and test
  together.
- `GET /v1/account` shows which key you are using, your API balance, prices and limits.

See [Authentication](/docs/api/authentication) and [Test mode](/docs/api/test-mode).

## Endpoints

| Method | Path | What it does | Credits |
|---|---|---|---|
| `POST` | `/v1/vectorizations` | Convert one image. Waits for the result, or answers at once and lets you poll. | 1 on success |
| `GET` | `/v1/vectorizations/{id}` | The conversion's status and result links. | 0 |
| `GET` | `/v1/vectorizations/{id}/result` | Download the SVG. | 0 |
| `GET` | `/v1/vectorizations/{id}/results/{index}` | Download the SVG of one result, by its position (`0` in v1). | 0 |
| `DELETE` | `/v1/vectorizations/{id}` | Delete the conversion's image and SVG now. | 0 |
| `GET` | `/v1/account` | Your API balance, prices, limits and the key you used. | 0 |

There is no endpoint that lists your conversions. Keep the `id` of each conversion you create.

- **Requests:** `POST /v1/vectorizations` takes `multipart/form-data` with the field `image`
  (required) and the field `options` (optional; leave it out in v1). No other fields. The other
  endpoints take no body.
- **Responses:** JSON, except the SVG itself. Errors are `application/problem+json`.
- **IDs:** `vec_…` for conversions, `key_…` for keys, `req_…` for requests. Times are RFC 3339 in
  UTC.
- **Headers on every response:** `X-Request-ID` and `X-Credits-Charged`.
- **New fields:** we may add fields to responses. Ignore fields you do not know.

## A complete example

Convert `logo.png` with a test key. Send an `Idempotency-Key` with every `POST`, so a retry after a
network error never converts or charges twice:

```bash
curl https://api.perfectvector.com/v1/vectorizations \
  -H "Authorization: Bearer $PERFECTVECTOR_API_KEY" \
  -H "Idempotency-Key: logo-0001" \
  -F image=@logo.png
```

When the conversion has finished, the answer is `201 Created`:

```http
HTTP/1.1 201 Created
Content-Type: application/json
Location: /v1/vectorizations/vec_6Jd2LpQ8sV1nX4cR7tY0aB
X-Credits-Charged: 0
X-Credits-Would-Charge: 1
X-Request-ID: req_4Tq8WmX2r9LcB0vZs7KpY1
```

```json
{
  "id": "vec_6Jd2LpQ8sV1nX4cR7tY0aB",
  "object": "vectorization",
  "mode": "test",
  "status": "succeeded",
  "request_type": "single",
  "created_at": "2026-10-26T09:30:12Z",
  "completed_at": "2026-10-26T09:30:12Z",
  "input": { "filename": "logo.png", "width": 1024, "height": 1024, "bytes": 182311 },
  "results": [
    {
      "index": 0,
      "status": "succeeded",
      "options": {},
      "result": {
        "format": "svg",
        "url": "https://api.perfectvector.com/v1/vectorizations/vec_6Jd2LpQ8sV1nX4cR7tY0aB/results/0",
        "expires_at": "2026-10-27T09:30:12Z"
      },
      "error": null
    }
  ],
  "result_counts": { "total": 1, "succeeded": 1, "failed": 0, "pending": 0 },
  "credits_charged": 0,
  "deleted_at": null
}
```

Download the SVG with the same API key. `-OJ` saves it under the name the API gives it
(`logo.svg`):

```bash
curl -OJ https://api.perfectvector.com/v1/vectorizations/vec_6Jd2LpQ8sV1nX4cR7tY0aB/result \
  -H "Authorization: Bearer $PERFECTVECTOR_API_KEY"
```

A test key returns a fixed sample SVG and charges nothing; `X-Credits-Would-Charge` shows what a live
key would have paid. With a live key (`pv_live_…`) the same request converts your image and uses 1
API credit when it succeeds.

### Handle every answer

| Answer | What to do |
|---|---|
| `201` with `"status": "succeeded"` | Download `results[0].result.url` with the same key. The result stays available for 24 hours, so keep your own copy. |
| `202 Accepted` | Not finished within the wait. Poll `GET /v1/vectorizations/{id}` about every 2 seconds until `status` is `succeeded` or `failed`. |
| `201` with `"status": "failed"` | No charge. `results[0].error.code` says why. Retry with a new `Idempotency-Key`. |
| `4xx` or `5xx` | An error: see [Errors](#errors) below. |

- **Waiting:** `POST /v1/vectorizations` waits up to 120 seconds by default. Set your HTTP client's
  timeout above that, or send `Prefer: wait=N` (1 to 120 seconds) or `Prefer: respond-async`.
- **The SVG in one call:** send `Accept: image/svg+xml`. When the conversion succeeds within the
  wait, the `201` body is the SVG itself. Check the `Content-Type` header before you save the body.
- **Retries:** the same `Idempotency-Key` with the same image within 24 hours returns the same
  conversion, never charged twice. The same key with another image answers
  `409 idempotency_conflict`. A key is 8 to 128 letters, digits, `-` and `_`.

Complete Python and Node.js programs that create, poll and save are in the
[Quickstart](/docs/api/quickstart#full-examples). See also
[Waiting and async](/docs/api/waiting-and-async), [Idempotency](/docs/api/idempotency) and
[Results](/docs/api/results).

## Prices

- A live request uses **1 API credit when its conversion succeeds**. `GET /v1/account` shows the
  current price in `pricing.credits_per_request`.
- **Free:** failed and timed-out conversions; requests the API refuses, including `402`, `429` and
  `503` answers; status checks, `GET /v1/account` and `DELETE`; downloading a result again while it
  is available; repeating a request with the same `Idempotency-Key` within 24 hours; every call with
  a test key.
- **API credits** are separate from the credits used on the website, and the two never convert into
  each other. They are bought as one-time packs on the Developer API page, with no subscription and
  no automatic top-up.
- There is no overdraft: when your available credits are lower than the price,
  `POST /v1/vectorizations` answers `402 insufficient_credits` before any work starts.

The current API credit packs:

API credits are separate from website credits. A live API request uses 1 API credit only when its conversion succeeds; test keys are free.
During the beta, every pack is 20% off at checkout; the regular price is listed for reference.

| Pack | API credits | Regular price | Price at checkout | Per conversion | Packs per order | Credit expiry |
| --- | --- | --- | --- | --- | --- | --- |
| Starter | 10 | USD 5.00 | USD 4.00 | USD 0.40 | up to 10 | 12 months after purchase |
| Bulk 100 | 100 | USD 20.00 | USD 16.00 | USD 0.16 | up to 10 | 12 months after purchase |
| Bulk 1,000 | 1000 | USD 100.00 | USD 80.00 | USD 0.08 | up to 10 | 12 months after purchase |

For 10000 API credits or more, email sales@perfectvector.com for custom pricing.

[API pricing](https://perfectvector.com/pricing/api)

Lemon Squeezy, our Merchant of Record, may add tax at checkout. See [Credits](/docs/api/credits).

## Limits

| Limit | Value | When you reach it |
|---|---|---|
| Conversions created per minute | 20 | `429 rate_limited` |
| `POST /v1/vectorizations` attempts per minute (every `POST`, also replays and refused requests) | about 40 | `429 rate_limited` |
| Other requests per minute (status, downloads, `DELETE`, `GET /v1/account`) | about 120 | `429 rate_limited` |
| Live conversions in progress at once | 1 (test keys: no limit) | `429 too_many_in_flight` |
| Active API keys | 10, live and test together | |

| File limit | Value |
|---|---|
| Formats | PNG, JPEG, WebP (checked from the file's content, not its name) |
| File size | 5 MiB (5,242,880 bytes) |
| Image size | 100 to 4096 pixels on each side |
| Frames | One. Animated images are refused. |

- Limits apply to the whole account, across all of its keys. Test keys have their own counters.
- **One live conversion at a time.** Send the next image when the first one has finished. Requests
  are not queued for you.
- On `429` and `503`, wait the `Retry-After` seconds, then retry with the same `Idempotency-Key`.
- `GET /v1/account` returns your current limits in `limits`. If you need more, email
  contact@perfectvector.com.

See [Limits](/docs/api/limits).

## Errors

Every error is [RFC 9457](https://www.rfc-editor.org/rfc/rfc9457) problem details, with
`Content-Type: application/problem+json`. **Branch on `code`**, never on `title` or `detail`. Errors
are never charged.

```json
{
  "type": "https://perfectvector.com/docs/api/errors#insufficient_credits",
  "title": "Not enough API credits",
  "status": 402,
  "detail": "This request needs 1 API credit. Available: 0.",
  "code": "insufficient_credits",
  "request_id": "req_4Tq8WmX2r9LcB0vZs7KpY1",
  "credits_required": 1,
  "credits_available": 0
}
```

| `code` | HTTP | Retry? |
|---|---|---|
| `invalid_request` | 400 | After fixing the request |
| `invalid_api_key` | 401 | After fixing the key |
| `insufficient_credits` | 402 | After buying credits |
| `account_suspended` | 403 | No; contact us |
| `access_not_granted` | 403 | No; contact us |
| `not_found` | 404 | No |
| `method_not_allowed` | 405 | No |
| `idempotency_conflict` | 409 | With a new key |
| `conversion_in_progress` | 409 | After it finishes |
| `result_not_ready` | 409 | After it succeeds |
| `result_expired` | 410 | No |
| `file_too_large` | 413 | With a smaller file |
| `unsupported_media_type` | 415 | With PNG, JPEG or WebP |
| `invalid_image` | 422 | With another image |
| `rate_limited` | 429 | After `Retry-After` |
| `too_many_in_flight` | 429 | When the running one finishes |
| `internal_error` | 500 | Yes, with the same `Idempotency-Key` |
| `service_busy` | 503 | After `Retry-After` |
| `api_paused` | 503 | After `Retry-After` |

A **failed conversion is not an error**: it answers `201` or `200` with `"status": "failed"`, and
costs nothing.

| `error.code` | What to do |
|---|---|
| `processing_failed` | Check the image; another image may work. |
| `timeout` | Retry with a new `Idempotency-Key`. |
| `provider_unavailable` | Retry later with a new `Idempotency-Key`. |

A `403` or `429` whose body is not problem details comes from our network edge, not from the API.
Slow down and retry later. See [Errors](/docs/api/errors) for every code.

## Privacy

From the [API Privacy Notice](/docs/api/privacy#3-why-we-use-it): "Your images are never used to
train AI models and never sold."

- Results stay available for 24 hours after the conversion completes. We delete images and results
  from storage within 48 hours.
- `DELETE /v1/vectorizations/{id}` deletes a conversion's image and SVG at once.
- With a test key, we check the image and then discard it.

## Terms and support

- You accept the [API Terms](/docs/api/terms) when you create your first key or buy credits for the
  first time.
- Questions: contact@perfectvector.com. Please include the `request_id`.

[Site index](https://perfectvector.com/llms.txt) — available pages and descriptions.
