Build AI image enhancement into your product
A REST API for AI enhancement (smooth-skin, upscale, auto-enhance, background removal) with first-class outbound webhooks. Authenticate with an API key and receive signed events when jobs finish.
Authentication
Every request is authenticated with an API key passed in the x-api-key header. Create and manage keys from your dashboard. Keys are shown in full only once — store them securely. Do not send Bearer tokens on the public API.
curl https://pickimg.com/api/ai/enhance \
-H "x-api-key: YOUR_API_KEY"Scopes
Each key carries scopes. Missing a required scope returns 403.
image:readRead image jobs and sourcesimage:writeUpload and process imagesai:enhanceUpload sources, create, and poll AI jobswebhook:manageCreate and inspect outbound webhooksNew keys default to image:read, image:write, and ai:enhance. webhook:manage is opt-in.
HTTP errors
Use these status codes when integrating retries and upgrades.
| 401 | Missing or invalid API key. |
| 403 | Key lacks the required scope. Monthly enhancement or storage quota exceeded — jobs are blocked, not billed as overage. |
| 429 | Per-key request-per-minute limit exceeded. Wait and retry. |
429 responses include Retry-After, X-RateLimit-Limit, X-RateLimit-Remaining.
AI enhancement
/api/ai/uploadUpload a source image/api/ai/enhanceCreate an enhancement job/api/ai/enhance/:idGet job status & result/api/ai/enhanceList your jobsPOST /api/ai/upload is multipart form field file (JPEG, PNG, WebP, or AVIF, max 20MB) and returns { url } for sourceUrl.
Operations: smooth_skin, upscale, auto_enhance, bg_remove. level: low | medium | high.
Poll GET until status is queued, processing, completed, failed.
Upload, enhance, poll
# 1. Upload a source image (multipart field: file, max 20MB)
curl -X POST https://pickimg.com/api/ai/upload \
-H "x-api-key: YOUR_API_KEY" \
-F "file=@./photo.jpg"
# { "url": "https://cdn.example.com/uploads/..." }
# 2. Queue enhancement
curl -X POST https://pickimg.com/api/ai/enhance \
-H "x-api-key: YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"sourceUrl": "https://cdn.example.com/uploads/photo.jpg",
"operation": "smooth_skin",
"level": "medium"
}'
# { "id": "job_123", "status": "queued", "operation": "smooth_skin" }
# 3. Poll until completed or failed
curl https://pickimg.com/api/ai/enhance/job_123 \
-H "x-api-key: YOUR_API_KEY"
# { "id": "job_123", "status": "completed", "resultUrl": "https://cdn.example.com/out.jpg" }// Node.js
const key = process.env.PICKIMG_KEY;
const form = new FormData();
form.append("file", blob, "photo.jpg");
const uploaded = await fetch("https://pickimg.com/api/ai/upload", {
method: "POST",
headers: { "x-api-key": key },
body: form,
}).then((r) => r.json());
const job = await fetch("https://pickimg.com/api/ai/enhance", {
method: "POST",
headers: {
"x-api-key": key,
"Content-Type": "application/json",
},
body: JSON.stringify({
sourceUrl: uploaded.url,
operation: "upscale",
}),
}).then((r) => r.json());
const poll = await fetch(`https://pickimg.com/api/ai/enhance/${job.id}`, {
headers: { "x-api-key": key },
}).then((r) => r.json());Official SDKs (v0.1.0)
JavaScript (@pickimg/sdk) and Python (pickimg) wrap upload → enhance → poll. Install from a clone of the repo until npm/PyPI publish. Do not put tokens in source.
# From a clone of the pickimg repo (v0.1.0)
pnpm add ./sdk/js
pip install ./sdk/python
# After npm / PyPI publish:
# npm install @pickimg/sdk
# pip install pickimgimport { PickimgClient } from "@pickimg/sdk";
const client = new PickimgClient({ apiKey: process.env.PICKIMG_API_KEY! });
// 1. Upload
const { url } = await client.uploadImage(file);
// 2. Enhance
const job = await client.enhance({ sourceUrl: url, operation: "smooth_skin" });
// 3. Poll
const done = await client.waitForEnhancement(job.id);
console.log(done.resultUrl);from pickimg import PickimgClient
client = PickimgClient(api_key="YOUR_KEY")
# 1. Upload
with open("photo.jpg", "rb") as fh:
upload = client.upload_image(fh)
# 2. Enhance
job = client.enhance(source_url=upload["url"], operation="smooth_skin")
# 3. Poll
done = client.wait_for_enhancement(job.id)
print(done.result_url)Webhooks
Register endpoints to receive events when jobs complete. Each delivery is signed so you can verify authenticity.
/api/webhooks/eventsList available event types/api/webhooks/endpointsCreate an endpoint/api/webhooks/deliveriesInspect delivery attemptsEvents: enhancement.queued, enhancement.completed, enhancement.failed, image.uploaded, quota.exceeded, ping, job.completed, upload.finished.
job.completed is also sent with image.completed and enhancement.completed. upload.finished is also sent with image.uploaded. Prefer the alias if you want one handler for both job types.
Verifying the signature
Deliveries include an X-PickIMG-Signature header formatted as t=<timestamp>,v1=<hmac>, where the HMAC-SHA256 is computed over <timestamp>.<raw-body>.
import crypto from "crypto";
function verify(rawBody, header, secret) {
const [t, v1] = header.split(",").map((p) => p.split("=")[1]);
const expected = crypto
.createHmac("sha256", secret)
.update(`${t}.${rawBody}`)
.digest("hex");
return crypto.timingSafeEqual(Buffer.from(v1), Buffer.from(expected));
}Rate limits & quotas
RPM is per API key (plan ceiling). Monthly AI enhancements and storage are shared by all keys of the same account.
| Plan | RPM | AI / month | Storage | API keys |
|---|---|---|---|---|
| Free | 30 | 50 | 1 GB | 1 |
| Personal | 120 | 2,000 | 50 GB | 3 |
| Business | 600 | 20,000 | 500 GB | 15 |
| Enterprise | custom | custom | custom | custom |
Hard quota returns 403. RPM returns 429 with Retry-After. Enterprise limits are custom — contact sales; do not assume a self-serve number.
When a quota is exceeded the API returns 403 with an upgrade hint. See pricing.