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
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"]
}'{
"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
Four ways to connect BannerMotion.
REST API v1
JSON over HTTPS under /api/v1: create concepts, correct them, add sizes and locales, export and download the .zip.
See the APIWebhooks
Receive a signed POST when a generation finishes, a review is approved or an export is ready.
See the webhooksMCP server
Connect Claude, Cursor or ChatGPT: they generate, correct, preview and export with your workspace's permissions and credits.
Connect ClaudeSDKs
Minimal TypeScript and Python clients, with retries, automatic idempotency and webhook verification.
See the SDKsFrom 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.
- Create
POST /api/v1/bannersResponds 202 with a job. Send
Idempotency-Keyto retry without being charged twice. - Wait
GET /api/v1/jobs/:idCheck the status, or follow it live with SSE at
/events. Thebanner.generatedwebhook also arrives. - Read
GET /api/v1/banners/:idReturns the spec, the version, the pieces (size by locale) and the texts, plus the link to the studio (
appUrl). - Export
POST /api/v1/exportsBuilds the package for the platform and downloads the .zip; check the
X-Content-SHA256header.
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
Jobs
Campaigns
Exports
X-Content-SHA256).Library
Credits
See the full reference · https://app.bannermotion.com/api/v1/openapi.json · https://app.bannermotion.com/api/v1/llms.txt
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 localesread· read concepts, jobs and campaignsexport· export packagesassets· manage the library
60 per minute per key. When you go over: 429 with Retry-After, in seconds. [ … ] confirm in production
Idempotency-Key of up to 200 characters. Same key and same body: the same response, with Idempotent-Replayed: true, nothing generated or charged again.
Lists respond data, hasMore and nextCursor. Pass nextCursor as cursor; limit goes from 1 to 100.
Every route answers OPTIONS and allows any origin. The key travels only in Authorization: Bearer, never in cookies.
Errors with one format
| HTTP | code | When |
|---|---|---|
| 400 | bad_request | Invalid JSON or invalid or unknown fields; check details. |
| 401 | unauthorized | The key is missing, invalid or was revoked. |
| 402 | insufficient_credits | The workspace does not have enough credits. |
| 403 | forbidden | The key lacks the permission or the workspace is not active. |
| 404 | not_found | It does not exist or belongs to another workspace (on purpose, not told apart). |
| 409 | conflict | Idempotency-Key in progress, unapproved export or resource not ready. |
| 413 | payload_too_large | Body over 64 KB (256 KB on POST /banners) or a file over 5 MB. |
| 422 | idempotency_mismatch | That Idempotency-Key was already used with another request. |
| 429 | rate_limited | Too many requests or jobs at once; read Retry-After. |
| 500 | internal_error | Our fault; retry with the same Idempotency-Key. |
Body of a 400 error
{
"code": "bad_request",
"message": "prompt no puede estar vacío.",
"details": [
{ "path": "prompt", "message": "prompt no puede estar vacío." }
]
}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
Verify the signature with the SDK
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 repetirseWithout an SDK, the Node and Python code is on the webhooks page.
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.
[ … ] final domain (today app.bannermotion.com; mcp.bannermotion.com later)
- Open Settings → Connectors.
- Click Add custom connector.
- Paste the server address and click Connect.
- BannerMotion opens: choose the workspace and approve the connection.
On Claude team plans, an administrator adds the connector for the organization.
https://app.bannermotion.com/mcpThe 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.
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
- generate_banner
- get_job
- preview_banner
- edit_banner / add_sizes
- export_banners
Campaigns with their sizes, locales and pieces.
A campaign's concepts, with their pieces and approval.
New concept from a brief; returns the job (wait_seconds to wait). Spends credits.
Status of a job, with optional waiting.
Corrects with an instruction, optionally only in some sizes. Spends credits.
An image of a frame and a 1-hour signed link to watch the animation.
Adapts the concept to new sizes. Credits per size.
.zip packages for a platform, with a 1-hour download link.
Export status and a fresh download link.
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.
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.
TypeScript · @bannermotion/sdk
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
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"]))Figma plugin
In development · not published [ … ]- You select one or more frames; each frame is a size.
- The plugin uploads images and logos to the library (
POST /api/v1/assets). - It creates the banner with your composition in
reference(POST /api/v1/banners). - 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.
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.