Reference
Jobs
Create a job, read its state, and check the account balance.
A job is one image through one tool. Creating a job charges immediately and returns before generation finishes; you collect the result by polling or by webhook.
Create a job
POST /v1/jobs
Headers
| Header | Type | Required | Description |
|---|---|---|---|
X-Api-Key | string | required | Key issued in the developer portal, prefixed |
Idempotency-Key | stringmax 128 characters | optional | Client-generated key that makes a retry safe. Repeating a request with the same key and the same body returns the original job — no second charge, no second render — and still answers |
Request body
| Parameter | Type | Required | Description |
|---|---|---|---|
tool | stringmax 100 characters | required | Tool slug, exactly as returned by |
image_url | string · urimax 2000 characters | optional | Public https URL of the input image (JPEG, PNG or WebP, max 20 MB). Provide exactly one of |
image_base64 | string | optional | The image inline, base64-encoded, optionally with a data-URI prefix ( |
options | object | optional | Tool-specific options. For tool handlers, unknown keys are ignored and an out-of-range value for a key the tool does know is rejected with Some tools require options: See |
webhook_url | string · urimax 2000 characters | optional | Overrides the account webhook for this job. Must be a public https URL; private and loopback hosts are rejected with |
Response
201 with the job id and what it cost. A replayed Idempotency-Key also answers 201,
re-returning the original job rather than 200.
| Parameter | Type | Required | Description |
|---|---|---|---|
job_id | string · uuid | required | Pass this to |
status | string | required | Always processing |
images_charged | integer | required | Images charged for this job. |
The status is always processing — the job is accepted, generation has not finished.
Choosing an image source
Provide exactly one of image_url or image_base64. Neither or both is invalid_image.
image_url
A public https URL serving JPEG, PNG or WebP, up to 20 MB.
Roomagen fetches it server-side with a 10-second timeout. Private, loopback and link-local
hosts are refused. At most 3 redirects are followed, and each hop is re-checked against the
same rules. A non-image Content-Type or a non-2xx status comes back as image_fetch_failed.
image_base64
The image inline, base64-encoded, optionally with a data-URI prefix
(data:image/png;base64,…). The prefix also selects the stored format; without one the image
is treated as JPEG.
The decoded image may be up to 20 MB, and the whole request body must stay under 30 MB —
but that larger body limit applies only when X-Api-Key is present. Anonymous requests
are capped at 1 MB and rejected with payload_too_large, which is what you see if the header
is missing or misspelled on a large upload.
For anything close to the limit, prefer image_url: it keeps your request small and avoids
base64's 33% size overhead.
Idempotency
Send an Idempotency-Key header to make a retry safe after a timeout or a dropped
connection.
- Repeating a request with the same key and the same body returns the original job — no
second charge, no second render — and still answers
201. - The same key with a different body is rejected with
409 idempotency_conflict. Send the original body to replay, or a new key for a genuinely new request. - Keys are scoped to your API key and may be at most 128 characters. Longer is rejected with
400 invalid_requestrather than truncated.
Derive the key from your own records
Use a value you can reconstruct, such as order-8842-image-1. A random key that you do not
store cannot be replayed, which defeats the point — on retry it looks like a new request and
charges again.
Read a job
GET /v1/jobs/{id}, where id is the job_id returned when the job was created. Poll about
once every 2–5 seconds; result_urls is empty until status is completed.
curl https://api.roomagen.com/api/v1/jobs/3f1c9d4e-5b2a-4c8e-9f10-7d6a2b3c4e5f \
-H "X-Api-Key: rmg_live_YOUR_KEY"{
"job_id": "3f1c9d4e-5b2a-4c8e-9f10-7d6a2b3c4e5f",
"tool": "virtual-staging",
"status": "completed",
"images_charged": 1,
"result_urls": [
"https://api.roomagen.com/api/uploads/9c2f7a10-render.jpg"
],
"error": null,
"created_at": "2026-08-24T09:14:02.114Z",
"completed_at": "2026-08-24T09:14:39.902Z",
"processing_ms": 37788
}A job id belonging to another account is reported as job_not_found — the API never confirms
that someone else's id exists.
The job object
| Parameter | Type | Required | Description |
|---|---|---|---|
job_id | string · uuid | required | |
tool | string | required | The slug that ran. |
status | string | required | Terminal states are processingcompletedfailed |
images_charged | integer | required | What this job cost at creation time. A |
result_urls | array<string · uri> | required | Rendered images, empty until |
error | string | null | required | Human-readable failure reason when |
created_at | string · date-time | required | |
completed_at | string | null · date-time | required | Set when the job reaches |
processing_ms | integer | null | required | Wall-clock generation time, available once the job is no longer processing. |
completed and failed are terminal; stop polling on either. A failed job is refunded
automatically, and images_charged is not rewritten — treat it as what was charged at
creation, not the net effect on your balance.
Check your balance
GET /v1/account returns the identity of the key and how many images it can still render.
Useful as a health check, and for a low-balance alert before a batch run.
curl https://api.roomagen.com/api/v1/account -H "X-Api-Key: rmg_live_YOUR_KEY"{
"name": "BRANDers AB",
"status": "active",
"image_credits": 486
}| Parameter | Type | Required | Description |
|---|---|---|---|
name | string | required | Client name recorded when the key was issued. |
status | string | required | A disabled key cannot authenticate at all — it is rejected with activedisabled |
image_credits | integer | required | Images still renderable with the balance behind this key. |
Rate limits
| Endpoint | Per minute |
|---|---|
POST /v1/jobs | 60 req/min |
GET /v1/jobs/{id} | 300 req/min |
GET /v1/tools, GET /v1/account | 120 req/min |
Per API key, in a sliding 60-second window, counted separately per route group. Counters live in the API process, so they reset on deploy.
IP backstop
600 req/min
Per client IP across all /v1 routes, applied before the key is identified. A fleet behind one NAT egress IP shares this budget; contact support if you need it raised.
| Header | Description |
|---|---|
X-RateLimit-Limit | Requests allowed in the current window for this route and key. |
X-RateLimit-Remaining | Requests left in the current window. |
Retry-After | Seconds to wait, sent only with a 429. |
When exceeded
429 · rate_limited
Retry after Retry-After seconds. The window slides rather than resetting on a fixed boundary, so a caller that keeps a steady rate under the limit never sees a 429.
Next step
Tools — the five slugs and the options each one accepts.