Erased

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
}
FieldTypeNotes
filenamestringUsed for response headers only; the server assigns the object_key.
content_typestringOne of image/png, image/jpeg, image/webp, image/avif, image/heic, image/heif, image/jxl.
sizeintegerMax 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-TypeRequired fieldsBest for
multipart/form-dataimage (file, ≤10MB) or object_key; mode; options (JSON string, optional)One-shot upload-and-process. Drop-in curl -F use.
application/json + image_base64image_base64 (≤1MB decoded), content_type, modeJSON-only toolchains (Zapier, n8n). Small images.
application/json + object_keyobject_key (from POST /uploads), modeLarge 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 ..." } },
      "exif_ai_tags": { "Software": "Gemini" },
      "png_text_chunks": null,
      "xmp_digital_source_type": null
    },
    "invisible_external": {
      "synthid": {
        "status": "unsupported_locally",
        "verify_url": "https://support.google.com/gemini/answer/16722517"
      }
    },
    "overall_assessment": "Likely AI-generated by Gemini. C2PA Content Credentials present."
  }
}

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 — still 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",
  "error_message": "..."
}

GET /clean — URL input, image output

One-shot synchronous endpoint: pass a public image URL, get the processed image back as the response body. Designed for <img src="..." /> embeds and CDN caching.

GET /api/v1/clean?image=<encoded-https-url>&mode=visible

→ 200 OK
   Content-Type: image/png
   Cache-Control: public, max-age=86400
   <bytes>
  • Modes: visible, metadata, invisible, all. detect is rejected (returns JSON, not an image).
  • Auth: Anonymous / Free only. Pro users should use POST /jobs.
  • Size limit: 10MB (HEAD-checked before fetch).
  • Errors: 4xx/5xx responses return a placeholder PNG body; the real error type is in the X-Error-Type header.
  • SSRF: only https:// URLs accepted; private IP ranges blocked.

Authentication tiers

  • Anonymous — no credentials required. Subject to per-hour quotas.
  • Free — sign in via Google OAuth or an email magic link at /en/sign-in. Higher hourly caps, still no API key.
  • Pro ($9 / month) — issued an API key. Pass it as Authorization: Bearer uk_.... Quotas are monthly. Includes 100 buffer credits/month for overage, and detect calls are unlimited.

Rate limits

Quota groupModesAnonymousFreePro
lightvisible, metadata30 / h100 / h5000 / month
heavyinvisible, all5 / h15 / h500 / month
uploadsPOST /uploads60 / h200 / hunlimited
detectdetect100 / h500 / hunlimited

heavy also has a per-day cap of 12/day (Anonymous & Free), on top of the hourly limit — Anonymous/Free by ip+fp, logged-in users by account. Pro is governed by its monthly quota.

Exceeding a limit returns 429 with a Retry-After header and an RFC 7807 body. Failed jobs automatically refund the bucket charge.