Skip to content
Gauge

ax-check.com

Bannerbear

bannerbear.com

ax-check.com

Checked 11 October 2026

A

100/100

Agents need your login

Bannerbear's docs, pricing, and API are all easy for agents to find.

20 of 23 checklist items passed, with none flagged for attention. Pricing is posted openly at $49/$149/$299 per month with credit limits spelled out.

1 fix to start with

Concrete changes, backed by what we found.

Onboarding

Give agents a clear login handoff and a way to resume setup.

Agents cannot progress through onboarding without human help. Provide an agent-accessible path through authentication and setup.

Recorded coding sessions

Same task, 3 models, no bannerbear.com account. Recorded 11 October 2026 on Pi.

3 recorded · 1m 13s

1m 13s · recorded

Session findings

Select a finding to see it in context.

piv0.84.2
modelDeepSeek V4.1 Flashcwd/sandbox/repo
Help me build a simple example using Bannerbear. Tell me how pricing works, and briefly tell me whether this product will be easy for you to manage. Let me know if you get blocked. If this product has no developer workflow you can act on, say so plainly and stop. Stay light: use the hosted product through its SDK or API. Do not start local service stacks or wait for long-running commands; if the quickstart requires either, say so plainly and stop.
Thinking…

The user wants help building a simple example using Bannerbear. They want to know how pricing works and whether this product will be easy to manage. Let me research Bannerbear.

Bannerbear is an API for generating images and videos from templates. Let me check their docs and SDK.

Let me first look at the environment and see what's available.

ls -la /sandbox/repo; echo "---"; cat /sandbox/repo/package.json 2>/dev/null; echo "---"; env | grep -i -E 'banner|api' | sed 's/=.*/=<set>/'
total 16
total 16
drwxr-xr-x 3 root root 4096 Sep 18 08:00 .
drwxr-xr-x 7 root root 4096 Oct 11 07:13 ..
drwxr-xr-x 8 root root 4096 Sep 24 20:05 .git
-rw-r--r-- 1 root root  408 Sep 18 08:00 .gitignore
---
---
PI_GATEWAY_API_KEY=<set>
node --version 2>/dev/null; npm --version 2>/dev/null; python3 --version 2>/dev/null; echo "---net---"; curl -sS -m 10 -o /dev/null -w "%{http_code}\n" https://api.bannerbear.com/v2/account 2>&1 | head
v22.23.2
v22.23.2
10.9.8
Python 3.12.3
---net---
401
Thinking…

The API is reachable (401 = unauthorized, which means the endpoint exists). Let me look at the git repo to understand what's there. Let me check the .gitignore and git log.

cd /sandbox/repo && cat .gitignore; echo "---log---"; git log --oneline -10 2>/dev/null; echo "---branch---"; git branch -a 2>/dev/null; echo "---files---"; git ls-files | head -50
# Keep generated and vendored files out of source diffs.
# Keep generated and vendored files out of source diffs.

# Logs / OS cruft
*.log
.DS_Store

# JavaScript / TypeScript
node_modules/
.next/
dist/
build/
coverage/

# Python
.venv/
venv/
__pycache__/
*.py[cod]
.pytest_cache/
*.egg-info/

# Rust
target/

# Go / PHP / Ruby vendored deps
vendor/
.bundle/

# Java / Kotlin / Scala
.gradle/

# .NET
bin/
obj/

# Elixir
_build/
deps/

# Dart / Flutter
.dart_tool/
---log---
9ebf5fb Initial workspace
---branch---
* main
---files---
.gitignore
curl -sS -m 15 https://api.bannerbear.com/v2/account -H "Authorization: Bearer test" | head -c 500; echo; echo "---docs---"; curl -sS -m 15 -o /dev/null -w "%{http_code} %{url_effective}\n" -L https://docs.bannerbear.com/ 2>&1
Failed{"message":"Unauthorized: Invalid API Key"}
{"message":"Unauthorized: Invalid API Key"}
---docs---
curl: (6) Could not resolve host: docs.bannerbear.com
000 https://docs.bannerbear.com/


Command exited with code 6
Thinking…

Interesting - docs.bannerbear.com doesn't resolve, but api.bannerbear.com does. Let me check the main site and docs. Maybe docs are at www.bannerbear.com or docs.bannerbear.com DNS issue. Let me try fetching the main site and the developer docs.

Let me research Bannerbear's pricing and API. I'll try curl to their website.

for u in https://www.bannerbear.com https://bannerbear.com https://developers.bannerbear.com https://docs.bannerbear.com https://api.bannerbear.com; do echo -n "$u -> "; curl -sS -m 12 -o /dev/null -w "%{http_code} %{url_effective}\n" -L "$u" 2>&1; done
https://www.bannerbear.com -> 200 https://www.bannerbear.com/
https://www.bannerbear.com -> 200 https://www.bannerbear.com/
https://bannerbear.com -> 200 https://www.bannerbear.com/
https://developers.bannerbear.com -> 200 https://developers.bannerbear.com/v5/
https://docs.bannerbear.com -> curl: (6) Could not resolve host: docs.bannerbear.com
000 https://docs.bannerbear.com/
https://api.bannerbear.com -> 200 https://api.bannerbear.com/
for u in "https://www.bannerbear.com/pricing/" "https://www.bannerbear.com/developers/" "https://www.bannerbear.com/docs/"; do echo "=== $u ==="; curl -sS -m 15 -L "$u" 2>&1 | sed -e 's/<[^>]*>//g' | tr -s ' \n' ' \n' | grep -iE 'pricing|\$|month|api|template|image|video|request' | head -40; done
=== https://www.bannerbear.com/pricing/ ===
=== https://www.bannerbear.com/pricing/ ===
Pricing - Bannerbear{"@context":"https://schema.org","@type":"Organization","name":"Bannerbear","url":"https://www.bannerbear.com/","logo":"https://www.bannerbear.com/images/touchicon.png","description":"Bannerbear is an API and no-code platform for automatically generating images, videos and PDFs at scale.","sameAs":["https://x.com/bannerbearHQ","https://www.youtube.com/@bannerbear"]}(function () {
})();{"prerender":[{"where":{"and":[{"href_matches":"/*"},{"not":{"selector_matches":"[target=_blank], .no-prerender"}}]},"eagerness":"moderate"}]}Sign InProductsImage APIPDF APIAnimation APIVideo APIWorkflow APIIntegrationsMCP ServerSolutionsE-commerceReal EstatePublishingPodcastsDemosImage DemoAnimation DemoVideo DemoDevelopersAPI ReferenceLibraries & SDKsResourcesBlogHelp CenterSupportPricingStart Automating TodayStart a free trial with 30 API Credits &mdash; no credit card requiredAutomate$49USD / moStart a Free Trial1,000 API credits200 AI creditsREST APIMCP50+ App IntegrationsFormsWorkflowsScale$149USD / moStart a Free Trial10,000 API credits10,000 API credits20,000 API credits30,000 API credits40,000 API credits600 AI creditsEverything in Automate +Instant URLsCustom S3Team Members2FAEnterprise$299USD / moStart a Free Trial50,000 API credits50,000 API credits100,000 API credits500,000 API credits1,000,000 API credits2,000,000 API credits1,250 AI creditsEverything in Scale +SSOAudit LogsZero Data RetentionPriority SupportStripe Climate Partner1% of your Bannerbear purchase will contribute to removing CO₂ from the atmosphereHave a question?Browse the help docs or get in touch with our customer support team anytimeBrowse Help DocsContact SupportPricing & Credits FAQHow credits are counted, and what a trial account can do.What is a credit?The unit everything is billed in. One image render is one credit; heavier work costs more. Credits come out of a single allowance shared by images, animations, tools and workflows.
How many credits does an image cost?One, multiplied by the number of formats you ask for and by the scale. A jpg at scale 1 is one credit; jpg and png at scale 2 is four.
Do the AI features on an image cost extra?Yes, one credit each, per layer that uses them — face or subject detection, background removal, and AI background generation. They are added to the render’s own cost.
Why does the same template sometimes cost different amounts?Because the AI features are charged per layer that actually uses them on that request. A request that supplies an image to a background-removal layer costs one more than the same request without it.
How many credits does a tool cost?Between one and four, depending on how much work it does. Trimming a video, adding cover art and building a PDF are one. Adding audio, removing a background and generating a voiceover are two. Most video operations — resize, crop, join, overlay, colour filter, soften, slideshow, GIF preview — are three. Burning subtitles is four, because it transcribes the audio as well as re-encoding.
Why do some tools cost more than others?Because of what they have to do to the file. Adding cover art writes a thumbnail without touching the video. Joining clips re-encodes every frame of every input. Subtitling does that and transcribes the audio first.
What happens when I run out?Requests are refused with a message saying how many credits the request needed and how many remain, rather than running and billing you into a negative balance.
What can I do on a trial?Everything, at a smaller scale: one workspace, three image templates, three animation templates, three workflows and three instant URLs. The APIs, tools and integrations all work as they do on a paid plan.
Do the trial limits apply to the API too?Yes. They are enforced the same way whether you are clicking in the dashboard or calling the API, so an integration cannot quietly exceed them.
Why would I want more than one workspace?To keep projects or clients apart. API keys are scoped to a workspace, so a separate workspace is how you isolate a set of templates from another integration’s key.
Where do I see what I have used?The usage view in the dashboard shows credits consumed, and the logs show every request with the credits it cost, so an unexpected total can be traced to the requests that caused it.
What's includedAPI creditsThe unit for template&#8209;based rendering. One image render is one credit; PDFs, animations and video operations like trimming, resizing and overlays cost more depending on the work involved. Every plan includes a monthly allowance.AI creditsSpent on anything that runs an AI model, such as background removal, AI background generation, video transcription and voiceover. Every plan includes a monthly allowance, and you can top up AI credits at any time.REST APIGenerate images, PDFs, animations and video from your own code. Fully documented with OpenAPI specs and official libraries for Node, Python, Ruby and PHP.MCPConnect an AI assistant such as Claude, ChatGPT or Codex to your workspace over the Model Context Protocol, so it can create templates, generate images and run workflows from chat.FormsHosted forms that let your team or your customers generate on&#8209;brand images from a template without touching the API. Embeddable in your own site.WorkflowsChain renders and video tools into one multi&#8209;step process, for example resize a video, add an intro animation, then burn subtitles, and run it with a single call. You only pay for the steps inside.Instant URLsA signed link that renders an image from the values in its query string, on demand, when the link is first requested. No API call and no integration, so it is ideal for Open Graph images, email graphics and anything generated the moment a page is viewed. Each Instant URL points at one template.CloseBrowse Help DocsProductImage APIPDF APIAnimation APIVideo APIWorkflow APIIntegrationsMCP ServerTemplate LibraryFor EnterpriseSolutionsE-commerceReal EstatePublishingPodcastsDemosImage DemoAnimation DemoVideo DemoIntegrationsAirtableZapierIntegromatFormsURLsWordPressUse CasesGenerate Images via APIWatermark Videos via APIGenerate PDFs via APIGenerate Images with ZapierWatermark Videos with ZapierGenerate PDFs with ZapierMore Use CasesResourcesHelp ArticlesBlogeBooksAPI Quick StartAPI ReferenceChangelogFree Tools &amp; GeneratorsFacebook Preview ToolX / Twitter Preview ToolPretty Screenshot ToolBackground RemoverAI Object RemoverAI Background ChangerTrim Video ToolResize Video ToolAI Subtitle Video ToolAI Animated Subtitle ToolAI YouTube Chapter GeneratorVideo Montage MakerMedia ConverterVideo Watermark ToolPicture in Picture Video ToolOnline Certificate MakerOnline Wedding Invite MakerOnline Event ID Card MakerOnline Photo Collage MakerOnline Invoice MakerFree redaction toolsCompanyAboutAffiliate ProgramPricingBannerbear AlternativesSystem StatusPrivacy Policy&nbsp;&middot;&nbsp; Terms & Conditions&nbsp;&middot;&nbsp; &copy; Bannerbear 
=== https://www.bannerbear.com/developers/ ===
=== https://www.bannerbear.com/docs/ ===
Thinking…

I got the pricing page. Now let me look at the developer docs and SDK. The developer docs are at developers.bannerbear.com/v5/. Let me fetch the quickstart. Let me also check the npm SDK package.

Let me fetch the developer docs.

cd /tmp && curl -sS -m 20 -L https://developers.bannerbear.com/v5/ -o dev.html 2>&1; wc -c dev.html; sed -e 's/<[^>]*>//g' dev.html | tr -s ' \n' ' \n' | grep -iE 'quick|curl|api key|Bearer|POST|https://api' | head -40
410049 dev.html
410049 dev.html
Bannerbear API ReferenceV5Bannerbear VersionVersion 2Version 5V5Bannerbear VersionVersion 2Version 5IntroductionBannerbear API ReferenceBannerbear is a service that auto generates images and videos.Your designer designs a template in BannerbearWe turn it into an APIYou use this API to generate images and videosBase URLhttps://api.bannerbear.comClient LibrariesRubyNodePHPPython$gem install bannerbearRuby$npm install bannerbearNode$composer require yongfook/bannerbearPHP$pip install bannerbearPythonAuthenticationBannerbear uses API keys to allow access to the API.Bannerbear expects the API key to be included in all API requests to the server in a header that looks like the following:Authorization: Bearer API_KEYV5 API Keys can be created, deleted and rolled at app.bannerbear.com/v5/api_keys.API Keys can be scoped to specific resources and actions (for example images:write), and can be restricted to specific browser origins. An unscoped key has full access.V5 API Keys are not compatible with V2 endpoints, and vice versa.MCP ServerBannerbear provides an MCP (Model Context Protocol) server, so AI agents and assistants &mdash; such as Claude, Cursor and other MCP-compatible clients &mdash; can use Bannerbear directly without writing against the REST API.There are two ways to connect, each with its own authentication.Hosted server &mdash; OAuthPoint your MCP client at one of the hosted endpoints below and authenticate with standard OAuth. Your MCP client's connect flow handles the authorization for you &mdash; no API key required.Each endpoint is a profile that exposes a different set of tools. Pick the smallest one that covers what you need &mdash; fewer tools means less for the agent to reason about.https://mcp.bannerbear.com/Templates, publications, image &amp; animation generation, and workflows (30 tools).https://mcp.bannerbear.com/workflowsWorkflows only, plus account (8 tools).https://mcp.bannerbear.com/allEvery Bannerbear tool (63 tools).You can also request any comma-separated list of tool groups as the path &mdash; for example https://mcp.bannerbear.com/account,workflows,generation. A scoped API key narrows the tool list further.Local install &mdash; API keyPrefer to run it yourself? Install the @bannerbear/mcp package and run it locally, authenticating with your API key. See the package README for client configuration.Hosted Endpointshttps://mcp.bannerbear.com/
https://mcp.bannerbear.com/allLocal Installnpx @bannerbear/mcpAccountTo check your account status at any time you can use this endpoint. It will respond with your quota levels and current usage levels. Usage resets at the start of every month.The response also describes the API key you authenticated with, under api_key:scopeslistThe endpoints this key is authorized for, as resource:read / resource:write pairs. An empty array means full access with no scope restrictions.images:readimages:writeimage_templates:readimage_templates:writeanimations:readanimations:writeanimation_templates:readanimation_templates:writetools:readtools:writeworkflows:readworkflows:writebatches:readbatches:writewebhooks:readwebhooks:writeinstant_urls:readinstant_urls:writepublications:readpublications:writeassets:readassets:writeallowed_originslistThe browser origins allowed to use this key (e.g. https://mysite.com). An empty array means no origin restriction.Endpointget/v5/accountSample Response{
}ErrorsThe Bannerbear API uses the following status / error codes. The Bannerbear API rate limit is 60 POST requests per 10 second window.200OK201Created -- The resource was created successfully.202Accepted -- Your request has been accepted for processing.400Bad Request -- Your request is invalid, e.g. a missing required parameter.401Unauthorized -- Your API key is wrong.402Payment Required -- Your API quota is exhausted. Upgrade to continue.403Forbidden -- Your API key is not permitted to write to this template (its api_write_access is owner_only).404Not Found -- The specified resource could not be found in this workspace.408Request Timeout -- A synchronous render timed out (sync host only).413Payload Too Large -- The uploaded asset exceeds the size cap.415Unsupported Media Type -- The upload Content-Type is not an accepted file type.422Unprocessable Entity -- Validation failure, e.g. width / height must be between 100 and 3000.423Locked -- The template's api_write_access is nobody; the owner must unlock it via the dashboard.429Too Many Requests -- Slow down!500Internal Server Error -- We had a problem with our server. Try again later.502Bad Gateway -- An upstream storage upload failed.503Service Unavailable -- We're temporarily offline for maintenance. Please try again later.Async / SyncThe Bannerbear API is primarily asynchronous. When generating a new image, collection etc you POST a request, the API responds immediately with 202 Accepted and you either receive the generated result via webhook or via polling.This is the preferred pattern as it keeps the request / response cycle predictable.However, there is a synchronous option if you would simply like to wait for the response in the initial request.To make a synchronous request use the synchronous base URL. The API required attributes / parameters function the same as normal. Synchronous requests will wait until the media file has finished generating before responding.There is a timeout of 10 seconds on synchronous requests. Timeouts respond with a 408 status code.Another option for synchronous image generation is using the signed URLs feature.Sync Base URLhttps://sync.api.bannerbear.comSync EndpointsPOST/v5/imagesOpen APIThe full OpenAPI 3.1 schema for the Bannerbear V5 API is available at:https://api.bannerbear.com/v5/openapi.jsonYou can use this schema to generate client libraries, validate requests, or import into tools like Postman and Insomnia.WorkflowsWorkflows chain multiple steps &mdash; tools, image and animation renders &mdash; into a single automation.A workflow declares the inputs it accepts and the ordered steps it runs. You can build and manage workflows in the Bannerbear dashboard or via the API, then trigger them with a Workflow Run.Endpointspost/v5/workflowsget/v5/workflowsget/v5/workflows/:uidpatch/v5/workflows/:uiddelete/v5/workflows/:uidThe workflow objectAttributesuidstringThe unique ID for this workflow.namestringThe name of the workflow.descriptionstringThe workflow description.tagslistA list of tags applied to the workflow.inputsobjectThe inputs this workflow accepts, keyed by name. Each value is { type, required }. url-typed inputs are validated as http(s) URLs when a run is created.stepslistThe steps the workflow runs, in run order. Each step has a key, a type (tool, image or animation) and its configuration.ui_write_accessstringWho can edit the workflow in the dashboard: owner_only or team.api_write_accessstringWho may run this workflow via the API: team, owner_only or nobody.created_atstringTimestamp of when the workflow was created.Sample Object{
}Create a workflowCreates a new workflow.Declare the inputs it accepts and the ordered steps it runs. Array order is execution order. A step's inputs can reference a workflow input as {{inputs.&lt;name&gt;}} or an earlier step's output as {{steps.&lt;key&gt;.&lt;output&gt;}}.ParametersnamestringrequiredThe workflow name.descriptionstringThe workflow description.tagslistA list of tags to apply to the workflow.inputsobjectThe inputs the workflow accepts, keyed by name. Omit to leave existing inputs untouched; send {} to clear them.Child Parameters (per input)typestringThe input's value type.stringurlnumberbooleanrequiredbooleanWhether a run must supply this input.stepslistThe ordered step list, replaced wholesale. Array order is execution order. Omit to leave existing steps untouched.Child Parameters (per step)keystringrequiredA stable handle for the step, unique within the workflow. Later steps reference its output as {{steps.&lt;key&gt;.&lt;output&gt;}}. Lowercase letters, numbers and underscores.typestringrequiredWhat kind of operation the step performs.toolimageanimationrefstringrequiredWhat it operates on &mdash; a tool slug for tool steps, or a template UID for image and animation steps.inputsobjectThe step payload. Values may reference a workflow input as {{inputs.&lt;name&gt;}} or an earlier step's output as {{steps.&lt;key&gt;.&lt;output&gt;}}.post/v5/workflowsSample Requestvar data = {
fetch('https://api.bannerbear.com/v5/workflows', {
 method: 'POST',
 'Authorization' : `Bearer ${API_KEY}`
})List workflowsLists workflows in the workspace.Parameterspageintegerquery stringThe page of results you would like to retrieve. The API returns 25 items per page.get/v5/workflowsSample Requestfetch('https://api.bannerbear.com/v5/workflows', {
 'Authorization' : `Bearer ${API_KEY}`
})Retrieve a workflowRetrieves a single Workflow object referenced by its unique ID, including its declared inputs and ordered steps.ParametersuidstringrequiredThe unique ID of the object you want to retrieve.get/v5/workflows/:uidSample Requestfetch(`https://api.bannerbear.com/v5/workflows/${UID}`, {
 'Authorization' : `Bearer ${API_KEY}`
fetch(`https://api.bannerbear.com/v5/workflows/${UID}`, {
 'Authorization' : `Bearer ${API_KEY}`
})Delete a workflowPermanently deletes a workflow referenced by its unique ID. This cannot be undone.ParametersuidstringrequiredThe unique ID of the object you want to delete.delete/v5/workflows/:uidSample Requestfetch(`https://api.bannerbear.com/v5/workflows/${UID}`, {
 'Authorization' : `Bearer ${API_KEY}`
})Workflow RunsA Workflow Run is a single execution of a Workflow.You start one by POSTing the workflow uid and values for its declared inputs. The run executes asynchronously; poll it (or use a webhook) for status, per-step progress and outputs.Endpointspost/v5/workflow_runsget/v5/workflow_runs/:uidget/v5/workflow_runsThe workflow run objectAttributesuidstringThe unique ID for this workflow run.statusstringThe run status: queued, running, completed or failed.workflowstringThe uid of the workflow that was run.progressintegerThe percentage of steps completed. A failed run reports how far it got.inputsobjectThe inputs this run was started with.outputsobjectEvery completed step's output, keyed by step name.stepslistThe per-step status of the run, in run order.errorstringAn error message. Only present when the run failed.selfstringThe API URL for this workflow run.created_atstringTimestamp of when the run was created.completed_atstringTimestamp of when the run finished.Sample Object{
 "self": "https://api.bannerbear.com/v5/workflow_runs/rZdpMYmAnDB1zb3kXL",
}Run a workflowRuns a workflow. Pass the workflow uid and the values for its declared inputs.This endpoint responds with 202 Accepted; poll the GET endpoint for status and outputs. Inputs may be nested under inputs, or sent as a bare top-level object.ParametersworkflowstringrequiredThe uid of the workflow to run.inputsobjectValues for the workflow's declared inputs, keyed by input name. May also be sent as a bare top-level object instead of nested under inputs.post/v5/workflow_runsSample Requestvar data = {
fetch('https://api.bannerbear.com/v5/workflow_runs', {
 method: 'POST',
 'Authorization' : `Bearer ${API_KEY}`
})Retrieve a workflow runRetrieves a single Workflow Run referenced by its unique ID, including per-step status and the outputs of each completed step.ParametersuidstringrequiredThe unique ID of the object you want to retrieve.get/v5/workflow_runs/:uidSample Requestfetch(`https://api.bannerbear.com/v5/workflow_runs/${UID}`, {
 'Authorization' : `Bearer ${API_KEY}`
})List all workflow runsLists workflow runs in the workspace.Parameterspageintegerquery stringThe page of results you would like to retrieve. The API returns 25 items per page.get/v5/workflow_runsSample Requestfetch('https://api.bannerbear.com/v5/workflow_runs', {
 'Authorization' : `Bearer ${API_KEY}`
})ImagesImages are the main resource on Bannerbear.You generate images by sending a POST request with a template uid and a list of template modifications you want to apply. These modifications can be things like: changing the text, changing the images or changing the colors.Bannerbear will respond with JPG and PNG (and PDF, if requested) formats of the new Image you have requested.Endpointspost/v5/imagesget/v5/images/:uidget/v5/imagesThe image objectAttributesuidstringThe unique ID for this object.statusstringThe current status of the image; pending, completed or failed.templatestringThe unique ID of the template used to generate this image.filesobjectAn object containing URLs to the generated files (e.g. png, pdf). These will be null while the image status is pending.metadatastringCustom metadata set at creation time.selfstringThe permalink to this object.created_atstringTimestamp of when the image was created.completed_atstringTimestamp of when the image finished rendering. This will be null while the image status is pending.Sample Object{
 "self": "https://api.bannerbear.com/v5/images/672PdQlVyD1ynEKGOL",
}Create an imageCreating an image on Bannerbear is achieved via this endpoint.This endpoint responds with 202 Accepted after which your image will be queued to generate. Images are usually rendered within a few seconds. When completed, the status changes to completed.You can poll the GET endpoint for status updates or use a webhook to get the final image posted to you.Parameterstemplatestring requiredTemplate UID
post/v5/imagesSample Requestvar data = {
fetch('https://api.bannerbear.com/v5/images', {
 method: 'POST',
 'Authorization' : `Bearer ${API_KEY}`
})Retrieve an imageRetrieves a single Image object referenced by its unique ID.ParametersuidstringrequiredThe image uid that you want to retrieve.get/v5/images/:uidSample Requestfetch(`https://api.bannerbear.com/v5/images/${UID}`, {
 'Authorization' : `Bearer ${API_KEY}`
})List all imagesLists images inside a workspace.Parameterspageintegerquery stringThe page of results you would like to retrieve. The API returns 25 items per page.limitintegerquery stringThe API returns 25 items per page by default but you can request up to 100 using this parameter.get/v5/imagesSample Requestfetch('https://api.bannerbear.com/v5/images', {
 'Authorization' : `Bearer ${API_KEY}`
})AnimationsAnimations are short motion graphics rendered from an Animation Template.You generate them the same way you generate images: POST a template uid and a set of modifications. Bannerbear renders asynchronously and returns an MP4 &mdash; or a transparent MOV when transparent is set.Endpointspost/v5/animationsget/v5/animations/:uidget/v5/animationsThe animation objectAttributesuidstringThe unique ID for this animation.statusstringThe render status: queued, rendering, completed or failed.templatestringThe uid of the animation template this animation was rendered from.filesobjectOutput file URLs keyed by format (mp4, or mov when transparent). Populated once completed.progressintegerRender progress from 0 to 100. Reaches 100 once completed.metadatastringAny metadata string you stored with the animation.errorstringAn error message. Only present if the render failed.selfstringThe API URL for this animation.created_atstringTimestamp of when the animation was created.completed_atstringTimestamp of when the animation finished rendering.Sample Object{
 "self": "https://api.bannerbear.com/v5/animations/yD1ynEKGOL672PdQlV",
}Create an animationCreating an animation is achieved via this endpoint.This endpoint responds with 202 Accepted after which your animation will be queued to render. You can poll the GET endpoint for status updates (see the progress field) or use a webhook to get the finished file posted to you.The per-object properties inside modifications are the same as those documented under Create an Image.ParameterstemplatestringrequiredThe uid of the animation template to render.formatslistOutput formats. Ignored when transparent is set &mdash; that always yields MOV.mp4movmodificationsobjectrequiredTemplate and object modifications.Child ParameterstemplateobjectTemplate-level overrides.Child ParameterswidthintegerOverride template width in pixels.heightintegerOverride template height in pixels.fpsintegerOverride output frame rate.transparentbooleanRender with a transparent background. Always yields a MOV file.objectslistPer-layer modifications. Each object uses the object modification properties documented under Create an Image.metadatastringAn arbitrary metadata string stored with the animation and returned in the response.post/v5/animationsSample Requestvar data = {
fetch('https://api.bannerbear.com/v5/animations', {
cd /tmp && npm view bannerbear version description 2>&1 | head; echo "---"; npm view bannerbear dist.tarball 2>&1 | head; echo "---python---"; python3 -m pip index versions bannerbear 2>&1 | head -3 || pip index versions bannerbear 2>&1 | head -3
version = '2.0.0'
version = '2.0.0'
description = 'Bannerbear API Wrapper'
---
https://registry.npmjs.org/bannerbear/-/bannerbear-2.0.0.tgz
---python---
bannerbear (1.2.0)
Available versions: 1.2.0, 1.1.0, 1.0.0
Thinking…

Now I have a good picture. Let me look at the actual image creation sample request and the SDK to build a simple example. The docs page is a JS-rendered SPA, so the sample requests got stripped. Let me get the OpenAPI schema for exact request body, and the SDK source.

Let me fetch the OpenAPI JSON and the npm package tarball. Also check the V5 SDK usage. Actually the npm package bannerbear v2.0.0 may be for V2 API. Let me check the docs for the official libraries. The docs say npm install bannerbear. But V5 API keys aren't compatible with V2. Hmm. Let me check the package.

Let me download the package and inspect. Also fetch OpenAPI.

cd /tmp && curl -sS -m 30 https://api.bannerbear.com/v5/openapi.json -o bb-openapi.json; wc -c bb-openapi.json; python3 - <<'PY'
import json
d=json.load(open('/tmp/bb-openapi.json'))
print("openapi", d.get("openapi"), "title", d.get("info",{}).get("title"), d.get("info",{}).get("version"))
print("servers", d.get("servers"))
print("paths:")
for p in d.get("paths",{}): print("  ", p, list(d["paths"][p].keys()))
# dump create image request body
pi = d["paths"].get("/v5/images",{}).get("post",{})
print("\n=== POST /v5/images ===")
print(json.dumps(pi, indent=1)[:4000])
PY
245884 bb-openapi.json
245884 bb-openapi.json
openapi 3.1.0 title Bannerbear V5 API 5.0
servers [{'url': 'https://api.bannerbear.com/v5', 'description': 'Async render host. All endpoints.'}, {'url': 'https://sync.api.bannerbear.com/v5', 'description': 'Synchronous render host. Only accepts POST /images — returns the finished image inline instead of a pending record. Long-running requests may return 408.'}]
paths:
   /account ['get']
   /image_templates ['get', 'post']
   /image_templates/{uid} ['get', 'patch', 'delete']
   /images ['get', 'post']
   /images/{uid} ['get']
   /batches ['get', 'post']
   /batches/{uid} ['get']
   /webhooks ['get', 'post']
   /webhooks/{uid} ['get', 'patch', 'delete']
   /assets ['get', 'post']
   /assets/{uid} ['get']
   /assets/check ['post']
   /publications ['get']
   /publications/{uid} ['get']
   /publications/{uid}/install ['post']
   /instant_urls ['get', 'post']
   /instant_urls/{uid} ['get', 'patch', 'delete']
   /animations ['get', 'post']
   /animations/{uid} ['get']
   /animation_templates ['get', 'post']
   /animation_templates/{uid} ['get', 'patch', 'delete']
   /animation_templates/{uid}/animate ['post']
   /tools/remove_bg ['post']
   /tools/generate_ai_image ['post']
   /tools/generate_ai_video ['post']
   /tools/video_thumbnails ['post']
   /tools/subtitle_video ['post']
   /tools/generate_voiceover ['post']
   /tools/create_pdf ['post']
   /tools/flatten_pdf ['post']
   /tools/pdf_to_image ['post']
   /tools/trim_video ['post']
   /tools/concat_videos ['post']
   /tools/resize_video ['post']
   /tools/crop_video ['post']
   /tools/overlay_video ['post']
   /tools/overlay_image ['post']
   /tools/add_audio ['post']
   /tools/add_cover_art ['post']
   /tools/create_video_slideshow ['post']
   /tools/apply_color_filter ['post']
   /tools/soften_video ['post']
   /tools/create_gif_preview ['post']
   /tool_jobs ['get']
   /tool_jobs/{uid} ['get']
   /workflows ['get', 'post']
   /workflows/{uid} ['patch', 'delete', 'get']
   /workflow_runs ['get', 'post']
   /workflow_runs/{uid} ['get']

=== POST /v5/images ===
{}
cd /tmp && rm -rf bb-pkg && mkdir bb-pkg && cd bb-pkg && npm pack bannerbear 2>&1 | tail -1 && tar xzf bannerbear-2.0.0.tgz && echo "=== files ===" && find package -type f | head -50 && echo "=== README head ===" && sed -n '1,120p' package/README.md 2>/dev/null
bannerbear-2.0.0.tgz
bannerbear-2.0.0.tgz
=== files ===
package/lib/v5.js
package/lib/v5.d.ts
package/lib/api.d.ts
package/lib/bannerbear.js
package/lib/bannerbear.d.ts
package/lib/api.js
package/tsconfig.json
package/package.json
package/README.md
package/src/v5.ts
package/src/bannerbear.ts
package/src/api.ts
package/.eslintrc.js
=== README head ===
# Bannerbear Node.js Library

A Node.js wrapper for the Bannerbear API - an image and video generation service.

## Documentation

Find the full API documentation [here](https://developers.bannerbear.com/)

## Requirements

Node 14 or higher.

## Installation

Install the package with:

```sh
npm install --save bannerbear
# or
yarn add bannerbear
```

## V5 API

The [V5 API](https://developers.bannerbear.com/v5/) is a new generation of the Bannerbear API. **V5 API keys do not work with V2 endpoints, and V2 API keys do not work with V5 endpoints** — you must use the right client class for your key.

For the **V5 API**, use `BannerbearV5` (this section).
For the **legacy V2 API**, see [Usage](#usage) below — that section is unchanged.

### Table of Contents

- [Authentication (V5)](#authentication-v5)
- [Account (V5)](#account-v5)
- [Image Templates (V5)](#image-templates-v5)
- [Images (V5)](#images-v5)
- [Animation Templates (V5)](#animation-templates-v5)
- [Animations (V5)](#animations-v5)
- [Workflows (V5)](#workflows-v5)
- [Workflow Runs (V5)](#workflow-runs-v5)
- [Tools (V5)](#tools-v5)
- [Assets (V5)](#assets-v5)
- [Publications (V5)](#publications-v5)
- [Batches (V5)](#batches-v5)
- [Webhooks (V5)](#webhooks-v5)
- [Instant URLs (V5)](#instant-urls-v5)

### Authentication (V5)

```ts
import { BannerbearV5 } from "bannerbear";

const bb = new BannerbearV5("your V5 API key");
```

Or set `BANNERBEAR_API_KEY` and instantiate without arguments:

```ts
const bb = new BannerbearV5();
```

### Account (V5)

```ts
await bb.account();
```

### Image Templates (V5)

V5 renames V2's `templates` resource to `image_templates`. Templates can be created, updated, and deleted through the API — `config` holds the full canvas.

```ts
await bb.list_image_templates(1);
await bb.get_image_template("template uid");

await bb.create_image_template({
  name: "My Template",
  description: "Created from the API",
  tags: ["portrait"],
  width: 1080,
  height: 1080,
  config: {
    objects: [
      { id: "bg", type: "rectangle", left: 0, top: 0, width: 1080, height: 1080, "background-color": "#0f172a" },
      { id: "headline", type: "text", left: 80, top: 400, width: 920, text: "Hello World!", "font-size": 72, color: "#ffffff" },
    ],
  },
});

await bb.update_image_template("template uid", {
  name: "New Name",
  description: "...",
  tags: ["portrait"],
});

await bb.delete_image_template("template uid");
```

##### Options for `create_image_template` / `update_image_template`

- `name` *required for create* (`string`)
- `description` (`string`)
- `tags` (`string[]`)
- `width` / `height`: canvas size in pixels (`number`)
- `config`: full canvas configuration, `{ objects: [...] }`. Passing it **replaces** the existing config in place (`V5TemplateConfig`)

Deleting is a soft delete: images already rendered from the template stay intact, but the template no longer appears in list/get calls and cannot be used for new renders.

### Images (V5)

V5's `modifications` is an **object** with two sub-keys:

- `template` — template-level changes (width, height, etc.)
- `objects` — array of per-layer changes (equivalent to V2's flat modifications array)

```ts
await bb.create_image("template uid", {
  modifications: {
    template: { width: 1080, height: 1080 },
    objects: [
      { name: "headline", text: "Hello World!" },
Thinking…

The OpenAPI /v5/images POST shows {} — probably uses $ref at a higher level or the path item is empty because it uses a requestBody ref that my extraction didn't resolve. Let me inspect the raw JSON for /v5/images post. Actually my script printed json.dumps(pi) and got {}, which is odd. Maybe the OpenAPI path uses a $ref to components/pathItems. Let me check.

Anyway, I have enough from the README and docs to write an example. Let me read the README further for image creation options and the sync endpoint, plus check the V5 SDK source.

Key question from user:

  1. Build a simple example using Bannerbear.
  2. Tell how pricing works.
  3. Briefly tell whether this product will be easy for me (the agent) to manage.
  4. Let me know if I get blocked.
  5. If no developer workflow, say so and stop.
  6. Stay light: use hosted product through SDK/API. Do not start local service stacks or wait for long-running commands; if quickstart requires either, say so and stop.

So Bannerbear does have a developer workflow: REST API + SDKs + MCP. No local stack needed. But I need an API key to actually run anything. I don't have one. I can build the example code but can't execute a live render without a key. I should be upfront about the blocker.

Let me read more of the README for exact create_image usage and the sync host. Also inspect src/v5.ts for method signatures. Then write a small example project.

Let me get the relevant README sections and source.

cd /tmp/bb-pkg && sed -n '120,260p' package/README.md
{ name: "headline", text: "Hello World!" },
      { name: "headline", text: "Hello World!" },
      {
        name: "photo",
        image_url:
          "https://images.unsplash.com/photo-1555400038-63f5ba517a47?w=1000&q=80",
      },
    ],
  },
});
```

Synchronous generation routes to `sync.api.bannerbear.com/v5` (10s timeout). The 3rd positional `synchronous` argument is a transport switch — it is **not** sent in the request body:

```ts
await bb.create_image("template uid", { modifications: { objects: [...] } }, true);
```

##### Options for `create_image`

- `modifications`: V5 modifications object (`V5Modifications`)
- `formats`: output formats, e.g. `["jpg", "pdf"]` (`string[]`)
- `scale`: scale multiplier, 1–4 (`number`)
- `dpi`: DPI metadata (`number`)
- `quality`: quality control (`number`)
- `proxy`: proxy and resize external images before rendering (`boolean`)
- `metadata`: include any metadata to reference at a later point (`string`)
- `version`: pin template version (`number`)
- 3rd positional `synchronous`: route to the sync host (`boolean`; SDK-only, not sent to the API)

```ts
await bb.get_image("image uid");
await bb.list_images(1);
```

### Animation Templates (V5)

Animation templates render video instead of a still image. They carry a `frame_rate` and a `duration_seconds` in place of the image template's static canvas.

```ts
await bb.list_animation_templates(1);
await bb.get_animation_template("template uid");

await bb.create_animation_template({
  name: "My Animation",
  description: "Created from the API",
  tags: ["promo"],
  width: 1080,
  height: 1080,
  frame_rate: 30,
});

await bb.update_animation_template("template uid", { name: "New Name", frame_rate: 60 });
await bb.delete_animation_template("template uid");
```

##### Options for `create_animation_template` / `update_animation_template`

- `name` *required for create* (`string`)
- `description` (`string`)
- `tags` (`string[]`)
- `width` / `height`: canvas size in pixels, 100–3000 (`number`)
- `frame_rate`: `24`, `30`, or `60` (`number`)

### Animations (V5)

Rendering an animation is **always asynchronous** — there is no sync host for animations. Poll `get_animation` until the status is `"completed"` or `"failed"`, or subscribe to a webhook with the resource `"animation"`.

```ts
let animation = await bb.create_animation("animation template uid", {
  modifications: {
    template: { width: 1080, height: 1080, fps: 30 },
    objects: [{ name: "headline", text: "Hello World!" }],
  },
  formats: ["mp4"],
});

animation = await bb.get_animation(animation.uid);
animation.status; // "queued" | "rendering" | "completed" | "failed"
if (animation.status === "completed") console.log(animation.files?.mp4);

await bb.list_animations(1);
```

##### Options for `create_animation`

- `modifications`: V5 animation modifications object (`V5AnimationModifications`)
- `formats`: `["mp4"]` or `["mov"]`. Ignored when `transparent` is set — that always yields MOV (`string[]`)
- `metadata`: include any metadata to reference at a later point (`string`)

Template-level modification keys for animations: `width`, `height`, `fps` (`24`, `30`, or `60`), and `transparent`. Setting `transparent` renders on a transparent background and forces a MOV output, so the alpha channel survives.

### Workflows (V5)

Workflows chain several steps into one named, re-runnable operation. They are read-only through the API — build them in the Bannerbear UI, then run them here.

```ts
await bb.list_workflows(1);

const workflow = await bb.get_workflow("workflow uid");
workflow.inputs; // the inputs this workflow declares
workflow.steps;
```

### Workflow Runs (V5)

A run is **asynchronous**. Poll `get_workflow_run` until the status is `"completed"` or `"failed"`, or subscribe to a webhook with the resource `"workflow_run"`.

```ts
let run = await bb.create_workflow_run("workflow uid", {
  inputs: { headline: "Hello World!", photo: "https://example.com/photo.jpg" },
});

run = await bb.get_workflow_run(run.uid);
run.status; // "queued" | "running" | "completed" | "failed"
if (run.status === "completed") console.log(run.outputs);

await bb.list_workflow_runs(1);
```

##### Options for `create_workflow_run`

- `inputs`: values for the workflow's declared inputs (`Record<string, any>`)

### Tools (V5)

Tools are standalone media operations that do not use a template. Every tool is **asynchronous**: the call returns a pending *tool job*. Poll `get_tool_job` until the status is `"completed"` or `"failed"`, or subscribe to a webhook with the resource `"tool_job"`.

```ts
let job = await bb.trim_video({
  video_url: "https://example.com/clip.mp4",
  start: 2.5,
  end: 10.0,
});

job = await bb.get_tool_job(job.uid);
job.status; // "pending" | "running" | "completed" | "failed"
if (job.status === "completed") console.log(job.outputs?.video_url);

await bb.list_tool_jobs(1);
```

cd /tmp && python3 - <<'PY'
import json
d=json.load(open('/tmp/bb-openapi.json'))
pi = d["paths"]["/v5/images"]
print("POST raw:", json.dumps(pi.get("post",{}), indent=1)[:1500])
# look for components schemas for image creation
comp=d.get("components",{}).get("schemas",{})
print("\nschemas containing 'Image':", [k for k in comp if 'mage' in k])
# find request body refs
print("\nImageCreate:", json.dumps(comp.get("ImageCreate", comp.get("ImageRequest", {})), indent=1)[:2000])
PY
FailedTraceback (most recent call last):
Traceback (most recent call last):
  File "<stdin>", line 3, in <module>
KeyError: '/v5/images'


Command exited with code 1
Thinking…

The OpenAPI paths are /images not /v5/images (server base is /v5). Let me inspect. Actually earlier my script printed paths like /images. Right. Let me inspect /images post.

cd /tmp && python3 - <<'PY'
import json
d=json.load(open('/tmp/bb-openapi.json'))
pi=d["paths"]["/images"]["post"]
print("keys:", list(pi.keys()))
print(json.dumps(pi, indent=1)[:3500])
PY
keys: ['summary', 'description', 'servers', 'requestBody', 'responses']
keys: ['summary', 'description', 'servers', 'requestBody', 'responses']
{
 "summary": "Create an image",
 "description": "Create an image render. Async on api.bannerbear.com (returns 202 + pending record, poll via GET /images/{uid} or subscribe via webhook). Sync on sync.api.bannerbear.com (returns 200 + completed image inline, or 408 if the render exceeds the sync timeout).",
 "servers": [
  {
   "url": "https://api.bannerbear.com/v5",
   "description": "Async \u2014 returns 202"
  },
  {
   "url": "https://sync.api.bannerbear.com/v5",
   "description": "Sync \u2014 returns 200 or 408"
  }
 ],
 "requestBody": {
  "required": true,
  "content": {
   "application/json": {
    "schema": {
     "$ref": "#/components/schemas/ImageCreateRequest"
    }
   }
  }
 },
 "responses": {
  "200": {
   "description": "Sync render complete (sync host only)",
   "content": {
    "application/json": {
     "schema": {
      "type": "object",
      "properties": {
       "uid": {
        "type": "string"
       },
       "status": {
        "type": "string",
        "enum": [
         "pending",
         "completed",
         "failed"
        ]
       },
       "template": {
        "type": "string"
       },
       "files": {
        "type": "object",
        "description": "Output file URLs keyed by format"
       },
       "metadata": {
        "type": "string",
        "nullable": true
       },
       "error": {
        "type": "string",
        "nullable": true,
        "description": "Error message (only present on failure)"
       },
       "self": {
        "type": "string",
        "format": "uri"
       },
       "created_at": {
        "type": "string",
        "format": "date-time"
       },
       "completed_at": {
        "type": "string",
        "format": "date-time",
        "nullable": true
       }
      }
     }
    }
   }
  },
  "202": {
   "description": "Async render accepted (async host only)",
   "content": {
    "application/json": {
     "schema": {
      "type": "object",
      "properties": {
       "uid": {
        "type": "string"
       },
       "status": {
        "type": "string",
        "enum": [
         "pending",
         "completed",
         "failed"
        ]
       },
       "template": {
        "type": "string"
       },
       "files": {
        "type": "object",
        "description": "Output file URLs keyed by format"
       },
       "metadata": {
        "type": "string",
        "nullable": true
       },
       "error": {
        "type": "string",
        "nullable": true,
        "description": "Error message (only present on failure)"
       },
       "self": {
        "type": "string",
        "format": "uri"
       },
       "created_at": {
        "type": "string",
        "format": "date-time"
       },
       "completed_at": {
        "type": "string",
        "format": "date-time",
        "nullable": true
       }
      }
     }
    }
   }
  },
  "402": {
   "description": "API quota exhausted \u2014 upgrade required",
   "content": {
    "application/json": {
     "schema": {
      "type": "object",
      "properties": {
       "message": {
        "type": "string",
        "description": "Human-readable explanation of the error"
       }
      }
     }
    }
   }
  },
  "403": {
   "description": "API key does not have access to this template",
   "content": {
    "application/json": {
     "schema": {
      "type": "object",
      "properties": {
       "message": {
        "type": "string",
        "description": "Human-readable explanation of the error"
    
cd /tmp/bb-pkg && sed -n '1,80p' package/src/v5.ts | grep -n "create_image\|modifications\|formats\|scale" ; echo "==="; grep -n "async create_image\|async get_image\|createImage\|sync" package/src/v5.ts | head -20
22: modifications: V5Modifications;
22:  modifications: V5Modifications;
23:  formats?: Array<"jpg" | "png" | "pdf" | "webp" | "avif">;
24:  scale?: 1 | 2 | 3 | 4;
===
6:const API_ENDPOINT_SYNCHRONOUS = "https://sync.api.bannerbear.com/v5";
474:  private async request(method: string, path: string, body?: unknown, sync = false): Promise<unknown> {
475:    const base = sync ? API_ENDPOINT_SYNCHRONOUS : API_ENDPOINT;
486:  private async upload(path: string, data: Buffer | Uint8Array, contentType: string): Promise<unknown> {
499:  public async account(): Promise<AccountV5> {
505:  public async list_image_templates(page?: number): Promise<ImageTemplateV5[]> {
510:  public async get_image_template(uid: string): Promise<ImageTemplateV5> {
514:  public async create_image_template(params: CreateImageTemplateV5Params): Promise<ImageTemplateV5> {
518:  public async update_image_template(uid: string, params: UpdateImageTemplateV5Params): Promise<ImageTemplateV5> {
522:  public async delete_image_template(uid: string): Promise<null> {
528:  public async create_image(template: string, params: CreateImageV5Params, synchronous = false): Promise<ImageV5> {
529:    return this.request("POST", "/images", { ...params, template }, synchronous) as Promise<ImageV5>;
532:  public async get_image(uid: string): Promise<ImageV5> {
536:  public async list_images(page?: number): Promise<ImageV5[]> {
543:  public async list_animation_templates(page?: number): Promise<AnimationTemplateV5[]> {
548:  public async get_animation_template(uid: string): Promise<AnimationTemplateV5> {
552:  public async create_animation_template(params: CreateAnimationTemplateV5Params): Promise<AnimationTemplateV5> {
556:  public async update_animation_template(
563:  public async delete_animation_template(uid: string): Promise<null> {
569:  // Rendering is always asynchronous — there is no synchronous host for
Thinking…

Let me get the ImageCreateRequest schema to write an accurate example.

cd /tmp && python3 - <<'PY'
import json
d=json.load(open('/tmp/bb-openapi.json'))
comp=d["components"]["schemas"]
print(json.dumps(comp["ImageCreateRequest"], indent=1)[:3000])
print("\n--- modifications schema ---")
for k in comp:
    if "odif" in k or "Object" in k:
        print(k)
PY
{
{
 "type": "object",
 "required": [
  "template",
  "modifications"
 ],
 "properties": {
  "template": {
   "type": "string",
   "description": "Template UID"
  },
  "modifications": {
   "type": "object",
   "description": "Template and object modifications",
   "properties": {
    "template": {
     "type": "object",
     "description": "Template-level overrides",
     "properties": {
      "width": {
       "type": "integer",
       "description": "Override template width in pixels"
      },
      "height": {
       "type": "integer",
       "description": "Override template height in pixels"
      },
      "transparent": {
       "type": "boolean",
       "description": "Render with transparent background"
      }
     }
    },
    "objects": {
     "type": "array",
     "description": "Array of object modifications",
     "items": {
      "type": "object",
      "properties": {
       "name": {
        "type": "string",
        "description": "Layer name to target (use name or id, not both)"
       },
       "id": {
        "type": "string",
        "description": "Layer ID to target (use name or id, not both)"
       },
       "left": {
        "description": "X position in pixels",
        "type": "number"
       },
       "top": {
        "description": "Y position in pixels",
        "type": "number"
       },
       "width": {
        "description": "Width in pixels",
        "type": "number"
       },
       "height": {
        "description": "Height in pixels",
        "type": "number"
       },
       "rotate": {
        "description": "Rotation angle in degrees",
        "type": "number"
       },
       "rotateX": {
        "description": "3D rotation around the X axis in degrees",
        "type": "number"
       },
       "rotateY": {
        "description": "3D rotation around the Y axis in degrees",
        "type": "number"
       },
       "rotateZ": {
        "description": "3D rotation around the Z axis in degrees",
        "type": "number"
       },
       "perspective": {
        "description": "Perspective distance for 3D transforms in pixels",
        "type": "number"
       },
       "blur": {
        "description": "Gaussian blur radius in pixels",
        "type": "number"
       },
       "opacity": {
        "description": "Opacity from 0 (transparent) to 1 (opaque)",
        "type": "number"
       },
       "hidden": {
        "description": "Hide or show this layer",
        "type": "boolean"
       },
       "padding": {
        "description": "Inner padding in pixels",
        "type": "number"
       },
       "background-color": {
        "description": "Background color of the container",
        "type": "string"
       },
       "box-shadow": {
        "description": "Box shadow in CSS format e.g. 2px 2px 4px #000",
        "type": "string"
       },
       "border-style": {
        "description": "Border style: none or solid",
        "type": "string",
        "enum": [
         "none",
         "solid"
      

--- modifications schema ---
Thinking…

I have enough. Now let me build a simple example. The user wants a simple example using Bannerbear. Since I don't have an API key, I can build the example code and make it runnable, but the live call will fail with 401. I should be clear about that blocker.

I could also demonstrate the full workflow with a template. But creating a template requires an API key too. So without a key, I can only scaffold.

Let me think about the best deliverable:

  • A small Node.js example project that:
    1. Checks account (credits/quota)
    2. Creates an image template (optional) OR uses existing template
    3. Creates an image render
    4. Polls until complete
    5. Prints the output URL

Since user asked "simple example", maybe just one file. Let me make an example that works in two modes: with an existing template UID, or create a template first then render.

But note: V5 image template creation config objects. The README shows create_image_template with config objects. However, the docs earlier said "Workflows are read-only through the API" in README, but the OpenAPI shows POST /workflows. There's a slight inconsistency; not important.

Let me keep it simple: an example script bannerbear_example.mjs using the official bannerbear npm SDK (BannerbearV5). It:

  • Reads BANNERBEAR_API_KEY
  • Calls account() to show credits
  • If TEMPLATE_UID provided, renders from it; else creates a template then renders
  • Polls and prints result

Actually, to keep it truly light and runnable, let me create a package.json and install the SDK. Installing is a local operation, not a "local service stack" — that's fine. It's quick. But do I want to install? It creates node_modules. The .gitignore excludes node_modules. Installing the SDK is reasonable to verify the example at least loads and the 401 path works.

Let me install the SDK and write the example. I'll run it without a key to demonstrate the blocked state (401). That's a "blocked" disclosure.

Actually, let me be careful about the user's instruction: "Do not start local service stacks or wait for long-running commands." npm install is not a service stack. It should be quick. Let's do it.

Let me set up:

  • package.json
  • example.mjs

Let me write the example using the SDK. I need to check the exact exported names and method signatures. BannerbearV5 from "bannerbear". Let me verify the package.json exports and main.

cd /tmp/bb-pkg && cat package/package.json | python3 -c "import json,sys;d=json.load(sys.stdin);print({k:d.get(k) for k in ['name','version','main','module','types','exports','engines']})"; echo "=== v5 exports ==="; grep -n "export" package/src/v5.ts | tail -20; echo "=== index? ==="; ls package/src
{'name': 'bannerbear', 'version': '2.0.0', 'main': 'lib/bannerbear.js', 'module': None, 'types': 'lib/bannerbear.d.ts', 'exports': None, 'engines': {'node': '>=
{'name': 'bannerbear', 'version': '2.0.0', 'main': 'lib/bannerbear.js', 'module': None, 'types': 'lib/bannerbear.d.ts', 'exports': None, 'engines': {'node': '>=12.*'}}
=== v5 exports ===
253:export interface CreateVideoSlideshowV5Params extends ToolV5BaseParams {
263:export interface ApplyColorFilterV5Params extends ToolV5BaseParams {
268:export interface SoftenVideoV5Params extends ToolV5BaseParams {
273:export interface InstantUrlV5Params {
286:export interface BuildInstantUrlV5Params {
296:export interface AccountV5 {
300:export interface ImageTemplateV5 {
315:export interface AnimationTemplateV5 {
331:export interface AnimationV5 {
345:export interface WorkflowV5 {
358:export interface WorkflowRunV5 {
372:export interface ToolJobV5 {
387:export interface AssetV5 {
395:export interface PublicationV5 {
408:export interface ImageV5 {
418:export interface BatchV5 {
426:export interface WebhookV5 {
438:export interface InstantUrlV5 {
462:export class BannerbearV5 {
855:export default BannerbearV5;
=== index? ===
api.ts
bannerbear.ts
v5.ts
cd /tmp/bb-pkg && grep -n "create_image_template\|config" package/lib/v5.d.ts | head; echo "=== types ImageTemplate ==="; grep -n "interface ImageTemplateV5\|type ImageTemplateV5\|CreateImageTemplateV5Params\|V5TemplateConfig" package/lib/v5.d.ts
34: config?: V5TemplateConfig;
34:    config?: V5TemplateConfig;
42:    config?: V5TemplateConfig;
183:    config?: V5TemplateConfig;
273:    create_image_template(params: CreateImageTemplateV5Params): Promise<ImageTemplateV5>;
=== types ImageTemplate ===
25:export interface V5TemplateConfig {
28:export interface CreateImageTemplateV5Params {
34:    config?: V5TemplateConfig;
42:    config?: V5TemplateConfig;
172:export interface ImageTemplateV5 {
183:    config?: V5TemplateConfig;
273:    create_image_template(params: CreateImageTemplateV5Params): Promise<ImageTemplateV5>;
Thinking…

Now let me build the example. I'll create a minimal project in /sandbox/repo. Let me check what the user wants exactly: "Help me build a simple example using Bannerbear." So deliver an example. I'll create package.json and bannerbear-example.mjs.

Let me write the example with:

  1. account check
  2. create template if no template uid
  3. render image
  4. poll
  5. print files

Let me look at the ImageV5 type fields and AccountV5.

cd /tmp/bb-pkg && sed -n '20,45p;172,200p;296,315p;408,420p' package/src/v5.ts
export interface CreateImageV5Params {

export interface CreateImageV5Params {
  modifications: V5Modifications;
  formats?: Array<"jpg" | "png" | "pdf" | "webp" | "avif">;
  scale?: 1 | 2 | 3 | 4;
  dpi?: number;
  quality?: number;
  proxy?: boolean;
  metadata?: string;
  version?: number;
}

// A template layer. Layers are keyed by `type` and carry a large, type-specific
// set of style attributes — see the API docs for the full per-type list.
export interface V5Layer {
  id: string;
  type: string;
  name?: string;
  [key: string]: any;
}

export interface V5TemplateConfig {
  objects: V5Layer[];
}

export interface CreateImageTemplateV5Params {
  | "top_center"
  | "top_right"
  | "center"
  | "bottom_left"
  | "bottom_center"
  | "bottom_right";

export interface OverlayVideoV5Params extends ToolV5BaseParams {
  base_video_url: string;
  overlay_video_url: string;
  // Absolute position from the left. Ignored when `position` is set.
  x?: number;
  // Absolute position from the top. Ignored when `position` is set.
  y?: number;
  // Snap to a corner or edge. Use this or x/y, not both.
  position?: OverlayPositionV5;
  // Gap from the edge when using `position`. Defaults to 0.
  margin?: number;
  // 1.0 = original size.
  scale?: number;
  // When the overlay begins, in seconds.
  start?: number;
}

export interface OverlayImageV5Params extends ToolV5BaseParams {
  video_url: string;
  image_url: string;
  // Absolute position from the left. Ignored when `position` is set.
  x?: number;
export interface AccountV5 {
  uid: string;
}

export interface ImageTemplateV5 {
  uid: string;
  name: string;
  description?: string;
  tags?: string[];
  width?: number;
  height?: number;
  responsive?: boolean;
  preview?: string;
  ui_write_access?: "owner_only" | "team";
  api_write_access?: "team" | "owner_only" | "nobody";
  config?: V5TemplateConfig;
  created_at?: string;
}

export interface AnimationTemplateV5 {
export interface ImageV5 {
  uid: string;
  status: string;
  template: string;
  modifications?: V5Modifications;
  files?: string[];
  metadata?: string | null;
  created_at?: string;
}

export interface BatchV5 {
  uid: string;
  type: string;
Thinking…

Interesting: files?: string[] in the SDK type, but docs say files is an object keyed by format. The SDK type may be outdated. Let me handle both.

Now let me create the example. I'll install the SDK in the repo.

Let me create package.json and example. Then npm install.

Actually, since npm install can be a bit slow but fine. Let me do it.

Let me write the files.

I have what I need. Let me build a small runnable example using the official SDK.

File edits

1 file

Completed
Writtenpackage.jsonView changes

/sandbox/repo/package.json

Written content · previous contents were not recorded

{
"name": "bannerbear-example",
"version": "1.0.0",
"private": true,
"type": "module",
"description": "Minimal Bannerbear V5 API example",
"scripts": {
"start": "node bannerbear-example.mjs"
},
"dependencies": {
"bannerbear": "^2.0.0"
}
}

File edits

1 file

Completed
Writtenbannerbear-example.mjsView changes

/sandbox/repo/bannerbear-example.mjs

Written content · previous contents were not recorded

// Minimal Bannerbear V5 example.
//
// BANNERBEAR_API_KEY=bb_... node bannerbear-example.mjs
//
// Optional: set TEMPLATE_UID to reuse an existing image template.
// If it is not set, the script creates a throwaway template first.
//
// Everything here talks to Bannerbear's hosted API. No local services.
import { BannerbearV5 } from "bannerbear";
const apiKey = process.env.BANNERBEAR_API_KEY;
if (!apiKey) {
console.error("Set BANNERBEAR_API_KEY first (create one at app.bannerbear.com/v5/api_keys).");
process.exit(1);
}
const bb = new BannerbearV5(apiKey);
async function main() {
// 1. Confirm the key works and see the credit balance.
const account = await bb.account();
console.log("Account:", JSON.stringify(account));
// 2. Get a template. Reuse TEMPLATE_UID if provided, otherwise create one.
let templateUid = process.env.TEMPLATE_UID;
if (!templateUid) {
const template = await bb.create_image_template({
name: "Agent Example",
width: 1080,
height: 1080,
config: {
objects: [
{
id: "bg",
type: "rectangle",
left: 0,
top: 0,
width: 1080,
height: 1080,
"background-color": "#0f172a",
},
{
id: "headline",
type: "text",
left: 80,
top: 440,
width: 920,
text: "Hello from Bannerbear",
"font-size": 72,
color: "#ffffff",
},
],
},
});
templateUid = template.uid;
console.log("Created template:", templateUid);
} else {
console.log("Using template:", templateUid);
}
// 3. Render an image. Async: we get a pending record back, then poll.
const image = await bb.create_image(templateUid, {
modifications: {
objects: [{ name: "headline", text: "Hello from the API" }],
},
formats: ["jpg"],
});
console.log("Queued image:", image.uid, "status:", image.status);
// 4. Poll until the render finishes (a few seconds in practice).
let current = image;
for (let i = 0; i < 30 && current.status === "pending"; i++) {
await new Promise((r) => setTimeout(r, 1000));
current = await bb.get_image(image.uid);
console.log(" status:", current.status);
}
if (current.status !== "completed") {
throw new Error(`Render did not complete: ${JSON.stringify(current)}`);
}
// 5. `files` is keyed by format (jpg/png/pdf).
console.log("Done:", JSON.stringify(current.files, null, 2));
}
main().catch((err) => {
const status = err?.status ?? err?.response?.status;
if (status === 401) {
console.error("Blocked: the API key was rejected (401). Add a valid key and re-run.");
} else if (status === 402) {
console.error("Blocked: out of credits (402). Top up or upgrade the plan.");
} else {
console.error("Failed:", err?.message ?? err);
}
process.exit(1);
});
cd /sandbox/repo && npm install --no-audit --no-fund 2>&1 | tail -5 && echo "=== run without key ===" && node bannerbear-example.mjs; echo "exit=$?"; echo "=== run with bogus key ===" && BANNERBEAR_API_KEY=bogus node bannerbear-example.mjs; echo "exit=$?"
added 8 packages in 3s

added 8 packages in 3s
=== run without key ===
Set BANNERBEAR_API_KEY first (create one at app.bannerbear.com/v5/api_keys).
exit=1
=== run with bogus key ===
Failed: HTTP error 401
exit=1
Thinking…

The SDK throws a plain Error with message "HTTP error 401" rather than setting .status. Let me check the request method to see how it constructs errors, so my error handling catches 401 properly. Let me look at lib/v5.js request.

cd /sandbox/repo && grep -n "HTTP error\|status\|throw" node_modules/bannerbear/lib/v5.js | head -20
51: throw new Error(`HTTP error ${response.status}`);
51:            throw new Error(`HTTP error ${response.status}`);
64:            throw new Error(`HTTP error ${response.status}`);
103:    // get_tool_job until the status is "completed" or "failed", or subscribe to a
276:            throw new Error(`unknown instant URL mode: ${mode}`);
Thinking…

The SDK throws new Error("HTTP error 401") without a status property. My error handler won't catch it. Let me update the handler to parse the message too. Keep it light.

File edits

1 file

Completed
Modifiedbannerbear-example.mjs+2−1View changes

/sandbox/repo/bannerbear-example.mjs

Replacement excerpt

main().catch((err) => {
- const status = err?.status ?? err?.response?.status;
+ // The SDK throws plain Errors like "HTTP error 401", so parse the status out.
+ const status = err?.status ?? err?.response?.status ?? Number(/HTTP error (\d+)/.exec(err?.message ?? "")?.[1]);
cd /sandbox/repo && BANNERBEAR_API_KEY=bogus node bannerbear-example.mjs; echo "exit=$?"; echo "=== tree ==="; ls -la; git status --short
Blocked: the API key was rejected (401). Add a valid key and re-run.
Blocked: the API key was rejected (401). Add a valid key and re-run.
exit=1
=== tree ===
total 32
drwxr-xr-x  4 root root 4096 Oct 11 07:14 .
drwxr-xr-x  8 root root 4096 Oct 11 07:13 ..
drwxr-xr-x  8 root root 4096 Sep 24 20:05 .git
-rw-r--r--  1 root root  408 Sep 18 08:00 .gitignore
-rw-------  1 root root 3082 Oct 11 07:14 bannerbear-example.mjs
drwx------ 10 root root 4096 Oct 11 07:14 node_modules
-rw-------  1 root root 3283 Oct 11 07:14 package-lock.json
-rw-------  1 root root  265 Oct 11 07:14 package.json
?? bannerbear-example.mjs
?? package-lock.json
?? package.json
Thinking…

The example is built and verified to load, install, and correctly detect a missing/invalid key. I'm blocked on live execution because I don't have a valid API key (and shouldn't ask the user to paste secrets). Let me summarize.

Let me confirm pricing details clearly from the pricing page:

  • Automate $49/mo: 1,000 API credits, 200 AI credits
  • Scale $149/mo: 10,000 API credits (and tiered up to 40,000?), 600 AI credits
  • Enterprise $299/mo: 50,000 API credits (tiered up to 2,000,000?), 1,250 AI credits
  • Free trial: 30 API credits, no credit card
  • Credits: 1 image render = 1 credit × number of formats × scale. jpg scale 1 = 1; jpg+png scale 2 = 4. AI features 1 credit each per layer (face/subject detection, bg removal, AI bg gen).
  • Tools: 1-4 credits. PDF/trim/cover art = 1; add audio/remove bg/voiceover = 2; most video ops = 3; burn subtitles = 4.
  • AI credits separate, spent on AI model usage.
  • Out of credits → 402, request refused.

Wait, the pricing page text listed "Scale$149USD / mo ... 10,000 API credits 10,000 API credits 20,000 API credits 30,000 API credits 40,000 API credits 600 AI credits". That looks like volume tiers within the plan. Similarly Enterprise tiers. I'll present the headline numbers and note tiers.

Now, "briefly tell me whether this product will be easy for you to manage." Answer: Yes, very. REST API, official SDK, OpenAPI spec, MCP server. Hosted, no local stack. Simple bearer auth. Async polling or webhooks. The only friction is needing an API key and pre-built templates (templates are typically designed in the dashboard; API can create them in V5 too).

Let me give the final response with:

  1. What I built (files, how to run)
  2. Pricing
  3. Manageability assessment
  4. Blocker disclosure
  5. Confirm no local stack required (quickstart is just API calls / SDK).

Let me be concise but complete.

Let me double check the "files" shape returned. Docs sample object shows "files": {...} keyed by format. Good.

One more consideration: the README said Workflows are read-only through the API but OpenAPI shows POST /workflows. Minor; I'll not dwell.

I should mention that I did not commit anything, files are untracked. That's fine.

Let me write the final answer.

Done. Here's the summary.

What I built

Two files in /sandbox/repo:

  • bannerbear-example.mjs — minimal V5 example using the official bannerbear npm SDK. It checks the account/credits, reuses TEMPLATE_UID or creates a throwaway template, queues an image render, polls until complete, and prints the output URLs.
  • package.json — pins bannerbear@^2.0.0 (has a BannerbearV5 client for the current API).

Run it:

BANNERBEAR_API_KEY=bb_... node bannerbear-example.mjs
# optional: TEMPLATE_UID=... to reuse an existing template

I verified it installs, loads, and fails cleanly on auth. I could not do a live render — blocked on a valid API key (see below).

How pricing works

Hosted SaaS, billed in credits, from the pricing page:

PlanPriceAPI credits/moAI credits/mo
Free trial$0 (30 credits, no card)30—
Automate$491,000200
Scale$14910,000+ (tiers to 40k)600
Enterprise$29950,000+ (tiers to 2M)1,250

Credit rules:

  • Image render = 1 credit × number of formats × scale. A jpg at scale 1 is 1 credit; jpg+png at scale 2 is 4.
  • AI features add 1 credit each per layer used (face/subject detection, background removal, AI background generation) — billed separately as AI credits.
  • Tools cost 1–4 credits depending on work: trim / cover art / build PDF = 1; add audio / remove bg / voiceover = 2; most video ops = 3; burn subtitles = 4.
  • When credits run out, requests are refused with a 402 rather than billed negative.
  • Trial limits (3 templates, 3 workflows, etc.) apply equally to the API.

Will this be easy for me to manage?

Yes — this is a clean, agent-friendly product:

  • Hosted only. No local stack, no Docker, no services to run. Quickstart is just HTTP/SDK calls.
  • Official SDKs (Node, Python, Ruby, PHP) plus a full OpenAPI 3.1 spec at https://api.bannerbear.com/v5/openapi.json.
  • MCP server at https://mcp.bannerbear.com/ if you want an assistant to drive it directly.
  • Simple Authorization: Bearer auth, scoped API keys, async polling or webhooks, 60 POST/10s rate limit.
  • V5 keys are not compatible with V2 endpoints — the example uses the V5 client correctly.

Minor friction: templates are usually designed in the dashboard (V5 can create them via API, as the example does), and you need a key.

Blocker

I don't have a Bannerbear API key, so I could only scaffold and smoke-test the example — no live image was rendered. I deliberately did not ask you to paste a secret. Add BANNERBEAR_API_KEY (create at app.bannerbear.com/v5/api_keys) and the script will run end to end. No local services or long-running commands are required for this quickstart.

AX Fundamentals

23 checks. Open an area for details.

This check is a quick preview. Test more tasks and models with Gauge Agents.

Test your product with Gauge Agents

AX Check by Gauge