Skip to Content
Authentication

Authentication

VoxBurst accepts two forms of Bearer authentication: API keys and Cognito session JWTs. Both are sent the same way, in the Authorization header. Some endpoint groups require one or the other specifically — see the notes throughout this page and Scopes below.

API Keys

Authorization: Bearer vb_live_xxxxxxxxxxxxx

API keys are prefixed with vb_live_. Keys created in the VoxBurst dashboard always use this prefix.

Create and manage your API keys in the VoxBurst dashboard .

Keep your API keys secret. Never commit them to version control or expose them in client-side code.

Cognito Session (JWT) Authentication

In addition to API keys, VoxBurst accepts a Cognito-issued JSON Web Token in the same Authorization header:

Authorization: Bearer <cognito-access-or-id-token>

There is no separate endpoint or request format for JWT auth — the API distinguishes a Cognito token from an API key by its structure, and both are accepted on any route that supports either.

  • Access and ID tokens expire 1 hour after issuance.
  • Refresh tokens last 30 days and are used to obtain a new access/ID token pair without requiring the user to sign in again.
  • Tokens come from the standard Cognito login flow used by the VoxBurst dashboard (app.voxburst.io). This page does not document that login/token-issuance flow in detail — there is currently no dedicated public doc page for it. If you are building an integration that needs to mint or refresh Cognito tokens directly (rather than using an API key), talk to VoxBurst support before building against this.

A Cognito session is required — an API key with any scope, including a wildcard key, is not sufficient — for the operations listed in There is no invitations:write: sending invitations, resending invitations, and accepting invitations.

A Cognito session is also accepted as an alternative to a wildcard-scoped API key for any endpoint group that no scoped key can reach.

Scopes

API keys carry a list of scopes that control which requests the key can make. Use the minimum scopes required for your use case.

How scopes are named and matched

Scopes follow the form <resource>:<read|write>. Resource names are lowercase and hyphenated (api-keys, audit-logs).

Granularity is method-based, not endpoint-based. Every endpoint belongs to one route group, and the group plus the HTTP method determine the single scope required:

  • GET and HEAD require the group’s :read scope.
  • Every other method (POST, PUT, PATCH, DELETE) requires the group’s :write scope.

There are two exceptions to the GET rule — GET /v1/ai/suggest-time/{accountId} and GET /v1/billing/portal both require their group’s :write scope, because both change state. See Two GET endpoints require a :write scope.

A key needs exactly one matching scope per request. Holding :write does not imply :read — a key that only reads and writes posts needs both posts:read and posts:write.

Some groups are read-only by design and define no :write scope. For those groups any non-GET request is rejected with 403, regardless of which scopes the key holds. The table below marks them.

Scope reference

ScopeRoute groupGrants
posts:read/v1/posts, /v1/batchRead posts, post status, and batch job state
posts:write/v1/posts, /v1/batchCreate, update, delete, publish, and import posts; submit batch operations
accounts:read/v1/accountsRead connected social accounts
accounts:write/v1/accountsConnect, update, and disconnect social accounts
media:read/v1/media, /v1/uploadsRead media assets and upload records
media:write/v1/media, /v1/uploadsCreate uploads and delete media assets
webhooks:read/v1/webhooksRead webhook endpoints and delivery history
webhooks:write/v1/webhooksCreate, update, delete, and test webhook endpoints
analytics:read/v1/analytics, /v1/reportsRead analytics and reports (read-only group)
workspaces:read/v1/workspacesRead workspace details, members, and settings
workspaces:write/v1/workspacesUpdate workspace details, members, and settings
api-keys:read/v1/workspaces/{id}/api-keysList API keys and their usage
api-keys:write/v1/workspaces/{id}/api-keysCreate, update, rotate, and delete API keys
schedule:read/v1/calendarRead the posting calendar and schedule configuration
schedule:write/v1/calendarUpdate the posting calendar and schedule configuration
queues:read/v1/approval-queueRead approval queue state (read-only group)
personas:read/v1/personasRead personas and their modules
personas:write/v1/personasCreate, update, and delete personas
audiences:read/v1/audience (/v1/audiences also matches)Read audience data
audiences:write/v1/audience (/v1/audiences also matches)Create, update, and delete audience data
users:read/v1/usersRead user profile information (read-only group)
inbox:read/v1/inboxRead comments, mentions, and inbox items
inbox:write/v1/inboxReply to, hide, delete, and sync inbox items
contacts:read/v1/contactsRead contacts and their tags
contacts:write/v1/contactsCreate, update, delete, and tag contacts
sequences:read/v1/sequencesRead sequences, steps, and enrollments
sequences:write/v1/sequencesCreate, update, delete sequences and manage enrollments
broadcasts:read/v1/broadcastsRead broadcasts and delivery results
broadcasts:write/v1/broadcastsCreate, update, delete, and send broadcasts
audit-logs:read/v1/audit-logsRead workspace audit logs (read-only group)
platforms:read/v1/platformsRead platform capabilities and metadata (read-only group)
hashtag-sets:read/v1/hashtag-setsRead saved hashtag sets
hashtag-sets:write/v1/hashtag-setsCreate, update, and delete hashtag sets
ai:read/v1/aiRead AI status, credit usage, usage history, and image job state. Does not cover GET /v1/ai/suggest-time/{accountId} — see below
ai:write/v1/aiRun AI generation (enhance, hashtags, images, post content), submit and retry image jobs, and call GET /v1/ai/suggest-time/{accountId}. All of these spend workspace AI credits
billing:read/v1/billingRead current plan, subscription, usage, scheduled post count, and credit balance. Does not cover GET /v1/billing/portal — see below
billing:write/v1/billingCreate checkout sessions, preview and change plan, buy credits and add-ons, cancel and resume a subscription, and create a Stripe billing portal session (GET /v1/billing/portal)
invitations:read/v1/workspaces/{id}/invitationsList a workspace’s pending invitations (read-only group — there is no invitations:write, see below)
comment-automations:read/v1/comment-automationsRead comment automations, their run logs, and their resolved posts
comment-automations:write/v1/comment-automationsCreate, update, and delete comment automations
*All mapped route groupsWildcard. Must be the sole scope in the array; cannot be combined with named scopes.

Any scope string not in this table is rejected — see scope validation below.

Batch operations use the posts scopes

/v1/batch has no scope of its own. It is mapped onto posts:read and posts:write:

  • GET /v1/batch/limits requires posts:read
  • POST /v1/batch requires posts:write

This is deliberate. A batch request dispatches its sub-operations internally without a second scope check, so a separate batch:write scope would grant whatever a batch can execute. Reusing the posts scopes keeps a batch key’s reach identical to what the same key could already do one request at a time. A key holding posts:write can therefore call POST /v1/batch; a key holding only posts:read cannot.

Uploads share the media scopes

/v1/uploads has no scope of its own either. It is mapped onto media:read and media:write, the same pair that covers /v1/media. A key that can create uploads needs media:write; there is no uploads:* scope to request.

Two GET endpoints require a :write scope

The method rule (GET:read) has two deliberate exceptions. Both are GET requests that are not reads in any meaningful sense, so both are carved out to the :write scope of their group. This is intentional, not a bug — do not expect a :read-only key to work on either.

EndpointRequired scopeWhy
GET /v1/ai/suggest-time/{accountId}ai:writeReserves and spends workspace AI credits before calling the provider, exactly like the POST routes. A nominally read-only key must not be able to incur cost.
GET /v1/billing/portalbilling:writeCreates a Stripe billing portal session and returns its URL. Anyone holding that URL can change the payment method, switch plan, or cancel the subscription. That is a billing mutation wearing a GET.

Every other endpoint in these two groups follows the normal rule: GET /v1/ai/status, GET /v1/ai/usage, GET /v1/ai/image-jobs, GET /v1/billing/current, GET /v1/billing/usage and GET /v1/billing/credits all require only the :read scope.

If a new credit-spending or state-mutating GET is added under /v1/ai or /v1/billing in future, it will be carved out the same way and announced in the changelog.

There is no invitations:write

invitations:read is the only invitation scope, and it grants exactly one endpoint: GET /v1/workspaces/{id}/invitations, which lists a workspace’s pending invitations. That is ordinary tenant-scoped data.

Invitation mutations are bound to a person, not to a workspace, and are not available to API keys at all:

EndpointResult with any API key
POST /v1/invitations403 AUTHORIZATION_ERROR
POST /v1/workspaces/{id}/invitations403 AUTHORIZATION_ERROR
POST /v1/invitations/{id}/resend403 AUTHORIZATION_ERROR
GET /v1/invitations403 AUTHORIZATION_ERROR
POST /v1/invitations/{token}/accept403 AUTHORIZATION_ERROR
DELETE /v1/invitations/{id}403 AUTHORIZATION_ERROR

The 403 names the reason in its message. There is no scope, wildcard included, that changes this — sending an invitation records and displays who sent it, and it grants a person workspace access that would outlive the key. Use a signed-in workspace admin (Cognito session) for these operations.

GET /v1/invitations/{token}, the lookup used by the accept-invite page, is public and requires no authentication or scope.

comment-automations:* does not replace the plan entitlement

The comment-automations scopes are an API-access gate only. They are checked independently of, and in addition to, the plan entitlement for the feature.

Comment automations are available on the PRO and AGENCY plans. A key holding comment-automations:write on a workspace whose plan does not include the feature still receives 403 with the code PLAN_UPGRADE_REQUIRED. Both gates must pass:

  1. The key must hold the matching comment-automations:read / comment-automations:write scope.
  2. The workspace plan must include the comment automations feature.

Granting the scope does not upgrade the plan, and upgrading the plan does not add the scope to an existing key.

Endpoint groups that no scoped key can reach

The scope table above is the complete map. Any authenticated endpoint group that does not appear in it has no scope mapping, so every key with named scopes receives 403 on it with the message This route is not accessible with this API key. Only a key issued with the wildcard * scope can call those groups.

If you receive that 403 with a correctly scoped key, that is the intended behaviour, not a bug. Use a wildcard key, or a Cognito session, for those calls.

/v1/ai, /v1/billing and workspace invitation listing were previously in this category and are now covered by named scopes — see ai:*, billing:* and invitations:read in the table above. Invitation mutations remain unavailable to API keys entirely, including wildcard keys.

Administrative endpoints (/v1/admin/...) are never reachable with an API key — including a wildcard key. They always return 403.

Scope validation is enforced on API key creation and updates. Submitting an unrecognized scope string returns 400 VALIDATION_ERROR with the message "Invalid scope(s): <scope>". The wildcard scope (*) must be the only scope specified — combining it with named scopes (e.g. ["*", "posts:read"]) is rejected.

Scope errors

Both cases return HTTP 403 with the code AUTHORIZATION_ERROR:

SituationMessage
The route has a scope and the key lacks itAPI key missing required scope: <scope>
The route has no scope mapping (and the key is not wildcard)This route is not accessible with this API key

Rate Limiting

This section is a summary. See Rate Limits for the full reference, including recommended client backoff behavior and endpoint-specific limits.

Rate limits are applied per API key and vary by plan. Higher plans receive higher limits. Do not hard-code a specific limit — read the current value from the X-RateLimit-Limit response header on every request, since the effective limit can change without notice.

Rate limit information is returned in response headers:

X-RateLimit-Limit: <requests allowed in the current window> X-RateLimit-Remaining: <requests remaining in the current window> X-RateLimit-Reset: <unix timestamp when the window resets>

When rate limited, the API returns 429 Too Many Requests with a Retry-After header indicating when to retry.

Rate Limit Headers

Every authenticated response includes headers to help you manage request pacing. Rate limiting is applied after authentication succeeds, so 401 responses (missing or invalid credentials) and unauthenticated public endpoints (like GET /v1/health) do not include these headers:

HeaderDescription
X-RateLimit-LimitMaximum requests allowed in the current window
X-RateLimit-RemainingRequests remaining in the current window
X-RateLimit-ResetUnix timestamp when the limit resets

Example Response with Rate Limit Headers

HTTP/1.1 200 OK Content-Type: application/json X-RateLimit-Limit: <requests allowed in the current window> X-RateLimit-Remaining: <requests remaining in the current window> X-RateLimit-Reset: <unix timestamp when the window resets> { "id": "post_abc123", "content": "Hello from VoxBurst!", "status": "scheduled" }

Use different API keys for development and production environments to limit the blast radius of a leaked key.

Environment Variables

Store your API key as an environment variable:

# .env VOXBURST_API_KEY=vb_live_xxxxxxxxxxxxx
curl https://api.voxburst.io/v1/accounts \ -H "Authorization: Bearer $VOXBURST_API_KEY"

Or with the TypeScript SDK:

import { VoxBurstClient } from '@voxburst/sdk' const client = new VoxBurstClient({ apiKey: process.env.VOXBURST_API_KEY! })

Health Check

The API exposes two health endpoints under the /v1 prefix:

EndpointDescription
GET /v1/healthReturns 200 when the API is up
GET /v1/readyReturns 200 when the API is up and dependencies are healthy
curl https://api.voxburst.io/v1/health

The health endpoints are at /v1/health — not /health. Requests to https://api.voxburst.io/health (without /v1) return 403 from the CloudFront distribution layer and do not reach the API. Always use the /v1/ prefix.

Both endpoints are unauthenticated and do not require an API key.


Resource ID Format

All VoxBurst resource IDs (post IDs, account IDs, etc.) follow the cuid2  format:

  • Exactly 25 characters total
  • Starts with the letter c
  • Followed by 24 lowercase alphanumeric characters (az, 09)
  • Example: cmp6v6bhf000369v60htcu3vc

Passing a malformed ID returns 400 Bad Request — not 404. The API validates the ID format before attempting a database lookup. Handle both 400 and 404 when an ID might be user-supplied:

{ "error": { "code": "BAD_REQUEST", "message": "Invalid path parameter 'id': Invalid ID format" } }

Idempotency

VoxBurst supports idempotency for all write operations (POST, PUT, PATCH, DELETE). This lets you safely retry requests on network failures without creating duplicate resources.

How to use

Add an Idempotency-Key header to any write request:

POST /v1/posts Authorization: Bearer vb_live_xxxxxxxxxxxxx Idempotency-Key: create-post-2026-06-01-campaign-a Content-Type: application/json

Key format

RuleValue
Header nameIdempotency-Key (case-insensitive)
Allowed charactersAlphanumeric, hyphens (-), underscores (_)
Min length1 character
Max length255 characters
TTL24 hours — keys are purged after expiry

Behavior

ScenarioBehavior
First requestExecutes normally, caches response against the key
Repeat request (same key, same body)Returns the cached response with Idempotency-Key-Used: true header — no duplicate action
Repeat request (same key, different body)Returns 409 Conflict — key is already locked to the original payload
Concurrent requests with same keyReturns 409 Conflict with code IN_PROGRESS — only one request proceeds
Key expired (>24h)Treated as a new request — processed normally

Response headers

HeaderDescription
Idempotency-Key-Usedtrue when the response is a replay of a cached result
X-VoxBurst-Supports-Idempotencytrue on every response — confirms the endpoint supports idempotency
# Use a UUID per attempt Idempotency-Key: 550e8400-e29b-41d4-a716-446655440000 # Use a stable identifier for your operation Idempotency-Key: schedule-post-campaign-123-slot-4 # Use a hash of the request intent for agent workflows Idempotency-Key: post-${workspaceId}-${contentHash}-${scheduledFor}

Error codes

CodeHTTPCause
IDEMPOTENCY_CONFLICT409Key already used with a different request body
IDEMPOTENCY_IN_PROGRESS409A request with this key is currently being processed
IDEMPOTENCY_INVALID_KEY400Key format invalid (invalid characters or out of length range)

Idempotency keys are scoped to your workspace. A key from one workspace cannot conflict with the same key in another workspace.


Contract precedence

When prose descriptions, inline examples, SDK documentation, and the OpenAPI spec ever appear to conflict, this ordering determines which source is authoritative:

  1. API source code — the implementation is the ground truth. All other sources describe it.
  2. This documentation — pages under /api-reference/* reflect the current implementation, validated against the codebase before publishing.
  3. OpenAPI spec (GET /v1/docs/openapi.yaml) — machine-readable contract. Kept in sync with implementation; the source code takes precedence on conflict.
  4. SDK docs (TypeScript, Go) — SDK method signatures and types are derived from the REST contract. Where SDK behavior diverges from REST, the REST contract wins.
  5. Examples — illustrative, not normative. Examples may omit optional fields or use placeholder values.

In practice: if you encounter a mismatch between a docs example and a field table, trust the field table. If a field table conflicts with an actual API response, file a bug  — the implementation may have changed without a docs update.

Backwards compatibility: non-breaking changes (new optional fields, new endpoints, new enum values) are deployed without notice. Your client should ignore unknown fields. Breaking changes (field removal, type changes, endpoint removal) are announced at least 30 days in advance in the changelog.


Error Responses

Authentication failures return:

{ "error": { "code": "AUTHENTICATION_ERROR", "message": "Invalid or missing API key" } }
StatusCodeCause
401AUTHENTICATION_ERRORMissing or invalid API key
403AUTHORIZATION_ERRORValid key, but it lacks the scope for this route — or the route is not reachable with an API key at all
429RATE_LIMITEDRate limit exceeded
Last updated on