API Reference
Base URL: https://erased.ink/api/v1
All successful responses are application/json. Errors are application/problem+json (RFC 7807) — see the Error codes page.
POST /uploads
Issue a short-lived presigned R2 PUT URL.
Request
{
"filename": "photo.png",
"content_type": "image/png",
"size": 4823109
}| Field | Type | Notes |
|---|---|---|
filename | string | Used for response headers only; the server assigns the object_key. |
content_type | string | One of image/png, image/jpeg, image/webp, image/avif, image/heic, image/heif, image/jxl. |
size | integer | Max 10485760 (10 MB). |
Response 200
{
"upload_url": "https://...",
"object_key": "in/2026-05-24/job_xxx.png",
"expires_at": "2026-05-24T10:15:00Z"
}You must PUT the file to upload_url before expires_at.
POST /jobs
Create a processing job.
Request body — three shapes
POST /api/v1/jobs accepts three body forms. Pick one based on what you have at hand:
| Content-Type | Required fields | Best for |
|---|---|---|
multipart/form-data | image (file, ≤10MB) or object_key; mode; options (JSON string, optional) | One-shot upload-and-process. Drop-in curl -F use. |
application/json + image_base64 | image_base64 (≤1MB decoded), content_type, mode | JSON-only toolchains (Zapier, n8n). Small images. |
application/json + object_key | object_key (from POST /uploads), mode | Large files / batch — pair with the presigned upload. |
Example (object_key form)
{
"object_key": "in/2026-05-24/job_xxx.png",
"mode": "visible"
}Response — sync (visible, metadata)
{
"id": "job_xxx",
"status": "succeeded",
"mode": "visible",
"result_url": "https://...",
"result_expires_at": "2026-05-25T10:00:00Z",
"processing_ms": 412
}Response — async (invisible, all)
{
"id": "job_xxx",
"status": "queued",
"mode": "invisible",
"poll_url": "/api/v1/jobs/job_xxx",
"poll_after_ms": 2000
}Response — detect (read-only, no result_url)
{
"id": "job_xxx",
"status": "succeeded",
"mode": "detect",
"processing_ms": 487,
"report": {
"visible": {
"detected": true,
"confidence": 0.87,
"region": [1820, 120, 80, 80],
"label": "Gemini sparkle"
},
"metadata": {
"c2pa": { "present": true, "manifest": { "claim_generator": "Gemini ..." } },
"ai_markers": { "Software": "Gemini" }
},
"invisible": { "status": "not_tested" },
"overall_assessment": "Markers found in the completed checks: Gemini sparkle; C2PA Content Credentials (not proof of AI generation); Metadata: Software. Invisible watermarks, including SynthID and TreeRing, were not tested."
}
}detect never writes an output object; the original image is unchanged.
GET /jobs/:id
Poll an async job (and a valid read for sync jobs while their record is still in KV — about a 30-minute TTL).
Response — waiting for GPU
{
"id": "job_xxx",
"status": "queued",
"mode": "invisible",
"poll_after_ms": 2000
}Response — GPU allocated and processing
{
"id": "job_xxx",
"status": "processing",
"mode": "invisible",
"poll_after_ms": 2000
}Response — succeeded
Same shape as the sync success response above.
Response — failed
{
"id": "job_xxx",
"status": "failed",
"mode": "invisible",
"error": {
"code": "pipeline_error",
"message": "..."
}
}Authentication tiers
detect is the only mode available without an account (100 req/hour unauthenticated). All strip modes require at least a free account.
- Free (signed in) — sign in via Google OAuth or an email magic link at /en/sign-in. Unlocks all strip modes. No API key issued.
- Pro ($12 / month for new subscriptions) — issued an API key. Pass it as
Authorization: Bearer uk_.... Each paid period includes 5,000 light operations, 100 heavy operations, unlimited detection, and 100 buffer credits for overage. Buffer credits expire at the end of that period. Existing subscriptions on the $9 early-bird plan keep that price.
Rate limits
detect is the only mode usable without an account: 100 req/hour unauthenticated, 500/hour for free accounts. All other modes require a free account (signed in) or Pro.
| Quota group | Modes | Free (signed in) | Pro |
|---|---|---|---|
light | visible, metadata | 10 / day | 5000 / period |
heavy | invisible, all | 3 / day | 100 / period |
uploads | POST /uploads | 200 / h | 5000 / h |
detect | detect | 500 / h | unlimited |
Anti-abuse rate limits still apply to all plans and return 429 with a Retry-After header and an RFC 7807 body. Daily free allowances reset at 00:00 UTC. Once the free or Pro allowance is used up, light jobs cost 1 credit and heavy jobs cost 10 credits; insufficient credits returns 402. Failed jobs automatically refund their quota and credits.