EZPhoto API

One REST API for image operations: background removal, upscaling, and AI product shots. You send an image, you get a URL back. Everything else on this page is detail.

Base URL   https://api.ezphoto.ai
Format     JSON in, JSON out (multipart accepted for uploads)
Auth       Authorization: Bearer <key>
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 — we store only a hash. Get one from the dashboard; the free plan includes 50 operations per month.

Authorization: Bearer ez_live_7d9fa9f5e084d1c4…

Requests without a valid key return 401. Disabled keys fail the same way, immediately.

Quickstart

Remove the background from an image in one call. The response is the result — no polling for this endpoint.

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"}'
import requests

r = requests.post(
    "https://api.ezphoto.ai/v1/remove-background",
    headers={"Authorization": f"Bearer {EZPHOTO_KEY}"},
    json={"image_url": "https://your.app/photo.jpg"},
)
print(r.json()["result_url"])
const res = await fetch("https://api.ezphoto.ai/v1/remove-background", {
  method: "POST",
  headers: {
    Authorization: `Bearer ${process.env.EZPHOTO_KEY}`,
    "Content-Type": "application/json",
  },
  body: JSON.stringify({ image_url: "https://your.app/photo.jpg" }),
});
const { result_url } = await res.json();

Response, typically in about a second:

{
  "request_id": "0f04c41c-a886-4da8-b211-3d4b12007110",
  "op": "remove-background",
  "result_url": "https://…/0f04c41c-….png",
  "expires_at": "2026-08-14T22:28:13Z"
}

Image inputs

Every operation accepts the image two ways. Pick one per request:

URL mode (recommended)

Send Content-Type: application/json with an image_url. The URL must be publicly reachable over http(s) — our inference providers fetch it directly, so localhost and private-network URLs will fail with a 422.

Direct upload

Send multipart/form-data with the file in a field named image. Other parameters (factor, scene, prompt, webhook_url) go in ordinary form fields.

LimitValue
Max upload size20 MB (413 beyond that)
FormatsPNG, JPEG, WebP, GIF — detected by content sniffing, not filename

Results & retention

Results are delivered as a result_url — a signed URL on our storage, valid for 24 hours (the exact moment is in expires_at). After that the file is deleted and the URL returns an error. Copy results to your own storage if you need them longer.

Retention is the product, not a caveat: originals are never stored, and results cannot outlive their TTL. There is no setting to change this.

Sync vs. async

remove-background is synchronous: the HTTP response contains the result. upscale and product-shot are heavier, so they return 202 Accepted with a job object immediately:

{
  "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 either way:

  • Poll GET /v1/jobs/{id} — most jobs finish in a few seconds.
  • Webhook — pass webhook_url and we POST the finished job to you. See Webhooks.

POST /v1/remove-background

Remove the background from an image. Synchronous · $0.01 per image.

Parameters

FieldTypeDescription
image_urlstringrequired*Public http(s) URL of the source image. *Or upload the file as multipart image.

Response 200

FieldTypeDescription
request_iduuidUnique id of this operation (also in your usage ledger).
opstringremove-background
result_urlstringSigned URL of the PNG cutout with transparent background.
expires_attimestampWhen result_url stops working (24h).
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"}'
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

FieldTypeDescription
image_urlstringrequired*Public http(s) URL of the source image. *Or multipart image.
factornumber2 (default) or 4. Anything else is a 400.
webhook_urlstringhttp(s) URL to POST the finished job to.

Response 202 — a job object. On success, result contains:

"result": {
  "result_url": "https://…/90ac31c0-….png",
  "expires_at": "2026-08-14T21:23:52Z"
}
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

FieldTypeDescription
image_urlstringrequired*Public http(s) URL of the product photo. *Or multipart image.
scenestringone of scene/promptA preset: studio-white, marble-table, or outdoor-lifestyle.
promptstringCustom scene description; overrides scene when both are sent.
webhook_urlstringhttp(s) URL to POST the finished job to.

Scene presets

PresetLook
studio-whiteClean white studio background, soft even lighting, subtle shadow.
marble-tableWhite marble surface, airy interior, natural window light.
outdoor-lifestyleGolden-hour outdoor scene, natural setting.

Generated images pass a safety check; flagged content fails the job with a 422-class error rather than returning a result.

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 key that created them.

curl https://api.ezphoto.ai/v1/jobs/90ac31c0-b2d8-4d2b-aace-66cd072d7429 \
  -H "Authorization: Bearer $EZPHOTO_KEY"

status is one of queued, processing, succeeded, failed. On failed, error holds a stable human-readable message. Unknown or foreign job ids return 404.

Idempotency

Network timeouts happen; paying twice for one image shouldn't. Send an Idempotency-Key header (any string up to your choosing, e.g. an order id) on any POST:

Idempotency-Key: order-4812-hero
  • Retrying the same request replays the original response — same request_id or job, no second charge — with an Idempotency-Replayed: true header.
  • Reusing a key with a different body is a 422.
  • Retrying while the original is still running is a 409 — back off and retry.
  • If the original request failed, the key is released and the retry executes normally.

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 and we POST the finished job object (identical to GET /v1/jobs/{id}) to that URL when it succeeds or fails.

BehaviorValue
Delivery attempts3 — immediately, then +5s, then +25s
Per-attempt timeout10 seconds
Success criterionAny 2xx response from your endpoint

Webhooks are best-effort: if all attempts fail, the job result is still available by polling. Design your handler to be idempotent — a slow 2xx can cause a duplicate delivery.

Errors

Every error is application/problem+json (RFC 7807) with a stable shape:

{
  "type": "about:blank",
  "title": "Bad Request",
  "status": 400,
  "detail": "factor must be 2 or 4."
}
StatusWhen
400Invalid parameters — missing image_url, bad factor, unknown scene, non-image upload.
401Missing, unknown, or disabled API key.
404Job not found (or belongs to another key).
409Idempotency-Key retry while the original request is still in flight.
413Upload over 20 MB.
415Content type is neither JSON nor multipart.
422Upstream rejected the input (unreachable image_url, safety-flagged content), or Idempotency-Key reused with a different body.
429Rate limit or monthly quota exceeded — see limits.
502The upstream operation failed. Safe to retry.

Rate limits & quotas

LimitDefaultOn exceeding
Request rate5 req/s sustained, bursts to 10, per key429 with Retry-After
Free plan50 operations per calendar month429, resets on the 1st
Spend capOptional per-key monthly ceiling429, resets on the 1st

Queued jobs count against your quota the moment they're accepted, so a burst of submissions can't overshoot the cap. Idempotent replays never count twice.

Pricing

OperationPrice
remove-background$0.01
upscale$0.05
product-shot$0.10

You're billed only for successful operations — failed jobs cost nothing.