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.

1

Create an account

Sign up for a free account to get your API key.

2

Get your API key

Generate an API key from your dashboard.

3

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.

POST/v1/detectand/v1/detect-from-url

Analyze an image to detect if it was AI-generated. Supports both file upload (multipart) and URL submission (JSON).

Parameters

ParameterTypeRequiredDescription
fileFileRequired*Image file (multipart upload)
urlStringRequired*Image URL (JSON body)
servicesStringRequiredComma-separated list of services to run. Available: detect-age, detect-labels, detect-moderation, detect-text, detect-face-emotions.
PreferHeaderOptionalrespond-async to get a 202 at once, or wait=N to wait at most N seconds. See async processing.
modeQueryOptional?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

200

Finished: the result, as above.

202

Accepted and still queued or running (not an error). Poll the statusUrl until the request has finished.

503

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"]
}
]
}
}
}
GET/v1/detect/jobs/:requestId

Get 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

ParameterTypeRequiredDescription
requestIdString (path)RequiredThe 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

StatusMeaning
QUEUEDWaiting in the queue. Poll again after Retry-After seconds (also in pollAfterMs).
PROCESSINGRunning. Poll again as for QUEUED.
COMPLETEDFinished. result has the same shape as a 200 response from the detect endpoints.
FAILEDDid 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 OK
Retry-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"
}
GET/v1/history

Retrieve a paginated list of your operation history.

Query Parameters

ParameterTypeDefaultDescription
pageInteger1Page number
perPageInteger50Results 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
}
}
}
GET/v1/history/:requestId

Retrieve 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 credit

Estimates the age range of every face detected in the image.

{
"faces": [
{
"ageRange": { "low": 25, "high": 35 },
"ageEstimate": 30,
"confidence": 99.3
}
]
}
detect-labels1 credit

Detects 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 credit

Detects 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 credit

Extracts 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 credit

Returns 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

PlanPriceIncludedOverage
Developer$0/mo1,000 operations-
Launch$25/mo10,000 operations$0.003 per extra operation
Scale$99/mo40,000 operations$0.0027 per extra operation
Business$299/mo200,000 operations$0.0021 per extra operation
EnterpriseCustomUnlimitedCustom

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

StatusMeaningWhat to do
200OK. Finished within the wait (about 45 seconds). Same body as always.Read the result.
202Accepted. Still queued or running. Not an error.Poll the statusUrl, waiting Retry-After seconds between polls.
503Service 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 Accepted
Location: /v1/detect/jobs/req_wUKl0im7IZg4bCefnE
Retry-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: result has the same body as a 200 response.
  • FAILED: error.code and error.message say 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 Unavailable
Retry-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.

OptionEffect
Prefer: respond-asyncAnswer 202 at once. The response has the header Preference-Applied: respond-async.
?mode=asyncSame as Prefer: respond-async, for clients that cannot set headers.
Prefer: wait=NWait 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

202 Accepted from the detect endpoints is not an error: the request is still being processed. See High Load & Async Processing.

400Bad Request
{
"statusCode": 400,
"error": "Bad Request",
"message": "Invalid services: unknown-service. Available: detect-age, detect-labels, detect-moderation, detect-text, detect-face-emotions"
}
401Unauthorized
{
"statusCode": 401,
"error": "Unauthorized",
"message": "Invalid or missing API key"
}
429Rate Limited (too many requests)
{
"statusCode": 429,
"error": "Too Many Requests",
"message": "Rate limit exceeded. Please wait 15 minutes."
}
429Monthly Limit Exceeded
{
"statusCode": 429,
"error": "Usage Limit Exceeded",
"message": "Requests rate-limited because usage has reached 200% of your monthly limit for this period."
}
500Detection Failed

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"
}
503Service Unavailable (busy)

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 Unavailable
Retry-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).

CodeStatusMeaning
INVALID_IMAGE500The image could not be processed. It may be corrupt or in an unsupported format. Do not retry it unchanged.
PROVIDER_TIMEOUT500The analysis provider did not respond in time. Retry later.
PROVIDER_UNAVAILABLE500The analysis provider is temporarily unavailable. Retry later.
PROVIDER_ERROR500The analysis provider rejected the request. Do not retry it unchanged.
DATABASE_UNAVAILABLE500The service is temporarily unavailable. Retry later.
INTERNAL_ERROR500An unexpected error. Retry later, and contact support if it persists.
STALEFAILED onlyThe request did not finish in time and was cancelled. Submit it again.
IMAGE_DOWNLOAD_FAILED422The image could not be downloaded from the URL (/v1/detect-from-url).
UNSUPPORTED_FILE_TYPE422The downloaded file is not a supported image type.
FILE_TOO_LARGE422The downloaded image is larger than 10MB.
QUEUE_FULL503The queue is full. Retry after the Retry-After header.
QUEUE_UNAVAILABLE503The 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