Accounts
Connect and manage social media accounts. VoxBurst supports OAuth-based account connection for all major platforms.
Base URL
https://api.voxburst.io/v1/accountsList Accounts
GET /v1/accounts
List all connected social media accounts.
Query Parameters
| Parameter | Type | Default | Description |
|---|---|---|---|
status | string | — | Filter by status. Accepted values: ACTIVE, INACTIVE, ERROR, DISCONNECTED, or all. Uppercase required for query params; response values are lowercase. DISCONNECTED accounts are excluded by default unless includeArchived=true is passed. |
platform | string | — | Filter by platform slug (e.g. twitter) |
includeArchived | boolean | false | When false (default), DISCONNECTED accounts are excluded from results. Pass true to include disconnected (archived) accounts in the response. |
limit | integer | 20 | Results per page |
cursor | string | — | Pagination cursor from previous response |
Default behavior changed 2026-06-15: Previously, DISCONNECTED accounts were implicitly included in list results. They are now excluded by default. Integrations that iterate all accounts and act on disconnected ones must add includeArchived=true to their requests.
curl "https://api.voxburst.io/v1/accounts?status=ACTIVE&platform=twitter" \
-H "Authorization: Bearer vb_live_xxxxxxxxxxxxx"Response
{
"data": [
{
"id": "acc_123",
"platform": "twitter",
"username": "@voxburst",
"displayName": "VoxBurst",
"avatarUrl": "https://cdn.example.com/avatar.jpg",
"accountType": "personal",
"status": "active",
"connectedAt": "2026-01-15T10:00:00Z"
}
],
"pagination": {
"has_more": false,
"next_cursor": null,
"limit": 20
}
}The list is paginated using cursor-based pagination. Pass cursor=<nextCursor> as a query parameter to fetch the next page.
Connect Account
POST /v1/accounts/connect/:platform
Initiate OAuth connection for a social media platform. Returns an authorization URL to redirect the user to.
Required scopes: accounts:write
Supported platforms: twitter, linkedin, instagram, facebook, bluesky, threads, youtube, mastodon, tiktok, pinterest, snapchat, reddit, telegram, googlebusiness, whatsapp
Request Body
| Field | Type | Required | Description |
|---|---|---|---|
callbackUrl | string | Yes | URI to redirect to after OAuth authorization |
forcePrompt | boolean | No | If true, forces the OAuth consent screen to re-display even if the user has previously authorized |
pageMode | boolean | No | For LinkedIn: true to connect as a LinkedIn Page (requires Pages app credentials), false or omit to connect as a personal profile |
curl -X POST https://api.voxburst.io/v1/accounts/connect/linkedin \
-H "Authorization: Bearer vb_live_xxxxxxxxxxxxx" \
-H "Content-Type: application/json" \
-d '{
"callbackUrl": "https://yourapp.com/oauth/callback",
"pageMode": true
}'Response
{
"authorizationUrl": "https://x.com/i/oauth2/authorize?...",
"state": "oauth_state_token"
}Redirect your user to the authorizationUrl. After authorization, the platform will redirect back to your callbackUrl with a code and state parameter.
The callbackUrl you pass here is validated server-side against an allowlist of permitted domains. URIs that do not match an approved domain are rejected. Use your production app domain (HTTPS required) or localhost for local development.
OAuth Callback
POST /v1/accounts/callback/:platform
Complete the OAuth flow by exchanging the authorization code for access tokens.
Required scopes: accounts:write
curl -X POST https://api.voxburst.io/v1/accounts/callback/twitter \
-H "Authorization: Bearer vb_live_xxxxxxxxxxxxx" \
-H "Content-Type: application/json" \
-d '{
"code": "oauth_authorization_code",
"state": "oauth_state_token"
}'Response
{
"account": {
"id": "acc_789",
"platform": "twitter",
"username": "@newuser",
"displayName": "New User",
"status": "active",
"connectedAt": "2026-02-20T10:00:00Z"
}
}Disconnect Account
DELETE /v1/accounts/:id
Disconnect and remove a social media account.
Required scopes: accounts:write
curl -X DELETE https://api.voxburst.io/v1/accounts/acc_123 \
-H "Authorization: Bearer vb_live_xxxxxxxxxxxxx"Response (200):
{ "success": true }Refresh Account Token
POST /v1/accounts/:id/refresh
Manually refresh the OAuth token for an account. VoxBurst refreshes tokens automatically, but this endpoint is available for troubleshooting.
Required scopes: accounts:write
curl -X POST https://api.voxburst.io/v1/accounts/acc_123/refresh \
-H "Authorization: Bearer vb_live_xxxxxxxxxxxxx"Response (200):
{ "success": true }Test Connection
POST /v1/accounts/:id/test
Test the connection for a social account by making a lightweight API call to the platform.
curl -X POST https://api.voxburst.io/v1/accounts/acc_123/test \
-H "Authorization: Bearer vb_live_xxxxxxxxxxxxx"Response
{
"success": true,
"message": "Connection verified"
}On success, some platforms return additional details:
{
"success": true,
"message": "Connection verified",
"details": {
"platformUserId": "1234567890",
"username": "@voxburst",
"displayName": "VoxBurst"
}
}Get Account
GET /v1/accounts/:id
Retrieve a single connected account by ID.
curl https://api.voxburst.io/v1/accounts/acc_123 \
-H "Authorization: Bearer vb_live_xxxxxxxxxxxxx"List Pages
GET /v1/accounts/:id/pages
List the pages or business accounts associated with a connected account. Currently supported for LinkedIn and Instagram (Facebook Login) accounts.
- LinkedIn: Returns the organizations the connected user administers. Use these to populate a page selector before creating a LinkedIn post targeting a company page.
- Instagram (Facebook Login only): Returns the Instagram Business accounts linked to the connected Facebook user token. Accounts connected via Instagram Direct Login (
oauthVersion: "instagram_login") return an emptypagesarray — only Facebook Login-connected accounts (oauthVersion: "fb_login") have access to page data.
For all other platforms, returns an empty pages array.
curl https://api.voxburst.io/v1/accounts/acc_123/pages \
-H "Authorization: Bearer vb_live_xxxxxxxxxxxxx"Response — LinkedIn
{
"pages": [
{
"id": "page_001",
"name": "VoxBurst Official",
"urn": "urn:li:organization:12345678",
"type": "organization",
"logoUrl": "https://cdn.example.com/logo.png"
}
]
}Response — Instagram
{
"pages": [
{
"id": "<ig_user_id>",
"name": "@username",
"pageId": "<fb_page_id>",
"pageName": "Page Display Name",
"type": "instagram",
"avatarUrl": "https://..."
}
]
}The response shape differs by platform. LinkedIn entries include urn and logoUrl; Instagram entries include pageId, pageName, type, and avatarUrl. Both use the top-level id and name fields.
Select Page
POST /v1/accounts/:id/select-page
Persist a page selection for an account. Supported for LinkedIn and Instagram accounts.
- LinkedIn: Saves the selected LinkedIn Page. Subsequent posts to this account are published to the selected page.
- Instagram (Facebook Login only): Re-fetches Instagram Business accounts from the Graph API, matches on
pageId, and updates the account withusername,instagramAccountId,pageAccessToken,pageId,pageName, andoauthVersion: 'fb_login'. Returns400 INVALID_PAGE_IDif the providedpageIdis not found among the accounts accessible via the connected Facebook token.
Required scopes: accounts:write
Request Body
| Field | Type | Required | Description |
|---|---|---|---|
pageId | string | Yes | ID of the page to select. For LinkedIn, this is the page’s internal ID. For Instagram, this is the Facebook Page ID (pageId from the List Pages response). |
pageName | string | No | Display name of the selected page |
# LinkedIn example
curl -X POST https://api.voxburst.io/v1/accounts/acc_123/select-page \
-H "Authorization: Bearer vb_live_xxxxxxxxxxxxx" \
-H "Content-Type: application/json" \
-d '{ "pageId": "page_001", "pageName": "VoxBurst Official" }'
# Instagram example
curl -X POST https://api.voxburst.io/v1/accounts/acc_456/select-page \
-H "Authorization: Bearer vb_live_xxxxxxxxxxxxx" \
-H "Content-Type: application/json" \
-d '{ "pageId": "<fb_page_id>", "pageName": "Page Display Name" }'Error Codes
| Code | HTTP | Description |
|---|---|---|
INVALID_PAGE_ID | 400 | Instagram only — the provided pageId was not found among the Instagram Business accounts accessible via the connected Facebook token. Re-fetch pages with GET /v1/accounts/:id/pages and use a valid pageId from that response. |
PAGE_SELECTION_NOT_SUPPORTED | 400 | Instagram only — the account is connected via Instagram Direct Login (oauthVersion: "instagram_login"), which has no Facebook Pages to select. Page selection is a Facebook Login concept. To choose a Page, reconnect the account using Facebook Login for Business. |
ACCOUNT_PAGE_MISMATCH | 400 | Instagram and Facebook — the selected Page does not belong to the account identified by :id. See Page ownership below. |
DESTINATIONS_NOT_LOADED | 400 | The account’s destination list has not been populated yet, so the supplied pageId cannot be validated. Wait a moment and retry. |
Page ownership
Each Page or Instagram Business account is stored as its own connected
account. A Page may only be selected onto the account it actually belongs to —
POST /v1/accounts/:id/select-page verifies ownership before writing and returns
400 ACCOUNT_PAGE_MISMATCH when the selected Page belongs to a different
account. Nothing is written when this error is returned; the account is left
exactly as it was.
This applies in three situations:
- Instagram — the
pageIdresolves to a different Instagram Business account than the one identified by:id. - Instagram — the platform does not report the selected Page as the owner of this account’s Instagram Business account. This can happen if the Page↔account link changed on the platform after the account was connected.
- Facebook — the account is already keyed to a specific Page, or the selected Page is already stored as its own connected account in the workspace.
What a client should do: re-fetch GET /v1/accounts/:id/pages and retry with
a Page belonging to that account. Retrying with the same pageId will fail
identically — this is not a transient condition. If the Page you want is
administered by the same user but belongs to a different account, manage it
through that account’s own id rather than reassigning this one. If neither
applies, reconnect the account to refresh the Page↔account relationships.
GET /v1/accounts/:id/pages may list every Page reachable by the connected
token, which is a superset of the Pages selectable onto any one account.
Do not assume that every entry in that response is a valid pageId for the
account you fetched it from. If you render a picker, scope it to the account
being edited and be prepared to surface ACCOUNT_PAGE_MISMATCH.
Check oauthVersion on the account before offering page selection in your UI.
An Instagram account with oauthVersion: "instagram_login" will always return
PAGE_SELECTION_NOT_SUPPORTED here, and GET /v1/accounts/:id/pages returns an
empty pages array for it — neither is a transient condition, and retrying will
not change the outcome.
YouTube Playlists
GET /v1/accounts/:id/youtube/playlists
Returns the YouTube playlists accessible by a connected YouTube account. Use this to populate a playlist selector before creating a YouTube post.
curl https://api.voxburst.io/v1/accounts/acc_123/youtube/playlists \
-H "Authorization: Bearer vb_live_xxxxxxxxxxxxx"Response (200)
{
"playlists": [
{ "id": "PLxxxxxxxxxxxxxxxxxx", "title": "My Tutorial Series" },
{ "id": "PLyyyyyyyyyyyyyyyyyy", "title": "Product Demos" }
]
}If the connected account does not have the YouTube playlist read scope, the response returns an empty list with a requiresReauth flag:
{
"playlists": [],
"requiresReauth": true
}When requiresReauth is true, prompt the user to reconnect their YouTube account to grant the required scope.
TikTok Creator Info
GET /v1/accounts/:id/tiktok/creator-info
Returns TikTok’s creator info for a connected TikTok account — the connected creator’s nickname, the privacy levels they’re allowed to post with, and their duet/stitch/comment interaction settings. TikTok’s Content Sharing Guidelines require this to be fetched and rendered before a “Post to TikTok” screen is shown: the privacy options offered must exactly match privacyLevelOptions, and the duet/stitch/comment toggles must be disabled wherever the creator has turned those interactions off on their own account.
curl https://api.voxburst.io/v1/accounts/acc_tiktok_123/tiktok/creator-info \
-H "Authorization: Bearer vb_live_xxxxxxxxxxxxx"Response (200) — available
{
"available": true,
"creatorInfo": {
"nickname": "jane.creates",
"username": "jane.creates",
"avatarUrl": "https://p16-sign.tiktokcdn.com/...",
"privacyLevelOptions": ["PUBLIC_TO_EVERYONE", "MUTUAL_FOLLOW_FRIENDS", "SELF_ONLY"],
"commentDisabled": false,
"duetDisabled": false,
"stitchDisabled": true,
"maxVideoPostDurationSec": 600,
"canPost": true
}
}Response (200) — unavailable
{
"available": false,
"reason": "NO_TOKEN"
}Response Fields
| Field | Type | Description |
|---|---|---|
available | boolean | false if creator info could not be retrieved — see reason |
reason | string | Present when available is false. One of NOT_TIKTOK, NO_TOKEN, QUERY_FAILED |
message | string | undefined | Present when reason is QUERY_FAILED — human-readable detail |
creatorInfo.nickname | string | Creator’s display name on TikTok |
creatorInfo.username | string | Creator’s TikTok handle |
creatorInfo.avatarUrl | string | Creator’s profile picture URL |
creatorInfo.privacyLevelOptions | string[] | Privacy levels this creator is allowed to publish with. Pass one of these values as platformMetadata.TIKTOK.tiktokPrivacyLevel on Create Post — see Platform Metadata |
creatorInfo.commentDisabled | boolean | true if the creator has disabled comments on their own account — comments cannot be enabled for this post regardless of tiktokAllowComment |
creatorInfo.duetDisabled | boolean | true if the creator has disabled duets on their own account |
creatorInfo.stitchDisabled | boolean | true if the creator has disabled stitches on their own account |
creatorInfo.maxVideoPostDurationSec | number | null | Maximum video duration this creator can post, in seconds |
creatorInfo.canPost | boolean | false when TikTok returned no privacy level options for this creator — the account cannot currently publish. POST /v1/posts will fail with TIKTOK_CREATOR_CANNOT_POST for this account until it resolves. |
Account Backfill
Get Backfill Status
GET /v1/accounts/:id/backfill
Get the backfill status for a connected account. Backfill pulls historical analytics from the platform.
curl https://api.voxburst.io/v1/accounts/acc_123/backfill \
-H "Authorization: Bearer vb_live_xxxxxxxxxxxxx"Trigger Backfill
POST /v1/accounts/:id/backfill
Trigger a historical analytics backfill for a connected account.
Required scopes: accounts:write
curl -X POST https://api.voxburst.io/v1/accounts/acc_123/backfill \
-H "Authorization: Bearer vb_live_xxxxxxxxxxxxx"Optimal Posting Times
GET /v1/accounts/:id/optimal-times
Returns the optimal posting time slots for a connected account based on historical engagement data.
Query Parameters
| Parameter | Type | Description |
|---|---|---|
timezone | string | IANA timezone identifier (default: America/Chicago) |
limit | integer | Number of slots to return (1–10, default: 5) |
curl "https://api.voxburst.io/v1/accounts/acc_123/optimal-times?timezone=America/New_York&limit=5" \
-H "Authorization: Bearer vb_live_xxxxxxxxxxxxx"Instagram Audio Search
GET /v1/instagram/audio/search
Search for audio tracks available for use in Instagram Reels. Returns trending tracks when q is omitted, or filtered results when a search term is provided.
Auth: Bearer token required (read:posts scope)
Feature flag: This endpoint returns 503 FEATURE_DISABLED until the INSTAGRAM_AUDIO_ENABLED environment variable is set. The feature is currently pending Meta App Review and is not yet available in production.
Facebook Login required: The account identified by accountId must have been connected via Facebook Login (oauthVersion: "fb_login"). Accounts connected via Instagram Direct Login return 400 — check the account’s oauthVersion before calling this endpoint. If the account’s flobProbe.flobAvailable is true, the user can upgrade to Facebook Login to enable this feature.
Query Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
accountId | string | Yes | ID of a connected Instagram account (must be Facebook Login-connected) |
q | string | No | Search term. Omit to retrieve trending tracks. |
limit | integer | No | Number of tracks to return (1–50, default: 20) |
# Trending tracks
curl "https://api.voxburst.io/v1/instagram/audio/search?accountId=acc_456&limit=10" \
-H "Authorization: Bearer vb_live_xxxxxxxxxxxxx"
# Search by term
curl "https://api.voxburst.io/v1/instagram/audio/search?accountId=acc_456&q=summer&limit=20" \
-H "Authorization: Bearer vb_live_xxxxxxxxxxxxx"Response (200)
{
"tracks": [
{
"id": "1234567890",
"title": "Track Title",
"artistName": "Artist Name",
"audioUrl": "https://...",
"coverImageUri": "https://...",
"durationMs": 30000
}
]
}Response Fields
| Field | Type | Description |
|---|---|---|
tracks | object[] | Array of audio track objects |
tracks[].id | string | Track identifier (use this when attaching audio to a Reel) |
tracks[].title | string | Track title |
tracks[].artistName | string | undefined | Artist name (may be absent for original audio) |
tracks[].audioUrl | string | undefined | Direct audio URL (may be absent depending on Meta’s response) |
tracks[].coverImageUri | string | undefined | Cover art URL |
tracks[].durationMs | number | undefined | Track duration in milliseconds |
Error Codes
| Code | HTTP | Description |
|---|---|---|
FEATURE_DISABLED | 503 | Audio search is not yet enabled in this environment (pending Meta App Review) |
GRAPH_API_ERROR | 502 | Meta Graph API returned an error. The message field contains the Meta error detail. |
| — | 404 | The accountId was not found or does not belong to this workspace |
| — | 400 | The account does not have a pageAccessToken — it was connected via Instagram Direct Login (oauthVersion: "instagram_login") rather than Facebook Login. Check the account’s flobProbe.flobAvailable field; if true, the user can upgrade to Facebook Login to enable audio search. |
Google Business Profile Locations
Connected Google Business Profile accounts can have multiple business locations. Use these endpoints to list available locations and configure which ones are selected for posting.
List GMB Locations
GET /v1/accounts/:id/gmb/locations
Returns all GMB locations available under a connected Google Business account, grouped by account, with each location’s current selection state.
Auth: Bearer token, workspace-scoped. The account identified by :id must be a GOOGLE_BUSINESS platform account.
curl https://api.voxburst.io/v1/accounts/acc_example123/gmb/locations \
-H "Authorization: Bearer eyJexample..."Response (200)
{
"groups": [
{
"accountName": "Acme Corp",
"accountPath": "accounts/123456789",
"locations": [
{
"path": "accounts/123456789/locations/987654321",
"title": "Acme Corp - Main St",
"address": "123 Main St, Springfield, IL 62701",
"selected": true
},
{
"path": "accounts/123456789/locations/111222333",
"title": "Acme Corp - Oak Ave",
"address": "456 Oak Ave, Springfield, IL 62702",
"selected": false
}
]
}
],
"selectedCount": 1
}Response Fields
| Field | Type | Description |
|---|---|---|
groups | object[] | Location groups keyed by GMB account |
groups[].accountName | string | Display name of the GMB account |
groups[].accountPath | string | GMB account resource path |
groups[].locations | object[] | Locations under this account |
groups[].locations[].path | string | GMB location resource path — use this as the locationPath identifier |
groups[].locations[].title | string | Business name for this location |
groups[].locations[].address | string | Formatted street address |
groups[].locations[].selected | boolean | Whether this location is currently selected for posting |
selectedCount | integer | Total number of currently selected locations across all groups |
Error Codes
| HTTP | Description |
|---|---|
| 404 | Account not found or does not belong to this workspace |
| 400 | Account is not a GOOGLE_BUSINESS platform account |
| 400 | Google Business token is missing or expired — prompt the user to reconnect |
Save GMB Location Selection
PUT /v1/accounts/:id/gmb/locations
Saves the set of selected location paths for a connected GMB account. Only the locations listed in locationPaths will be marked as selected; all others will be deselected.
Auth: Bearer token, workspace-scoped. Requires accounts:write scope.
Request Body
| Field | Type | Required | Description |
|---|---|---|---|
locationPaths | string[] | Yes | GMB location resource paths to select (max 100). Use the path values from GET /v1/accounts/:id/gmb/locations. Pass an empty array to deselect all locations. |
curl -X PUT https://api.voxburst.io/v1/accounts/acc_example123/gmb/locations \
-H "Authorization: Bearer eyJexample..." \
-H "Content-Type: application/json" \
-d '{
"locationPaths": [
"accounts/123456789/locations/987654321"
]
}'Response (200)
{
"success": true,
"selectedCount": 1
}Error Codes
| HTTP | Description |
|---|---|
| 404 | Account not found or does not belong to this workspace |
| 400 | Account is not a GOOGLE_BUSINESS platform account |
| 400 | locationPaths contains more than 100 entries |
Shared Accounts
When the same social media account (identified by platform + platform user ID) is connected in more than one VoxBurst workspace, VoxBurst marks it as a shared account. Posts published from any workspace that share the account count toward a combined platform publishing limit.
When you connect an account that is already connected in another workspace, the connect response includes a warning:
{
"account": { "id": "acc_789", "platform": "instagram", "username": "brandname", ... },
"warning": "This account is already connected in another workspace. Posts to this account from all connected workspaces share a combined publishing limit."
}The isSharedAccount field on the account record is true when this condition applies.
Account Fields
All string enum fields (platform, status, accountType) are returned as lowercase strings in REST API responses.
| Field | Type | Description |
|---|---|---|
id | string | Account ID (cuid2 format, starts with c) |
platform | string | Platform slug — always lowercase (e.g. "twitter", "instagram") |
username | string | null | Platform username or handle |
displayName | string | null | Display name on the platform |
avatarUrl | string | null | Profile avatar URL |
accountType | string | null | personal, business, or creator — platform-dependent |
status | string | See Account Status below |
connectedAt | string | ISO 8601 timestamp of when the account was connected |
needsPlaylistReauth | boolean | YouTube only — true when the account lacks the playlist read scope |
oauthVersion | string | undefined | Instagram only. The OAuth path used to connect this account: "instagram_login" (Direct Login) or "fb_login" (Facebook Login for Business). Present on all connected Instagram accounts. Absent on all other platforms. |
flobProbe | object | undefined | Instagram Direct Login accounts only. Eligibility information for upgrading to Facebook Login for Business. See Instagram OAuth paths below. Absent if the eligibility check has not yet completed, or if the account was connected via Facebook Login. |
scopeHealth | object | undefined | Result of the permission check run when the account was connected. See Scope health below. Absent on accounts connected before this check shipped — absent is not the same as healthy. |
Scope Health
When an account is connected or reconnected, VoxBurst compares the permissions
the platform actually granted against the permissions each VoxBurst feature
requires for that platform, and records the result on the account as
scopeHealth.
This exists because a connection can succeed, and publishing can work, while a
narrower operation on the same account cannot — the shortfall is otherwise
invisible until a user attempts that operation. scopeHealth lets an
integration surface the shortfall at connect time instead.
{
"id": "acc_123",
"platform": "instagram",
"status": "active",
"scopeHealth": {
"status": "DEGRADED",
"degradedCapabilities": [
{ "capability": "deletePost", "label": "deleting posts" }
],
"message": "This connection is missing permissions for deleting posts. Reconnect the account and accept all requested permissions to restore it.",
"verifiedAt": "2026-08-21T14:02:11.000Z"
}
}Fields
| Field | Type | Description |
|---|---|---|
scopeHealth.status | string | OK, DEGRADED, UNKNOWN, or NOT_APPLICABLE. See below. |
scopeHealth.degradedCapabilities | array | Capabilities that cannot work with the permissions actually granted. Always [] unless status is DEGRADED. |
scopeHealth.degradedCapabilities[].capability | string | Stable machine-readable capability key — see the table below. Safe to branch on. |
scopeHealth.degradedCapabilities[].label | string | Human-readable description of the capability, e.g. "deleting posts". Suitable for display. Wording may change; do not branch on it. |
scopeHealth.degradedCapabilities[].grantedForOtherTargetsOnly | boolean | undefined | Present and true when the permission was granted, but for a different Page or account rather than this one. The key is omitted otherwise. |
scopeHealth.message | string | undefined | One user-facing sentence summarising the shortfall. Present only when status is DEGRADED. |
scopeHealth.reason | string | undefined | Short diagnostic explaining why the result is UNKNOWN or NOT_APPLICABLE. Opaque and unstable — log it, do not parse or display it. |
scopeHealth.verifiedAt | string | undefined | ISO 8601 timestamp of the check. |
Note that scopeHealth never contains raw platform permission names. Branch on
capability; display label or message.
Status values
| Status | Meaning | Suggested handling |
|---|---|---|
OK | Every feature VoxBurst ships for this platform has the permissions it needs. | No action. |
DEGRADED | At least one feature cannot work. degradedCapabilities is non-empty. | Prompt the user to reconnect and accept all requested permissions. Disable the affected features in your UI. |
UNKNOWN | The platform did not report which permissions it granted and no introspection was available. | Neither healthy nor degraded — do not treat as either. Proceed, and handle permission errors at call time. |
NOT_APPLICABLE | The platform has no permission-scope concept, or no scope-gated features. | No action. |
Capability keys
capability | label |
|---|---|
publish | publishing posts |
deletePost | deleting posts |
readComments | reading comments |
replyComments | replying to comments |
readMessages | reading direct messages |
sendMessages | sending direct messages |
insights | audience insights and analytics |
listDestinations | listing the Pages, channels or locations you can post to |
readFollowers | audience discovery |
readOwnPosts | reading your published posts |
New capability keys may be added over time. Treat an unrecognised capability
as a generic degradation and fall back to displaying label.
scopeHealth absent is not status: "OK". The field is only populated for
accounts connected or reconnected after this check shipped. On an older account
it is omitted entirely, which means never verified — not no problems found.
Do not infer health from its absence.
Verification never blocks a connect. An account with
scopeHealth.status: "DEGRADED" is still status: "active" and will still
publish, provided publish is not among its degradedCapabilities.
Account Status
All status and platform values in REST API responses are lowercase strings (e.g. "twitter", "active"). This applies to all account endpoints: list, get, connect callback, refresh, and test.
Query parameters that filter by status or platform accept UPPERCASE values (e.g. status=ACTIVE, platform=TWITTER). The response always returns lowercase regardless of how the query parameter was cased.
Webhook payloads (account.connected, account.error) use UPPERCASE for platform and status — this is intentionally different from REST responses. See Webhooks for details.
| Status (response) | Query parameter value | Description |
|---|---|---|
active | ACTIVE | Account is connected and tokens are valid |
expired | (use ERROR) | Token has expired — call /refresh or reconnect |
disconnected | DISCONNECTED | Account was disconnected by the user. Excluded from list results by default — pass includeArchived=true to include. |
suspended | (not filterable) | Account has been suspended by the platform |
error | ERROR | Publishing error — check the account’s error details |
OAuth Edge Cases
Reconnecting an already-connected account
If a user completes the OAuth flow for an account that is already connected in the same workspace (same platform + platform user ID), VoxBurst upserts the account record: tokens are refreshed, status is reset to ACTIVE, and any previous errors are cleared. No duplicate account is created.
Connecting an account that exists in another workspace
When the same social account is connected across multiple workspaces, VoxBurst flags it as a shared account. The callback response includes a warning field:
{
"account": { "id": "acc_789", "platform": "instagram", ... },
"warning": "This account is already connected in another workspace. Posts to this account from all connected workspaces share a combined publishing limit."
}OAuth state expiry
The OAuth state token stored during the connect step expires after 10 minutes. If the user does not complete authorization within that window, the callback endpoint will return:
{ "error": { "code": "INVALID_PARAM", "message": "Invalid or expired OAuth state" } }Restart the connect flow to get a fresh state token.
OAuth error codes
| Code | HTTP | When it occurs |
|---|---|---|
INVALID_PARAM | 400 | Invalid or expired state, or invalid auth code |
PLATFORM_RESTRICTED | 403 | Platform is not permitted in this workspace |
PLAN_LIMIT_EXCEEDED | 402 | Workspace has reached its connected account limit for the current plan |
Instagram OAuth result error codes
When an Instagram OAuth attempt fails, the callback redirect includes an errorCode query parameter on the result page. These codes are distinct from the API error codes above — they are propagated via the redirect URL rather than a JSON error response. Integrations that present or process the OAuth result URL should handle these values:
| Code | Meaning |
|---|---|
NO_FACEBOOK_PAGES | The user has no Facebook Pages associated with their account |
NO_IG_ON_PAGES | Facebook Pages were found, but none are linked to an Instagram Business or Creator account |
ACCOUNTS_CENTER_MISMATCH | The Instagram account is not associated with the user’s personal Accounts Center — usually the result of a Business Manager-only account |
CREDENTIALS_NOT_CONFIGURED | Instagram OAuth credentials are not configured on this VoxBurst instance |
Token expiry and refresh
VoxBurst refreshes tokens automatically when they are within 5 minutes of expiry. If automatic refresh fails (e.g. the user revoked access), the account status is set to EXPIRED or ERROR. At that point:
- Call
POST /v1/accounts/:id/refreshto attempt a manual refresh - If refresh fails, the user must reconnect via the full OAuth flow
Bluesky and Mastodon accounts do not use expiring tokens and do not require refresh.
Instagram OAuth Paths
VoxBurst supports two active OAuth paths for connecting Instagram accounts. The path used is recorded in the oauthVersion field on the account and governs which API scopes and endpoints are available.
| Path | oauthVersion value | OAuth host | Description |
|---|---|---|---|
| Instagram Direct Login | "instagram_login" | www.instagram.com | The default path used in the VoxBurst app UI. Grants scopes for business content publishing, comments, insights, and messages directly via Instagram’s OAuth. |
| Facebook Login for Business | "fb_login" | www.facebook.com | The path always initiated when connecting via the API. Available for accounts where an Instagram Business account is linked to a Facebook Page through Accounts Center. Provides access to the Facebook Graph API in addition to Instagram publishing. |
API behavior: POST /v1/accounts/connect/instagram always initiates the Facebook Login for Business (FLOB) path. There is no loginMethod parameter — the login path is not selectable via the API. Direct Login is the default in the VoxBurst app UI but is not available to API callers.
Checking FLOB eligibility
For Instagram accounts connected via Direct Login, VoxBurst runs an eligibility check after connection and writes the result to the flobProbe field. You can read this field from GET /v1/accounts/:id:
{
"id": "acc_example123",
"platform": "instagram",
"oauthVersion": "instagram_login",
"flobProbe": {
"flobAvailable": true,
"facebookAccountId": "...",
"checkedAt": "2026-07-06T01:29:37Z"
}
}| Field | Type | Description |
|---|---|---|
flobProbe.flobAvailable | boolean | true if this account is eligible for Facebook Login upgrade |
flobProbe.facebookAccountId | string | null | The associated Facebook account identifier when eligible; null otherwise |
flobProbe.checkedAt | string | ISO 8601 timestamp of the most recent eligibility check |
flobProbe may be absent if the check has not yet completed. If you need to wait for it, poll GET /v1/accounts/:id — the field will be populated within a few seconds of account connection.
Feature availability by OAuth path
Some Instagram-specific endpoints require a Facebook Login-connected account (i.e. oauthVersion: "fb_login"):
| Feature | instagram_login | fb_login |
|---|---|---|
| Feed posts (IMAGE, VIDEO, CAROUSEL, REEL) | ✓ | ✓ |
| First comment | ✓ | ✓ |
| Analytics / Insights | ✓ | ✓ |
GET /v1/accounts/:id/pages (returns pages) | ✗ (empty) | ✓ |
GET /v1/instagram/audio/search | ✗ | ✓ |
User Endpoints
Store GA4 Client ID
POST /v1/users/me/ga4-client-id
Stores the browser-side GA4 client ID for server-side Measurement Protocol event stitching. Call this once per session after the GA4 client ID becomes available in the browser (typically via gtag('get', ...) or ga.getAll()[0].get('clientId')).
Auth: Bearer token required
Request Body
| Field | Type | Required | Description |
|---|---|---|---|
clientId | string | Yes | GA4 client ID in the format XXXXXXXXX.XXXXXXXXX (two numeric segments separated by a dot). Must match /^\d+\.\d+$/. |
curl -X POST https://api.voxburst.io/v1/users/me/ga4-client-id \
-H "Authorization: Bearer vb_live_xxxxxxxxxxxxx" \
-H "Content-Type: application/json" \
-d '{ "clientId": "1234567890.9876543210" }'Response (200)
{ "ok": true }Error Codes
| HTTP | Description |
|---|---|
| 400 | clientId is missing or does not match the required format |
| 401 | Bearer token is missing or invalid |