Doppl API Docs
Generate AI-powered avatars from photos. One API call, six styles, every game engine. No 3D pipeline required.
The Doppl API is a REST API that accepts JSON request bodies and returns JSON responses. All endpoints are accessed via https://doppl.polsia.app/api/v1.
Quick Start
From zero to avatar in 3 steps. No signup, no credit card.
Generate an API Key
Get a free demo key instantly. No authentication required.
curl -X POST https://doppl.polsia.app/api/v1/keys \ -H "Content-Type: application/json" \ -d '{"name": "My Game Studio"}'
Upload a Photo
Send a photo URL and pick a style. The API handles the rest.
curl -X POST https://doppl.polsia.app/api/v1/avatars \ -H "Content-Type: application/json" \ -H "X-API-Key: dppl_YOUR_KEY_HERE" \ -d '{"photo": "https://example.com/selfie.jpg", "style": "stylized"}'
Get Your Avatar
The response includes the generated avatar URL immediately. Use it in your game, app, or wherever you need it.
{
"id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"status": "completed",
"style": "stylized",
"avatar_url": "https://...",
"format": "png",
"size": "1024x1024",
"processing_time_ms": 2340
}
Code Examples
Copy-pasteable examples in your language of choice. Each snippet covers key generation, avatar generation, response parsing, and error handling.
Get an API Key
curl -X POST https://doppl.polsia.app/api/v1/keys \ -H "Content-Type: application/json" \ -d '{"name": "My App"}'
Generate an Avatar
curl -X POST https://doppl.polsia.app/api/v1/avatars \ -H "Content-Type: application/json" \ -H "X-API-Key: dppl_YOUR_KEY_HERE" \ -d '{"photo": "https://example.com/selfie.jpg", "style": "stylized"}' # On success, the response JSON contains "avatar_url"
Authentication
All avatar endpoints require an API key. Pass it via the X-API-Key header or as a Bearer token.
Header Format
X-API-Key: dppl_your_api_key_here
Authorization: Bearer dppl_your_api_key_here
API keys are prefixed with dppl_. Demo keys are rate-limited to 5 requests/minute. Missing or invalid keys return a 401 error.
OAuth 2.0
For server-to-server integrations, Doppl supports OAuth 2.0 with the authorization code flow. This is recommended when you need to act on behalf of users or want to avoid embedding API keys in client code.
Authorization Code Flow
The authorization code flow involves three steps: redirect the user to Doppl's authorization endpoint, exchange the authorization code for tokens, and use the access token for API requests.
# Redirect user to Doppl authorization endpoint curl -X GET "https://doppl.polsia.app/oauth/authorize?client_id=YOUR_CLIENT_ID&redirect_uri=https://yourapp.com/callback&response_type=code&scope=avatars:read avatars:write"
curl -X POST https://doppl.polsia.app/oauth/token \n # Replace CODE with the authorization code from the redirect -H "Content-Type: application/json" \n -d ‘{ "grant_type": "authorization_code", "code": "AUTHORIZATION_CODE", "client_id": "YOUR_CLIENT_ID", "client_secret": "YOUR_CLIENT_SECRET", "redirect_uri": "https://yourapp.com/callback" ‘}
curl -X POST https://doppl.polsia.app/api/v1/avatars \n -H "Authorization: Bearer ACCESS_TOKEN" -H "Content-Type: application/json" \n -d ‘{"photo": "https://example.com/photo.jpg", "style": "stylized"}‘
Token Refresh
Access tokens expire after 1 hour. Use the refresh token to obtain a new access token without re-authenticating the user.
curl -X POST https://doppl.polsia.app/oauth/token \n -H "Content-Type: application/json" -d '{ "grant_type": "refresh_token", "refresh_token": "YOUR_REFRESH_TOKEN", "client_id": "YOUR_CLIENT_ID", "client_secret": "YOUR_CLIENT_SECRET" }'
Webhooks
Receive a POST notification the moment an avatar generation completes — no polling, no waiting on long responses. Pair this with the OAuth guide above for a complete automation story.
Registering a Webhook
Pass a webhook_url on the POST /api/v1/avatars request body and Doppl will fire a single POST to that URL when the avatar finishes generating — successfully or otherwise.
curl -X POST https://doppl.polsia.app/api/v1/avatars \ -H "Content-Type: application/json" \ -H "X-API-Key: dppl_YOUR_KEY_HERE" \ -d '{ "photo": "https://example.com/selfie.jpg", "style": "stylized", "webhook_url": "https://yourapp.com/webhooks/doppl" }'
https://. Doppl enforces a 10-second timeout and treats any 2xx response (within the timeout) as a successful delivery. Anything else will trigger a retry (see below).
Payload Format
The webhook body is a JSON document mirroring the shape of the POST /api/v1/avatars response, plus a couple of envelope fields:
{
"event": "avatar.completed",
"api_key_id": "key_abc123",
"id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"status": "completed",
"style": "stylized",
"avatar_url": "https://oaidalleapiprodscus.blob.core.windows.net/...",
"source_photo_url": "https://example.com/selfie.jpg",
"format": "png",
"size": "1024x1024",
"processing_time_ms": 2340,
"created_at": "2026-04-05T12:00:00.000Z",
"completed_at": "2026-04-05T12:00:02.340Z"
}
| Field | Type | Description |
|---|---|---|
| event | string | Event type. Currently always avatar.completed |
| api_key_id | string | ID of the API key that triggered the generation |
| id | string | Avatar ID — same value across all retries; dedupe on this |
| status | string | completed on success, failed if generation errored |
| style | string | Style ID used for generation (e.g. stylized, anime) |
| avatar_url | string | URL of the generated avatar image. null when status is failed |
| source_photo_url | string | The original photo URL you submitted |
| format | string | Image format of the avatar (e.g. png) |
| size | string | Image dimensions (e.g. 1024x1024) |
| processing_time_ms | number | Time taken to generate, in milliseconds |
| created_at | string | ISO-8601 timestamp when the request was received |
| completed_at | string | ISO-8601 timestamp when generation finished |
Signature Verification
Every webhook is delivered with an X-Doppl-Signature header of the form sha256=<hex>, where the hex digest is HMAC-SHA256 of the raw request body, keyed by your API key. Verify it before trusting the payload:
const crypto = require("crypto"); function verifyDopplSignature(rawBody, signatureHeader, apiKey) { const expected = "sha256=" + crypto .createHmac("sha256", apiKey) .update(rawBody) .digest("hex"); return crypto.timingSafeEqual( Buffer.from(expected), Buffer.from(signatureHeader) ); }
rawBody/verify option on the body parser. If the signature header is missing, malformed, or fails to match, respond with 401 and drop the request.
Retry Behavior
If your handler does not return 2xx within the 10-second window, Doppl automatically retries the delivery. Receivers must be idempotent.
| Behavior | Value |
|---|---|
| Max attempts | 4 (1 initial + 3 retries) |
| Retried on | 5xx response, 429 response, network timeout, connection error |
| Backoff | 1s, 4s, 16s (between attempts) |
| Idempotency | Same id across every attempt — dedupe on it |
For client-side retry strategy against 429s when calling Doppl, see Rate Limits & Best Practices.
Minimal Handler
A complete webhook receiver that verifies the signature and echoes the parsed avatar back:
# Forward a sample Doppl payload to your local handler curl -X POST http://localhost:3000/webhooks/doppl \ -H "Content-Type: application/json" \ -H "X-Doppl-Signature: sha256=<computed-hex>" \ -d '{ "event": "avatar.completed", "id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890", "status": "completed", "avatar_url": "https://example.com/avatar.png" }'
Rate Limits
Rate limits are enforced per API key on a 60-second rolling window.
| Key Type | Limit | Window |
|---|---|---|
| Demo | 5 requests/minute | 60 seconds |
| Standard | 10 requests/minute | 60 seconds |
Response Headers
Every authenticated response includes rate limit headers:
| Header | Description |
|---|---|
| X-RateLimit-Limit | Maximum requests allowed per window |
| X-RateLimit-Remaining | Requests remaining in current window |
| X-RateLimit-Reset | Unix timestamp when the window resets |
When the limit is exceeded, the API returns 429 Too Many Requests with a retry_after_seconds field.
Rate Limits & Best Practices
The limits in the previous section are enforced per API key across all endpoints. The table below breaks down the effective limit per endpoint.
Per-Endpoint Limits
| Endpoint | Limit | Window |
|---|---|---|
| POST /api/v1/keys | 5 requests/minute | 60 seconds |
| POST /api/v1/avatars | 10 requests/minute | 60 seconds |
| GET /api/v1/avatars/:id | Shared with the per-key limit | 60 seconds |
| GET /api/v1/avatars | Shared with the per-key limit | 60 seconds |
| GET /api/v1/styles | Shared with the per-key limit | 60 seconds |
| GET /api/v1/stats | Shared with the per-key limit | 60 seconds |
Handling 429 Responses
When Doppl returns a 429 Too Many Requests, the response body includes a retry_after_seconds field. Standard practice: respect the server-supplied value before retrying, fall back to exponential backoff with jitter when the field is absent, and add a small random delay before sleeping so concurrent callers don't all retry in lockstep.
| Condition | Action |
|---|---|
retry_after_seconds present |
Sleep for that long, then retry once |
retry_after_seconds absent |
Exponential backoff with jitter (see snippet below) |
| Before any sleep | Add random jitter of 0–500 ms to spread out colliding retries |
Idempotency Keys
The POST /api/v1/avatars and POST /api/v1/keys endpoints accept an optional Idempotency-Key header. Submitting the same request body with the same key returns the cached response without re-running generation — safe to use when retrying after a network error or a 5xx response.
curl -X POST https://doppl.polsia.app/api/v1/avatars \ -H "Content-Type: application/json" \ -H "X-API-Key: dppl_YOUR_KEY_HERE" \ -H "Idempotency-Key: 8f4e9c2a-1b7d-4e3f-9a2c-6d5b8e1f0a3b" \ -d '{"photo": "https://example.com/selfie.jpg", "style": "stylized"}'
Exponential Backoff (Node.js)
Reference helper for client-side retries against Doppl's POST endpoints:
async function fetchWithRetry(url, opts = {}) { const maxAttempts = 5; for (let attempt = 0; attempt < maxAttempts; attempt++) { const res = await fetch(url, opts); if (res.status !== 429) { if (res.ok) return res; throw new Error("HTTP " + res.status); } if (attempt === maxAttempts - 1) return res; let delayMs; try { const body = await res.clone().json(); if (typeof body.retry_after_seconds === "number") { delayMs = body.retry_after_seconds * 1000; } } catch (_) { /* fall through */ } if (delayMs == null) { delayMs = (2 ** attempt) * 1000; } const jitter = Math.floor(Math.random() * 500); await new Promise(r => setTimeout(r, delayMs + jitter)); } throw new Error("unreachable"); }
Generate a new demo API key. No authentication required.
Request Body
| Parameter | Type | Required | Description |
|---|---|---|---|
| name | string | optional | Label for your API key (default: "Demo Key") |
| string | optional | Email to associate with the key for account linking |
Example
curl -X POST https://doppl.polsia.app/api/v1/keys \ -H "Content-Type: application/json" \ -d '{"name": "My Game Studio", "email": "dev@studio.com"}'
{
"api_key": "dppl_a1b2c3d4e5f6...",
"name": "My Game Studio",
"rate_limit": "5 requests/minute",
"is_demo": true,
"created_at": "2026-04-05T12:00:00.000Z",
"usage": "Pass via X-API-Key header: curl -H \"X-API-Key: dppl_...\" ..."
}
Create an avatar from a photo. The API analyzes the photo using computer vision and generates a styled avatar image.
Request Body
| Parameter | Type | Required | Description |
|---|---|---|---|
| photo | string | required* | Public URL to a photo of a person (HTTP or HTTPS). Alias: photo_url |
| style | string | optional | Avatar style. Default: stylized. See Avatar Styles |
| webhook_url | string | optional | URL to receive a webhook when generation completes |
Example
curl -X POST https://doppl.polsia.app/api/v1/avatars \ -H "Content-Type: application/json" \ -H "X-API-Key: dppl_YOUR_KEY_HERE" \ -d '{ "photo": "https://example.com/photo.jpg", "style": "anime" }'
{
"id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"status": "completed",
"style": "anime",
"avatar_url": "https://oaidalleapiprodscus.blob.core.windows.net/...",
"source_photo_url": "https://example.com/photo.jpg",
"format": "png",
"size": "1024x1024",
"processing_time_ms": 2340,
"created_at": "2026-04-05T12:00:00.000Z",
"_links": {
"self": "/api/v1/avatars/a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"styles": "/api/v1/styles"
}
}
Retrieve a specific avatar by its ID. Only returns avatars created with your API key.
Path Parameters
| Parameter | Type | Description |
|---|---|---|
| id | string (UUID) | The avatar's unique identifier returned from the create endpoint |
Example
curl https://doppl.polsia.app/api/v1/avatars/a1b2c3d4-e5f6-7890-abcd-ef1234567890 \ -H "X-API-Key: dppl_YOUR_KEY_HERE"
{
"id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"status": "completed",
"style": "stylized",
"avatar_url": "https://...",
"source_photo_url": "https://example.com/photo.jpg",
"format": "png",
"size": "1024x1024",
"processing_time_ms": 2340,
"created_at": "2026-04-05T12:00:00.000Z",
"completed_at": "2026-04-05T12:00:02.340Z",
"_links": {
"self": "/api/v1/avatars/a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"styles": "/api/v1/styles"
}
}
List all avatars created with your API key. Supports pagination and status filtering.
Query Parameters
| Parameter | Type | Default | Description |
|---|---|---|---|
| limit | integer | 20 | Results per page (max: 100) |
| offset | integer | 0 | Number of results to skip |
| status | string | — | Filter by status: processing, completed, or failed |
Example
curl "https://doppl.polsia.app/api/v1/avatars?limit=10&status=completed" \ -H "X-API-Key: dppl_YOUR_KEY_HERE"
{
"avatars": [
{
"id": "a1b2c3d4-...",
"status": "completed",
"style": "stylized",
"avatar_url": "https://...",
"format": "png",
"processing_time_ms": 2340,
"created_at": "2026-04-05T12:00:00.000Z",
"completed_at": "2026-04-05T12:00:02.340Z"
}
],
"total": 25,
"limit": 10,
"offset": 0,
"has_more": true
}
List all available avatar styles with their descriptions. No authentication required.
Example
curl https://doppl.polsia.app/api/v1/styles
{
"styles": [
{ "id": "stylized", "name": "Stylized", "description": "..." },
{ "id": "realistic", "name": "Realistic", "description": "..." },
{ "id": "anime", "name": "Anime", "description": "..." },
{ "id": "pixel", "name": "Pixel", "description": "..." },
{ "id": "clay", "name": "Clay", "description": "..." },
{ "id": "minimal", "name": "Minimal", "description": "..." }
]
}
Get usage statistics for your API key, including avatar counts, processing times, and request totals.
Example
curl https://doppl.polsia.app/api/v1/stats \ -H "X-API-Key: dppl_YOUR_KEY_HERE"
{
"total_avatars": 25,
"completed": 22,
"failed": 2,
"processing": 1,
"avg_processing_time_ms": 2340,
"first_avatar_at": "2026-04-01T10:00:00.000Z",
"last_avatar_at": "2026-04-05T12:00:00.000Z",
"api_key": {
"name": "My Game Studio",
"total_requests": 450,
"rate_limit": "10/minute",
"created_at": "2026-03-15T08:00:00.000Z"
}
}
Avatar Styles
Doppl supports 6 distinct avatar styles. Pass the style id when creating an avatar.
Error Codes
All errors follow a consistent format with an error code and human-readable message.
{
"error": "error_code",
"message": "Human-readable description"
}
| Status | Error Code | Description |
|---|---|---|
| 400 | validation_error | Invalid request body — missing required fields, invalid URL, or unsupported style |
| 401 | authentication_required | No API key provided in the request headers |
| 401 | invalid_api_key | The API key is invalid or has been deactivated |
| 404 | not_found | The requested avatar or endpoint does not exist |
| 429 | rate_limit_exceeded | Too many requests. Includes retry_after_seconds field — see Rate Limits & Best Practices for retry strategy |
| 500 | generation_failed | Avatar generation failed — photo may be inaccessible or not contain a person |
| 500 | internal_error | Unexpected server error. Retry the request |
API Playground
Send a live avatar-generation request directly from this page.
1. API Key
Paste an existing key, or click Get demo key to generate one now.
2. Request
# Fill in an API key and photo URL to see the curl command