GuidesAI APIS

Runway API from browser JavaScript

Browsers cannot call api.dev.runwayml.com: on 28 October 2026 it answered the CORS preflight with 401 and no Access-Control-Allow-Origin header, so the POST is never sent. The free cors.dev proxy does not help, because it allows only GET and HEAD, refuses Authorization with 400 and drops X-Runway-Version; cors.dev Pro ($5 a month for 500,000 requests, 7-day trial with 1,000 requests, no card) forwards all three from origins you register. A gen4_image task costs 5 credits ($0.05) at 720p, and you poll GET /v1/tasks/{id} no more than every 5 seconds.

Why the browser cannot reach api.dev.runwayml.com

Probes sent on 28 October 2026 from Origin: https://example.com:

  • The OPTIONS preflight for POST https://api.dev.runwayml.com/v1/text_to_image answered 401 No API key was provided, with no Access-Control-Allow-Origin header.
  • A direct POST to /v1/text_to_image with a test key returned 401 and no CORS headers.
  • A GET to /v1/tasks/{id} also answered 401 with no CORS headers.

A call to Runway sets Authorization, Content-Type and X-Runway-Version, so the browser sends an OPTIONS preflight first, and a preflight never carries credentials. Runway rejects it with 401 and no Access-Control-Allow-Origin, so the browser stops there and your POST is never sent. DevTools reports a failed preflight, and fetch rejects with TypeError: Failed to fetch, with no status or body to read.

What the free proxy does with Runway

The free tier at https://proxy.cors.dev/ needs no signup or key and allows about 600 requests a minute per IP, but only GET and HEAD. A POST to create a task answers 405 method_not_allowed, and an Authorization header answers 400 header_not_allowed.

The free tier also drops unsupported custom headers. A task GET through it returned Runway's 401 with CORS headers and x-cors-dropped-headers: x-runway-version, while Runway requires both Authorization: Bearer <key> and X-Runway-Version: 2024-11-06 on every call.

Create a text_to_image task through cors.dev Pro

  1. Start a cors.dev trial or Pro

    sign in with Google at cors.dev. The 7-day trial needs no card and includes 1,000 requests at up to 600 a minute and 10 in flight; cors.dev Pro is $5 a month for 500,000 requests, with 3,000 requests a minute per account and 100 in flight.

  2. Create a Connection

    list the exact page origins your app runs on and add api.dev.runwayml.com as an API host. Its key, sent as X-Cors-Key, is a publishable identifier that only works from those origins.

  3. Send the proxied request

    prefix the Runway URL with https://proxy.cors.dev/ and send X-Cors-Key, Authorization: Bearer <key>, Content-Type and X-Runway-Version: 2024-11-06. The preflight to https://proxy.cors.dev/https://api.dev.runwayml.com/v1/text_to_image answered 204 and allows all four headers; a wrong X-Cors-Key answers 401 invalid_key.

Create a gen4_image task through cors.dev Pro and poll it
const PROXY = 'https://proxy.cors.dev/';
const CORS_KEY = 'YOUR_CONNECTION_KEY'; // publishable, works only from your registered origins

async function runway(path, apiKey, init = {}) {
  const res = await fetch(PROXY + 'https://api.dev.runwayml.com/v1/' + path, {
    ...init,
    headers: {
      'X-Cors-Key': CORS_KEY,
      Authorization: `Bearer ${apiKey}`,
      'X-Runway-Version': '2024-11-06',
      'Content-Type': 'application/json',
    },
  });
  const body = await res.json();
  if (!res.ok) throw new Error(`HTTP ${res.status}: ${res.headers.get('X-Cors-Error') ?? body.error}`);
  return body;
}

async function textToImage(promptText, apiKey) {
  const { id } = await runway('text_to_image', apiKey, {
    method: 'POST',
    body: JSON.stringify({ model: 'gen4_image', ratio: '1280:720', promptText }),
  });

  for (;;) {
    await new Promise((resolve) => setTimeout(resolve, 5000));
    const task = await runway(`tasks/${id}`, apiKey);
    if (task.status === 'SUCCEEDED') return task.output;
    if (task.status === 'FAILED' || task.status === 'CANCELLED') throw new Error(task.failure ?? task.status);
  }
}

const userKey = prompt('Your Runway API key');
const [imageUrl] = await textToImage('a red bicycle at dusk', userKey);

The POST /v1/text_to_image JSON body takes model (gen4_image), promptText (1 to 1,000 characters), and ratio, one of 16 values: 1024:1024, 1080:1080, 1168:880, 1360:768, 1440:1080, 1080:1440, 1808:768, 1920:1080, 1080:1920, 2112:912, 1280:720, 720:1280, 720:720, 960:720, 720:960 and 1680:720. It also takes an optional seed from 0 to 4,294,967,295 (the same seed with the same request gives similar results) and up to 3 referenceImages, each with a uri and a tag you can mention in the prompt as @tag. gen4_image_turbo requires referenceImages. A successful call returns the task id and estimatedCost.credits.

Proxy errors arrive in the X-Cors-Error header:

  • invalid_key or origin_not_allowed: check X-Cors-Key and the Connection's origins; scheme, host and port must match exactly.
  • target_not_allowed: enable api.dev.runwayml.com on the Connection.
  • request_too_large: the body is over 1 MiB.
  • upstream_timeout: Runway took longer than the 30-second deadline.
  • rate_limited or quota_exceeded: a 429; wait for Retry-After, which your code can read.

Poll the task and use the output

Poll GET /v1/tasks/{id} through the proxy no more than once every 5 seconds; Runway says not to expect updates more often than that.

  • PENDING: waiting to run; poll again in 5 seconds.
  • THROTTLED: you are over your concurrency limit; Runway stored the task and queues it in submission order. Keep polling.
  • RUNNING: generating; progress runs from 0 to 1.
  • SUCCEEDED: output holds an array of result URLs.
  • FAILED: stop; read failure and the machine-readable failureCode.
  • CANCELLED: stop polling.

The URLs in output expire within 24 to 48 hours; fetch the task again for fresh URLs, and download anything you want to keep. Put them straight into <img> or <video> tags, which need no CORS and no proxy.

What a generation costs

Credits cost $0.01 each, bought in the Runway developer portal. Prices from Runway's API pricing page, checked 28 October 2026:

Runway API credits per generation, checked October 2026
ModelOutputCreditsCost
gen4_image at 720pImage5$0.05
gen4_image at 1080pImage8$0.08
gen4_image_turboImage, any resolution2$0.02
gen4_turboVideo, per second5$0.05
gen4.5Video, per second12$0.12
veo3.1_fast without audioVideo, per second10$0.10
veo3.1 with audioVideo, per second40$0.40

Video is billed per second of output: veo3.1 without audio is 20 credits a second and veo3.1_fast with audio 15. Every create response includes estimatedCost.credits, so log it per task to track spend.

Keys, bodies and time limits

  • Keys: Runway keys are key_ followed by 128 hex characters; a shorter test key was rejected with a message saying so. Any key in browser code is visible in DevTools, and cors.dev does not hide it. Keep your own production key on a backend; browser calls fit internal tools, prototypes and bring-your-own-key apps where each user pastes their own key.
  • Bodies: Runway accepts HTTPS URLs or base64 data URIs up to 5MB in referenceImages, but cors.dev Pro caps request bodies at 1 MiB. Pass HTTPS URLs through the proxy.
  • Time: cors.dev ends every upstream request after 30 seconds, on cors.dev Pro too. Creating a task and each poll return quickly, so polling fits; do not hold one request open while a video renders.
  • Runway limits: no requests-per-minute limit while you stay within your maximum daily generations. Concurrency, daily generations and monthly spend are capped per model and per project by your usage tier; tasks past the concurrency cap wait as THROTTLED. On Tier 3, for example, a project can run 5 Gen-4.5 and 5 Veo 3.1 generations at the same time, and a project moves up a tier automatically once it meets the next tier's spend criteria.

Good questions.

Does the Runway API support CORS?

No, api.dev.runwayml.com doesn't return Access-Control-Allow-Origin on its responses. In our 28 October 2026 probes, the browser's OPTIONS preflight to /v1/text_to_image came back 401 No API key was provided without CORS headers because browsers don't send credentials on preflights. Both POST /v1/text_to_image and GET /v1/tasks/{id} also omit CORS headers.

Can I call Runway with the free cors.dev proxy?

No, the free proxy only supports GET and HEAD, so POST requests return 405 method_not_allowed. It also blocks the Authorization header with 400 header_not_allowed and strips X-Runway-Version (listing it in X-Cors-Dropped-Headers), both of which Runway requires on every request. cors.dev Pro ($5 a month for 500,000 requests, 7-day trial with 1,000 requests, no card) forwards all three from origins registered on a Connection, identified by its X-Cors-Key header.

How often should I poll a Runway task?

Runway states that you shouldn't expect task updates more than once every 5 seconds per task. Poll GET /v1/tasks/{id} on a 5-second timer while the status is PENDING, THROTTLED or RUNNING. Stop polling once the status changes to SUCCEEDED, FAILED or CANCELLED.

How long do Runway output URLs last?

The file URLs in the output array expire within 24 to 48 hours. If you need to display the image again after the link expires, call GET /v1/tasks/{id} again to get fresh URLs, or download the file to your own storage if you want to keep it permanently. While the URL is live, you can put it directly into an <img> or <video> tag without routing it through cors.dev.

How much does a Runway image cost through the API?

Runway sells credits in the developer portal at $0.01 per credit. On gen4_image, a 720p image costs 5 credits ($0.05) and a 1080p image costs 8 credits ($0.08). On gen4_image_turbo, which requires referenceImages, every image costs 2 credits ($0.02) at any resolution.