RoomagenDevelopersGet API key

Reference

Jobs

Create a job, read its state, delete it, check the account balance, and how long images are kept.

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

HeaderTypeRequiredDescription
X-Api-Keystringrequired

Key issued in the developer portal, prefixed rmg_live_. Send it on every request. Keys are stored hashed and shown only once — rotate by creating a new key and revoking the old one. A revoked or disabled key answers 401 key_revoked.

Idempotency-Keystringmax 128 charactersoptional

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 201, with the job as it stands now (processing, completed with result_urls, or failed with error). The same key with a different body is rejected with 409 idempotency_conflict. Keys are scoped to your API key; use a value derived from your own record (for example order-8842-image-1), not a random one, or retries will not match. Longer than 128 characters is rejected with 400 invalid_request rather than truncated.

Request body

ParameterTypeRequiredDescription
toolstringmax 100 charactersrequired

Tool slug, exactly as returned by GET /v1/tools. Marketing names used on roomagen.com are not slugs — GET /v1/tools is the authoritative list, and anything outside it is rejected with invalid_tool.

image_urlstring · urimax 2000 charactersoptional

Public https URL of the input image (JPEG, PNG or WebP, max 20 MB). Provide exactly one of image_url or image_base64 — neither or both is invalid_image. The URL is fetched server-side with a 10-second timeout; private, loopback and link-local hosts are refused, and at most 3 redirects are followed, each re-checked. A non-image Content-Type or a non-2xx status is reported as image_fetch_failed.

image_base64stringoptional

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. Max 20 MB decoded, and the whole request body must stay under 30 MB.

optionsobjectoptional

Tool-specific options. For tool handlers, an unknown key is ignored — and named in the response's warnings — while an out-of-range value for a key the tool does know is rejected with invalid_request.

Some tools require options: virtual-staging requires style, and rejects unknown keys outright rather than ignoring them — including resolution, tier and customPrompt, which do not apply to it.

See x-tool-options at the root of this document for per-tool keys, and x-common-options for the keys every tool handler accepts.

webhook_urlstring · urimax 2000 charactersoptional

Overrides the account webhook for this job. Must be a public https URL; private and loopback hosts are rejected with webhook_url_rejected. Omit it to use the endpoint configured in the developer portal, or to poll instead. See x-webhooks.

Response

201 with the job id and what it cost.

ParameterTypeRequiredDescription
job_idstring · uuidrequired

Pass this to GET /v1/jobs/{id}. It is also the job_id in webhook payloads.

statusstringrequired

Always processing for a new job — it is accepted, generation has not finished. A replayed Idempotency-Key answers with the Job object instead, whose status can already be completed or failed.

processing
images_chargedintegerrequired

Images charged for this job. 0 never appears; a 4K render charges 2.

warningsarray<object>required

Options in the request that the tool will not use: a key it does not have, a customPrompt it does not read (or sent without tier: "custom"), a resolution other than 2K or 4K, an alias that lost to its canonical key. Always present; empty when there is nothing to report. The job runs either way — log these, do not treat them as errors.

For a new job the status is always processing — the job is accepted, generation has not finished. A replayed Idempotency-Key also answers 201, but with the job as it stands now: the same job object GET /v1/jobs/{id} returns, plus warnings. See Idempotency.

Warnings

An option the tool will not use never fails the request — the job runs as it always has — but the response names it in warnings, so a typo does not cost you a render in silence:

  • a key the tool does not have, such as stlye for style (the message suggests the key you probably meant);
  • customPrompt on a tool that does not read one, or sent without tier: "custom";
  • a resolution other than 2K or 4K, in which case the default size is used;
  • on floor plan to 3D, a view or style alias that lost to its canonical key, or a style that is not one of the interior styles.
ParameterTypeRequiredDescription
codestringrequired

Stable machine-readable code. Branch on this, never on message.

option_ignored
optionstringrequired

The options key the warning is about.

messagestringrequired

Human-readable detail, often with the key you probably meant. May change without notice.

json
{
  "job_id": "3f1c9d4e-5b2a-4c8e-9f10-7d6a2b3c4e5f",
  "status": "processing",
  "images_charged": 1,
  "warnings": [
    {
      "code": "option_ignored",
      "option": "stlye",
      "message": "\"stlye\" is not an option of virtual-renovation, so it was ignored. Did you mean \"style\"?"
    }
  ]
}

warnings is always present and usually empty. Log it while you build the integration, and branch on code, never on message. Virtual staging is the exception: it rejects an unknown key with invalid_request instead, so its warnings is always empty.

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 body is that job as it stands now, the same job object GET /v1/jobs/{id} returns: processing while it renders, completed with result_urls, or failed with error. A retry that lands after the render finished therefore gets the result straight away. It also carries the original request's warnings.
  • If the job that key created no longer exists, the replay answers 404 job_not_found. The key stays spent on that job: send a new key to render again.
  • 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_request rather 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"
Response
200
{
  "job_id": "3f1c9d4e-5b2a-4c8e-9f10-7d6a2b3c4e5f",
  "tool": "virtual-staging",
  "status": "completed",
  "images_charged": 1,
  "result_urls": [
    "https://uploads.roomagen.com/6f0c2b9e-4a1d-4c3b-9e7f-2d8a1b5c3e90/9c2f7a10-5d4e-4f3a-8b2c-1e6d7f8a9b0c-virtual-staging-result.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. A job that was deleted answers job_not_found too.

The job object

ParameterTypeRequiredDescription
job_idstring · uuidrequired
toolstringrequired

The slug that ran.

statusstringrequired

Terminal states are completed and failed; stop polling on either.

processingcompletedfailed
images_chargedintegerrequired

What this job cost at creation time. A failed job is refunded automatically — this field is not rewritten, so treat it as the charge, not the final balance impact.

result_urlsarray<string · uri>required

Rendered images, empty until status is completed. Each is a public, unsigned URL of the form https://uploads.roomagen.com/<account-id>/<file-id>-<name>.<ext>; <name> is an internal label that can differ from tool, so use the URLs as given and do not build or parse them. The images are deleted when the job is deleted — DELETE /v1/jobs/{id}, the key's retention_days, or the account's limit of 2,000 completed jobs — so download and store the ones you need. Until the account buys a pack, renders carry a Roomagen watermark (the free images).

errorstring | nullrequired

Human-readable failure reason when status is failed, otherwise null.

created_atstring · date-timerequired
completed_atstring | null · date-timerequired

Set when the job reaches completed or failed.

processing_msinteger | nullrequired

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.

Delete a job

DELETE /v1/jobs/{id} deletes a finished job — completed or failed — together with the images stored for it: the rendered results and the input image. It answers 204 with an empty body.

curl -X DELETE https://api.roomagen.com/api/v1/jobs/3f1c9d4e-5b2a-4c8e-9f10-7d6a2b3c4e5f \
  -H "X-Api-Key: rmg_live_YOUR_KEY"
  • A job that is still processing answers 409 job_in_progress. Send the DELETE again once GET /v1/jobs/{id} reports completed or failed.
  • A job id that belongs to another account, an unknown id, or a job that was already deleted answers 404 job_not_found. After a delete, GET /v1/jobs/{id}, a second DELETE and a replay of the job's Idempotency-Key answer 404 job_not_found as well.
  • A webhook for the job that has not been delivered yet is not sent.
  • Deleting a job does not refund it, and it still counts in your usage.
  • The job's record — its id, tool, status, images charged and timestamps — is kept for billing. The options sent with it are deleted.
  • If a stored image cannot be removed at that moment, the delete still answers 204 and the image is removed by a retry that runs once a day.

Deletes have a rate limit of their own: 60 per minute per key, separate from creating and reading jobs.

Storage and retention

Result images are served from public URLs of this form:

text
https://uploads.roomagen.com/<account-id>/<file-id>-<name>.<ext>

The URLs are not signed and do not expire: anyone who has a URL can download the image until the image is deleted. <account-id> identifies the account behind your API keys, and <name> is an internal label that can differ from the job's tool. Use the URLs exactly as the API returns them; do not build or parse them.

WhenWhat is deleted
You call DELETE /v1/jobs/{id}The job's results and its input image, at once.
The key has a retention_daysEach job created with that key once it is older than that many days, counted from its created_at: results and input image. A daily run at 04:20 UTC applies it; a large backlog can take more than one run.
The account holds more than 2,000 completed jobsThe oldest completed jobs, results and input image, when a job of a tool other than virtual-staging completes. Jobs of every tool count toward the limit, and it counts all keys of the account together; completing a virtual-staging job does not apply it.
A job failedIts input image, 30 days after the job was created. The job itself can still be read.
A request is rejected without creating a job, for example with 402 insufficient_creditsThe image it sent, at once.

Without a retention_days, nothing else deletes a job: it stays until you delete it or the 2,000-job limit removes it. An account that only runs virtual-staging therefore keeps its jobs until it deletes them.

retention_days is set by Roomagen on request. Email [email protected] with the prefix of the key (rmg_live_…) and a number of days from 1 to 365. GET /v1/account returns the value for the key that calls it, null when the key has none.

The input image of a completed job is stored with the job and deleted with it; the API does not return its URL.

Deleting removes the images from storage. A copy that the content delivery network in front of uploads.roomagen.com has already cached can still be served until that cache expires. Download and store the images you need on your side.

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"
Response
200
{
  "name": "Example Realty Ltd",
  "status": "active",
  "image_credits": 486,
  "retention_days": null
}
ParameterTypeRequiredDescription
namestringrequired

Client name recorded when the key was issued.

statusstringrequired

A disabled key cannot authenticate at all — it is rejected with key_revoked — so in practice this reads active whenever the call succeeds.

activedisabled
image_creditsintegerrequired

Images still renderable with the balance behind this key.

retention_daysinteger | null1–365required

Days after which jobs created with this key are deleted, images included — counted from each job's created_at and applied by a daily run. null when the key has no retention period: its jobs then stay until the account's 2,000-most-recent-jobs limit removes them, or until they are deleted with DELETE /v1/jobs/{id}. Set by Roomagen on request.

Rate limits

EndpointPer minute
POST /v1/jobs60 req/min
GET /v1/jobs/{id}300 req/min
DELETE /v1/jobs/{id}60 req/min
GET /v1/tools, GET /v1/account120 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.

HeaderDescription
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 documented slugs and the options each one accepts.

curl -X POST https://api.roomagen.com/api/v1/jobs \
  -H "X-Api-Key: rmg_live_YOUR_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: order-8842-image-1" \
  -d '{
    "tool": "virtual-staging",
    "image_url": "https://example.com/empty-living-room.jpg",
    "options": { "style": "scandinavian", "roomType": "living-room" }
  }'
Response
201
{
  "job_id": "3f1c9d4e-5b2a-4c8e-9f10-7d6a2b3c4e5f",
  "status": "processing",
  "images_charged": 1,
  "warnings": []
}