MotionLab for developers

AI generation API quickstart

Create an API key, discover models, estimate credits and submit your first image with Node.js and cURL.

1. Create a personal key and choose a model

Sign in to MotionLab and open API key settings. Keep the key on your server in MOTIONLAB_API_KEY. A browser or mobile client should call your backend, which makes the authenticated MotionLab request. Your existing wallet funds generation; creating a key does not start a generation.

Read the model catalog before choosing an ID. GET /api/v1/models returns capabilities in result, with the catalog in result.models. GET /api/v1/actions describes the action inputs and outputs. Availability and supported settings can change.

curl https://www.motionlab.art/api/v1/models \
  -H "Authorization: Bearer $MOTIONLAB_API_KEY"

curl https://www.motionlab.art/api/v1/actions \
  -H "Authorization: Bearer $MOTIONLAB_API_KEY"

2. Estimate an image, then submit it with Node.js

This server-side example uses built-in fetch in Node.js 22 or newer. Set MOTIONLAB_IMAGE_MODEL to an image model ID from discovery. First run it without MOTIONLAB_SUBMIT to read the estimate. To generate, set MOTIONLAB_SUBMIT=yes and MOTIONLAB_REQUEST_ID to a unique ID that your application has saved. Reuse that ID if the submission response is lost.

The example uses default image settings. For a model-specific resolution or image edit, first read its supported settings and the estimate assumptions. An estimate does not reserve a price; submission checks the current price and balance again.

const base = "https://www.motionlab.art/api/v1";
const key = process.env.MOTIONLAB_API_KEY;
const model = process.env.MOTIONLAB_IMAGE_MODEL;
if (!key || !model) throw new Error("Set your API key and discovered image model ID");

async function action(name, input) {
  const response = await fetch(base + "/actions/" + name, {
    method: "POST",
    headers: { Authorization: "Bearer " + key, "Content-Type": "application/json" },
    body: JSON.stringify(input),
    signal: AbortSignal.timeout(60000),
  });
  const data = await response.json();
  if (!response.ok || data.error) {
    throw new Error(data.error?.message ?? "HTTP " + response.status);
  }
  return data.result;
}

const estimate = await action("motionlab_estimate_cost", { kind: "image", model });
console.log("Estimate:", estimate);
if (process.env.MOTIONLAB_SUBMIT !== "yes") process.exit(0);
if (!estimate.sufficient) throw new Error("Add credits before submitting");
const idempotencyKey = process.env.MOTIONLAB_REQUEST_ID;
if (!idempotencyKey) throw new Error("Set a saved, unique request ID before spending");

const job = await action("motionlab_generate_image", {
  model,
  prompt: "A glass pavilion in a desert at golden hour, architectural photograph",
  idempotencyKey,
});
console.log("Save this job ID:", job.jobId);

for (let attempt = 0; attempt < 60; attempt++) {
  await new Promise(resolve => setTimeout(resolve, 10000));
  const result = await action("motionlab_get_job", { jobId: job.jobId });
  if (result.status === "COMPLETED") {
    console.log("Download your output:", result.outputUrl);
    process.exit(0);
  }
  if (result.status === "FAILED" || result.status === "CANCELLED") {
    throw new Error("Job ended: " + result.status);
  }
}
console.log("Still pending. Resume polling this job ID; do not create another job.");

3. Handle retries and keep your result

Persist both your request ID and the returned job ID in your application database. A generation request that times out may already have been accepted. Retry the same action and input with the same idempotencyKey, rather than charging for a second creation. Keys must be 8–120 characters using letters, numbers, periods, underscores, colons or hyphens.

The example stops on HTTP errors. In a production client, handle 429 responses with a delay and backoff, resolve invalid inputs before retrying, and keep polling separate from generation. Download successful output to your own storage before its signed URL expires. Reaching the polling timeout does not cancel a job.

Common API responses

  • 401: check the Bearer key and whether it has been revoked.
  • 402: check your wallet and the latest estimate before creating another job.
  • 429: slow down requests; polling and generation share account limits.
  • Invalid input or unavailable model: refresh discovery and compare your payload with the action schema.

Continue building

Explore live models and prices · Create your API key

AI generation API quickstart | MotionLab