Picverce AI Public API v1.0.0
Run Picverce AI from your own server
Edit images with the same tools the website uses, or generate new ones from a prompt with 26 models. Jobs are asynchronous, so a slow model never becomes a timeout on your side.
API jobs draw from the same credit balance as your account. There is nothing separate to buy.
Base URL: https://api.picverce.com. Create a key in your account and start calling it.
Two ways to use it
Both go through the same endpoint. If you already have the picture and want it changed, that is a Tool. If you want a picture that does not exist yet, that is a Model.
Tools API
Run a Picverce AI product tool on an image you supply. You give it an image, it gives back a new one.
2 to 8 credits. Send type: "tool".
Models API
Pick a generation model and a prompt, optionally with a reference image. You give it words, it gives back an image.
3 to 14 credits. Send type: "generate".
Quickstart
1. Create a key
Go to Account, API Keys and click Create key. The secret is shown once and cannot be retrieved again, so copy it before closing the dialog.
Keys look like
pk_live_.... Apk_test_key behaves identically and is for development.Public API access comes with a paid plan or a credit pack. An active Basic, Standard or Premium subscription grants it, and so does any credit pack you have bought. See plans
2. Check your balance
curl curl https://api.picverce.com/v1/me \ -H "Authorization: Bearer pk_live_..."3. Create a job
curl curl -X POST https://api.picverce.com/v1/jobs \ -H "Authorization: Bearer pk_live_..." \ -H "Content-Type: application/json" \ -d '{ "type": "tool", "tool": "background_remover", "input": { "image_url": "https://example.com/product.jpg" } }'Answers in milliseconds with
status: "queued"and a job id.4. Poll until it finishes
curl curl https://api.picverce.com/v1/jobs/JOB_ID \ -H "Authorization: Bearer pk_live_..."Poll every couple of seconds until
statusreadssucceededorfailed. Results arrive inoutput.images.
The same thing in JavaScript
Node 18 or newer. Run this on your server, never in a browser.
const KEY = process.env.PICVERCE_API_KEY;
const BASE = 'https://api.picverce.com';
async function api(path, options = {}) {
const response = await fetch(BASE + path, {
...options,
headers: {
Authorization: `Bearer ${KEY}`,
'Content-Type': 'application/json',
...options.headers,
},
});
const body = await response.json();
if (!response.ok) {
throw new Error(`${body.error.code}: ${body.error.message}`);
}
return body;
}
async function runJob(payload) {
const job = await api('/v1/jobs', { method: 'POST', body: JSON.stringify(payload) });
while (true) {
const current = await api(`/v1/jobs/${job.id}`);
if (current.status === 'succeeded') return current.output.images;
if (current.status === 'failed') throw new Error(current.error.message);
await new Promise((r) => setTimeout(r, 2000));
}
}
const images = await runJob({
type: 'generate',
model: 'flux-schnell',
input: { prompt: 'a red bicycle against a white wall' },
});Keys belong on your server
This API sends no CORS headers, on purpose. A key pasted into browser JavaScript will not work from a web page.
Treat a key like a password. Keep it out of mobile apps and public repositories. If one leaks, revoke it in your account and it stops working on the next request.
Tools you can run today
Three tools accept jobs in v1.0.0. The full catalog is larger and readable from GET /v1/tools, with prices and input schemas for everything that is coming.
| Tool | Credits | What it does |
|---|---|---|
| background_remover | 2 | Cut the subject out, transparent PNG back |
| enhance | 2 | General cleanup, recovers detail and reduces noise |
| upscale | 2, 3 or 6 | Raise resolution 2x, 4x or 8x |
All 26 generation models accept jobs. List them with GET /v1/models and read capabilities rather than hardcoding ratios and resolutions, since they differ per model.
Credits
Credits are reserved when a job is created and charged only when it succeeds. A failed job is refunded in full and reports credits.charged: 0.
Because credits are held up front, your balance drops the moment you create a job. GET /v1/me reports reserved_open so you can see how much is held by work still running.
Errors
Every failure has the same shape, so one handler covers all of them.
{
"error": {
"code": "insufficient_credits",
"message": "This job costs 6 credits and your balance is 2.",
"request_id": "req_4f2a9c1b7e3d5a8c0b6e1f92"
}
}| HTTP | Code | Meaning |
|---|---|---|
| 401 | invalid_api_key | Missing, malformed, unknown or revoked key |
| 402 | insufficient_credits | Valid key, not enough balance |
| 403 | plan_upgrade_required | Your account does not include Public API access |
| 404 | invalid_tool | No such tool, or not a live job target yet |
| 404 | invalid_model | No such model |
| 404 | job_not_found | No job with that id under your account |
| 422 | validation_error | The request was understood and refused |
| 429 | rate_limited | Too many requests this minute |
| 500 | internal_error | Something failed on our side |
Invalid input is refused, not repaired
Ask for a ratio or a variation count a model does not support and you get a 422 naming the supported values. The website quietly substitutes something valid in the same situation, which is fine when a person is watching. For a machine caller it would mean paying for an image you did not ask for, so the API refuses instead.
Rate limits
60 requests per rolling minute, per key. Every authenticated endpoint counts. Each response carries your standing:
X-RateLimit-Limit: 60
X-RateLimit-Remaining: 58
X-RateLimit-Reset: 1787644920Over the limit you get a 429 with Retry-After in seconds. It is a rolling window, so 60 requests at the top of the minute does not earn you 60 more a second later.
Being straight with you about the ceiling: the counter currently runs per edge instance, so traffic spread across regions can go somewhat above 60 in a real minute. Design for 60 rather than leaning on it. This becomes a hard global limit in a later release.
Webhooks
Rather than polling, register an endpoint and we will POST a signed event when a job finishes. Add one in Account, API, Webhooks. Two events exist today: job.succeeded and job.failed.
POST https://api.yourcompany.com/picverce/webhook
Content-Type: application/json
User-Agent: Picverce-Webhooks/1.0
X-Picverce-Event: job.succeeded
X-Picverce-Delivery: 0f2b1d94-6a3c-4b1e-9d77-2c9e5a1b3d84
X-Picverce-Timestamp: 1756300000
X-Picverce-Signature: v1=8f3c1a...
{
"id": "evt_11111111222233334444555555555555s",
"object": "event",
"type": "job.succeeded",
"created_at": "2026-08-27T10:00:09.412Z",
"data": { "object": { ...the same job GET /v1/jobs/{id} returns... } }
}Verify the signature before you trust the body. The signing string is the timestamp, a full stop, then the raw body exactly as received.
const crypto = require('crypto');
// rawBody must be the raw bytes, not a re-serialized object.
function verify(rawBody, headers, secret) {
const timestamp = headers['x-picverce-timestamp'];
const received = headers['x-picverce-signature'];
if (!timestamp || !received) return false;
// The signature itself never expires, so this is what stops a replay.
if (Math.abs(Math.floor(Date.now() / 1000) - Number(timestamp)) > 300) return false;
const expected =
'v1=' +
crypto.createHmac('sha256', secret).update(timestamp + '.' + rawBody).digest('hex');
const a = Buffer.from(expected);
const b = Buffer.from(received);
return a.length === b.length && crypto.timingSafeEqual(a, b);
}Parse after you verify
Re-encoding JSON changes key order and whitespace, and the signature stops matching.
Expect duplicates
Delivery is at least once. The event id is stable per job and state, so use it as your idempotency key.
Answer fast
Queue the work and return 200. We time out at 10 seconds and retry twice, about 2 and 8 seconds later.
Endpoints must be https and publicly reachable. Private, loopback and cloud metadata addresses are refused when you register and again before every delivery. Redirects are not followed, so register the final URL. A 404 or 410 from your endpoint stops the retries.
Full reference
The complete OpenAPI 3.0 description covers every endpoint, field and error. Point a client generator at it rather than writing request types by hand.