أنشئ حملتي الأولى
Developers

Generate banners from your code, your assistant or your design.

A REST API, signed webhooks, an MCP server for Claude and SDKs. Each request creates a concept in all the sizes and locales you ask for, and is charged once.

  • OpenAPI 3.1
  • Test keys spend no credits
  • Idempotency-Key
  • Open CORS
POST/api/v1/banners
curl -X POST https://app.bannermotion.com/api/v1/banners \
  -H "Authorization: Bearer bm_live_..." \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: 7f3c9a10-5b2e-4c1d-9a77-0e1f2a3b4c5d" \
  -d '{
    "prompt": "Banner for a high-altitude coffee, warm tone",
    "sizes": ["300x250", "728x90"],
    "locales": ["es-CR", "en-US"]
  }'
202Job accepted: generation continues in the background
{
  "jobId": "job_x4k2m9q1a7bz",
  "status": "queued",
  "campaignId": "cmp_8f2a1c3d4e5f",
  "statusUrl": "/api/v1/jobs/job_x4k2m9q1a7bz",
  "eventsUrl": "/api/v1/jobs/job_x4k2m9q1a7bz/events"
}

Base address: https://app.bannermotion.com [ … ] confirm final domain

Connect

Four ways to connect BannerMotion.

REST API v1

From brief to .zip in four steps.

Creating, correcting, adding sizes or locales and exporting all respond with a job. You check it, follow it live with SSE, or wait for the webhook.

  1. CreatePOST /api/v1/banners

    Responds 202 with a job. Send Idempotency-Key to retry without being charged twice.

  2. WaitGET /api/v1/jobs/:id

    Check the status, or follow it live with SSE at /events. The banner.generated webhook also arrives.

  3. ReadGET /api/v1/banners/:id

    Returns the spec, the version, the pieces (size by locale) and the texts, plus the link to the studio (appUrl).

  4. ExportPOST /api/v1/exports

    Builds the package for the platform and downloads the .zip; check the X-Content-SHA256 header.

curl -X POST https://app.bannermotion.com/api/v1/banners \
  -H "Authorization: Bearer bm_live_..." \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: 7f3c9a10-5b2e-4c1d-9a77-0e1f2a3b4c5d" \
  -d '{
    "prompt": "Banner for a high-altitude coffee, warm tone",
    "sizes": ["300x250", "728x90"],
    "locales": ["es-CR", "en-US"]
  }'

All the routes

The key's minimum permission on each route. Keys with generate, export or assets can also read.

Banners

POST/api/v1/bannersgenerateCreates a concept in all the requested sizes and locales.
GET/api/v1/bannersreadLists the concepts, newest first.
GET/api/v1/banners/:idreadSpec, version, pieces and texts.
POST/api/v1/banners/:id/editsgenerateCorrects with a plain-language instruction.
POST/api/v1/banners/:id/sizesgenerateAdds sizes to an existing concept.

Jobs

GET/api/v1/jobs/:idreadStatus: queued, running, succeeded, failed or canceled.
GET/api/v1/jobs/:id/eventsreadLive progress with Server-Sent Events.

Campaigns

GET/api/v1/campaignsreadLists the campaigns, with search and status filter.
DELETE/api/v1/campaigns/:idgenerateArchives the campaign; it can be restored from the app.
POST/api/v1/campaigns/:id/localesgenerateAdds a locale and translates the concepts.
DELETE/api/v1/campaigns/:id/locales/:localegenerateRemoves a locale (not the base).

Exports

POST/api/v1/exportsexportBuilds the package for a platform.
GET/api/v1/exportsexportLists the exports.
GET/api/v1/exports/:idexportStatus, checks and pieces.
GET/api/v1/exports/:id/downloadexportDownloads the .zip (X-Content-SHA256).

Library

POST/api/v1/assetsassetsUploads an image, a logo or a font (max. 5 MB).
GET/api/v1/assetsreadLists the library.
GET/api/v1/assets/:idreadA file's data.
DELETE/api/v1/assets/:idassetsRemoves the file; it is kept for 30 days.

Credits

POST/api/v1/estimatereadEstimates the credits of an operation; reserves nothing.
GET/api/v1/openapi.jsonno keyThe OpenAPI 3.1 specification.
GET/api/v1/llms.txtno keyMarkdown summary for AI assistants.

See the full reference · https://app.bannermotion.com/api/v1/openapi.json · https://app.bannermotion.com/api/v1/llms.txt

Keys and limits

Try it without spending credits.

Create keys in Integrations & API, inside your workspace (only the Owner and Admin members). The value is shown only once. Use them only from your server.

Production

bm_live_… keys. They generate for real and spend the workspace's credits. If the job fails, the credits are returned.

Testing

bm_test_… keys. They always return the same sample concept, with no AI and no credits. Test ids (job_test_…, crt_test_…) can only be read with test keys.

Permissions

  • generate · create, correct, sizes and locales
  • read · read concepts, jobs and campaigns
  • export · export packages
  • assets · manage the library
Request limit

60 per minute per key. When you go over: 429 with Retry-After, in seconds. [ … ] confirm in production

Idempotency

Idempotency-Key of up to 200 characters. Same key and same body: the same response, with Idempotent-Replayed: true, nothing generated or charged again.

Cursor pagination

Lists respond data, hasMore and nextCursor. Pass nextCursor as cursor; limit goes from 1 to 100.

Open CORS

Every route answers OPTIONS and allows any origin. The key travels only in Authorization: Bearer, never in cookies.

Errors with one format

HTTPcodeWhen
400bad_requestInvalid JSON or invalid or unknown fields; check details.
401unauthorizedThe key is missing, invalid or was revoked.
402insufficient_creditsThe workspace does not have enough credits.
403forbiddenThe key lacks the permission or the workspace is not active.
404not_foundIt does not exist or belongs to another workspace (on purpose, not told apart).
409conflictIdempotency-Key in progress, unapproved export or resource not ready.
413payload_too_largeBody over 64 KB (256 KB on POST /banners) or a file over 5 MB.
422idempotency_mismatchThat Idempotency-Key was already used with another request.
429rate_limitedToo many requests or jobs at once; read Retry-After.
500internal_errorOur fault; retry with the same Idempotency-Key.

Body of a 400 error

400
{
  "code": "bad_request",
  "message": "prompt no puede estar vacío.",
  "details": [
    { "path": "prompt", "message": "prompt no puede estar vacío." }
  ]
}
Webhooks

A signed notice every time something important happens.

Register an address on your server, choose the events and receive a POST with JSON signed with Standard Webhooks. Up to 3 attempts per delivery and an automatic pause after 5 consecutive failures.

The 13 events

Generation
banner.generatedbanner.updatedgeneration.failedtranslation.completed
Review and approval
review.requestedreview.commentedreview.approvedreview.changes_requested
Export
export.readyexport.failed
Team and usage
member.invitedmember.joinedusage.threshold_reached

Verify the signature with the SDK

verifyWebhook
import { verifyWebhook } from "@bannermotion/sdk";

// rawBody: el cuerpo EXACTO recibido, como texto (antes de convertirlo a JSON).
// Durante una rotación del secreto pasa los dos: [nuevo, anterior].
const event = await verifyWebhook(process.env.BM_WEBHOOK_SECRET, req.headers, rawBody);
// lanza BannerMotionError ("invalid_signature") si la firma o la hora no valen
if (alreadyProcessed(event.id)) return; // las entregas pueden repetirse

Without an SDK, the Node and Python code is on the webhooks page.

MCP server

Connect Claude to BannerMotion.

BannerMotion is a remote MCP server (Streamable HTTP). Claude generates, corrects, previews, adapts and exports concepts with the same permissions and credits you have in the app.

Server addresshttps://app.bannermotion.com/mcp

[ … ] final domain (today app.bannermotion.com; mcp.bannermotion.com later)

  1. Open Settings → Connectors.
  2. Click Add custom connector.
  3. Paste the server address and click Connect.
  4. BannerMotion opens: choose the workspace and approve the connection.
  5. On Claude team plans, an administrator adds the connector for the organization.

Connector URL
https://app.bannermotion.com/mcp
OAuth 2.1 with PKCE

The client registers itself (dynamic registration), opens the BannerMotion screen and you choose the workspace. Access lasts 1 hour and renews itself; the refresh lasts 30 days. The connection shows up in your account as an "MCP · client" session and ends when you sign it out.

API key bm_live_…

Send it in Authorization: Bearer. The key's permissions limit the tools (read, generate, export). bm_test_ keys do not work with MCP.

The 10 tools

  1. generate_banner
  2. get_job
  3. preview_banner
  4. edit_banner / add_sizes
  5. export_banners
list_campaignsread

Campaigns with their sizes, locales and pieces.

list_bannersread

A campaign's concepts, with their pieces and approval.

generate_bannergenerate

New concept from a brief; returns the job (wait_seconds to wait). Spends credits.

get_jobread

Status of a job, with optional waiting.

edit_bannergenerate

Corrects with an instruction, optionally only in some sizes. Spends credits.

preview_bannerread

An image of a frame and a 1-hour signed link to watch the animation.

add_sizesgenerate

Adapts the concept to new sizes. Credits per size.

export_bannersexport

.zip packages for a platform, with a 1-hour download link.

get_exportexport

Export status and a fresh download link.

get_credit_balanceread

Balance, plan, renewal and the cost of each operation per model.

The rules are the app's: if the workspace requires approval before exporting, or the click URL is missing, the tool says so and does not export.

See the MCP server reference

SDKs and integrations

Minimal clients, no dependencies.

They retry 429 (respecting Retry-After), 5xx and network cuts; put an Idempotency-Key on every POST that creates work; wait for the job over SSE and verify webhooks.

Not yet published on npm or PyPI [ … date ]. Ask for them at soporte@bannermotion.com

TypeScript · @bannermotion/sdk

TypeScript
import { BannerMotion } from "@bannermotion/sdk";

const bm = new BannerMotion({ apiKey: process.env.BM_API_KEY! });

const { jobId } = await bm.banners.create({
  prompt: "Iced coffee for summer. Order now",
  sizes: ["300x250", "728x90"],
});
const job = await bm.jobs.wait(jobId); // por SSE; cae a sondeo si el flujo se corta
if (job.status === "succeeded") {
  console.log(job.appUrl); // el banner en el estudio de la app
  console.log(await bm.banners.get(job.creativeId!));
}

Python · bannermotion

Python
import os
from bannermotion import BannerMotion

bm = BannerMotion(os.environ["BM_API_KEY"])

acc = bm.banners.create("Iced coffee for summer. Order now", sizes=["300x250", "728x90"])
job = bm.jobs.wait(acc["jobId"])  # por SSE; cae a sondeo si el flujo se corta
if job["status"] == "succeeded":
    print(job["appUrl"])  # el banner en el estudio de la app
    print(bm.banners.get(job["creativeId"]))

See SDKs and OpenAPI

Figma plugin

In development · not published [ … ]
  1. You select one or more frames; each frame is a size.
  2. The plugin uploads images and logos to the library (POST /api/v1/assets).
  3. It creates the banner with your composition in reference (POST /api/v1/banners).
  4. It follows the job and opens the banner in the studio (appUrl).

It needs a key with generate and read permissions. Up to 12 frames and 60 layers per frame. The frame's texts are the banner's; the uploaded images are mandatory.

Claude Code skill

Download channel [ … ]

A folder that teaches Claude Code the flow with the MCP server: check credits, generate, look at the result, correct with one instruction at a time, add sizes and export.

Copy it to ~/.claude/skills/ (or the project's .claude/skills/). It requires the MCP server to be connected.

Availability

API and webhooks: Team, Agency and Enterprise plans; the Starter plan does not include them. MCP server: [ … ] plans to be confirmed.

Frequently asked questions

Do I spend credits when I test?

Not with bm_test_ keys: they always return a sample concept, with no AI and no credits. bm_live_ keys use the same credits as the app.

Can I retry without being charged twice?

Yes. Send Idempotency-Key on POST requests: the same key with the same body returns the previous response, with Idempotent-Replayed: true, without generating or charging again.

How do I verify a webhook is from BannerMotion?

Each delivery carries webhook-id, webhook-timestamp and webhook-signature (Standard Webhooks). The exact body is signed with the whsec_… secret and compared in constant time.

Are there SDKs?

There are TypeScript and Python clients, not yet published on npm or PyPI; ask for them at soporte@bannermotion.com. For any other language, use a client generated from the OpenAPI 3.1 specification.

Start with a test key.

It spends no credits. When everything works, switch to a real key.

Create my first campaignRead the documentation