Quickstart
From zero to a clean image in four steps.
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)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.
{
"jobId": "job_abc123",
"status": "queued",
"tokensCharged": 10,
"queuePosition": 2,
"etaMs": 4800,
"poll": "/api/jobs/job_abc123",
"result": "/api/jobs/job_abc123/result"
}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.
{
"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.
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.pngBulk (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.
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/process | Submit one or more images. Returns 202 with a jobId, tokens charged, queue position, ETA, and poll/result URLs. |
GET /api/jobs/:id | Job status and progress. Reports status, queuePosition, queueAhead, and etaMs. Poll until done. |
GET /api/jobs/:id/result | The 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/:index | A 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 |
|---|---|---|---|
| 400 | NO_FILES | No files were attached to the request. | No |
| 400 | UNSUPPORTED_TYPE | A file is not PNG, JPEG, or WebP, or is empty. No tokens are charged. | No |
| 401 | UNAUTHORIZED | The key is missing, revoked, or malformed. | No |
| 402 | INSUFFICIENT_TOKENS | The key's balance cannot cover the job. No tokens are charged. | No |
| 402 | SPEND_CAP_EXCEEDED | The key has reached its per-key spend cap. Raise the cap or use another key. No tokens are charged. | No |
| 404 | NOT_FOUND | The job or file does not exist, or is not owned by your key. | No |
| 409 | NOT_READY | You fetched the result before the job finished. Poll until done. | Yes |
| 429 | RATE_LIMITED | The key exceeded its requests-per-minute limit. Back off and retry. | Yes |
| 500 | PROCESS_FAILED | The 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:
Use it from any language
Prefer another language? The API is plain REST with multipart uploads. Any HTTP client works.
fetch() with FormDataBuilt in to Node 18 and up. No dependency to install.
requests.post()Use the requests library. One call.
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.
etaMs field is an estimate to help you plan, not a commitment.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.
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.