Skip to content
Adwright API

API reference

Everything under one base URL, with your key. JSON in and out, snake_case, every refusal with a stable code.

Base URLhttps://api.adwright.co/v1/platform

Introduction

The Adwright API does what the app does: it reads a product’s link, researches it and its market, and makes ads for it: researched image ads, video ads in a dozen styles, and the Ad Council’s verdicts. It also serves the image and video models those are made with, each at its own price.

Everything is paid from your API balance, in dollars, as you go: no plan needed. A failed generation is refunded. Prices are on Pricing and on each model in GET /models.

Authentication

Send your key on every request as Authorization: Bearer adw_live_…. Make keys on API keys: each is shown once, so keep it in a secret store or an environment variable, never in a browser or an app people download. A key works on this API only, never on your Adwright account’s settings or billing. Revoke one and its next request answers 401.

Jobs and polling

Making things takes from seconds (a picture) to minutes (a project, a video), so every request that makes something answers 202 straight away with what it started and a status: queued, running, then succeeded or failed. Poll its GET every few seconds, or send a webhook_url and we'll POST to it when it ends. Every one also has a job (GET /jobs/{id}) with its webhook's state.

Errors

A refusal is application/problem+json: a title to show people, the HTTP status, a stable code to act on, and a request_id (also in the Request-Id header) to quote to us.

CodeStatusWhat it means
invalid_request400A field is missing or wrong. The title says which.
unauthorized401No key, or one that's revoked.
insufficient_balance402The balance can't cover it. Add funds, or turn on auto top-up.
forbidden403The key can't do that.
not_found404No such thing on this account, or no such route.
conflict409It changed meanwhile (a price, a state). Read it again and retry.
idempotency_conflict409The Idempotency-Key was used for another request, or its first request is still running.
too_large413The body or file is too big.
rate_limited429Too many requests for this key. Wait for Retry-After.
concurrency_limited429As many jobs are running as the account allows. Wait for one to finish.
unavailable503That model or feature is off for now. Try again later.
server_error500Our side failed. Retry with backoff; tell us the request_id if it keeps happening.
{
  "type": "https://developer.adwright.co/docs/errors#insufficient_balance",
  "title": "Your API balance can't cover this. Add funds in the developer portal, or turn on auto top-up.",
  "status": 402,
  "code": "insufficient_balance",
  "request_id": "req_0hn4c8m2kq7d1"
}

Limits

Each key makes up to 120 requests a minute, and of those 30 that start work. Over that, 429 rate_limited with a Retry-After in seconds. An account runs a number of jobs at once that grows with the funds it added in the last 28 days: 2 from $0, 10 from $25, 20 from $500 and 40 from $1,000 (the dashboard shows yours). One more answers 429 concurrency_limited until one finishes. Retry both with backoff.

Idempotency

Send an Idempotency-Key header (a UUID) with every POST that makes something. A retry with the same key answers what the first request answered, for 24 hours, without making or charging anything again (it carries Idempotent-Replayed: true). The same key on a different request is 409 idempotency_conflict. A refusal isn't kept, so after topping up the same key can try again.

Webhooks

With a webhook_url, we POST an event to it when the job ends. Answer with any 2xx within 10 seconds; otherwise we try again after 30 seconds, 2 and 10 minutes, then 1, 3, 6 and 12 hours. Each is signed with your webhook secret (on API keys): Adwright-Signature: t=<unix>,v1=<hex>, where v1 is the HMAC-SHA256 of <t>.<raw body>.

{
  "id": "evt_4a1f9c2e7b3d8e6f0a1b",
  "type": "generation.succeeded",
  "created_at": "2026-10-29T09:41:24Z",
  "data": {
    "job_id": "job_4a1f9c2e7b3d8e6f0a1b",
    "kind": "generation",
    "status": "succeeded",
    "target_id": "med_8f2c1a9e4b7d3f60a1c2",
    "project_id": null,
    "video_ids": null,
    "finished_at": "2026-10-29T09:41:24Z"
  }
}
import crypto from "node:crypto";

// rawBody: the request's body exactly as it arrived (a string), before any JSON parsing.
export function verifyAdwright(rawBody, header, secret) {
  const parts = Object.fromEntries(header.split(",").map((p) => p.split("=")));
  const t = Number(parts.t);
  if (!t || Math.abs(Date.now() / 1000 - t) > 300) return false; // older than five minutes
  const expected = crypto.createHmac("sha256", secret).update(`${t}.${rawBody}`).digest("hex");
  return crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(parts.v1 ?? ""));
}

Models

Every image and video model, and Adwright's own workflows, with what each costs on your account.

List models

GET/models

The catalogue, most used first. Models' price_from is one generation at their defaults; workflows' is the cheapest way to run them.

Query

typestring

image, video or workflow: only that kind.

curl https://api.adwright.co/v1/platform/models \
  -H "Authorization: Bearer $ADWRIGHT_API_KEY"
{
  "data": [
    {
      "id": "gw:openai/gpt-image-2.5-flare",
      "object": "model",
      "type": "image",
      "name": "GPT Image 2.5",
      "variant": "",
      "maker": "openai",
      "description": "Sharp, legible type and faithful product shots.",
      "does": [
        "text to image",
        "edit"
      ],
      "unit": "image",
      "price_from": {
        "usd": 0.05
      },
      "popular_rank": 1,
      "inputs": {
        "images": 4,
        "aspects": [
          "1:1",
          "4:5",
          "9:16",
          "16:9"
        ],
        "resolutions": [
          "2k",
          "4k"
        ]
      }
    },
    {
      "id": "workflow/project",
      "object": "model",
      "type": "workflow",
      "name": "Project from a link",
      "unit": "project",
      "price_from": {
        "usd": 1.1
      }
    }
  ]
}

Generations

One request, one model, one picture or clip (or up to eight at once). Give pictures and clips to work from by uploading them first.

Create a generation

POST/generations

Starts count generations on a model. Each is charged its exact price when it starts and refunded if it fails.

Starts paid work: send an Idempotency-Key.

Body

modelstringrequired

A model id from GET /models.

promptstring

What to make.

aspectstring

1:1, 4:5, 9:16, 16:9… as the model's inputs.aspects list. Pictures default to 1:1, clips to 9:16.

inputsobject

duration, resolution, quality, sound, negative, seed, and upload ids: start, end, images[], videos[].

countinteger

1 to 8 (default 1). All or none: if the balance can't cover them all, none start.

webhook_urlstring

An https address to POST the outcome to when it finishes. See Webhooks.

curl -X POST https://api.adwright.co/v1/platform/generations \
  -H "Authorization: Bearer $ADWRIGHT_API_KEY" \
  -H "Idempotency-Key: $(uuidgen)" \
  -H "Content-Type: application/json" \
  -d '{"model":"gw:openai/gpt-image-2.5-flare","prompt":"A glass serum bottle on wet black stone, morning light","aspect":"4:5","count":2}'
{
  "data": [
    {
      "id": "med_8f2c1a9e4b7d3f60a1c2",
      "object": "generation",
      "job_id": "job_4a1f9c2e7b3d8e6f0a1b",
      "model": "gw:openai/gpt-image-2.5-flare",
      "type": "image",
      "status": "queued",
      "prompt": "A glass serum bottle on wet black stone, morning light",
      "aspect": "4:5",
      "output": null,
      "error": null,
      "cost": {
        "usd": 0.052
      },
      "created_at": "2026-10-29T09:41:00Z",
      "completed_at": null
    }
  ]
}

Price a generation

POST/generations/quote

What the same request would cost, and whether your balance covers it. Nothing is made or charged.

Body

modelstringrequired

A model id.

promptstring

As for a generation.

aspectstring

As for a generation.

inputsobject

As for a generation: the length and resolution change the price.

countinteger

1 to 8.

curl -X POST https://api.adwright.co/v1/platform/generations/quote \
  -H "Authorization: Bearer $ADWRIGHT_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"model":"kling-video/v3.0/std/image-to-video","inputs":{"duration":5},"count":1}'
{
  "model": "kling-video/v3.0/std/image-to-video",
  "each": {
    "usd": 0.42
  },
  "total": {
    "usd": 0.42
  },
  "affordable": true,
  "missing": "a start image"
}

Retrieve a generation

GET/generations/{id}

Where it's got to. When status is succeeded, output.url is the file.

curl https://api.adwright.co/v1/platform/generations/{ID} \
  -H "Authorization: Bearer $ADWRIGHT_API_KEY"
{
  "id": "med_8f2c1a9e4b7d3f60a1c2",
  "object": "generation",
  "job_id": "job_4a1f9c2e7b3d8e6f0a1b",
  "model": "gw:openai/gpt-image-2.5-flare",
  "type": "image",
  "status": "succeeded",
  "prompt": "A glass serum bottle on wet black stone, morning light",
  "aspect": "4:5",
  "output": {
    "url": "https://adwright.co/media/med_8f2c1a9e4b7d3f60a1c2.png",
    "content_type": "image/png"
  },
  "error": null,
  "cost": {
    "usd": 0.052
  },
  "created_at": "2026-10-29T09:41:00Z",
  "completed_at": "2026-10-29T09:41:24Z"
}

List generations

GET/generations

The ones your keys made, newest first.

Query

limitinteger

1 to 100 (default 20).

beforestring

A generation id: the page after it.

curl https://api.adwright.co/v1/platform/generations \
  -H "Authorization: Bearer $ADWRIGHT_API_KEY"
{
  "data": [
    {
      "id": "med_8f2c1a9e4b7d3f60a1c2",
      "object": "generation",
      "job_id": "job_4a1f9c2e7b3d8e6f0a1b",
      "model": "gw:openai/gpt-image-2.5-flare",
      "type": "image",
      "status": "succeeded",
      "prompt": "A glass serum bottle on wet black stone, morning light",
      "aspect": "4:5",
      "output": {
        "url": "https://adwright.co/media/med_8f2c1a9e4b7d3f60a1c2.png",
        "content_type": "image/png"
      },
      "error": null,
      "cost": {
        "usd": 0.052
      },
      "created_at": "2026-10-29T09:41:00Z",
      "completed_at": "2026-10-29T09:41:24Z"
    }
  ],
  "has_more": false
}

Upload a file

POST/uploads

A picture (PNG, JPEG, WebP, GIF, up to 30 MB) or a clip (MP4, MOV, WebM, up to 100 MB) to make from. Send it as multipart/form-data, field file.

Form

filefilerequired

The picture or clip.

curl -X POST https://api.adwright.co/v1/platform/uploads \
  -H "Authorization: Bearer $ADWRIGHT_API_KEY" \
  -F "file=@product.jpg"
{
  "id": "med_1c9e7a3f5b2d8e4a6c0f",
  "object": "upload",
  "type": "image",
  "content_type": "image/jpeg",
  "url": "https://adwright.co/media/med_1c9e7a3f5b2d8e4a6c0f.jpg",
  "created_at": "2026-10-29T09:41:00Z"
}

Workflows

What only Adwright does. A project reads a link (a website, store, product page or app), researches the product, its audience and its competitors, finds the angles and makes its first image ads. More ads, video ads and the Ad Council build on it.

Start a project

POST/projects

Research and the first ads take a few minutes. The project's status is succeeded once its ads are made.

Starts paid work: send an Idempotency-Key.

Body

urlstring

The link to start from. Required unless you send a brief.

briefstring

What the product is, who it's for, what to focus on.

platformsstring[]

Any of meta, stories, tiktok, linkedin, google, x, pinterest (default: meta, stories, tiktok).

languagestring

Write the ads in this language (default: the site's own).

researchstring

sharp or frontier: the research mode, at its flat price.

webhook_urlstring

An https address to POST the outcome to when it finishes. See Webhooks.

curl -X POST https://api.adwright.co/v1/platform/projects \
  -H "Authorization: Bearer $ADWRIGHT_API_KEY" \
  -H "Idempotency-Key: $(uuidgen)" \
  -H "Content-Type: application/json" \
  -d '{"url":"https://example.com/products/serum","platforms":["meta","tiktok"],"research":"sharp","webhook_url":"https://example.com/hooks/adwright"}'
{
  "id": "prj_5d2a8c1e9f3b",
  "object": "project",
  "name": "Example Serum",
  "url": "https://example.com/products/serum",
  "status": "queued",
  "stage": "queued",
  "progress": 0,
  "ads": 0,
  "created_at": "2026-10-29T09:41:00Z",
  "updated_at": "2026-10-29T09:41:00Z"
}

Retrieve a project

GET/projects/{id}

Its research (the product, who it's for, the angles), its image ads with their copy and pictures, and its videos.

curl https://api.adwright.co/v1/platform/projects/{ID} \
  -H "Authorization: Bearer $ADWRIGHT_API_KEY"
{
  "id": "prj_5d2a8c1e9f3b",
  "object": "project",
  "name": "Example Serum",
  "status": "succeeded",
  "product": {
    "name": "Example Serum",
    "one_liner": "A vitamin C serum for dull skin",
    "audience": [
      "Women 25-40 with dull skin"
    ]
  },
  "angles": [
    {
      "key": "glow-fast",
      "name": "Visible glow in a week"
    }
  ],
  "ads": [
    {
      "id": "cre_9a1f",
      "platform": "meta",
      "aspect": "4:5",
      "angle": "glow-fast",
      "headline": "Glow by Friday",
      "primary_text": "…",
      "image_url": "https://adwright.co/media/….png"
    }
  ],
  "videos": []
}

List projects

GET/projects

Newest first.

Query

limitinteger

1 to 100 (default 20).

curl https://api.adwright.co/v1/platform/projects \
  -H "Authorization: Bearer $ADWRIGHT_API_KEY"
{
  "data": [
    {
      "id": "prj_5d2a8c1e9f3b",
      "object": "project",
      "name": "Example Serum",
      "status": "succeeded",
      "ads": 6
    }
  ],
  "has_more": false
}

Make more image ads

POST/projects/{id}/ads

Written for the project's angles and drawn in its brand. Follow the job; the ads appear on the project.

Starts paid work: send an Idempotency-Key.

Body

countinteger

1 to 8 (default 4).

platformstring

Where they'll run (default: the project's platforms).

anglestring

An angle's key from the project, to write them all for it.

instructionstring

Anything to steer them: a message, an offer, a look.

webhook_urlstring

An https address to POST the outcome to when it finishes. See Webhooks.

curl -X POST https://api.adwright.co/v1/platform/projects/{ID}/ads \
  -H "Authorization: Bearer $ADWRIGHT_API_KEY" \
  -H "Idempotency-Key: $(uuidgen)" \
  -H "Content-Type: application/json" \
  -d '{"count":4,"platform":"meta","angle":"glow-fast","instruction":"Lead with the 20% launch offer"}'
{
  "id": "job_7c3e9a1f2b4d6e8a0c1f",
  "object": "job",
  "kind": "ads",
  "status": "running",
  "target_id": "sjb_2b9e4c1a7f3d",
  "project_id": "prj_5d2a8c1e9f3b",
  "video_ids": null,
  "ads_requested": 4,
  "created_at": "2026-10-29T09:41:00Z",
  "finished_at": null,
  "webhook": {
    "url": "https://example.com/hooks/adwright",
    "delivered_at": null,
    "attempts": 0,
    "last_error": null
  }
}

List video styles

GET/video-styles

The styles videos are made in (claymation, 3D, talking heads, podcasts, UGC…) and what one costs at each length.

curl https://api.adwright.co/v1/platform/video-styles \
  -H "Authorization: Bearer $ADWRIGHT_API_KEY"
{
  "data": [
    {
      "id": "claymation",
      "label": "Claymation",
      "description": "A stop-motion clay story…",
      "family": "animated",
      "seconds": [
        15,
        30
      ],
      "price_by_seconds": {
        "15": {
          "usd": 4.1
        },
        "30": {
          "usd": 7.2
        }
      }
    }
  ],
  "lengths": [
    15,
    30,
    45,
    60
  ]
}

Make video ads

POST/projects/{id}/videos

Written from the project's research, made scene by scene, edited and closed on the brand's end card. Several minutes each.

Starts paid work: send an Idempotency-Key.

Body

stylestringrequired

A style id from GET /video-styles.

secondsinteger

15, 30, 45 or 60 (default 30).

countinteger

1 to 4 (default 1).

aspectstring

9:16 (default), 4:5 or 1:1.

anglestring

An angle's key from the project.

instructionstring

A message, an offer, a story idea.

webhook_urlstring

An https address to POST the outcome to when it finishes. See Webhooks.

curl -X POST https://api.adwright.co/v1/platform/projects/{ID}/videos \
  -H "Authorization: Bearer $ADWRIGHT_API_KEY" \
  -H "Idempotency-Key: $(uuidgen)" \
  -H "Content-Type: application/json" \
  -d '{"style":"claymation","seconds":30,"count":1,"aspect":"9:16"}'
{
  "job": {
    "id": "job_7c3e9a1f2b4d6e8a0c1f",
    "object": "job",
    "kind": "videos",
    "status": "running",
    "target_id": "prj_5d2a8c1e9f3b",
    "project_id": "prj_5d2a8c1e9f3b",
    "video_ids": [
      "vid_3e8a1c"
    ],
    "ads_requested": null,
    "created_at": "2026-10-29T09:41:00Z",
    "finished_at": null,
    "webhook": {
      "url": "https://example.com/hooks/adwright",
      "delivered_at": null,
      "attempts": 0,
      "last_error": null
    }
  },
  "videos": [
    {
      "id": "vid_3e8a1c",
      "object": "video",
      "status": "running",
      "style": "claymation"
    }
  ]
}

Retrieve a video

GET/videos/{id}

Its status, length and thumbnail. Render it to get one MP4.

curl https://api.adwright.co/v1/platform/videos/{ID} \
  -H "Authorization: Bearer $ADWRIGHT_API_KEY"
{
  "id": "vid_3e8a1c",
  "object": "video",
  "project_id": "prj_5d2a8c1e9f3b",
  "title": "Glow by Friday",
  "style": "claymation",
  "aspect": "9:16",
  "status": "succeeded",
  "seconds": 29.6,
  "thumbnail_url": "https://adwright.co/media/….jpg"
}

Render a video to MP4

POST/videos/{id}/render

Renders a finished video to one MP4 on our side. Read it with GET /videos/{id}/render until it has a url.

Starts paid work: send an Idempotency-Key.

curl -X POST https://api.adwright.co/v1/platform/videos/{ID}/render \
  -H "Authorization: Bearer $ADWRIGHT_API_KEY" \
  -H "Idempotency-Key: $(uuidgen)"
{
  "id": "rnd_6b1e",
  "object": "render",
  "video_id": "vid_3e8a1c",
  "status": "rendering",
  "progress": 10,
  "url": null,
  "error": null,
  "stale": false
}

Ask the Ad Council

POST/council

Seven ad experts read the market and the ads winning in it, argue it out and give a verdict: which angle, whether to launch, what to change.

Starts paid work: send an Idempotency-Key.

Body

questionstringrequired

The question, in a sentence or two.

project_idstring

A project, so the council knows the product and its market.

optionsstring[]

Two to five options to choose between.

webhook_urlstring

An https address to POST the outcome to when it finishes. See Webhooks.

curl -X POST https://api.adwright.co/v1/platform/council \
  -H "Authorization: Bearer $ADWRIGHT_API_KEY" \
  -H "Idempotency-Key: $(uuidgen)" \
  -H "Content-Type: application/json" \
  -d '{"question":"Which angle should we launch with?","project_id":"prj_5d2a8c1e9f3b","options":["Visible glow in a week","Dermatologist-made"]}'
{
  "id": "cs_2f8e1a",
  "object": "council",
  "question": "Which angle should we launch with?",
  "status": "queued",
  "verdict": null
}

Read the council's verdict

GET/council/{id}

Once status is succeeded: what it recommends, how sure it is, why, and the next steps.

curl https://api.adwright.co/v1/platform/council/{ID} \
  -H "Authorization: Bearer $ADWRIGHT_API_KEY"
{
  "id": "cs_2f8e1a",
  "object": "council",
  "status": "succeeded",
  "verdict": {
    "recommended": "Visible glow in a week",
    "band": "strong",
    "confidence": 0.78,
    "summary": "…",
    "next_steps": [
      "…"
    ]
  }
}

Jobs and balance

Every request that starts work makes a job: its status and its webhook's, in one place.

Retrieve a job

GET/jobs/{id}

queued, running, succeeded or failed, what it made, and how its webhook went.

curl https://api.adwright.co/v1/platform/jobs/{ID} \
  -H "Authorization: Bearer $ADWRIGHT_API_KEY"
{
  "id": "job_7c3e9a1f2b4d6e8a0c1f",
  "object": "job",
  "kind": "ads",
  "status": "succeeded",
  "target_id": "sjb_2b9e4c1a7f3d",
  "project_id": "prj_5d2a8c1e9f3b",
  "video_ids": null,
  "ads_requested": 4,
  "created_at": "2026-10-29T09:41:00Z",
  "finished_at": "2026-10-29T09:44:10Z",
  "webhook": {
    "url": "https://example.com/hooks/adwright",
    "delivered_at": null,
    "attempts": 0,
    "last_error": null
  }
}

Retrieve the balance

GET/balance

What the next request can spend, and what running work holds.

curl https://api.adwright.co/v1/platform/balance \
  -H "Authorization: Bearer $ADWRIGHT_API_KEY"
{
  "object": "balance",
  "available": {
    "usd": 48.32
  },
  "held": {
    "usd": 0.42
  }
}