Skip to content
Sign inStart free
FeaturesPricingAPIAboutBlogChangelogStart free. 5 images on us
Sign inContactChangelog

For developers

The clean image API.

One REST endpoint. Single or bulk. Async by design — submit a job, poll its status and queue position, then fetch the clean result. Token-based. Spend-capped.

POSThttps://api.aiunmark.com/api/process

Quickstart

From zero to a clean image in four steps.

1

Create an account and generate a key

1. Sign up at app.aiunmark.com
2. Open API Keys
3. Click "Generate key"
4. Set a spend cap and rate limit for the key
5. Copy the key (shown once)
2

Submit a job (async)

POST your image. Processing is asynchronous: the request is accepted immediately and returns a jobId plus the URLs to poll and fetch the result.

curl -X POST https://api.aiunmark.com/api/process \
 -H "Authorization: Bearer $AIUNMARK_KEY" \
 -F "[email protected]"
202 Accepted
{
  "jobId": "job_abc123",
  "status": "queued",
  "tokensCharged": 10,
  "queuePosition": 2,
  "etaMs": 4800,
  "poll": "/api/jobs/job_abc123",
  "result": "/api/jobs/job_abc123/result"
}
3

Track the job

Poll the returned URL to follow the job. The response reports status (queued → running → done | failed), how many jobs are ahead of yours, and an estimate based on recent throughput.

GET /api/jobs/job_abc123
{
  "jobId": "job_abc123",
  "status": "running",
  "progress": "running",
  "tokensCharged": 10,
  "queuePosition": 0,
  "queueAhead": 0,
  "etaMs": 2100,
  "files": [{ "name": "photo.png", "status": "running" }]
}

Queue position counts jobs ahead of yours that are not yet finished. etaMs is an estimate computed from recent job throughput, not a guarantee.

4

Fetch the clean result

Once status is done, GET the result URL. A single-file job streams the clean image directly. A bulk job returns a JSON listing each file with its own download URL.

# Single file -> streams the clean image
curl https://api.aiunmark.com/api/jobs/job_abc123/result \
 -H "Authorization: Bearer $AIUNMARK_KEY" -o clean.png

Bulk (multiple files) returns a per-file listing — fetch each via its download URL:

{ "ok": true,
  "results": [ { "name": "a.png", "bytes": 94210, "url": "/api/jobs/job_abc123/files/0" } ],
  "tokens_used": 10 }

Authentication

Every request requires an API key in the Authorization header as a Bearer token. Keys are scoped to your account. Generate as many as you need. Cap spend and set rate limits per key.

Keys can be revoked at any time from the API Keys panel. Revocation is immediate.

Request header
Authorization: Bearer <your_api_key>

Endpoints

Four endpoints make up the whole API. Submit a job, track it, then fetch the result. Same endpoints for single and bulk.

Method & path Purpose
POST /api/processSubmit one or more images. Returns 202 with a jobId, tokens charged, queue position, ETA, and poll/result URLs.
GET /api/jobs/:idJob status and progress. Reports status, queuePosition, queueAhead, and etaMs. Poll until done.
GET /api/jobs/:id/resultThe clean result. Single file → streams the image. Bulk → JSON listing each file with a download URL. Returns 409 if not done yet.
GET /api/jobs/:id/files/:indexA single processed file from a bulk job, streamed. Use the index from the result listing.

Every endpoint requires the Authorization: Bearer header. Jobs are scoped to your key — you can only poll and fetch jobs your key created.

Error handling

Clean codes, predictable shapes. The 402 and 500 rows reflect the no-result, no-charge guarantee: a failed job refunds its tokens automatically.

HTTP Code Meaning Retry
400NO_FILESNo files were attached to the request.No
400UNSUPPORTED_TYPEA file is not PNG, JPEG, or WebP, or is empty. No tokens are charged.No
401UNAUTHORIZEDThe key is missing, revoked, or malformed.No
402INSUFFICIENT_TOKENSThe key's balance cannot cover the job. No tokens are charged.No
402SPEND_CAP_EXCEEDEDThe key has reached its per-key spend cap. Raise the cap or use another key. No tokens are charged.No
404NOT_FOUNDThe job or file does not exist, or is not owned by your key.No
409NOT_READYYou fetched the result before the job finished. Poll until done.Yes
429RATE_LIMITEDThe key exceeded its requests-per-minute limit. Back off and retry.Yes
500PROCESS_FAILEDThe job failed during processing. Tokens are refunded automatically.Yes
{ "error": { "code": "PROCESS_FAILED", "message": "...", "retryable": true } }

When retryable is true, use exponential backoff. We recommend a base of 1 second with jitter, up to 5 attempts.

Watermark classes reference

You do not pass which watermark to remove. The engine detects every class in the file and strips them all. The six classes it handles:

synthidGoogle hidden pixel watermark
stable-signatureMeta hidden pixel watermark
meta-video-sealMeta hidden frame watermark
c2paC2PA provenance metadata manifest
tiled_patternVisible repeating tiled mark
branded_logoVisible corner logo

Use it from any language

Prefer another language? The API is plain REST with multipart uploads. Any HTTP client works.

Node
fetch() with FormData

Built in to Node 18 and up. No dependency to install.

Python
requests.post()

Use the requests library. One call.

curl
No install. Standard tool.

Every example here runs with curl.

Rate limits and spend caps

Every key has two independent dials you set from the API Keys panel: a per-key rate limit (requests per minute) and a per-key spend cap (cumulative tokens). Hitting either returns a 429 or 402 SPEND_CAP_EXCEEDED respectively. A spend cap of 0 means unlimited.

Both are self-serve and take effect immediately. Set them per key so a runaway script or a misconfigured integration is bounded before it can drain your balance.

Requests per minute vs. delivery. A rate limit caps how many jobs you can submit in a window. It is not a measure of how long a job takes to finish. Different jobs take different amounts of time depending on image size and current server load, and processing is handled first-come, first-served as server capacity allows. We do not warrant or guarantee any specific delivery speed or turnaround for a job — the etaMs field is an estimate to help you plan, not a commitment.
SettingValue
Default rate limit (every key, every plan)Unlimited
Self-serve per-key limitup to 600 rpm
Default spend cap (every key)0 = unlimited

Polling for completion

The API is poll-based. After you submit a job, poll GET /api/jobs/:id until status is done or failed, then fetch the result. A simple exponential backoff works well: start around 1 second and grow it as the ETA shrinks.

poll loop (pseudo-code)
let delay = 1000;
while (true) {
  const job = await get(`/api/jobs/${jobId}`);
  if (job.status === "done")   return fetchResult(jobId);
  if (job.status === "failed") throw new Error(job.error_code);
  // queuePosition + etaMs help you decide how long to wait
  await sleep(delay);
  delay = Math.min(delay * 1.5, 5000);
}

Each poll response includes queuePosition, queueAhead, and an etaMs estimate, so you can show progress to your users instead of an opaque spinner.

Ship clean images today.