# EZPhoto API — Complete Documentation REST API for image operations: background removal, upscaling, and AI product shots. You send an image, you get a signed URL back. Base URL https://api.ezphoto.ai Format JSON in, JSON out (multipart accepted for uploads) Auth Authorization: Bearer Errors RFC 7807 application/problem+json ## Authentication Every request needs an API key in the `Authorization` header. Keys start with `ez_live_` and are shown exactly once when created (only a hash is stored). Sign up at https://api.ezphoto.ai/signup — the free plan includes 50 operations per month, no card required. Authorization: Bearer ez_live_7d9fa9f5e084d1c4… Requests without a valid key return 401. Disabled keys fail identically, immediately. ## Image inputs Every operation accepts the image two ways: 1. **URL mode (recommended):** `Content-Type: application/json` with an `image_url` field. The URL must be publicly reachable over http(s) — inference providers fetch it directly, so localhost/private URLs fail with 422. 2. **Direct upload:** `multipart/form-data` with the file in a field named `image`. Other parameters (`factor`, `scene`, `prompt`, `webhook_url`) go in ordinary form fields. Limits: max 20 MB (413 beyond). Formats: PNG, JPEG, WebP, GIF — detected by content sniffing, not filename. ## Results & retention Results are delivered as a `result_url` — a signed URL valid for **24 hours** (exact moment in `expires_at`). After that the file is deleted and the URL stops working. Copy results to your own storage if you need them longer. Originals are never stored at all; there is no setting to change this. ## Sync vs. async `remove-background` is synchronous: the HTTP response contains the result. `upscale` and `product-shot` return `202 Accepted` with a job object: { "id": "90ac31c0-b2d8-4d2b-aace-66cd072d7429", "op": "upscale", "status": "queued", "result": null, "error": null, "created_at": "2026-08-13T21:23:47Z", "updated_at": "2026-08-13T21:23:47Z" } A job moves `queued → processing → succeeded | failed`. Get the outcome by polling `GET /v1/jobs/{id}` (most jobs finish in a few seconds) or by passing `webhook_url` when submitting. ## POST /v1/remove-background Remove the background from an image. Synchronous. $0.01 per image. Parameters: - `image_url` (string, required*): public http(s) URL of the source image. *Or upload the file as multipart field `image`. Response 200: - `request_id` (uuid): unique id of this operation. - `op` (string): `remove-background`. - `result_url` (string): signed URL of the PNG cutout (transparent background). - `expires_at` (timestamp): when `result_url` stops working (24h). Example: curl -X POST https://api.ezphoto.ai/v1/remove-background \ -H "Authorization: Bearer $EZPHOTO_KEY" \ -H "Content-Type: application/json" \ -d '{"image_url": "https://your.app/photo.jpg"}' # or direct upload: curl -X POST https://api.ezphoto.ai/v1/remove-background \ -H "Authorization: Bearer $EZPHOTO_KEY" \ -F "image=@product-photo.jpg" ## POST /v1/upscale Upscale an image 2x or 4x with detail enhancement. Async. $0.05 per image. Parameters: - `image_url` (string, required*): public http(s) URL. *Or multipart `image`. - `factor` (number): 2 (default) or 4. Anything else is a 400. - `webhook_url` (string): http(s) URL to POST the finished job to. Response 202: a job object (see "Sync vs. async"). On success, `result` contains `result_url` and `expires_at`. curl -X POST https://api.ezphoto.ai/v1/upscale \ -H "Authorization: Bearer $EZPHOTO_KEY" \ -H "Content-Type: application/json" \ -d '{"image_url": "https://your.app/photo.jpg", "factor": 4, "webhook_url": "https://your.app/hooks/ezphoto"}' ## POST /v1/product-shot Place a product photo into a styled scene. Async. $0.10 per image. Parameters: - `image_url` (string, required*): public http(s) URL. *Or multipart `image`. - `scene` (string): preset — `studio-white`, `marble-table`, or `outdoor-lifestyle`. Required unless `prompt` is given. - `prompt` (string): custom scene description; overrides `scene`. - `webhook_url` (string): http(s) URL to POST the finished job to. Scene presets: - `studio-white`: clean white studio background, soft even lighting, shadow. - `marble-table`: white marble surface, airy interior, natural window light. - `outdoor-lifestyle`: golden-hour outdoor scene, natural setting. Generated images pass a safety check; flagged content fails the job instead of returning a result. Response 202: a job object. curl -X POST https://api.ezphoto.ai/v1/product-shot \ -H "Authorization: Bearer $EZPHOTO_KEY" \ -F "image=@flat-lay.jpg" \ -F "scene=marble-table" ## GET /v1/jobs/{id} Fetch the current state of an async job. Jobs are visible only to the API key that created them; unknown or foreign ids return 404. `status` is one of `queued`, `processing`, `succeeded`, `failed`. On `failed`, `error` holds a stable human-readable message. curl https://api.ezphoto.ai/v1/jobs/90ac31c0-… \ -H "Authorization: Bearer $EZPHOTO_KEY" ## Idempotency Send an `Idempotency-Key` header (any client-chosen string, e.g. an order id) on any POST: - Retrying the same request replays the original response — same `request_id` or job, no second charge — with `Idempotency-Replayed: true`. - Reusing a key with a different body: 422. - Retrying while the original is still running: 409 (back off and retry). - If the original request failed, the key is released and the retry runs. Replays match on the normalized request, so re-uploading the same file works even though multipart encodings differ between attempts. ## Webhooks Pass `webhook_url` when creating an async job; the finished job object (identical to `GET /v1/jobs/{id}`) is POSTed there on success or failure. - Delivery attempts: 3 — immediately, then +5s, then +25s. - Per-attempt timeout: 10 seconds. - Success criterion: any 2xx from your endpoint. Webhooks are best-effort; polling always works as a fallback. Make your handler idempotent — a slow 2xx can cause a duplicate delivery. ## Errors Every error is `application/problem+json` (RFC 7807): { "type": "about:blank", "title": "Bad Request", "status": 400, "detail": "factor must be 2 or 4." } Status codes: - 400: invalid parameters (missing image_url, bad factor, unknown scene, non-image upload). - 401: missing, unknown, or disabled API key. - 404: job not found (or belongs to another key). - 409: Idempotency-Key retry while the original request is in flight. - 413: upload over 20 MB. - 415: content type is neither JSON nor multipart. - 422: upstream rejected the input (unreachable image_url, safety-flagged content), or Idempotency-Key reused with a different body. - 429: rate limit or monthly quota exceeded (see below). - 502: upstream operation failed; safe to retry. ## Rate limits & quotas - Request rate: 5 req/s sustained, bursts to 10, per key. Exceeding returns 429 with a Retry-After header. - Free plan: 50 operations per calendar month; resets on the 1st. - Optional per-key monthly spend cap; resets on the 1st. Queued jobs count against quota the moment they're accepted, so bursts can't overshoot. Idempotent replays never count twice. You're billed only for successful operations — failed jobs cost nothing. ## Pricing - remove-background: $0.01 per image - upscale: $0.05 per image - product-shot: $0.10 per image Free plan: 50 operations/month, all operations included, no card required. ## Machine-readable resources - OpenAPI 3.1 spec: https://api.ezphoto.ai/openapi.yaml - This document: https://api.ezphoto.ai/llms-full.txt - llms.txt index: https://api.ezphoto.ai/llms.txt