Quickstart
Create a test key, convert your first image, then switch to a live key.
The Developer API opens soon. You can read the docs now; API keys arrive when it opens.
On this page
This guide converts one image with a free test key, then with a live key.
1. Create a test key
- Sign in at perfectvector.com and open the Developers area of your account.
- Create a key in test mode. The first time, you accept the API Terms.
- 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:
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.
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/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
{
"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):
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.
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
- Buy API credits in the Developers area of your account. See Credits.
- 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.
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). Set your HTTP client's timeout above the 120-second wait.
Python
Uses Requests (pip install requests).
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.
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: managing and rotating keys.
- Limits: one conversion at a time per account, and the rate limits.
- Errors: every error code and what to do.