Skip to content

API reference

Base URL: https://pixbix.app/api — every endpoint below is relative to it. All requests and responses are JSON unless stated otherwise.

All endpoints#

Every endpoint in the public API, grouped by what it renders. Image, video and screenshot endpoints are documented on their own pages — the links below land on the exact one.

Conventions#

Authentication

Send your key as x-api-key, or as Authorization: Bearer <key>. Both are equivalent.

Headers
x-api-key: pk_live_1a2b3c4d…
Content-Type: application/json
PrefixBehaviour
pk_live_Consumes credits. Watermark follows your plan.
pk_test_Never consumes credits. Always watermarked. Lower rate limit.

Response envelope

Every JSON response uses the same shape, so error handling can be written once.

{
  "success": true,
  "message": "Render queued",
  "data": { "id": "rnd_8f2a1c", "status": "queued" },
  "pagination": { "page": 1, "limit": 20, "total": 84, "totalPages": 5 }
}

Branch on code, never on message — messages are written for humans and will change.

Idempotency

Send an Idempotency-Key header on any render request. A retry with the same key returns the original render instead of creating and billing a second one. Keys are scoped to your workspace and never expire.

Rate limits

Limits are per workspace, per minute, and set by your plan — 20/min on Free up to 1,200/min on Scale. Exceeding one returns 429 with code RATE_LIMIT_EXCEEDED. Concurrency is not a rate limit and never returns an error — renders past your plan’s concurrent allowance are queued and start automatically. See LIMIT_QUEUE_DEPTH below for the one ceiling that does refuse.

Pagination, sorting and filtering

Every list endpoint accepts the same four parameters: page (default 1), limit (default 20, max 100), sort and order (asc or desc). Most also accept search, plus filters of their own — each endpoint below lists the columns it can sort by and the filters it takes.

curl "https://pixbix.app/api/v1/render?status=failed&sort=createdAt&order=desc&page=2&limit=50" \
  -H "x-api-key: $PIXBIX_API_KEY"

Filters that name a set — status, kind, type — accept several values comma-separated and match any of them. Date filters come in pairs, such as createdFrom and createdTo, and take an ISO date.

Sorting is restricted to the columns each endpoint names, so that every ordering the API offers is one the database can serve from an index. An unknown column returns 422 with code INVALID_LIST_QUERY and a message naming the ones that are allowed — as does a filter value outside its set. Results are always tiebroken by id, so a row cannot appear on two pages of the same walk.

Paging very deep is refused rather than served slowly: past an offset of 100,000 the same 422 asks you to narrow the query with a filter or a date range instead.

Errors#

StatusCodeMeaning
400INVALID_REQUESTMalformed body, or neither templateId nor edit supplied
401NO_API_KEYNo key sent
401INVALID_API_KEYKey unknown, revoked, inactive or expired
403INSUFFICIENT_SCOPEThe key lacks the scope this endpoint requires
403IP_NOT_ALLOWEDCaller IP is not on the key’s allowlist
403ORG_SUSPENDEDWorkspace suspended — contact support
402INSUFFICIENT_CREDITSNot enough credits for this render
402LIMIT_QUEUE_DEPTHThis workspace already has the most renders your plan will hold queued at once
402LIMIT_VIDEO_DURATIONEdit is longer than your plan allows
402LIMIT_VIDEO_RESOLUTIONRequested resolution exceeds your plan
402LIMIT_VIDEO_FPSRequested frame rate exceeds your plan
402LIMIT_IMAGE_SIZERequested image dimension exceeds your plan
402FEATURE_VIDEOENABLEDVideo rendering is not included in your plan
402FEATURE_WORKFLOWSENABLEDWorkflow automation is not included in your plan
402LIMIT_WORKFLOWRUNSMonthly workflow run allowance used up
402LIMIT_WORKFLOWMAXSTEPSWorkflow has more steps than your plan allows
402LIMIT_WEBHOOKDELIVERIESMonthly webhook delivery allowance used up
402LIMIT_APIREQUESTSMonthly API request allowance used up
404TEMPLATE_NOT_FOUNDTemplate does not exist or is not available to you
403TEMPLATE_NOT_OWNEDThe template belongs to another workspace — copy it into yours and render the copy
409TEMPLATE_NOT_PUBLISHEDThe template is a draft, or archived. Publish it before rendering from it
409TEMPLATE_STALESomeone else saved this template since you loaded it — reload before saving
422INVALID_CATEGORYThe main category is missing, or the subcategory does not belong to it
422NOT_PUBLISHABLEThe template is not ready to publish; see the checks in the response
409ALREADY_SETTLEDRender already finished — cannot be cancelled
422TEMPLATE_VALIDATION_FAILEDField values failed validation; see the errors object
422EMPTY_TIMELINEThe timeline has no duration
429RATE_LIMIT_EXCEEDEDToo many requests this minute
503ENGINE_NOT_CONFIGUREDVideo rendering unavailable on this deployment

402 is not a failure to retry

A 402 means a business rule stopped the request — no credits, or a plan ceiling. Retrying will fail identically until the customer upgrades or buys credits. Surface it to a human rather than putting it in a retry loop.

Renders#

Create a render

POST/v1/renderAPI keyrenders:write
Renders a template, or a raw edit, to an image or video. Still images return synchronously with status: "done" and a URL. Video is queued and returns 202 with status: "queued" — poll the render, or wait for a webhook.

Parameters

templateId
stringoptional
The template to render. Required unless you supply an `edit` instead.
values
objectoptional
Field values keyed by field key. Validated against the template schema. Also accepted as `fields`, or as `merge` find/replace pairs.
edit
objectoptional
A complete edit spec (timeline + output), for rendering without a saved template. See the video guide.
output
objectoptional
Overrides the template’s output settings — format, resolution, size, fps, quality, codec, range, poster, thumbnail.
callback
string (URL)optional
Notified when the render settles. Overrides the workspace default.
idempotencyKey
stringoptional
Alternative to the Idempotency-Key header.

Query parameters

format
stringoptional
Set to binary to receive raw image bytes instead of JSON. Still images only.
type
stringoptional
Shorthand output format: png, jpeg, pdf, mp4, gif.
scale
integeroptional
Device scale factor for stills, 1–3.Default: 1
resolution
stringoptional
preview, mobile, sd, hd, fhd or 4k.
fps
integeroptional
Frame rate for video output.
curl -X POST https://pixbix.app/api/v1/render \
  -H "x-api-key: pk_live_…" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: order-4471-card" \
  -d '{
    "templateId": "sale-card-square",
    "values": {
      "product": "https://cdn.shop.com/sku-4471.jpg",
      "title": "Cotton Kurta Set",
      "price": "₹1,249",
      "badge": "50% OFF"
    },
    "output": { "format": "png", "scale": 2 }
  }'

Retrieve a render

GET/v1/render/{id}API keyrenders:read
Current state of a render. Poll this for video, or rely on a webhook and skip polling entirely.
statusMeaning
queuedAccepted, waiting for a worker
fetchingDownloading and probing assets
renderingCompositing and encoding
savingGenerating poster/thumbnail and uploading
doneFinished — `url` is populated
failedFailed — `error` explains why; credits were refunded
cancelledCancelled by you; credits were refunded
Poll no more than once every 2 seconds. A typical 15-second 1080p video settles in well under two minutes, and webhooks remove the need to poll at all.

List renders

GET/v1/renderAPI keyrenders:read
Render history for the workspace, newest first.

Query parameters

status
stringoptional
Filter by render status.
kind
stringoptional
image or video.
templateId
stringoptional
Only renders from this template.
page
integeroptional
Page number.Default: 1
limit
integeroptional
Items per page, max 100.Default: 20

Cancel a render

POST/v1/render/{id}/cancelAPI keyrenders:write
Stops a queued or in-flight render and refunds its credits in full. Returns 409 if it has already settled. A render already being encoded stops at the next stage boundary rather than instantly.

Estimate cost

POST/v1/render/estimateAPI keyrenders:read
Prices a render without running it. Same body as create. Costs nothing and consumes no credits.
Response
{
  "success": true,
  "data": {
    "kind": "video",
    "credits": 15,
    "durationSec": 15,
    "size": { "width": 1080, "height": 1920 },
    "breakdown": {
      "duration": "15.00s",
      "size": "1080×1920",
      "fps": 30,
      "format": "mp4",
      "codec": "h264"
    }
  }
}

Templates#

List templates

GET/v1/templatesAPI keytemplates:read
Your workspace's templates, plus the shared public library. Only your own published templates can be rendered — check canRender on each row.

Query parameters

scope
stringoptional
mine for your own, library for public only. Omit for both.
status
stringoptional
draft, published or archived. Applies to your own templates only.
kind
stringoptional
image or video.
categoryId
stringoptional
Filter by category.
search
stringoptional
Match name, tags and description.
sort
stringoptional
popular, newest, oldest, updated or name.Default: popular

Notable response fields

canRender
booleanoptional
A render naming this template would be accepted. False for every library template you have not copied.
isOwn
booleanoptional
Owned by your workspace.
inLibrary
booleanoptional
Listed in the shared library, so anyone may copy it.
status
stringoptional
draft, published or archived.
visibility
stringoptional
private or public.

Copy a template into your workspace

POST/v1/templates/{id}/duplicateAPI keytemplates:write
The only route from the public library to a render. Takes a copy of a template — one from the library, or one of your own — into your workspace, where you own it and can render it.

The copy arrives as a private draftwith its usage, version and review history cleared. Publish it before rendering. It counts against your plan’s template allowance like any other template you own.

Response
{
  "success": true,
  "message": "Added to your templates. Publish it when you are ready to generate from it.",
  "data": {
    "id": "tpl_9c41b2",
    "name": "Diwali sale story",
    "status": "draft",
    "visibility": "private",
    "version": 1
  }
}

Retrieve a template schema

GET/v1/templates/{id}API keytemplates:read
The field schema for a template — what to send in `values`. Read this once and cache it; the `version` tells you when it changed.
Response
{
  "success": true,
  "data": {
    "id": "sale-card-square",
    "name": "Sale card — square",
    "kind": "image",
    "width": 1080,
    "height": 1080,
    "aspectRatio": "1:1",
    "version": 4,
    "fieldGroups": ["Content", "Branding"],
    "fields": [
      {
        "key": "title",
        "label": "Product name",
        "type": "text",
        "group": "Content",
        "required": true,
        "defaultValue": null,
        "placeholder": "Cotton Kurta Set",
        "config": { "maxLength": 48 }
      },
      {
        "key": "product",
        "label": "Product photo",
        "type": "image",
        "group": "Content",
        "required": true,
        "config": { "aspectRatio": "1:1" }
      },
      {
        "key": "badge",
        "label": "Offer badge",
        "type": "text",
        "group": "Content",
        "required": false,
        "config": { "maxLength": 12 }
      }
    ]
  }
}

Assets#

The media a template draws from. Rendered from the same catalogue as the image and video references, so the parameters here are the ones the runner sends.

List caption providers

GET/api/v1/captions/providersAPI keyintegrations:read
Every speech provider, with your workspace’s connection state on each, plus what the transcript cache has saved you so far. Start here — it tells you which provider will be used if you name none, and which ones can read video.
Response
{
  "success": true,
  "message": "2 caption providers connected",
  "data": {
    "providers": [
      {
        "slug": "deepgram",
        "name": "Deepgram",
        "connected": true,
        "account": "Acme Media",
        "recommended": true,
        "support": {
          "video": true,
          "autoLanguage": true,
          "multiLanguage": true,
          "diarize": true,
          "keywords": true,
          "profanityFilter": true,
          "maxBytes": 524288000
        }
      },
      {
        "slug": "aws-transcribe",
        "name": "Amazon Transcribe",
        "connected": true,
        "account": "acme-transcribe · ap-south-1",
        "recommended": false
      },
      {
        "slug": "google-speech",
        "name": "Google Speech-to-Text",
        "connected": false,
        "account": null,
        "recommended": false
      }
    ],
    "connected": 2,
    "cache": {
      "transcribed": 41,
      "reused": 386,
      "secondsSaved": 9142,
      "hitRate": 0.904
    }
  }
}

Try it

/api/v1/captions/providers
Show as cURL
curl -X GET "https://pixbix.app/api/v1/captions/providers" \
  -H "x-api-key: pk_live_your_key"

Get the default provider’s options

GET/api/v1/captions/optionsAPI keyintegrations:read
The languages, models and capabilities of whichever provider would be used if you named none. The call to make when you do not want to choose a provider at all — the language codes it returns are the ones your requests should send.
Response
{
  "success": true,
  "data": {
    "provider": "deepgram",
    "name": "Deepgram",
    "support": {
      "video": true,
      "autoLanguage": true,
      "multiLanguage": true,
      "diarize": true,
      "keywords": true,
      "profanityFilter": true,
      "maxBytes": 524288000
    },
    "languages": [
      {
        "code": "auto",
        "label": "Detect automatically"
      },
      {
        "code": "en-IN",
        "label": "English (India)"
      }
    ],
    "models": [
      {
        "id": "nova-3",
        "label": "Nova 3",
        "hint": "The default. Best accuracy, real-world audio, and the fastest of the three."
      }
    ]
  }
}

Try it

/api/v1/captions/options
Show as cURL
curl -X GET "https://pixbix.app/api/v1/captions/options" \
  -H "x-api-key: pk_live_your_key"

Get a provider’s options

GET/api/v1/captions/options/:providerAPI keyintegrations:read
The languages, models and capabilities of one provider. Worth reading rather than assuming: the three providers do not use the same language codes — Deepgram takes `hi`, Amazon insists on `hi-IN`. Omit the provider to get the one that would be chosen for you.

Path parameters

provider
stringoptional
One of `deepgram`, `aws-transcribe`, `google-speech`. Omit the segment entirely for the provider that would be used by default.
Response
{
  "success": true,
  "data": {
    "provider": "deepgram",
    "name": "Deepgram",
    "support": {
      "video": true,
      "autoLanguage": true,
      "multiLanguage": true,
      "diarize": true,
      "keywords": true,
      "profanityFilter": true,
      "maxBytes": 524288000
    },
    "languages": [
      {
        "code": "auto",
        "label": "Detect automatically"
      },
      {
        "code": "en-IN",
        "label": "English (India)"
      }
    ],
    "models": [
      {
        "id": "nova-3",
        "label": "Nova 3",
        "hint": "The default. Best accuracy, real-world audio, and the fastest of the three."
      }
    ]
  }
}

Try it

/api/v1/captions/options/deepgram
Show as cURL
curl -X GET "https://pixbix.app/api/v1/captions/options/deepgram" \
  -H "x-api-key: pk_live_your_key"

Generate captions

POST/api/v1/captionsAPI keyassets:write
Transcribes a recording — audio or video — into word-timed cues, and writes WebVTT and SRT to your library. Returns 202 with a job to poll, or 200 with the finished transcription when the same recording has been transcribed before, in which case nothing is billed. `POST /v1/transcriptions` is the same endpoint under its older name.

Cost No render credits. The provider bills your own account per audio-minute — and not at all on a cache hit.

Body parameters

audioUrl
stringoptional
URL of the recording. Audio or video. One of this, `assetId` or `renderId` is required.
assetId
stringoptional
An audio or video asset in your library, instead of a URL. The fastest route to a cache hit — the library already knows the file’s checksum, so a repeat needs no download at all.
renderId
stringoptional
A finished render, instead of a URL — transcribes its audio track.
provider
stringoptional
Which speech provider. Omit it and the best connected one is chosen — Deepgram, then Amazon, then Google. Naming a provider that cannot read your file returns 422 saying which ones can.
languageCode
stringoptional
BCP-47 tag, or `auto` to detect. Codes differ per provider — see `/v1/captions/options`.
alternativeLanguageCodes
string[]optional
Other languages the speaker may switch to mid-recording.
model
stringoptional
Provider model id. Omit it for the provider’s own default.
diarize
booleanoptional
Label who is speaking. Deepgram and Amazon only.
keywords
string[]optional
Names, jargon and product words to bias the model towards. Deepgram only.
wordsPerCue
numberoptional
Words per caption. Changing this on a repeat is free — the cache re-cuts the stored word timings rather than transcribing again.
charsPerCue
numberoptional
Characters per caption. The limit that actually governs on a phone. Defaults to 42.
maxCueSec
numberoptional
Never hold one caption longer than this. Defaults to 6.
punctuation
booleanoptional
Ask the recogniser to punctuate. Defaults to on — captions without it read badly, and sentence ends are what cues are broken on.
filterProfanity
booleanoptional
Mask profanity in the transcript. Deepgram and Google only.
cache
booleanoptional
Set false (or `"fresh"`) to force a new transcription. On by default: an identical recording with identical settings is answered from an earlier transcription for nothing.
save
booleanoptional
Set false to skip writing .vtt and .srt files to storage.
{
  "assetId": "ast_7f21",
  "provider": "deepgram",
  "languageCode": "en-IN",
  "wordsPerCue": 5,
  "diarize": false
}

Try it

/api/v1/captions
Show as cURL
curl -X POST "https://pixbix.app/api/v1/captions" \
  -H "x-api-key: pk_live_your_key" \
  -H "Content-Type: application/json" \
  -d '{ "assetId": "ast_7f21", "provider": "deepgram", "languageCode": "en-IN", "wordsPerCue": 5, "diarize": false }'

Generate captions (older path)

POST/api/v1/transcriptionsAPI keyassets:write
Identical to `POST /v1/captions` — same body, same defaults, same response. Kept because integrations already post here; new work should use `/v1/captions`, which is what the endpoint actually does now that it captions video as well as audio.

Cost No render credits. The provider bills your own account per audio-minute — and not at all on a cache hit.

Body parameters

audioUrl
stringoptional
URL of the recording. Audio or video. One of this, `assetId` or `renderId` is required.
assetId
stringoptional
An audio or video asset in your library, instead of a URL.
renderId
stringoptional
A finished render, instead of a URL.
provider
stringoptional
Which speech provider. Omit it and the best connected one is chosen.
languageCode
stringoptional
BCP-47 tag, or `auto` to detect.
wordsPerCue
numberoptional
Words per caption.
cache
booleanoptional
Set false to force a new transcription.
save
booleanoptional
Set false to skip writing .vtt and .srt files to storage.
{
  "audioUrl": "https://cdn.acme.com/voice.mp3",
  "languageCode": "en-IN",
  "wordsPerCue": 7
}

Try it

/api/v1/transcriptions
Show as cURL
curl -X POST "https://pixbix.app/api/v1/transcriptions" \
  -H "x-api-key: pk_live_your_key" \
  -H "Content-Type: application/json" \
  -d '{ "audioUrl": "https://cdn.acme.com/voice.mp3", "languageCode": "en-IN", "wordsPerCue": 7 }'

List transcriptions

GET/api/v1/transcriptionsAPI keyassets:read
Your workspace’s transcriptions, newest first.

Query parameters

limit
numberoptional
Rows to return, 1–100. Defaults to 20.
Response
{
  "success": true,
  "data": [
    {
      "id": "trs_31c8",
      "status": "done",
      "languageCode": "en-IN",
      "srtUrl": "https://cdn.pixbix.app/media/org_4c1d/caption/trs_31c8.srt"
    }
  ]
}

Try it

/api/v1/transcriptions?limit=20
Show as cURL
curl -X GET "https://pixbix.app/api/v1/transcriptions?limit=20" \
  -H "x-api-key: pk_live_your_key"

Get a transcription

GET/api/v1/transcriptions/:idAPI keyassets:read
One transcription, for polling. Carries the cues, the plain text and the caption file URLs once it is done.

Path parameters

id
stringrequired
Transcription id.
Response
{
  "success": true,
  "data": {
    "id": "trs_31c8",
    "status": "done",
    "text": "Diwali offers are live now.",
    "cues": [
      {
        "start": 0,
        "end": 2.4,
        "text": "Diwali offers are live now."
      }
    ],
    "vttUrl": "https://cdn.pixbix.app/media/org_4c1d/caption/trs_31c8.vtt",
    "srtUrl": "https://cdn.pixbix.app/media/org_4c1d/caption/trs_31c8.srt"
  }
}

Try it

/api/v1/transcriptions/trs_31c8
Show as cURL
curl -X GET "https://pixbix.app/api/v1/transcriptions/trs_31c8" \
  -H "x-api-key: pk_live_your_key"

List assets

GET/api/v1/assetsAPI keyassets:read
Media in your workspace library, most recently uploaded first. Set `scope=global` to browse the pixbix stock library instead — the same media the editors show under “Other assets” — or `scope=all` for both at once.

Query parameters

page
numberoptional
Page number, from 1.
limit
numberoptional
Rows per page, 1–100. Defaults to 24.
sort
stringoptional
Column to order by: createdAt, name, sizeBytes, type, durationSec. Defaults to `createdAt`.
order
"asc" | "desc"optional
Sort direction. Defaults to `desc` for dates and counts.
search
stringoptional
Case-insensitive match across name, description, tags. Up to 128 characters.
scope
stringoptional
`workspace` (default) for your own uploads, `global` for the pixbix stock library, `all` for both. The stock library is read-only.
type
stringoptional
Filter by media type: `image`, `video`, `audio`, `font`, `lottie` or `svg`. Comma-separate several to match any of them.
status
stringoptional
`ready`, `processing`, `uploading` or `failed`. Only `ready` assets can be rendered with.
folder
stringoptional
Only assets filed in this collection.
tags
stringoptional
One or more tags, comma-separated.
Response
{
  "success": true,
  "data": [
    {
      "id": "ast_19fa",
      "type": "image",
      "scope": "workspace",
      "name": "lamp.jpg",
      "description": null,
      "url": "https://cdn.pixbix.app/assets/ast_19fa.jpg",
      "mimeType": "image/jpeg",
      "sizeBytes": 184320,
      "width": 1200,
      "height": 1200,
      "durationSec": null,
      "tags": [
        "product",
        "lighting"
      ]
    }
  ]
}

Try it

/api/v1/assets?page=1&limit=24&sort=createdAt&order=desc&search=diwali&scope=workspace&type=image&status=ready&folder=Backgrounds&tags=harbour%2Cdusk
Show as cURL
curl -X GET "https://pixbix.app/api/v1/assets?page=1&limit=24&sort=createdAt&order=desc&search=diwali&scope=workspace&type=image&status=ready&folder=Backgrounds&tags=harbour%2Cdusk" \
  -H "x-api-key: pk_live_your_key"

Upload an asset

POST/api/v1/assetsAPI keyassets:write
multipart/form-data with a single `file` part. Dimensions and duration are probed on upload. Re-uploading identical bytes returns the existing asset rather than storing it twice.

Cost Counts against your plan’s storage, not against credits.

Body parameters

file
filerequired
The asset itself. Max 200 MB by default.
tags
stringoptional
Comma-separated keywords, up to 20. Searchable.
description
stringoptional
Free text, up to 2000 characters. Searchable.
folder
stringoptional
Collection to file the asset under.
Response
{
  "success": true,
  "data": {
    "id": "ast_19fa",
    "url": "https://cdn.pixbix.app/media/org_4c1d/image/ast_19fa.jpg"
  }
}

This endpoint takes multipart/form-data, which the JSON runner below cannot construct.

Delete an asset

DELETE/api/v1/assets/:idAPI keyassets:write
Removes the asset. Templates already referencing it keep rendering from the cached copy.

Path parameters

id
stringrequired
Asset id.
Response
{
  "success": true,
  "message": "Asset deleted"
}

Deleting is destructive — run it against an asset id you own from your own client.

Integrations#

Read and write the third-party accounts a workspace has connected. Connecting an account happens once, in the dashboard under Integrations — OAuth needs a person at a consent screen, so there is no API endpoint for it. Everything below drives a connection somebody has already made.

Which integrations a workspace may connect is set by its plan. A call to a provider the plan does not include returns 402 with LIMIT_INTEGRATION_NOT_ALLOWED.

Two failures are worth handling separately. INTEGRATION_NOT_CONNECTED means nobody has connected that provider yet. INTEGRATION_REAUTH_REQUIRED means somebody did, and the grant has since lapsed — a customer revoked access at Google, or changed their password. Only the second one means a job that used to work has just stopped, and it is the one worth alerting on.

Sheets and Drive are one integration — google-workspace — behind a single Google connection, so a workspace that has connected Google can call both. The paths are namespaced by product under the connection that owns the grant.

ProductPath prefixWhat it does
Sheets/v1/integrations/google-workspace/sheetsRead rows to drive a batch of renders, and write the finished URLs back beside them.
Drive/v1/integrations/google-workspace/driveImport source assets into the media library, and deliver finished renders into a folder.
ElevenLabs/v1/integrations/elevenlabsGenerate a voice-over from a script, saved to your media library as an MP3.
MotherBot/v1/integrations/motherbotDeliver a finished render to WhatsApp — one personalised video per contact, or one broadcast to many.
Google Speech-to-Text/v1/integrations/google-speechTranscribe a voice-over into word-timed captions, as engine cues and as WebVTT and SRT files.
ElevenLabs is connected with your own API key rather than by signing in, so synthesis is billed in characters to your ElevenLabs plan and costs no pixbix render credits. Pair a generated voice-over with a caption script and the engine times the words against the voice — see the video guide.
Captions come back in two forms because they are wanted in two places. cuesis shaped exactly like the engine’s caption asset, carrying per-word timings, so it drops onto a timeline and drives the karaoke highlight to the syllable. The .vtt and .srtfiles are what a player’s subtitle track and a social upload take. Google bills your own Cloud project per audio-minute; pixbix charges no render credits. A finished video cannot be transcribed directly — caption the voice-over that went into it.
MotherBot is connected with your own API key too, so messages go out from your WhatsApp Business number and are billed as conversations on your MotherBot plan — pixbix charges no render credits for delivery. WhatsApp only lets a business open a conversation with a template Meta has approved, and only a template whose header is VIDEO can carry a video: list your templates first and read headerFormat.

List connected accounts

GET/api/v1/integrationsAPI keyintegrations:read
Every third-party account this workspace has connected, with the status of each grant. Poll this to notice a connection that has expired before a nightly job discovers it the hard way.
Response
{
  "success": true,
  "data": [
    {
      "id": "int_7f21",
      "provider": "google-workspace",
      "status": "connected",
      "account": {
        "email": "ops@acme.in",
        "name": "Acme Ops"
      },
      "connectedAt": "2026-08-01T09:12:44.000Z"
    }
  ]
}

Try it

/api/v1/integrations
Show as cURL
curl -X GET "https://pixbix.app/api/v1/integrations" \
  -H "x-api-key: pk_live_your_key"

List spreadsheets

GET/api/v1/integrations/google-workspace/sheets/spreadsheetsAPI keyintegrations:read
Spreadsheets visible to the connected Google account, most recently modified first. Use it to resolve a name to the id every other Sheets call needs.

Query parameters

search
stringoptional
Filter by name, substring match.
limit
numberoptional
1–100. Defaults to 25.
pageToken
stringoptional
Cursor from a previous response’s `nextPageToken`.
Response
{
  "success": true,
  "data": {
    "files": [
      {
        "id": "1BxiMVs0XRA5nFMdKvBdBZjgmUUqptlbs74OgvE2upms",
        "name": "Product catalogue",
        "url": "https://docs.google.com/spreadsheets/d/1BxiMVs0.../edit",
        "modifiedAt": "2026-08-12T04:31:00.000Z"
      }
    ],
    "nextPageToken": null
  }
}

Try it

/api/v1/integrations/google-workspace/sheets/spreadsheets?search=products&limit=25
Show as cURL
curl -X GET "https://pixbix.app/api/v1/integrations/google-workspace/sheets/spreadsheets?search=products&limit=25" \
  -H "x-api-key: pk_live_your_key"

Get spreadsheet tabs

GET/api/v1/integrations/google-workspace/sheets/spreadsheets/:spreadsheetIdAPI keyintegrations:read
A spreadsheet’s tabs and their dimensions. Metadata only — no cell values — so it is cheap enough to call before deciding on a range.

Path parameters

spreadsheetId
stringrequired
Spreadsheet id, the long string in its Google URL.
Response
{
  "success": true,
  "data": {
    "id": "1BxiMVs0XRA5nFMdKvBdBZjgmUUqptlbs74OgvE2upms",
    "name": "Product catalogue",
    "sheets": [
      {
        "id": 0,
        "title": "Products",
        "index": 0,
        "rowCount": 1000,
        "columnCount": 12
      }
    ]
  }
}

Try it

/api/v1/integrations/google-workspace/sheets/spreadsheets/1BxiMVs0XRA5nFMdKvBdBZjgmUUqptlbs74OgvE2upms
Show as cURL
curl -X GET "https://pixbix.app/api/v1/integrations/google-workspace/sheets/spreadsheets/1BxiMVs0XRA5nFMdKvBdBZjgmUUqptlbs74OgvE2upms" \
  -H "x-api-key: pk_live_your_key"

Read rows

GET/api/v1/integrations/google-workspace/sheets/spreadsheets/:spreadsheetId/valuesAPI keyintegrations:read
Read a range. By default the first row is treated as a header and each row comes back as an object keyed by it, which is the shape a render loop wants; pass `as=values` for the raw grid instead.

Path parameters

spreadsheetId
stringrequired
Spreadsheet id.

Query parameters

range
stringoptional
A1 notation. A bare tab name reads that whole tab. Defaults to `Sheet1`.
as
"records" | "values"optional
`records` keys each row by the header row; `values` returns the raw grid.
renderOption
"FORMATTED_VALUE" | "UNFORMATTED_VALUE" | "FORMULA"optional
How cells are rendered. Defaults to the formatted value, as displayed in the sheet.
Response
{
  "success": true,
  "data": {
    "range": "Products!A1:F51",
    "headers": [
      "sku",
      "name",
      "price",
      "image"
    ],
    "records": [
      {
        "sku": "AC-100",
        "name": "Copper bottle",
        "price": "₹899",
        "image": "https://cdn.acme.in/ac-100.jpg"
      }
    ]
  }
}

Try it

/api/v1/integrations/google-workspace/sheets/spreadsheets/1BxiMVs0XRA5nFMdKvBdBZjgmUUqptlbs74OgvE2upms/values?range=Products%21A1%3AF&as=records&renderOption=FORMATTED_VALUE
Show as cURL
curl -X GET "https://pixbix.app/api/v1/integrations/google-workspace/sheets/spreadsheets/1BxiMVs0XRA5nFMdKvBdBZjgmUUqptlbs74OgvE2upms/values?range=Products%21A1%3AF&as=records&renderOption=FORMATTED_VALUE" \
  -H "x-api-key: pk_live_your_key"

Write rows

POST/api/v1/integrations/google-workspace/sheets/spreadsheets/:spreadsheetId/valuesAPI keyintegrations:write
Append rows after the last used row, or overwrite a range outright. Appending is the default because it cannot destroy a cell that was already there — set `mode: "overwrite"` deliberately.

Path parameters

spreadsheetId
stringrequired
Spreadsheet id.

Body parameters

range
stringrequired
A1 notation, e.g. `Products!A1`.
values
array[array]required
Rows, each an array of cell values.
mode
"append" | "overwrite"optional
Defaults to `append`, which inserts new rows rather than writing over what follows the range.
raw
booleanoptional
Store values verbatim instead of interpreting them as a person typing would — set it for codes that look like dates.
{
  "range": "Renders!A1",
  "values": [
    [
      "AC-100",
      "https://cdn.pixbix.app/renders/ac-100.png",
      "2026-08-15"
    ],
    [
      "AC-101",
      "https://cdn.pixbix.app/renders/ac-101.png",
      "2026-08-15"
    ]
  ],
  "mode": "append"
}

This writes to a real spreadsheet in your Google account. Run it from your own client, against a sheet you are happy to change.

Create a spreadsheet

POST/api/v1/integrations/google-workspace/sheets/spreadsheetsAPI keyintegrations:write
Create a spreadsheet in the connected account, optionally seeded with a header row — useful as the destination for a batch of render URLs.

Body parameters

title
stringrequired
Name of the new spreadsheet.
sheetTitle
stringoptional
Name of its first tab. Defaults to `Sheet1`.
headers
array[string]optional
Written into the first row.
{
  "title": "August campaign renders",
  "sheetTitle": "Renders",
  "headers": [
    "sku",
    "render url",
    "created"
  ]
}

This creates a real file in your Google account.

List Drive files

GET/api/v1/integrations/google-workspace/drive/filesAPI keyintegrations:read
Files and folders visible to the connected account, folders first. Visibility is limited to files pixbix created or that you explicitly opened with it — not your whole Drive.

Query parameters

folderId
stringoptional
List inside one folder. Omit for the top level.
search
stringoptional
Filter by name, substring match.
kind
stringoptional
`image`, `video`, `audio`, `font`, `folder`, or a full MIME type.
limit
numberoptional
1–200. Defaults to 50.
pageToken
stringoptional
Cursor from a previous response’s `nextPageToken`.
Response
{
  "success": true,
  "data": {
    "files": [
      {
        "id": "1a2B3c4D5e6F",
        "name": "brand-logo.png",
        "mimeType": "image/png",
        "sizeBytes": 48211,
        "isFolder": false,
        "webViewUrl": "https://drive.google.com/file/d/1a2B3c4D5e6F/view"
      }
    ],
    "nextPageToken": null
  }
}

Try it

/api/v1/integrations/google-workspace/drive/files?search=logo&kind=image&limit=50
Show as cURL
curl -X GET "https://pixbix.app/api/v1/integrations/google-workspace/drive/files?search=logo&kind=image&limit=50" \
  -H "x-api-key: pk_live_your_key"

Upload to Drive

POST/api/v1/integrations/google-workspace/drive/filesAPI keyintegrations:write
Upload any public URL into a Drive folder — normally a finished render’s `url`. The bytes travel from our storage to Google directly, so the file never passes through your machine.

Body parameters

url
stringrequired
Public http(s) URL to upload.
name
stringoptional
Filename in Drive. Defaults to the last path segment of the URL.
folderId
stringoptional
Destination folder. Omit for the account’s root.
mimeType
stringoptional
Overrides the content type reported by the source URL.
{
  "url": "https://cdn.pixbix.app/renders/ac-100.png",
  "name": "AC-100 poster.png",
  "folderId": "1QfolderIdFromDrive"
}

This writes a real file into your Google Drive.

Create a Drive folder

POST/api/v1/integrations/google-workspace/drive/foldersAPI keyintegrations:write
Create a folder to deliver output into — one per campaign or per client, created as part of the job rather than by hand.

Body parameters

name
stringrequired
Folder name.
parentId
stringoptional
Parent folder. Omit for the account’s root.
{
  "name": "August campaign"
}

This creates a real folder in your Google Drive.

Read Google Contacts

GET/api/v1/integrations/google-workspace/contactsAPI keyintegrations:read
The connected account's address book, one flat object per person. The People API returns every field as an array of objects with metadata about which entry is primary; this lifts the primary value of each to a top-level key, so a contact drops straight into a template as {{item.firstName}} without any reshaping. Read-only — pixbix never writes to your contacts. Pass `search` to look someone up, or page with `pageToken` to walk the whole book.

Query parameters

search
stringoptional
Match against name, email or phone. Omit to page through everyone.
limit
numberoptional
Contacts per page, up to 1000.
pageToken
stringoptional
From the previous response, to fetch the next page.
Response
{
  "success": true,
  "message": "Contacts",
  "data": {
    "contacts": [
      {
        "id": "people/c1234567890",
        "displayName": "Priya Sharma",
        "firstName": "Priya",
        "lastName": "Sharma",
        "email": "priya@acme.in",
        "allEmails": "priya@acme.in, priya.sharma@gmail.com",
        "phone": "+91 98765 43210",
        "allPhones": "+91 98765 43210",
        "organization": "Acme Retail",
        "jobTitle": "Head of Marketing",
        "city": "Mumbai",
        "country": "India",
        "photoUrl": "https://lh3.googleusercontent.com/…",
        "notes": "",
        "groupIds": [
          "contactGroups/myContacts"
        ]
      }
    ],
    "nextPageToken": "CJHm2wIQAQ",
    "total": 412
  }
}

Try it

/api/v1/integrations/google-workspace/contacts?search=priya&limit=50
Show as cURL
curl -X GET "https://pixbix.app/api/v1/integrations/google-workspace/contacts?search=priya&limit=50" \
  -H "x-api-key: pk_live_your_key"

List contact labels

GET/api/v1/integrations/google-workspace/contacts/groupsAPI keyintegrations:read
The labels the connected account keeps in Google Contacts, largest first, each with its member count. These are what a workflow points at to narrow a run to "Clients" rather than everyone. Empty labels are omitted, since targeting one would produce a run with nothing in it.
Response
{
  "success": true,
  "message": "3 label(s)",
  "data": [
    {
      "id": "contactGroups/myContacts",
      "name": "My Contacts",
      "count": 412
    },
    {
      "id": "contactGroups/7a3f1c2e",
      "name": "Clients",
      "count": 86
    },
    {
      "id": "contactGroups/starred",
      "name": "Starred",
      "count": 12
    }
  ]
}

Try it

/api/v1/integrations/google-workspace/contacts/groups
Show as cURL
curl -X GET "https://pixbix.app/api/v1/integrations/google-workspace/contacts/groups" \
  -H "x-api-key: pk_live_your_key"

Import a Drive file

POST/api/v1/integrations/google-workspace/drive/files/:fileId/importAPI keyassets:write
Copy a Drive file into your pixbix media library, where templates can reference it. Goes through the same path as an upload, so it deduplicates by content and counts against your storage allowance. Google-native documents (Docs, Sheets, Slides) are refused — export them first.

Path parameters

fileId
stringrequired
Drive file id.

Body parameters

folder
stringoptional
Media-library folder to file it under.
tags
stringoptional
Comma-separated tags.
{
  "folder": "brand",
  "tags": "logo,brand"
}

Importing consumes your storage allowance — run it from your own client.

List voices

GET/api/v1/integrations/elevenlabs/voicesAPI keyintegrations:read
Voices available to the connected ElevenLabs account, including your own clones. Use a voice id with the speech endpoint.
Response
{
  "success": true,
  "data": [
    {
      "id": "21m00Tcm4TlvDq8ikWAM",
      "name": "Rachel",
      "category": "premade",
      "previewUrl": "https://storage.googleapis.com/eleven-public-prod/…/sample.mp3",
      "labels": {
        "accent": "american",
        "gender": "female"
      }
    }
  ]
}

Try it

/api/v1/integrations/elevenlabs/voices
Show as cURL
curl -X GET "https://pixbix.app/api/v1/integrations/elevenlabs/voices" \
  -H "x-api-key: pk_live_your_key"

List speech models

GET/api/v1/integrations/elevenlabs/modelsAPI keyintegrations:read
Speech models the connected plan can use, with the languages each supports and its per-request character ceiling. Voice-changer models are filtered out — only models that synthesise from text are returned.
Response
{
  "success": true,
  "data": [
    {
      "id": "eleven_multilingual_v2",
      "name": "Eleven Multilingual v2",
      "languages": [
        {
          "id": "en",
          "name": "English"
        },
        {
          "id": "hi",
          "name": "Hindi"
        }
      ],
      "maxCharacters": 5000
    }
  ]
}

Try it

/api/v1/integrations/elevenlabs/models
Show as cURL
curl -X GET "https://pixbix.app/api/v1/integrations/elevenlabs/models" \
  -H "x-api-key: pk_live_your_key"

Check character allowance

GET/api/v1/integrations/elevenlabs/usageAPI keyintegrations:read
Characters used and remaining on the connected ElevenLabs plan, and when the allowance resets. Check this before a long batch — synthesis is billed by ElevenLabs, not by pixbix.
Response
{
  "success": true,
  "data": {
    "tier": "creator",
    "characterCount": 41200,
    "characterLimit": 100000,
    "charactersRemaining": 58800,
    "resetsAt": "2026-09-01T00:00:00.000Z"
  }
}

Try it

/api/v1/integrations/elevenlabs/usage
Show as cURL
curl -X GET "https://pixbix.app/api/v1/integrations/elevenlabs/usage" \
  -H "x-api-key: pk_live_your_key"

Generate a voice-over

POST/api/v1/integrations/elevenlabs/speechAPI keyassets:write
Synthesises a script into an MP3 and saves it to your media library, returning the asset. Drop the resulting URL onto a timeline as an audio clip — and pass the same script as a caption `script` with the clip as its `audio`, and the engine times the captions against the voice for you. Costs characters on your ElevenLabs plan; pixbix charges no render credits.

Cost No pixbix credits. Consumes characters from your own ElevenLabs plan, and storage from your pixbix quota.

Body parameters

text
stringrequired
The script to speak. Up to 5,000 characters per request.
voiceId
stringrequired
From the voices endpoint.
modelId
stringoptional
Defaults to eleven_multilingual_v2.
stability
numberoptional
0–1. Low is expressive and varies between takes; high is consistent and flatter.
similarityBoost
numberoptional
0–1. How closely the output holds to the original voice.
style
numberoptional
0–1. Delivery emphasis. Adds latency above 0.
name
stringoptional
Filename in the media library. Defaults to the opening words of the script.
folder
stringoptional
Media-library folder to file it under.
{
  "text": "Diwali sale — fifty percent off, this week only.",
  "voiceId": "21m00Tcm4TlvDq8ikWAM",
  "modelId": "eleven_multilingual_v2",
  "name": "diwali-vo.mp3",
  "folder": "voice-overs"
}

Generating speech spends characters from your ElevenLabs plan and storage from your quota, so it is not fired from the docs.

List caption languages

GET/api/v1/integrations/google-speech/languagesAPI keyintegrations:read
The languages the caption pickers offer, as BCP-47 codes. A curated list rather than Google’s full set — any code Google accepts still works when passed to the captions endpoint.
Response
{
  "success": true,
  "data": [
    {
      "code": "en-IN",
      "label": "English (India)"
    },
    {
      "code": "hi-IN",
      "label": "Hindi"
    }
  ]
}

Try it

/api/v1/integrations/google-speech/languages
Show as cURL
curl -X GET "https://pixbix.app/api/v1/integrations/google-speech/languages" \
  -H "x-api-key: pk_live_your_key"

Generate captions

POST/api/v1/integrations/google-speech/captionsAPI keyassets:write
Transcribes a voice-over into captions timed to the word. Returns cues shaped exactly like the engine’s `caption` asset — drop them straight onto a timeline for burned-in karaoke captions — plus finished WebVTT and SRT text, saved to your media library unless you pass `save: false`. Name the audio however you have it: `audioUrl`, an `assetId` from your library, or a finished `renderId`.

Cost No pixbix credits. Google bills your own Cloud project per audio-minute, and the two subtitle files count against your storage quota.

Body parameters

audioUrl
stringoptional
A public MP3, WAV, FLAC or OGG. A video file is refused — transcribe the voice-over that went into it.
assetId
stringoptional
An audio asset in your media library, instead of a URL.
renderId
stringoptional
A finished audio render, instead of a URL.
languageCode
stringoptional
BCP-47. Defaults to en-US. Getting this wrong returns confident nonsense rather than an error.
alternativeLanguageCodes
arrayoptional
Up to three more languages Google may pick from — worth setting for audio that switches mid-sentence.
wordsPerCue
numberoptional
1–12, default 4. Captions also break at a full stop and at any pause over a second.
punctuation
booleanoptional
Infer full stops and commas. On by default; captions read badly without them.
filterProfanity
booleanoptional
Mask strong language. Off by default — Google’s filter is heavy-handed.
save
booleanoptional
Write the .vtt and .srt to your media library. On by default; pass false for the cues alone.
name
stringoptional
Base filename for the saved files. Defaults to the audio’s own name.
{
  "assetId": "ast_4b7c",
  "languageCode": "en-IN",
  "wordsPerCue": 4
}

Transcription is billed by audio-minute to your own Google Cloud project and writes two files to your library, so it is not fired from the docs.

List messaging channels

GET/api/v1/integrations/motherbot/channelsAPI keyintegrations:read
Every sending identity the connected MotherBot workspace has — WhatsApp numbers, SMS and email senders, and the chat channels — each with what it can actually carry. Read this first: `capabilities` is derived from the account itself, so it is the honest answer to “can I send buttons on this”, and `id` is what to pass as `accountId` when sending.

Query parameters

channel
stringoptional
Restrict to one channel: whatsapp, sms, rcs, email, telegram, line, viber, messenger, instagram or webchat.
status
stringoptional
Defaults to connected.
Response
{
  "success": true,
  "data": [
    {
      "id": "66f1c2a0d3b4e5f6a7b8c9d0",
      "channel": "whatsapp",
      "provider": "meta",
      "label": "Acme Retail",
      "identifier": "919876543210",
      "isDefault": true,
      "status": "connected",
      "capabilities": {
        "template": true,
        "text": true,
        "media": [
          "image",
          "video",
          "document",
          "audio"
        ],
        "interactive": [
          "button",
          "list",
          "cta_url"
        ]
      }
    }
  ]
}

Try it

/api/v1/integrations/motherbot/channels?channel=whatsapp&status=connected
Show as cURL
curl -X GET "https://pixbix.app/api/v1/integrations/motherbot/channels?channel=whatsapp&status=connected" \
  -H "x-api-key: pk_live_your_key"

List WhatsApp templates

GET/api/v1/integrations/motherbot/templatesAPI keyintegrations:read
The WhatsApp templates approved on the connected MotherBot account. `headerFormat` is the field that matters: only a template whose header is `VIDEO` can deliver a rendered video, and `bodyVariableCount` is how many `{{n}}` slots you must fill. Approved templates only unless you pass `status`.

Query parameters

status
stringoptional
APPROVED (default), PENDING, REJECTED, DISABLED or DRAFT.
category
stringoptional
MARKETING, UTILITY or AUTHENTICATION.
language
stringoptional
Language code, e.g. en_US or hi.
search
stringoptional
Partial name match.
accountId
stringoptional
Only templates approved on this WhatsApp number. Templates are approved per number, so a send from one number cannot use another’s.
limit
numberoptional
Up to 100 per page.
Response
{
  "success": true,
  "data": [
    {
      "id": "66f1c2a0d3b4e5f6a7b8c9d0",
      "accountId": "66f1c2a0d3b4e5f6a7b8c9d0",
      "name": "order_ready_video",
      "category": "MARKETING",
      "language": "en",
      "status": "APPROVED",
      "headerFormat": "VIDEO",
      "bodyVariableCount": 2,
      "variables": [
        "name",
        "order_id"
      ],
      "usageCount": 1284
    }
  ]
}

Try it

/api/v1/integrations/motherbot/templates?status=APPROVED&category=MARKETING&language=en&search=order&accountId=66f1c2a0d3b4e5f6a7b8c9d0&limit=100
Show as cURL
curl -X GET "https://pixbix.app/api/v1/integrations/motherbot/templates?status=APPROVED&category=MARKETING&language=en&search=order&accountId=66f1c2a0d3b4e5f6a7b8c9d0&limit=100" \
  -H "x-api-key: pk_live_your_key"

List WhatsApp contacts

GET/api/v1/integrations/motherbot/contactsAPI keyintegrations:read
The contact list on the connected MotherBot account, filterable by tag. `waId` is the number in WhatsApp’s own form, which is what the send endpoint wants. Contacts who opted out or are blocked are returned with those flags set — do not message them.

Query parameters

tag
stringoptional
Only contacts carrying this tag.
search
stringoptional
Partial name or number match.
limit
numberoptional
Up to 100 per page.
page
numberoptional
1-based page number.
Response
{
  "success": true,
  "data": {
    "contacts": [
      {
        "id": "66f1c2a0d3b4e5f6a7b8c9d1",
        "waId": "919876543210",
        "name": "Priya Sharma",
        "firstName": "Priya",
        "lastName": "Sharma",
        "email": "priya@acme.in",
        "tags": [
          "leads",
          "diwali-2026"
        ],
        "optedIn": true,
        "blocked": false
      }
    ],
    "total": 412,
    "page": 1,
    "pages": 5
  }
}

Try it

/api/v1/integrations/motherbot/contacts?tag=leads&search=priya&limit=100&page=1
Show as cURL
curl -X GET "https://pixbix.app/api/v1/integrations/motherbot/contacts?tag=leads&search=priya&limit=100&page=1" \
  -H "x-api-key: pk_live_your_key"

Check WhatsApp allowance

GET/api/v1/integrations/motherbot/usageAPI keyintegrations:read
The plan, the per-minute rate limit for your key and every quota on the connected MotherBot account, including messages left this month. Read this before a batch — sends are billed by MotherBot, not by pixbix, and a run that discovers the ceiling halfway through has already spent the renders.
Response
{
  "success": true,
  "data": {
    "plan": "growth",
    "subscriptionStatus": "active",
    "active": true,
    "rateLimitPerMinute": 300,
    "messages": {
      "key": "messagesPerMonth",
      "label": "Messages",
      "used": 8420,
      "limit": 50000,
      "unlimited": false,
      "remaining": 41580,
      "percentUsed": 17
    }
  }
}

Try it

/api/v1/integrations/motherbot/usage
Show as cURL
curl -X GET "https://pixbix.app/api/v1/integrations/motherbot/usage" \
  -H "x-api-key: pk_live_your_key"

Send a render on WhatsApp

POST/api/v1/integrations/motherbot/messagesAPI keyintegrations:write
Sends one approved template to one number from your WhatsApp Business number, with a render in the template’s media header. Pass `renderId` and the finished file’s URL is looked up for you — no polling, no second call. WhatsApp only lets a business open a conversation with an approved template; `type: "text"` is accepted and reaches only contacts who messaged you in the last 24 hours.

Cost No pixbix credits. Consumes a conversation on your own MotherBot plan.

Body parameters

to
stringrequired
How the chosen channel addresses somebody: a phone number for WhatsApp, SMS and RCS (international form, digits only — a leading zero, spaces and a “+” are cleaned up, and a number with no country code is refused rather than guessed at), an email address for email, or the platform id for a chat channel.
channel
stringoptional
Defaults to whatsapp — the only channel that can open a conversation. A type the channel cannot carry is refused rather than silently downgraded.
accountId
stringoptional
Which connected identity to send from, from the channels endpoint. Defaults to the workspace’s default for the channel.
type
stringoptional
template (default), text, image, video, document, audio or interactive.
templateName
stringrequired
An approved template. Required for template sends.
languageCode
stringoptional
Only needed when one template name is approved in several languages.
renderId
stringoptional
A finished render in this workspace; its URL goes in the template’s media header.
mediaUrl
stringoptional
Any public URL, if you would rather name the file directly — a render, a capture, or a file you host. Takes precedence over renderId. Its type must match what the template was approved for; a mismatch is refused here rather than per recipient by Meta.
variables
objectoptional
Body variables by slot number: { "1": "Priya", "2": "AC-4192" }. Numbered, because WhatsApp fills them positionally.
text
stringoptional
The message body, for type: "text".
subject
stringoptional
Required on the email channel; ignored elsewhere.
caption
stringoptional
Shown under an image, video or document sent on its own. Audio carries no caption.
interactive
objectoptional
For type: "interactive". Meta’s own shape: { type: "button" | "list" | "cta_url", body: { text }, header?, footer?, action }. Reply buttons cap at 3, list rows at 10 overall, and both are checked here rather than at Meta.
headerFilename
stringoptional
Filename shown for a document header. Derived from the URL when omitted, and ignored for image and video.
{
  "to": "919876543210",
  "templateName": "order_ready_video",
  "renderId": "rnd_8fK2mQ",
  "variables": {
    "1": "Priya",
    "2": "AC-4192"
  }
}

A send reaches a real person on WhatsApp and spends a conversation on your MotherBot plan, so it is not fired from the docs.

Broadcast a render on WhatsApp

POST/api/v1/integrations/motherbot/campaignsAPI keyintegrations:write
Queues one broadcast of the SAME render to many numbers, paced by MotherBot — which protects the number’s quality rating in a way a loop of individual sends does not. For personalised video, where each recipient gets a different file, call the send endpoint once per person instead: a campaign carries a single media URL for the whole run.

Cost No pixbix credits. Consumes one conversation per recipient on your own MotherBot plan.

Body parameters

templateName
stringrequired
An approved template.
recipients
arrayrequired
Up to 5,000 per campaign. Each entry takes `to`, an optional `name`, and optional per-recipient `variables` that override the common ones.
name
stringoptional
What the campaign is called in MotherBot. Defaults to “pixbix — <today>”.
languageCode
stringoptional
Only needed when one template name is approved in several languages.
commonVariables
objectoptional
Body variables shared by every recipient, by slot number: { "2": "50%" }.
renderId
stringoptional
A finished render in this workspace, used as the campaign’s media header.
mediaUrl
stringoptional
A public URL instead of a render id.
rateLimitPerSecond
numberoptional
Messages a second. MotherBot’s own default is 3.
{
  "name": "Diwali 2026 — video",
  "templateName": "diwali_offer_video",
  "renderId": "rnd_8fK2mQ",
  "commonVariables": {
    "2": "50%"
  },
  "recipients": [
    {
      "to": "919876543210",
      "name": "Priya",
      "variables": {
        "1": "Priya"
      }
    },
    {
      "to": "919812345678",
      "name": "Arjun",
      "variables": {
        "1": "Arjun"
      }
    }
  ]
}

A campaign messages real people and spends a conversation per recipient on your MotherBot plan, so it is not fired from the docs.

Credits#

Current balance

GET/creditsSession token
Image and video balances, each split into the expiring monthly grant and permanent purchased top-up.
Response
{
  "success": true,
  "data": {
    "credits": {
      "image": { "granted": 380, "topup": 500, "available": 880, "held": 2 },
      "video": { "granted": 96,  "topup": 250, "available": 346, "held": 15 }
    },
    "grants": { "imageCredits": 500, "videoCredits": 150 },
    "periodEnd": "2026-09-01T00:00:00.000Z",
    "planCode": "starter"
  }
}

held is reserved by renders currently in flight. It has already been removed from the balance — it is shown so you can explain a temporary dip.

Credit ledger

GET/credits/historySession token
Append-only history of every credit movement: grants, purchases, spends, refunds and expiries.

Webhooks#

Register endpoints in the dashboard under Settings → Webhooks, with the endpoints below, or set callback per render. All three receive the same payload.

A registered endpoint describes the whole request — method, body encoding, headers and the payload itself, all of which accept {{variables}} resolved per delivery. The reference for those settings is on the webhooks guide; everything below describes the default shape.

Managing endpoints

Endpoints can be registered and edited with a key as well as from the dashboard, so a deployment can point its own webhook at a fresh environment without anyone opening a browser. These take webhooks:read and webhooks:write.

The signing secret is returned exactly once, by the create call. It is never readable afterwards — later responses carry only secretHint, the last four characters, which is enough to tell two endpoints apart and useless to anyone else.

List webhook endpoints

GET/api/v1/webhooksAPI keywebhooks:read
Every delivery endpoint registered in the workspace, newest first. The signing secret is never returned — only a hint at its last four characters, so you can tell two endpoints apart without the secret being readable after creation.

Query parameters

page
numberoptional
Page number, from 1.
limit
numberoptional
Rows per page, 1–100. Defaults to 50.
sort
stringoptional
Column to order by: createdAt, description, url, lastSuccessAt, lastFailureAt. Defaults to `createdAt`.
order
"asc" | "desc"optional
Sort direction. Defaults to `desc` for dates and counts.
search
stringoptional
Case-insensitive match across description, url. Up to 128 characters.
isActive
booleanoptional
Restrict to live or paused endpoints.
events
stringoptional
Only endpoints subscribed to these events, comma-separated.
Response
{
  "success": true,
  "data": [
    {
      "id": "whk_3d81",
      "url": "https://acme.example/hooks/pixbix",
      "description": "Production",
      "events": [
        "render.completed",
        "render.failed",
        "render.cancelled"
      ],
      "method": "POST",
      "bodyFormat": "json",
      "payloadMode": "default",
      "secretHint": "whsec_…8f2a",
      "isActive": true,
      "consecutiveFailures": 0,
      "lastSuccessAt": "2026-08-15T09:41:02.118Z"
    }
  ]
}

Try it

/api/v1/webhooks?page=1&limit=50&sort=createdAt&order=desc&search=diwali&isActive=true&events=render.completed
Show as cURL
curl -X GET "https://pixbix.app/api/v1/webhooks?page=1&limit=50&sort=createdAt&order=desc&search=diwali&isActive=true&events=render.completed" \
  -H "x-api-key: pk_live_your_key"

Register a webhook endpoint

POST/api/v1/webhooksAPI keywebhooks:write
Registers an endpoint and returns its signing secret. The secret appears in this response and nowhere else — store it before moving on, because it cannot be read again. Counts against the plan's webhook allowance.

Body parameters

url
stringrequired
Absolute HTTPS URL. May contain {{variables}}, resolved per delivery.
description
stringoptional
Your own label. Up to 200 characters.
events
string[]optional
Defaults to the three terminal endings: render.completed, render.failed and render.cancelled.
method
"POST" | "PUT" | "PATCH" | "GET"optional
Defaults to POST.
bodyFormat
"json" | "form" | "multipart" | "none"optional
How the body is encoded. Defaults to json.
payloadMode
"default" | "custom"optional
Send our event envelope, or your own body built from {{variables}}.
payloadTemplate
stringoptional
The custom body, when payloadMode is custom. Required in that case.
headers
{ key, value }[]optional
Up to 20 extra request headers. Values accept {{variables}}.
{
  "url": "https://acme.example/hooks/pixbix",
  "description": "Production",
  "events": [
    "render.completed",
    "render.failed",
    "render.cancelled"
  ]
}

Creating an endpoint returns a secret that is shown once; run it from your own client so the response is not left in a browser tab.

Update a webhook endpoint

PATCH/api/v1/webhooks/:idAPI keywebhooks:write
Changes only the fields you send, so a call that toggles isActive leaves the request configuration alone. Re-enabling a disabled endpoint clears the failure streak that disabled it.

Path parameters

id
stringrequired
Webhook endpoint id.

Body parameters

isActive
booleanoptional
Pause or resume deliveries.
url
stringoptional
Absolute HTTPS URL.
events
string[]optional
Replaces the subscribed event list.
method
"POST" | "PUT" | "PATCH" | "GET"optional
HTTP method used for delivery.
bodyFormat
"json" | "form" | "multipart" | "none"optional
Body encoding.
payloadMode
"default" | "custom"optional
Our envelope, or your own body.
payloadTemplate
stringoptional
The custom body, when payloadMode is custom.
headers
{ key, value }[]optional
Replaces the extra header list.
{
  "events": [
    "render.completed"
  ],
  "isActive": true
}

Editing a live endpoint changes where your production events go.

Send a test event

POST/api/v1/webhooks/:id/testAPI keywebhooks:write
Delivers one sample render.completed event, built and signed exactly like a real one — so it exercises the configured method, encoding, headers and payload rather than a simplified stand-in. Carries X-Pixbix-Test: true, and a failure does not count towards the auto-disable streak.

Path parameters

id
stringrequired
Webhook endpoint id.
Response
{
  "success": true,
  "message": "Delivered — your endpoint replied 200.",
  "data": {
    "ok": true,
    "status": 200
  }
}

A test delivery hits your own server; fire it from a client you control.

Delete a webhook endpoint

DELETE/api/v1/webhooks/:idAPI keywebhooks:write
Deliveries stop immediately. Renders are unaffected — results stay readable from GET /v1/render/:id, which is the fallback when a delivery never arrives.

Path parameters

id
stringrequired
Webhook endpoint id.
Response
{
  "success": true,
  "message": "Webhook deleted",
  "data": null
}

Deleting an endpoint stops production deliveries.

Events

EventFired when
render.queuedA render is accepted into the queue
render.startedA worker picks it up
render.completedThe output is stored and ready
render.failedThe render failed; credits refunded
template.publishedA template passed review
credits.lowBalance falls below the warning threshold
credits.exhaustedBalance reaches zero
subscription.updatedPlan or subscription status changed
payment.capturedA payment succeeded

Default payload

render.completed
{
  "id": "evt_m1x8k2p9",
  "type": "render.completed",
  "created": "2026-08-13T09:15:48.221Z",
  "data": {
    "id": "rnd_8f2a1c",
    "status": "done",
    "kind": "video",
    "templateId": "story-reel",
    "url": "https://cdn.pixbix.app/render/…/render.mp4",
    "posterUrl": "https://cdn.pixbix.app/render/…/poster.jpg",
    "thumbnailUrl": "https://cdn.pixbix.app/render/…/thumbnail.jpg",
    "format": "mp4",
    "width": 1080,
    "height": 1920,
    "duration": 15.02,
    "fileSizeBytes": 2841022,
    "renderTime": 42180,
    "credits": 15,
    "watermarked": false,
    "expiresAt": "2026-09-12T09:15:48.221Z"
  }
}

Verifying the signature

Every delivery carries these headers:

Headers
X-Pixbix-Event: render.completed
X-Pixbix-Timestamp: 1786000548
X-Pixbix-Signature: sha256=9f2c…
X-Pixbix-Attempt: 1

The signature is an HMAC-SHA256 over `${timestamp}.${rawBody}` using your endpoint secret. Including the timestamp in the signed string is what prevents a captured payload being replayed later.

import crypto from "crypto";

// IMPORTANT: verify against the RAW body. Re-serialising a parsed body
// changes key order and whitespace, and the signature will never match.
app.post("/hooks/pixbix", express.raw({ type: "application/json" }), (req, res) => {
  const timestamp = req.get("X-Pixbix-Timestamp");
  const signature = req.get("X-Pixbix-Signature");

  // Reject anything older than five minutes.
  if (Math.abs(Date.now() / 1000 - Number(timestamp)) > 300) {
    return res.status(400).send("stale");
  }

  const expected = "sha256=" + crypto
    .createHmac("sha256", process.env.PIXBIX_WEBHOOK_SECRET)
    .update(timestamp + "." + req.body.toString())
    .digest("hex");

  const a = Buffer.from(expected);
  const b = Buffer.from(signature || "");
  if (a.length !== b.length || !crypto.timingSafeEqual(a, b)) {
    return res.status(401).send("bad signature");
  }

  const event = JSON.parse(req.body.toString());
  // Acknowledge fast, then do the work asynchronously.
  res.sendStatus(200);
  void handle(event);
});

Respond quickly

Return 2xx within 10 seconds and do the work afterwards. Non-2xx responses are retried with backoff up to 5 times; an endpoint that keeps failing is disabled automatically after 20 consecutive failures.

Managing endpoints

The dashboard form writes through these. They take a session token and X-Pixbix-Org, not an API key — script them when you are provisioning several workspaces and would rather not fill the same form in repeatedly.

List endpoints

GET/organization/webhooksSession token
Every endpoint in the workspace, newest first. The signing secret is never returned — only secretHint, its last four characters.

Create an endpoint

POST/organization/webhooksSession token
Registers an endpoint and returns its signing secret. That response is the only place the secret ever appears.

Parameters

url
stringrequired
HTTPS in production. May contain {{variables}}.
events
string[]required
Which events to subscribe to.
description
stringoptional
Free text, for your own reference.
method
stringoptional
POST (default), PUT, PATCH or GET.
bodyFormat
stringoptional
json (default), form, multipart or none. Forced to none for GET.
payloadMode
stringoptional
default sends our envelope; custom sends payloadTemplate.
payloadTemplate
stringoptional
A JSON object, as text, holding {{variables}}. Required when payloadMode is custom; rejected if it is not valid JSON.
headers
arrayoptional
Up to 20 { key, value } pairs sent with every delivery. Values take variables; X-Pixbix-* headers cannot be overridden.
Request
{
  "url": "https://api.acme.com/hooks/pixbix",
  "events": ["render.completed", "render.failed"],
  "method": "POST",
  "bodyFormat": "json",
  "payloadMode": "custom",
  "payloadTemplate": "{\"renderId\": \"{{data.id}}\", \"fileUrl\": \"{{data.url}}\"}",
  "headers": [{ "key": "Authorization", "value": "Bearer your-receiver-token" }]
}

Update an endpoint

PATCH/organization/webhooks/{id}Session token
Changes only the fields you send, so toggling isActive leaves the request configuration alone. Re-enabling a disabled endpoint clears its failure streak.

Send a test event

POST/organization/webhooks/{id}/testSession token
Delivers one sample render.completed event, built and signed exactly like a real one, carrying X-Pixbix-Test: true. Returns the status your endpoint replied with; a failure here does not count towards the auto-disable streak.

Delete an endpoint

DELETE/organization/webhooks/{id}Session token
Deliveries stop immediately. Renders are unaffected — results stay readable from GET /v1/render/{id}.