Sign in

Welcome to the xMode API

Generate AI images and videos from one REST API — images on POST /v1/generations, video on POST /v1/videos.

Introduction

xMode is a REST API for AI image and video generation. You integrate it into your product, your end-users trigger generations, and you pay one consolidated bill in xTokens.

Every request is tagged with the email of YOUR end-user — the real person inside your product who triggered the generation. This is required, not optional: it gives you the ability to monitor, list, and remove a specific user's content, and it gives us a clear chain of responsibility if anything is misused.

  • Async by default — pick polling OR webhookPOST /v1/generations returns `202` with `{ requestId, pollUrl, status: "queued" }` immediately. Get the result either by polling `GET /v1/generations/:requestId` (recommended every 5 s) or by passing `webhookUrl` and receiving a signed POST when the generation completes.
  • Survives restarts and traffic spikesYour request is durably queued before we reply. Server restarts, network blips, and bursts above the upstream rate limit do not lose work — we retry transparently and the result is waiting when the worker picks it up.
  • You are accountable for your end-usersYou hold one API key. Every request carries `endUserEmail`. You are responsible for what those users generate — keep an eye on activity and act on abuse, or your account may be suspended.
  • 24-hour storage by defaultEvery generated image is stored on our CDN for 24 hours, no extra cost. After that the URL stops working. Need longer? See the Storage plan section below.
  • No vendor lock-in on outputImage URLs are signed but otherwise standard HTTPS. Save them or proxy them however you want.

Machine-readable spec. If you use Postman, Insomnia, n8n, ChatGPT custom GPT actions, or any tool that imports OpenAPI — point it at `https://api.xmode.ai/openapi.json`. The spec is regenerated from our Zod schemas automatically.

Authentication

Every request must include an API key in the `Authorization` header. Keys look like `xm_live_…` and are issued in your dashboard.

  1. 1
    Sign in to your dashboardOpen the dashboard and log in with your account email.
  2. 2
    Create an API keyGo to **Settings → API Keys → Create new**. Give it a memorable name (e.g. "production-server"). Copy the key once — we never show it again.
  3. 3
    Send it as a Bearer tokenAdd the header `Authorization: Bearer xm_live_<your-key>` to every request.

Example request (async — returns 202 + requestId)

curl
# 1. Submit — returns immediately with a requestId
curl https://api.xmode.ai/v1/generations \
  -H "Authorization: Bearer xm_live_<your-key>" \
  -H "Content-Type: application/json" \
  -d '{"endUserEmail":"alice@your-product.com","prompt":"hello world"}'

# → 202 Accepted
# {
#   "requestId": "req_3xN7kQ1aJpL9wDvE",
#   "status": "queued",
#   "pollUrl": "https://api.xmode.ai/v1/generations/req_3xN7kQ1aJpL9wDvE",
#   ...
# }

# 2. Poll until status="succeeded" (an image takes from a few seconds to a couple of minutes, depending on the model and its options)
curl https://api.xmode.ai/v1/generations/req_3xN7kQ1aJpL9wDvE \
  -H "Authorization: Bearer xm_live_<your-key>"

Security tips

  • Treat keys like passwords. Keep them on your server. Never embed them in client code, mobile apps, or browser bundles.
  • Rotate keys regularly. Compromised keys can be revoked from the dashboard.
  • One key per environment is best practice — separate production from staging from CI.

Async lifecycle

Every generation moves through a small state machine — the same for images and video. The same shape is returned by `GET /v1/generations/:requestId` (images) and `GET /v1/videos/:requestId` (video), and inside a webhook delivery — only the `status` field changes.

  • queuedWe accepted the request, debited xTokens, and pushed it to the worker queue. The worker has not started yet.
  • processingA worker has picked up the request and is calling the model. An image takes from a few seconds to a couple of minutes, depending on the model and its options.
  • succeededGeneration finished. The response now includes the output array — `images[]` for image models, `videos[]` for video models — with signed URLs (valid 23 hours) and `cost.xTokens`.
  • failedGeneration failed terminally. The response includes `error.code` and `error.message`. xTokens are refunded automatically.
  • expiredMore than 23 hours have passed since success — the signed URLs are no longer fetchable. The metadata record is kept for 90 days for your audit trail.

Poll once every 5 seconds. An image takes from a few seconds to a couple of minutes, depending on the model and its options; video can take longer (up to a few minutes). The loop is identical for both — keep polling until `status` is one of `succeeded`, `failed`, or `expired`.

Webhook delivery (optional)

Pass `webhookUrl` in the create request — works the same for images (`/v1/generations`) and video (`/v1/videos`). We POST the final record to it once the request reaches a terminal status. The body is identical to what the matching `GET …/:requestId` would return at that moment (so a succeeded image carries `images[]`, a succeeded video carries `videos[]`). Each delivery is HMAC-signed so you can verify it came from us — and replay-protected via a timestamp. Find and rotate your account webhook secret in the dashboard under **Profile → Webhook signing secret**.

Headers we send

  • X-XMode-Signaturev1=<hex>

    Hex-encoded HMAC-SHA256 of `"<timestamp>.<rawBody>"` using your account webhook secret. Use a timing-safe equality check.

  • X-XMode-Timestamp<unixSeconds>

    Unix seconds when we built the payload. Reject deliveries older than 5 minutes — that is the replay window.

  • X-XMode-Request-Id<requestId>

    Mirror of the requestId in the body. Use it for your own dedup key — we may redeliver on retry.

We retry on network errors and 5xx responses: **6 attempts total** — the first delivery plus 5 retries, spaced on a **linear** backoff of 1, 2, 3, 4 and 5 minutes. That puts the whole cycle inside roughly **15 minutes**; after the last attempt we give up and the delivery is not retried again. Size your expectations around that window: if your endpoint can be down longer than ~15 minutes, poll `GET /v1/generations/{requestId}` as the fallback rather than relying on the webhook alone — the record stays readable regardless of whether delivery succeeded. 4xx responses are treated as terminal — fix the endpoint and we will stop trying immediately. We never follow redirects (3xx is treated as terminal too).

Your `webhookUrl` MUST be public HTTPS on port 443. We resolve the hostname before connecting and refuse private/loopback/link-local IPs to prevent SSRF — internal hostnames will fail with a terminal `delivery_failed`.

Verify in Node.js

js
import { createHmac, timingSafeEqual } from 'node:crypto';

function verifyXModeSignature(req, secret) {
  const ts = req.headers['x-xmode-timestamp'];
  const sig = req.headers['x-xmode-signature']; // "v1=<hex>"
  if (!ts || !sig?.startsWith('v1=')) return false;

  // Replay protection — reject anything older than 5 minutes.
  const ageSec = Math.floor(Date.now() / 1000) - Number(ts);
  if (Number.isNaN(ageSec) || ageSec < 0 || ageSec > 5 * 60) return false;

  const expected = createHmac('sha256', secret)
    .update(`${ts}.${req.rawBody}`)
    .digest('hex');
  const received = sig.slice('v1='.length);

  if (expected.length !== received.length) return false;
  return timingSafeEqual(Buffer.from(expected, 'hex'), Buffer.from(received, 'hex'));
}

Storage & expiry

Every successful generation is uploaded to our CDN and you receive a signed URL in the response. Output (images and videos) is kept for 24 hours by default. After that the URL returns 404 and the bytes are deleted. **Every read mints a fresh signature.** `GET …/{requestId}`, the list endpoint and each webhook payload all sign the URL again at the moment you read, so a link you just received is always freshly signed — there is no need to "refresh" it yourself, and a link that went stale in your database is fixed by re-reading the record. **Re-reading does NOT extend the retention window.** `expiresAt` is fixed when the generation completes and is never pushed forward by polling. Once it passes, the record comes back with status `expired` and no URL at all — the stored bytes have been deleted. So: do not cache URLs (re-read instead), and do not expect polling to keep an old generation alive. If you need the output beyond the window, download it inside the window.

Free 24-hour storageNo extra setup, no extra cost. The 24-hour clock starts at the moment the output is created. If you want to keep an image or video beyond that, download it and store it on your own infrastructure — or upgrade to a Storage plan (next).
Storage plan (coming soon)For products that need persistent image archives — galleries, dashboards, content history — we will offer a paid Storage plan that keeps every generation under your account for 30 / 90 / 365 days (or longer, by tier). Pricing is billed in xTokens per month, scales with the volume of GB you keep. Detail published when it ships.

If you only need an image short-term (preview, single-use generation), the free 24-hour window is enough. If you ship persistent UX (a user gallery, a brand asset library, etc.) you should either save the bytes yourself the moment you receive them, or wait for the Storage plan.

Acceptable use & account responsibility

xMode runs strict content and compliance policies. As the API-key holder you are accountable for everything generated under your account — including by the end-users you onboard. Read this section before going live.

  • You attribute every requestEvery call to the generation API requires `endUserEmail`. This is the real, stable identifier of the person inside your product who triggered the generation. Do not pass a fake address, a shared placeholder, or a different end-user's email — these are policy violations.
  • You monitor your end-usersYou are expected to look at usage per end-user (using the listing endpoints under that email) and to act when an end-user produces disallowed content or attempts abuse. We block obvious policy violations on our side, but the first line of defence is you.
  • You take action on abuseIf an end-user violates the rules — disallowed content, prompt-injection of safety controls, attempts to extract personal data of others, mass-generation for resale, etc. — remove that end-user's access in your product and call `DELETE /v1/end-users/{email}` to wipe their generations. If you do nothing, the responsibility shifts to you.
  • Inaction can suspend your accountRepeated policy violations under the same `endUserEmail` that the owner does not address can lead to throttling, suspension, or termination of your API account. We will reach out before suspending where possible, but the policy is enforced — not best-effort.
  • No bypass mechanismsDo not attempt to circumvent content filters, lie about request origin, or call the upstream provider directly with our keys. These actions immediately suspend the account.

These rules exist so legitimate builders can ship fast without inheriting the abuse caused by other tenants. The xMode team is small; we cannot review every prompt — we rely on you to police your own corner of the product.

Ready to generate?

Start with xSD 4.5 — full reference, parameters and runnable examples. Every other model is in the sidebar.

Continue to xSD 4.5