Documentation
Everything you need to integrate Pixicular API photo analysis into your app.
See implementation guides for OCR text extraction, ID document OCR for KYC, and UGC image moderation.
Getting Started
Get up and running with the Pixicular API in just a few minutes.
Create an account
Sign up for a free account to get your API key.
Get your API key
Generate an API key from your dashboard.
Make your first request
Use the code examples in the POST /v1/detect or POST /v1/detect-from-url section below to make your first detection request.
Authentication
All API requests require authentication using a Bearer token in the Authorization header.
Authorization: Bearer apd_live_your_api_key_here
API keys have the format: apd_live_... or apd_test_...
Use apd_test_... keys for development — they work identically but are tracked separately. Use apd_live_... keys in production.
/v1/detectand/v1/detect-from-urlAnalyze an image to detect if it was AI-generated. Supports both file upload (multipart) and URL submission (JSON).
Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
file | File | Required* | Image file (multipart upload) |
url | String | Required* | Image URL (JSON body) |
services | String | Required | Comma-separated list of services to run. Available: detect-age, detect-labels, detect-moderation, detect-text, detect-face-emotions. |
Prefer | Header | Optional | respond-async to get a 202 at once, or wait=N to wait at most N seconds. See async processing. |
mode | Query | Optional | ?mode=async is the same as Prefer: respond-async. |
* Either file or url is required
Code Examples
File Upload (multipart/form-data)
curl -X POST "https://api.pixicular.com/v1/detect" \-H "Authorization: Bearer apd_live_your_api_key_here" \-F "services=detect-age,detect-labels,detect-moderation,detect-text,detect-face-emotions" \-F "file=@/path/to/image.jpg"
URL Submission (application/json)
curl -X POST "https://api.pixicular.com/v1/detect-from-url" \-H "Authorization: Bearer apd_live_your_api_key_here" \-H "Content-Type: application/json" \-d '{"services": "detect-age,detect-labels,detect-moderation,detect-text,detect-face-emotions","url": "https://example.com/image.jpg"}'
Response
{"success": true,"requestId": "req_wUKl0im7IZg4bCefnE","services": {"detect-age": {"faces": [{"ageRange": {"low": 22,"high": 28},"ageEstimate": 25,"confidence": 99.99726104736328}]},"detect-labels": {...},"detect-moderation": {...},"detect-text": {...},"detect-face-emotions": {...}},"photo": {"id": "photo_Dy2csYO9nhYgdDxn9B","status": "COMPLETED"},"creditCost": 1,"processingTimeMs": 1250,"processedAt": "2025-12-31T23:59:59.002Z"}
Responses
Finished: the result, as above.
Accepted and still queued or running (not an error). Poll the statusUrl until the request has finished.
Busy: retry after the Retry-After header. No credits are charged.
See High Load & Async Processing for how to handle them, and Error Handling for the other status codes.
Service response examples
{"services": {"detect-age": {"faces": [{"ageRange": { "low": 25, "high": 35 },"ageEstimate": 30,"confidence": 98.4}]}}}
{"services": {"detect-labels": {"labels": [{"name": "Person","confidence": 99.8,"categories": ["Person Description"]}]}}}
/v1/detect/jobs/:requestIdGet the status of a detection request, and its result once it has completed. This is the statusUrl of a 202 response: poll it while the status is QUEUED or PROCESSING. Rate limit: 600 req/min. See High Load & Async Processing.
Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
requestId | String (path) | Required | The requestId (req_...) from a detection response |
Code Examples
curl -i "https://api.pixicular.com/v1/detect/jobs/req_wUKl0im7IZg4bCefnE" \-H "Authorization: Bearer apd_live_your_api_key_here"
Response
| Status | Meaning |
|---|---|
QUEUED | Waiting in the queue. Poll again after Retry-After seconds (also in pollAfterMs). |
PROCESSING | Running. Poll again as for QUEUED. |
COMPLETED | Finished. result has the same shape as a 200 response from the detect endpoints. |
FAILED | Did not finish. error.code and error.message say why (see error codes). No credits are charged. |
attempts counts processing attempts (a failed attempt is retried automatically when retrying can help). queueWaitMs and runMs appear once the request has started and finished.
Still running
HTTP/1.1 200 OKRetry-After: 2{"requestId": "req_wUKl0im7IZg4bCefnE","status": "PROCESSING","createdAt": "2026-09-23T10:00:00.000Z","startedAt": "2026-09-23T10:00:01.500Z","completedAt": null,"attempts": 1,"queueWaitMs": 1500,"pollAfterMs": 2000}
Completed
{"requestId": "req_wUKl0im7IZg4bCefnE","status": "COMPLETED","createdAt": "2026-09-23T10:00:00.000Z","startedAt": "2026-09-23T10:00:01.500Z","completedAt": "2026-09-23T10:00:04.000Z","attempts": 1,"queueWaitMs": 1500,"runMs": 2500,"result": {"success": true,"requestId": "req_wUKl0im7IZg4bCefnE","services": {"detect-age": {"faces": [{ "ageRange": { "low": 22, "high": 28 }, "ageEstimate": 25, "confidence": 99.9 }]}},"photo": { "id": "photo_Dy2csYO9nhYgdDxn9B", "status": "COMPLETED" },"creditCost": 1,"processingTimeMs": 4000,"processedAt": "2026-09-23T10:00:04.000Z"}}
Failed
{"requestId": "req_wUKl0im7IZg4bCefnE","status": "FAILED","createdAt": "2026-09-23T10:00:00.000Z","startedAt": "2026-09-23T10:00:01.500Z","completedAt": "2026-09-23T10:01:40.000Z","attempts": 3,"queueWaitMs": 1500,"runMs": 98500,"error": {"code": "PROVIDER_TIMEOUT","message": "The analysis provider did not respond in time."}}
Not found
Unknown request IDs and request IDs of another account both return 404.
{"statusCode": 404,"error": "Not Found","message": "Detect request not found"}
/v1/historyRetrieve a paginated list of your operation history.
Query Parameters
| Parameter | Type | Default | Description |
|---|---|---|---|
page | Integer | 1 | Page number |
perPage | Integer | 50 | Results per page (max 100) |
Code Examples
curl "https://api.pixicular.com/v1/history?page=1&perPage=50" \-H "Authorization: Bearer apd_live_your_api_key_here"
Response
{"data": [{"requestId": "req_wUKl0im7IZg4bCefnE","operationId": "op_QtLqxId519-4muCl_L","services": {"detect-age": {"faces": [{ "ageRange": { "low": 30, "high": 40 }, "ageEstimate": 35, "confidence": 97.2 }]}},"processedAt": "2025-12-31T23:59:59.002Z"}],"meta": {"pagination": {"currentPage": 1,"perPage": 50,"totalRecords": 150,"totalPages": 3,"hasNextPage": true,"hasPreviousPage": false}}}
/v1/history/:requestIdRetrieve detailed information about a specific operation result.
Code Examples
curl "https://api.pixicular.com/v1/history/req_wUKl0im7IZg4bCefnE" \-H "Authorization: Bearer apd_live_your_api_key_here"
Response
{"data": {"requestId": "req_wUKl0im7IZg4bCefnE","processedAt": "2025-12-31T23:59:59.002Z","services": {"detect-age": {"faces": [{"ageRange": {"low": 22,"high": 28},"ageEstimate": 25,"confidence": 99.99726104736328}]},},"photo": {"id": "photo_Dy2csYO9nhYgdDxn9B","status": "COMPLETED"},"creditCost": 3,"processingTimeMs": 1156,"processedAt": "2025-12-31T23:59:59.002Z"}}
Request still queued, running or failed
A request without results yet returns its status (QUEUED, PROCESSING or FAILED) and empty services instead of a 404. A failed request also has an error with code and message. details.photo is included as above (left out below; it can be null while the image is being stored). To wait for the result, poll GET /v1/detect/jobs/:requestId. The history list only includes completed requests.
{"data": {"requestId": "req_wUKl0im7IZg4bCefnE","status": "PROCESSING","processedAt": null,"services": {}}}
Photo Analysis Services
Pass a comma-separated list of service IDs in the services field. Each service costs 1 credit per request.
detect-age1 creditEstimates the age range of every face detected in the image.
{"faces": [{"ageRange": { "low": 25, "high": 35 },"ageEstimate": 30,"confidence": 99.3}]}
detect-labels1 creditDetects objects, scenes, and activities in the image with confidence scores and category tags.
{"labels": [{ "name": "Person", "confidence": 99.8, "categories": ["People"] },{ "name": "Outdoors", "confidence": 87.3, "categories": ["Nature"] },{ "name": "Mountain", "confidence": 74.1, "categories": ["Nature"] }]}
detect-moderation1 creditDetects unsafe or sensitive content — nudity, violence, drugs, hate symbols, and more. Returns a hierarchical list of moderation labels with confidence scores.detected is true when any label is found.
{"detected": true,"labels": [{"name": "Suggestive","parentName": "","confidence": 90.5},{"name": "Female Swimwear Or Underwear","parentName": "Suggestive","confidence": 90.5}]}
detect-text1 creditExtracts lines of text found in the image using OCR. Only full lines are returned (individual words are omitted).
{"detections": [{ "detectedText": "Hello World", "type": "LINE", "confidence": 99.1 },{ "detectedText": "Open 9am – 5pm", "type": "LINE", "confidence": 96.4 }]}
detect-face-emotions1 creditReturns confidence scores for eight emotions — HAPPY, SAD, ANGRY, SURPRISED, DISGUSTED, CALM, CONFUSED, and FEAR — for each face detected.
{"faces": [{"faceConfidence": 99.9,"emotions": [{ "type": "HAPPY", "confidence": 97.2 },{ "type": "CALM", "confidence": 1.5 },{ "type": "SURPRISED", "confidence": 0.8 }]}]}
Limits & Pricing
General Limits
Max file size
10MB per image
Supported formats
PNG, JPG, JPEG, GIF, WebP
Rate limit
Detection: 60 req/min, History and job status: 600 req/min
Image dimensions
100x100 to 4096x4096 px
Pricing Plans
| Plan | Price | Included | Overage |
|---|---|---|---|
| Developer | $0/mo | 1,000 operations | - |
| Launch | $25/mo | 10,000 operations | $0.003 per extra operation |
| Scale | $99/mo | 40,000 operations | $0.0027 per extra operation |
| Business | $299/mo | 200,000 operations | $0.0021 per extra operation |
| Enterprise | Custom | Unlimited | Custom |
A request is one uploaded and processed photo via Pixicular API (or via the Pixicular website), regardless of how many services it includes.
An operation is one service run on a request.
So if 3 services are selected on one request, it counts as 3 operations. For example, if there is one request and it has two services defined, `detect-age` and `detect-labels`, it counts as two operations.
Usage Limits & Overage Policy
Developer plan: Free with no time limit and hard-capped at 1,000 operations per month. Once reached, operations are blocked until the next calendar month or until you upgrade.
Paid plans (Launch, Scale, Business): Once 100% of your monthly usage is reached, you will be charged per extra operation at your plan's overage rate. By default, when you reach 200% of the original plan usage, operations will be rate-limited (HTTP 429) until the next payment period or until you upgrade your plan.
Email alerts: You will receive email notifications at 80%, 90%, and 100% of your monthly usage limit.
Traffic spikes: Detection requests may wait in a queue during spikes. Requests on paid plans are processed before trial and Developer plan requests. Credits are charged only when a request completes. See High Load & Async Processing.
To request a higher overage cap for your plan, contact us at support@pixicular.com.
High Load & Async Processing
Detection requests go through a queue. Normally the result still comes back in the same response, as a 200 with the usual body. During traffic spikes a request may wait in the queue first, so handle these responses from POST /v1/detect and POST /v1/detect-from-url:
How It Works
| Status | Meaning | What to do |
|---|---|---|
| 200 | OK. Finished within the wait (about 45 seconds). Same body as always. | Read the result. |
| 202 | Accepted. Still queued or running. Not an error. | Poll the statusUrl, waiting Retry-After seconds between polls. |
| 503 | Service Unavailable. Too busy to accept the request. Nothing was queued. | Retry after Retry-After seconds, with exponential backoff and jitter. |
Credits are charged only when a request completes. While it is queued or running, its credits are reserved against your monthly limit. A failed or rejected request costs nothing.
Priority: during spikes, requests on paid plans are processed before trial and Developer plan requests.
202 Accepted
The request was accepted and is still being processed. The Location header and statusUrl point to its status, relative to https://api.pixicular.com. Retry-After (seconds) and pollAfterMs say when to poll. status is QUEUED or PROCESSING.
HTTP/1.1 202 AcceptedLocation: /v1/detect/jobs/req_wUKl0im7IZg4bCefnERetry-After: 2{"success": true,"requestId": "req_wUKl0im7IZg4bCefnE","status": "QUEUED","statusUrl": "/v1/detect/jobs/req_wUKl0im7IZg4bCefnE","pollAfterMs": 2000}
Polling for the Result
Send a GET to the statusUrl with the same API key (see GET /v1/detect/jobs/:requestId). Wait Retry-After seconds (about 2) between polls until the status is:
COMPLETED:resulthas the same body as a200response.FAILED:error.codeanderror.messagesay why. No credits are charged.
Stop polling after a few minutes. The request is not cancelled: look it up later with the same URL or GET /v1/history/:requestId.
503 Service Unavailable
The system is busy (code: QUEUE_FULL) or the queue is temporarily unavailable (code: QUEUE_UNAVAILABLE). Nothing was queued and no credits are charged. Wait at least Retry-After seconds, then submit again. On repeated 503 responses, back off exponentially and add random jitter, so that many clients do not retry at the same moment.
HTTP/1.1 503 Service UnavailableRetry-After: 15{"statusCode": 503,"error": "Service Unavailable","code": "QUEUE_FULL","message": "Detection is busy right now. Please retry after the time in the Retry-After header."}
Async Mode for Batch Jobs
To submit many images without holding a connection open for each, ask for a 202 straight away and poll the results. Still handle a 200: a preference is a request, not a guarantee.
| Option | Effect |
|---|---|
Prefer: respond-async | Answer 202 at once. The response has the header Preference-Applied: respond-async. |
?mode=async | Same as Prefer: respond-async, for clients that cannot set headers. |
Prefer: wait=N | Wait at most N seconds for the result before answering 202. It can only shorten the default wait of about 45 seconds. |
curl -i -X POST "https://api.pixicular.com/v1/detect-from-url" \-H "Authorization: Bearer apd_live_your_api_key_here" \-H "Content-Type: application/json" \-H "Prefer: respond-async" \-d '{"services": "detect-age,detect-labels,detect-moderation,detect-text,detect-face-emotions", "url": "https://example.com/image.jpg"}'
Code Examples
Submit, handle 200, 202 and 503, and poll until the request has finished.
# 1. Submit. -i prints the status line and the Location / Retry-After headers.curl -i -X POST "https://api.pixicular.com/v1/detect-from-url" \-H "Authorization: Bearer apd_live_your_api_key_here" \-H "Content-Type: application/json" \-d '{"services": "detect-age,detect-labels,detect-moderation,detect-text,detect-face-emotions", "url": "https://example.com/image.jpg"}'# 200: the body is the result.# 202: poll the statusUrl, waiting Retry-After seconds between polls.# 503: wait at least Retry-After seconds (with backoff and jitter), then submit again.# 2. Poll until "status" is COMPLETED (the result is in "result") or FAILED.curl -i "https://api.pixicular.com/v1/detect/jobs/req_wUKl0im7IZg4bCefnE" \-H "Authorization: Bearer apd_live_your_api_key_here"
Error Handling
The API uses standard HTTP status codes and returns errors in a consistent JSON format.
202 Accepted from the detect endpoints is not an error: the request is still being processed. See High Load & Async Processing.
{"statusCode": 400,"error": "Bad Request","message": "Invalid services: unknown-service. Available: detect-age, detect-labels, detect-moderation, detect-text, detect-face-emotions"}
{"statusCode": 401,"error": "Unauthorized","message": "Invalid or missing API key"}
{"statusCode": 429,"error": "Too Many Requests","message": "Rate limit exceeded. Please wait 15 minutes."}
{"statusCode": 429,"error": "Usage Limit Exceeded","message": "Requests rate-limited because usage has reached 200% of your monthly limit for this period."}
The code field says why (see error codes). No credits are charged.
{"statusCode": 500,"error": "Internal Server Error","message": "An error occurred while processing the operation","code": "PROVIDER_TIMEOUT"}
Retry after the number of seconds in the Retry-After header, with exponential backoff and jitter. No credits are charged.
HTTP/1.1 503 Service UnavailableRetry-After: 15{"statusCode": 503,"error": "Service Unavailable","code": "QUEUE_FULL","message": "Detection is busy right now. Please retry after the time in the Retry-After header."}
Error Codes
Detection errors carry a machine-readable code. A request that fails after a 202 has the same codes in error.code (status FAILED).
| Code | Status | Meaning |
|---|---|---|
INVALID_IMAGE | 500 | The image could not be processed. It may be corrupt or in an unsupported format. Do not retry it unchanged. |
PROVIDER_TIMEOUT | 500 | The analysis provider did not respond in time. Retry later. |
PROVIDER_UNAVAILABLE | 500 | The analysis provider is temporarily unavailable. Retry later. |
PROVIDER_ERROR | 500 | The analysis provider rejected the request. Do not retry it unchanged. |
DATABASE_UNAVAILABLE | 500 | The service is temporarily unavailable. Retry later. |
INTERNAL_ERROR | 500 | An unexpected error. Retry later, and contact support if it persists. |
STALE | FAILED only | The request did not finish in time and was cancelled. Submit it again. |
IMAGE_DOWNLOAD_FAILED | 422 | The image could not be downloaded from the URL (/v1/detect-from-url). |
UNSUPPORTED_FILE_TYPE | 422 | The downloaded file is not a supported image type. |
FILE_TOO_LARGE | 422 | The downloaded image is larger than 10MB. |
QUEUE_FULL | 503 | The queue is full. Retry after the Retry-After header. |
QUEUE_UNAVAILABLE | 503 | The queue is temporarily unavailable. Retry after the Retry-After header. |
Need Help?
Visit your Dashboard to monitor usage
View Pricing plans and upgrade options
Email us at support@pixicular.com for general support
Contact support for Enterprise plan inquiries