# Quickstart

Create a test key, convert your first image, then switch to a live key.

> **Coming soon:** The Developer API opens soon. You can read the docs now; API keys arrive when it opens.

This guide converts one image with a free test key, then with a live key.

## 1. Create a test key

1. Sign in at perfectvector.com and open the [Developers area of your account](/account/developers).
2. Create a key in **test** mode. The first time, you accept the [API Terms](/docs/api/terms).
3. Copy the key now. It starts with `pv_test_`, and we show it only once.

Keep the key out of your code. The examples read it from an environment variable:

```bash
export PERFECTVECTOR_API_KEY="pv_test_..."
```

## 2. Convert an image

Send the image as a `multipart/form-data` field named `image`. PNG, JPEG and WebP work, up to 5 MiB
and 100 to 4096 pixels per side.

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

The request waits up to 120 seconds for the result. 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
}
```

A test key returns a fixed sample SVG instead of converting your image, and charges nothing.
`X-Credits-Would-Charge` shows what a live key would have paid. Everything else works as it does in
live mode: the key check, the file checks, the rate limits and the response shape.

## 3. Download the SVG

Download the result 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"
```

The result stays available for 24 hours. See [Results](/docs/api/results).

You can also get the SVG in the first response. Add `-H "Accept: image/svg+xml"`: when the conversion
succeeded within the wait, the body is the SVG itself. Otherwise it is the JSON object, so check the
`Content-Type` header.

## 4. Switch to a live key

1. Buy API credits in the [Developers area of your account](/account/developers). See [Credits](/docs/api/credits).
2. Create a key in **live** mode (`pv_live_…`) and use it instead of the test key.

A live conversion takes some seconds. If it has not finished within the wait, the answer is
`202 Accepted` with the same object, `"status": "queued"` or `"processing"`, and a `Location` header.
Poll `GET /v1/vectorizations/{id}` about every 2 seconds until the status is `succeeded` or
`failed`. See [Waiting and async](/docs/api/waiting-and-async).

A live request that succeeds uses 1 API credit (`X-Credits-Charged: 1`). A failed conversion answers
`201` with `"status": "failed"`, an `error` in its result, and no charge.

## Full examples

Each example creates a conversion, polls until it has finished, and saves the SVG. They send an
`Idempotency-Key`, so running them again after a network error never converts or charges twice (see
[Idempotency](/docs/api/idempotency)). Set your HTTP client's timeout above the 120-second wait.

### Python

Uses [Requests](https://requests.readthedocs.io/) (`pip install requests`).

```python
import os
import time

import requests

API = "https://api.perfectvector.com/v1"
AUTH = {"Authorization": f"Bearer {os.environ['PERFECTVECTOR_API_KEY']}"}


def check(response):
    """Raise with the API's error code and request ID on an error response."""
    if response.status_code >= 400:
        if response.headers.get("Content-Type", "").startswith("application/problem+json"):
            problem = response.json()
            raise RuntimeError(f"{problem['code']}: {problem['detail']} ({problem['request_id']})")
        response.raise_for_status()
    return response


with open("logo.png", "rb") as image:
    response = check(
        requests.post(
            f"{API}/vectorizations",
            headers={**AUTH, "Idempotency-Key": "quickstart-logo-0001"},
            files={"image": ("logo.png", image)},
            timeout=130,
        )
    )
conversion = response.json()

while conversion["status"] in ("queued", "processing"):
    time.sleep(2)
    conversion = check(
        requests.get(f"{API}/vectorizations/{conversion['id']}", headers=AUTH, timeout=30)
    ).json()

result = conversion["results"][0]
if result["status"] == "succeeded":
    svg = check(requests.get(result["result"]["url"], headers=AUTH, timeout=60))
    with open("logo.svg", "wb") as file:
        file.write(svg.content)
    print("Saved logo.svg; credits charged:", conversion["credits_charged"])
else:
    print("Conversion failed:", result["error"]["code"], result["error"]["message"])
```

### Node.js

Uses the built-in `fetch` (Node.js 18 or later). Save it as `quickstart.mjs`.

```js
import { readFile, writeFile } from "node:fs/promises";
import { setTimeout as sleep } from "node:timers/promises";

const API = "https://api.perfectvector.com/v1";
const AUTH = { Authorization: `Bearer ${process.env.PERFECTVECTOR_API_KEY}` };

async function check(response) {
  if (response.status >= 400) {
    const type = response.headers.get("content-type") ?? "";
    if (type.startsWith("application/problem+json")) {
      const problem = await response.json();
      throw new Error(`${problem.code}: ${problem.detail} (${problem.request_id})`);
    }
    throw new Error(`HTTP ${response.status}`);
  }
  return response;
}

const form = new FormData();
form.append("image", new Blob([await readFile("logo.png")]), "logo.png");

let response = await check(
  await fetch(`${API}/vectorizations`, {
    method: "POST",
    headers: { ...AUTH, "Idempotency-Key": "quickstart-logo-0001" },
    body: form,
    signal: AbortSignal.timeout(130_000),
  }),
);
let conversion = await response.json();

while (conversion.status === "queued" || conversion.status === "processing") {
  await sleep(2000);
  response = await check(
    await fetch(`${API}/vectorizations/${conversion.id}`, { headers: AUTH }),
  );
  conversion = await response.json();
}

const [result] = conversion.results;
if (result.status === "succeeded") {
  const svg = await check(await fetch(result.result.url, { headers: AUTH }));
  await writeFile("logo.svg", Buffer.from(await svg.arrayBuffer()));
  console.log("Saved logo.svg; credits charged:", conversion.credits_charged);
} else {
  console.log("Conversion failed:", result.error.code, result.error.message);
}
```

## Next steps

- [Authentication](/docs/api/authentication): managing and rotating keys.
- [Limits](/docs/api/limits): one conversion at a time per account, and the rate limits.
- [Errors](/docs/api/errors): every error code and what to do.

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