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
All 18 catalog tools accept jobs. Prices and complete input schemas, including the preset lists for the three that take them, come from GET /v1/tools, so a settings UI can be built from the catalog rather than from this page.
| Tool | Credits | What it does |
|---|---|---|
| anime_enhance | 2 | Enhancement tuned for anime and illustrated artwork |
| background_remover | 2 | Cut the subject out, transparent PNG back |
| coloring_page | 3 | Redraw a photo as a printable black and white coloring page |
| colorize | 3 | Add colour to a black and white photograph |
| enhance | 2 | General cleanup, recovers detail and reduces noise |
| enhance_pro | 6 | Higher quality pass for images that need more |
| face_restore | 2 | Rebuild facial detail in low resolution portraits |
| hairstyle | 4 | Restyle the hair in a portrait, optionally recolouring it |
| object_remover | 2 | Erase an object and fill in what was behind it |
| outfit | 4 | Change the clothing in a portrait using a preset outfit |
| photo_to_anime | 3 | Redraw a photo as anime artwork |
| photo_to_cartoon | 3 | Redraw a photo in a cartoon style |
| photo_to_sketch | 3 | Redraw a photo as a pencil or ink sketch |
| restore | 4 | Repair scratches, fading and grain in old photos |
| sharpen | 2 | Recover focus in soft or slightly blurred images |
| text_clarity | 8 | Make small or smeared text legible |
| upscale | 2, 3 or 6 | Raise resolution 2x, 4x or 8x |
| watermark_remover | 3 | Remove an overlay and rebuild what was under it |
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.
Tool options
Every tool takes image_url and nothing else is required. These are the optional fields, and any tool not listed here takes no options at all. An unknown field name is refused by name rather than ignored.
| Field | Type | Default | Tools |
|---|---|---|---|
| face_enhance | boolean | false | enhance, enhance_pro, anime_enhance |
| scale | 2 | 4 | 8 | 2 | upscale |
| upscale | 1 | 2 | 4 | 2 | face_restore |
| face_upsample | boolean | true | face_restore |
| background_enhance | boolean | true | face_restore |
| codeformer_fidelity | number, 0 to 1 | 0.5 | face_restore |
| style | string, see the tables below | per tool | photo_to_anime, photo_to_cartoon, photo_to_sketch, coloring_page |
| mask_url | https URL | none | object_remover |
| haircut | string, 96 presets | none | hairstyle |
| hair_color | string, 31 colours | No change | hairstyle |
| outfit | string, 38 presets | none | outfit |
| garment_scope | full | top | bottom | dress | outerwear | full | outfit |
| outfit_color | string, 17 colours | No change | outfit |
| outfit_color_2 | string, 17 colours | No change | outfit |
| outfit_color_3 | string, 17 colours | No change | outfit |
| outfit_color_count | 1, 2 or 3 | 1 | outfit |
| aspect_ratio | auto and seven ratios | auto | hairstyle, outfit |
scale on upscale is the only option that changes the price: 2 credits at 2x, 3 at 4x, 6 at 8x. Everything else costs what the table above says whatever you pass.
Style keys
The four redraw tools take a style key. These are the complete lists. Keys are lowercase, and a key this API does not know is refused with a 422 naming what is supported, rather than quietly redrawn in the default style.
photo_to_anime9 keys, default ghiblighiblishonencyberpunkchibirealisticmangashojoseinenwebtoonphoto_to_cartoon24 keys, default disney-3ddisney-3dghiblichibi-3dkawaii-flatwatercolornekofantasy-gameshonen-comicwestern-comiccyborggothiccandyexpressive-3dimpressionistpop-artpencil-sketchink-mangafairytalefantasy-animeslice-of-lifepixel-artbotanicalgraphic-novelcyberpunkphoto_to_sketch18 keys, default pencil-sketchpencil-sketchcharcoal-sketchink-sketchcolored-pencil-sketchwatercolor-sketchballpoint-pen-sketchpastel-sketchfine-detail-sketchda-vinci-manuscriptbold-sketchminimalist-line-sketchfigure-quick-sketchcartoon-sketchconcept-sketchmanga-sketchaesthetic-sketchgraffiti-sketchink-wash-sketchcoloring_page30 keys, default classic-outlinetoddler-thickkids-simpleclassic-outlineadult-detailedfine-line-intricatehalloweenchristmaseastervalentinesbirthdaymothers-dayfathers-daysummer-holidayback-to-schoolthanksgivingdinosaursunicornspaceunderwaterfarm-animalsvehiclessweets-and-treatsfairy-talemandalabotanicalarchitecturalstained-glasspaper-cutwoodcutdoodle{ "type": "tool", "tool": "colorize",
"input": { "image_url": "https://example.com/photo.jpg" } }
{ "type": "tool", "tool": "photo_to_sketch",
"input": { "image_url": "https://example.com/photo.jpg",
"style": "charcoal-sketch" } }
{ "type": "tool", "tool": "face_restore",
"input": { "image_url": "https://example.com/portrait.jpg",
"upscale": 4, "codeformer_fidelity": 0.8,
"face_upsample": true, "background_enhance": false } }The three that take more than an image
Preset values are exact strings, and the complete lists are enums on GET /v1/tools/{id}. They are names, not slugs: it is Smart casual, never smart-casual. An unknown value is a 422 naming what is supported.
object_remover| mask_url | https URL | required |
hairstyle| haircut | 96 presets | required |
| hair_color | 31 colours | No change |
| aspect_ratio | auto, 1:1, 16:9, 9:16, 4:3, 3:4, 3:2, 2:3 | auto |
outfit| outfit | 38 presets | required |
| garment_scope | full, top, bottom, dress, outerwear | full |
| outfit_color | 17 colours | No change |
| outfit_color_2 | 17 colours | No change |
| outfit_color_3 | 17 colours | No change |
| outfit_color_count | 1, 2 or 3 | 1 |
| aspect_ratio | auto, 1:1, 16:9, 9:16, 4:3, 3:4, 3:2, 2:3 | auto |
object_remover takes a second image as mask_url, the same size as the source: white marks what to remove, black is kept. Both URLs are downloaded and checked the same way, so a mask on a private address is refused like any other.
hairstyle takes one preset per job. The website can apply several at once; the API does not, and a comma separated value is refused rather than guessed at. outfit takes up to three colours, and outfit_color_count decides how many are used. The top and bottom scopes only have two zones, so a count of 3 behaves as 2 there.
{ "type": "tool", "tool": "object_remover",
"input": { "image_url": "https://example.com/room.jpg",
"mask_url": "https://example.com/room-mask.png" } }
{ "type": "tool", "tool": "hairstyle",
"input": { "image_url": "https://example.com/portrait.jpg",
"haircut": "Blunt Bangs", "hair_color": "Auburn" } }
{ "type": "tool", "tool": "outfit",
"input": { "image_url": "https://example.com/portrait.jpg",
"outfit": "Smart casual", "garment_scope": "full",
"outfit_color": "Navy", "outfit_color_2": "Cream",
"outfit_color_count": 2 } }Generate settings
A generate job takes a model id and these six fields. Nothing else is accepted, and a field name that is close but wrong is refused rather than ignored: it is ratio, not aspect_ratio, and variations, not n.
| Field | Type | Default |
|---|---|---|
| prompt | string, up to 2000 characters | required |
| ratio | string | 1:1 |
| resolution | string | first entry in capabilities.resolutions |
| variations | integer, 1 or more | 1 |
| reference_image_url | https URL | none |
| enhance_prompt | boolean | false |
What each model supports differs, so read GET /v1/models rather than hardcoding a list. The notes below cover the things you cannot work out from a single model response.
Credits, and why more images do not cost more
A generate job costs the same whatever variations you ask for. The price is set by resolution alone, which is why the catalog gives each model a price per resolution rather than a single number. Four images at 1K cost what one image at 1K costs.
Resolution tokens are per model and are not a size in pixels. Some models use 1K through 4K, some use 1 MP, 2 MP and 4 MP, and a few use Low, Medium, High or Auto. Read capabilities.resolutions and credits.values together.
Ratios
Three sets across the catalog. A ratio a model does not support is a 422 naming the ones it does.
Most models
1:116:99:164:33:4gpt-image-2, chatgpt-1-5
1:13:22:3stable-diffusion-3-5
16:91:121:92:33:24:55:49:169:21There is one more accepted value: match_input_image, which keeps the shape of your reference image. It needs both a model whose capabilities.match_input_ratio is true and a reference_image_url in the same request. 13 models support it today.
Multiple images
There is no batch endpoint. Asking for more than one image is variations on a normal job, and the successful job comes back with that many URLs in output.images. Most models cap at 1. Today only gpt-image-2 and chatgpt-1-5 go higher, both to 4. Over a model's cap is a 422 naming the cap.
Reference images and prompt help
Required by qwen-edit-2511, qwen-layered. A job without one is a 422.
Not accepted by flux-schnell, imagen-4, imagen-4-ultra, recraft-v3. Sending one is a 422 rather than a silently ignored field.
Optional everywhere else. The same URL rules as image_url apply: public https, 5 MB or smaller.
enhance_prompt rewrites a short prompt into a longer one before generating. Supported by seedream-4, flux-2-flex, ideogram-3, ideogram-3-turbo only; asking for it elsewhere is a 422.
qwen-layered is the one model that will run with no prompt at all, given a reference image.
curl -s https://api.picverce.com/v1/models/nano-banana-2 \
-H "Authorization: Bearer $PICVERCE_API_KEY"
# capabilities.ratios which ratio strings are accepted
# capabilities.resolutions which resolution tokens are accepted
# capabilities.max_variations the ceiling for variations
# capabilities.reference_image unsupported | optional | required
# credits.values price per resolution token{ "type": "generate", "model": "flux-schnell",
"input": { "prompt": "a lighthouse in fog, 35mm" } }
{ "type": "generate", "model": "gpt-image-2",
"input": {
"prompt": "a lighthouse in fog, 35mm",
"ratio": "3:2",
"resolution": "High",
"variations": 4
} }
{ "type": "generate", "model": "nano-banana-2",
"input": {
"prompt": "the same room, repainted deep green",
"reference_image_url": "https://example.com/room.jpg",
"ratio": "match_input_image"
} }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 |
| 404 | invalid_model | No such model |
| 404 | job_not_found | No job with that id under your account |
| 409 | idempotency_conflict | That Idempotency-Key is in use for something else |
| 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.
Retries
Reads are safe to repeat. Creating a job is not, unless you say so: two creates are two jobs and two charges. Send an Idempotency-Key and the retry becomes safe.
curl -sS -X POST https://api.picverce.com/v1/jobs \
-H "Authorization: Bearer $PICVERCE_API_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: 8f1c1b8e-2d1a-4f9b-9c3f-0a6d4b2e7c51" \
-d '{"type":"tool","tool":"enhance",
"input":{"image_url":"https://example.com/photo.jpg"}}'The second call returns the first call's job with Idempotent-Replay: true in the headers, and charges nothing more. It comes back in whatever state the job has reached, so a replay a minute later may already say succeeded.
| Rule | What it means |
|---|---|
| Optional | Without the header every create makes a new job, as before |
| Up to 255 characters | Printable ASCII. A UUID per job is the simplest thing that works |
| Remembered 24 hours | After that the same key starts a new job |
| Scoped to one API key | Two keys on one account do not share a namespace |
| POST /v1/jobs only | Ignored on every other endpoint |
Two requests count as the same job when the validated request matches: the type, the tool or model id, and the validated input. Reordering fields, or leaving out a value that resolves to the same default, does not make them different. Sending a genuinely different body under a used key answers 409 idempotency_conflict, because replaying you a job you did not ask for would be worse than making you pick a new key.
Reusing a key while the first request is still in flight is the same 409, with a message saying so. Wait a few seconds and retry.
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, then keep retrying for up to 24 hours.
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.
An endpoint that was down for all three quick attempts does not lose the event. It is queued and tried again about 30 minutes later, then at 2, 6 and 12 hour intervals, stopping after 10 attempts or 24 hours. A deploy that takes your endpoint down for an hour therefore costs you nothing. Pausing an endpoint, or unsubscribing from an event, stops its queued retries too.
Claude & MCP
The REST API at api.picverce.com is the platform. MCP at mcp.picverce.com is the connector layer for Claude, Cursor, and other MCP clients. The hosted MCP server calls the same POST /v1/jobs endpoints with your API key, polls jobs when needed, and returns image URLs in tool results. Credits, rate limits, and idempotency behave the same as direct API use.
Manage connector setup from your Account MCP tab. Create a key on the Keys tab first.
https://mcp.picverce.com/mcpIn Claude web, open Settings, Connectors, add a custom connector named Picverce AI, paste the URL above, and set authentication to your pk_live_ API key as a Bearer token. The key never appears in chat; only the connector stores it.
claude mcp add --transport http --scope user picverce-ai https://mcp.picverce.com/mcpMCP tools (v1)
These are the tools Claude sees. Each maps to Public API jobs or catalog endpoints.
| Tool | What it does |
|---|---|
| upload_image | Stage chat or base64 image bytes on Picverce AI and get a public URL (no credits). |
| generate_image | Text to image with any catalog model, ratio, resolution, and variations. |
| edit_image | Run a catalog tool on an image URL (enhance, upscale, remove background, and more). |
| get_job | Poll job status and read output URLs when a job is still running. |
| list_tools | List runnable tools and credit costs. |
| list_models | List generation models and credit ranges. |
The MCP server sends an Idempotency-Key on every job it creates so Claude retries do not double-charge. Direct API callers still control their own keys on POST /v1/jobs.
Shopify and other platforms
MCP is for AI assistants in chat. A store or a SaaS product uses the REST API plus webhooks instead: your backend holds the merchant's Picverce AI API key, posts jobs for product images, and listens for job.succeeded to put the results back where they belong.
The Picverce AI app for Shopify is built that way. A merchant installs it, pastes their own API key, picks a tool and some products, and every finished image is added to the product as an extra photo. Originals are never replaced. Jobs are charged to the merchant's own credits, so there is nothing separate to buy.
| Step | What happens |
|---|---|
| Install | The merchant installs the app and pastes their own Picverce AI API key |
| Pick | A tool and some products, or a collection, or the whole catalog |
| Run | One POST /v1/jobs per product photo, with an Idempotency-Key so a retry costs nothing |
| Finish | A signed job.succeeded webhook arrives and the image is added to the product |
| Uninstall | Shopify sends app/uninstalled and every credential stored for that shop is deleted |
The app is on development stores while it is being finished, so there is no App Store listing yet. If you want to build the same thing into your own software, everything it uses is on this page: POST /v1/jobs with an Idempotency-Key per image, and a signed webhook when each one finishes.
See the MCP landing page for a short setup guide, or read Anthropic's MCP documentation for connector details.
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.
Using the API, the MCP connector, or the apps for Shopify and WordPress is covered by the same Terms of Service and Privacy Policy as the website, which describe what a job sends us and how long we keep it.