zernio-cli 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (63) hide show
  1. package/README.md +145 -0
  2. package/SKILL.md +98 -0
  3. package/claude/plugin.json +23 -0
  4. package/claude/skills/zernio/SKILL.md +83 -0
  5. package/claude/skills/zernio/references/zernio-api-surface.md +48 -0
  6. package/claude/skills/zernio/references/zernio-best-practices.md +33 -0
  7. package/claude/skills/zernio/references/zernio-workflows.md +58 -0
  8. package/dist/client.d.ts +6 -0
  9. package/dist/client.js +14 -0
  10. package/dist/commands/accounts.d.ts +3 -0
  11. package/dist/commands/accounts.js +53 -0
  12. package/dist/commands/analytics.d.ts +3 -0
  13. package/dist/commands/analytics.js +85 -0
  14. package/dist/commands/api.d.ts +2 -0
  15. package/dist/commands/api.js +108 -0
  16. package/dist/commands/auth.d.ts +3 -0
  17. package/dist/commands/auth.js +138 -0
  18. package/dist/commands/automations.d.ts +5 -0
  19. package/dist/commands/automations.js +139 -0
  20. package/dist/commands/broadcasts.d.ts +5 -0
  21. package/dist/commands/broadcasts.js +184 -0
  22. package/dist/commands/contacts.d.ts +6 -0
  23. package/dist/commands/contacts.js +198 -0
  24. package/dist/commands/doctor.d.ts +2 -0
  25. package/dist/commands/doctor.js +51 -0
  26. package/dist/commands/inbox.d.ts +6 -0
  27. package/dist/commands/inbox.js +222 -0
  28. package/dist/commands/media.d.ts +3 -0
  29. package/dist/commands/media.js +76 -0
  30. package/dist/commands/platforms.d.ts +2 -0
  31. package/dist/commands/platforms.js +27 -0
  32. package/dist/commands/posts.d.ts +3 -0
  33. package/dist/commands/posts.js +129 -0
  34. package/dist/commands/profiles.d.ts +3 -0
  35. package/dist/commands/profiles.js +82 -0
  36. package/dist/commands/sequences.d.ts +5 -0
  37. package/dist/commands/sequences.js +178 -0
  38. package/dist/generated/openapi-catalog.d.ts +8961 -0
  39. package/dist/generated/openapi-catalog.js +3 -0
  40. package/dist/index.d.ts +1 -0
  41. package/dist/index.js +61 -0
  42. package/dist/utils/api-request.d.ts +31 -0
  43. package/dist/utils/api-request.js +115 -0
  44. package/dist/utils/argument-parsing.d.ts +3 -0
  45. package/dist/utils/argument-parsing.js +27 -0
  46. package/dist/utils/config.d.ts +27 -0
  47. package/dist/utils/config.js +111 -0
  48. package/dist/utils/errors.d.ts +5 -0
  49. package/dist/utils/errors.js +12 -0
  50. package/dist/utils/openapi-catalog.d.ts +16 -0
  51. package/dist/utils/openapi-catalog.js +58 -0
  52. package/dist/utils/output.d.ts +9 -0
  53. package/dist/utils/output.js +24 -0
  54. package/docs/architecture.md +37 -0
  55. package/docs/cli.md +99 -0
  56. package/docs/code-standards.md +11 -0
  57. package/docs/contributing.md +34 -0
  58. package/docs/development-roadmap.md +32 -0
  59. package/docs/openapi/zernio-api-openapi.yaml +30967 -0
  60. package/docs/project-changelog.md +15 -0
  61. package/docs/project-overview-pdr.md +25 -0
  62. package/docs/system-architecture.md +28 -0
  63. package/package.json +82 -0
@@ -0,0 +1,3 @@
1
+ /* This file is generated by scripts/generate-openapi-catalog.mjs. Do not edit manually. */
2
+ export const openApiInfo = { "title": "Zernio API", "version": "1.0.4", "servers": [{ "url": "https://zernio.com/api", "description": "Production" }, { "url": "http://localhost:3000/api", "description": "Local" }], "operationCount": 383 };
3
+ export const openApiEndpoints = [{ "operationId": "listAccountGroups", "method": "GET", "path": "/v1/account-groups", "tags": ["Account Groups"], "summary": "List groups", "description": "Returns all account groups visible to the authenticated user. Groups can contain accounts from multiple profiles. For API keys scoped to specific profiles, only groups whose accounts all live in allowed profiles are returned.", "parameters": [] }, { "operationId": "createAccountGroup", "method": "POST", "path": "/v1/account-groups", "tags": ["Account Groups"], "summary": "Create group", "description": "Creates a new account group with a name and a list of social account IDs. Accounts can belong to different profiles; the caller must have access to every account's profile. Group names must be unique per user.", "parameters": [], "requestBody": { "required": true, "contentTypes": ["application/json"], "schemaRefs": { "application/json": { "schemaType": "object" } } } }, { "operationId": "deleteAccountGroup", "method": "DELETE", "path": "/v1/account-groups/{groupId}", "tags": ["Account Groups"], "summary": "Delete group", "description": "Permanently deletes an account group. The accounts themselves are not affected.", "parameters": [{ "name": "groupId", "in": "path", "required": true, "schemaType": "string" }] }, { "operationId": "updateAccountGroup", "method": "PUT", "path": "/v1/account-groups/{groupId}", "tags": ["Account Groups"], "summary": "Update group", "description": "Updates the name or account list of an existing group. You can rename the group, change its accounts, or both.", "parameters": [{ "name": "groupId", "in": "path", "required": true, "schemaType": "string" }], "requestBody": { "required": true, "contentTypes": ["application/json"], "schemaRefs": { "application/json": { "schemaType": "object" } } } }, { "operationId": "deleteInstagramIceBreakers", "method": "DELETE", "path": "/v1/accounts/{accountId}/instagram-ice-breakers", "tags": ["Account Settings"], "summary": "Delete IG ice breakers", "description": "Removes the ice breaker questions from an Instagram account's Messenger experience.", "parameters": [{ "name": "accountId", "in": "path", "required": true, "schemaType": "string" }] }, { "operationId": "getInstagramIceBreakers", "method": "GET", "path": "/v1/accounts/{accountId}/instagram-ice-breakers", "tags": ["Account Settings"], "summary": "Get IG ice breakers", "description": "Get the ice breaker configuration for an Instagram account.", "parameters": [{ "name": "accountId", "in": "path", "required": true, "schemaType": "string" }] }, { "operationId": "setInstagramIceBreakers", "method": "PUT", "path": "/v1/accounts/{accountId}/instagram-ice-breakers", "tags": ["Account Settings"], "summary": "Set IG ice breakers", "description": "Set ice breakers for an Instagram account. Max 4 ice breakers, question max 80 chars.", "parameters": [{ "name": "accountId", "in": "path", "required": true, "schemaType": "string" }], "requestBody": { "required": true, "contentTypes": ["application/json"], "schemaRefs": { "application/json": { "schemaType": "object" } } } }, { "operationId": "deleteMessengerMenu", "method": "DELETE", "path": "/v1/accounts/{accountId}/messenger-menu", "tags": ["Account Settings"], "summary": "Delete FB persistent menu", "description": "Removes the persistent menu from Facebook Messenger conversations for this account.", "parameters": [{ "name": "accountId", "in": "path", "required": true, "schemaType": "string" }] }, { "operationId": "getMessengerMenu", "method": "GET", "path": "/v1/accounts/{accountId}/messenger-menu", "tags": ["Account Settings"], "summary": "Get FB persistent menu", "description": "Get the persistent menu configuration for a Facebook Messenger account.", "parameters": [{ "name": "accountId", "in": "path", "required": true, "schemaType": "string" }] }, { "operationId": "setMessengerMenu", "method": "PUT", "path": "/v1/accounts/{accountId}/messenger-menu", "tags": ["Account Settings"], "summary": "Set FB persistent menu", "description": "Set the persistent menu for a Facebook Messenger account. Max 3 top-level items, max 5 nested items.", "parameters": [{ "name": "accountId", "in": "path", "required": true, "schemaType": "string" }], "requestBody": { "required": true, "contentTypes": ["application/json"], "schemaRefs": { "application/json": { "schemaType": "object" } } } }, { "operationId": "deleteTelegramCommands", "method": "DELETE", "path": "/v1/accounts/{accountId}/telegram-commands", "tags": ["Account Settings"], "summary": "Delete TG bot commands", "description": "Clears all bot commands configured for a Telegram bot account.", "parameters": [{ "name": "accountId", "in": "path", "required": true, "schemaType": "string" }] }, { "operationId": "getTelegramCommands", "method": "GET", "path": "/v1/accounts/{accountId}/telegram-commands", "tags": ["Account Settings"], "summary": "Get TG bot commands", "description": "Get the bot commands configuration for a Telegram account.", "parameters": [{ "name": "accountId", "in": "path", "required": true, "schemaType": "string" }] }, { "operationId": "setTelegramCommands", "method": "PUT", "path": "/v1/accounts/{accountId}/telegram-commands", "tags": ["Account Settings"], "summary": "Set TG bot commands", "description": "Set bot commands for a Telegram account.", "parameters": [{ "name": "accountId", "in": "path", "required": true, "schemaType": "string" }], "requestBody": { "required": true, "contentTypes": ["application/json"], "schemaRefs": { "application/json": { "schemaType": "object" } } } }, { "operationId": "listAccounts", "method": "GET", "path": "/v1/accounts", "tags": ["Accounts"], "summary": "List accounts", "description": "Returns connected social accounts. Only includes accounts within the plan limit by default. Follower data requires analytics add-on. Supports optional server-side pagination via page/limit params. When omitted, returns all accounts (backward-compatible).", "parameters": [{ "name": "profileId", "in": "query", "required": false, "description": "Filter accounts by profile ID", "schemaType": "string" }, { "name": "platform", "in": "query", "required": false, "description": "Filter accounts by platform (e.g. \"instagram\", \"twitter\").", "schemaType": "string" }, { "name": "status", "in": "query", "required": false, "description": "Filter accounts by connection status. `connected` returns healthy accounts; `disconnected` returns accounts that need reconnection (per the same reconnection check surfaced in the dashboard). Omit to return accounts in any status. When com...", "schemaType": "string", "schemaEnum": ["connected", "disconnected"] }, { "name": "includeOverLimit", "in": "query", "required": false, "description": "When true, includes accounts from over-limit profiles.", "schemaType": "boolean" }, { "name": "page", "in": "query", "required": false, "description": "Page number (1-based). When provided with limit, enables server-side pagination. Omit for all accounts.", "schemaType": "integer" }, { "name": "limit", "in": "query", "required": false, "description": "Page size. Required alongside page for pagination.", "schemaType": "integer" }] }, { "operationId": "deleteAccount", "method": "DELETE", "path": "/v1/accounts/{accountId}", "tags": ["Accounts"], "summary": "Disconnect account", "description": "Disconnects and removes a connected social account.", "parameters": [{ "name": "accountId", "in": "path", "required": true, "schemaType": "string" }] }, { "operationId": "moveAccountToProfile", "method": "PATCH", "path": "/v1/accounts/{accountId}", "tags": ["Accounts"], "summary": "Move account to a different profile", "description": "Moves a connected social account to a different profile owned by the same user. The target profile must belong to the same user as the account. For API keys restricted to specific profiles, BOTH the source account's current profile AND the target profile must be in the key's allowed set. Calls with a target profile outside the key's scope return 403.", "parameters": [{ "name": "accountId", "in": "path", "required": true, "schemaType": "string" }], "requestBody": { "required": true, "contentTypes": ["application/json"], "schemaRefs": { "application/json": { "schemaType": "object" } } } }, { "operationId": "updateAccount", "method": "PUT", "path": "/v1/accounts/{accountId}", "tags": ["Accounts"], "summary": "Update account", "description": "Updates a connected social account's display name or username override. For X/Twitter accounts on usage-based billing, also accepts an `xCapabilities` object to toggle background API operations that incur X API pass-through costs. Both fields are opt-in (default `false`) — when off, no analytics syncs or DM polling are performed for that account, and no API call is metered for those operations. Publishing and deleting posts are always available regardless of these toggles. Setting `xCapabilitie...", "parameters": [{ "name": "accountId", "in": "path", "required": true, "schemaType": "string" }], "requestBody": { "required": true, "contentTypes": ["application/json"], "schemaRefs": { "application/json": { "schemaType": "object" } } } }, { "operationId": "getAccountHealth", "method": "GET", "path": "/v1/accounts/{accountId}/health", "tags": ["Accounts"], "summary": "Check account health", "description": "Returns detailed health info for a specific account including token status, permissions, and recommendations.", "parameters": [{ "name": "accountId", "in": "path", "required": true, "description": "The account ID to check", "schemaType": "string" }] }, { "operationId": "getTikTokCreatorInfo", "method": "GET", "path": "/v1/accounts/{accountId}/tiktok/creator-info", "tags": ["Accounts"], "summary": "Get TikTok creator info", "description": "Returns TikTok creator details, available privacy levels, posting limits, and commercial content options for a specific TikTok account. Only works with TikTok accounts.", "parameters": [{ "name": "accountId", "in": "path", "required": true, "description": "The TikTok account ID", "schemaType": "string" }, { "name": "mediaType", "in": "query", "required": false, "description": "The media type to get creator info for (affects available interaction settings)", "schemaType": "string", "schemaEnum": ["video", "photo"] }] }, { "operationId": "getFollowerStats", "method": "GET", "path": "/v1/accounts/follower-stats", "tags": ["Accounts", "Analytics"], "summary": "Get follower stats", "description": "Returns follower count history and growth metrics for connected social accounts. Requires analytics add-on subscription. Follower counts are refreshed once per day.", "parameters": [{ "name": "accountIds", "in": "query", "required": false, "description": "Comma-separated list of account IDs (optional, defaults to all user's accounts)", "schemaType": "string" }, { "name": "profileId", "in": "query", "required": false, "description": "Filter by profile ID", "schemaType": "string" }, { "name": "fromDate", "in": "query", "required": false, "description": "Start date in YYYY-MM-DD format (defaults to 30 days ago)", "schemaType": "string" }, { "name": "toDate", "in": "query", "required": false, "description": "End date in YYYY-MM-DD format (defaults to today)", "schemaType": "string" }, { "name": "granularity", "in": "query", "required": false, "description": "Data aggregation level", "schemaType": "string", "schemaEnum": ["daily", "weekly", "monthly"] }] }, { "operationId": "getAllAccountsHealth", "method": "GET", "path": "/v1/accounts/health", "tags": ["Accounts"], "summary": "Check accounts health", "description": "Returns health status of all connected accounts including token validity, permissions, and issues needing attention.", "parameters": [{ "name": "profileId", "in": "query", "required": false, "description": "Filter by profile ID", "schemaType": "string" }, { "name": "platform", "in": "query", "required": false, "description": "Filter by platform", "schemaType": "string", "schemaEnum": ["facebook", "instagram", "linkedin", "twitter", "tiktok", "youtube", "threads", "pinterest", "reddit", "bluesky", "googlebusiness", "telegram", "snapchat", "discord", "whatsapp"] }, { "name": "status", "in": "query", "required": false, "description": "Filter by health status", "schemaType": "string", "schemaEnum": ["healthy", "warning", "error"] }] }, { "operationId": "listAdAudiences", "method": "GET", "path": "/v1/ads/audiences", "tags": ["Ad Audiences"], "summary": "List custom audiences", "description": "Returns custom audiences for the given ad account. Supports Meta, Google, TikTok, Pinterest, LinkedIn, and X (Twitter).", "parameters": [{ "name": "accountId", "in": "query", "required": true, "description": "Social account ID", "schemaType": "string" }, { "name": "adAccountId", "in": "query", "required": true, "description": "Platform ad account ID", "schemaType": "string" }, { "name": "platform", "in": "query", "required": false, "schemaType": "string", "schemaEnum": ["facebook", "instagram", "googleads", "tiktok", "tiktokads", "pinterest", "linkedin", "linkedinads", "twitter", "xads"] }, { "name": "type", "in": "query", "required": false, "description": "Filter to one audience type. `saved_targeting` returns stored TargetingSpec audiences (each item carries a `spec`); the other types return uploaded/derived audiences.", "schemaType": "string", "schemaEnum": ["customer_list", "website", "lookalike", "saved_targeting"] }] }, { "operationId": "createAdAudience", "method": "POST", "path": "/v1/ads/audiences", "tags": ["Ad Audiences"], "summary": "Create custom audience", "description": "Create a custom audience. `customer_list` is supported on Meta, Google, X, LinkedIn, TikTok, and Pinterest; `website` and `lookalike` are Meta-only. `saved_targeting` stores a reusable TargetingSpec (no member upload, no adAccountId) that you reference later via `savedTargetingId` on `POST /v1/ads/create`. Upload-backed audiences are created empty, add members via `POST /v1/ads/audiences/{audienceId}/users`. On TikTok and Pinterest the audience is provisioned lazily on the first member upload (...", "parameters": [], "requestBody": { "required": true, "contentTypes": ["application/json"], "schemaRefs": { "application/json": {} } } }, { "operationId": "deleteAdAudience", "method": "DELETE", "path": "/v1/ads/audiences/{audienceId}", "tags": ["Ad Audiences"], "summary": "Delete custom audience", "description": "Deletes the audience from both Meta and the local database.", "parameters": [{ "name": "audienceId", "in": "path", "required": true, "schemaType": "string" }] }, { "operationId": "getAdAudience", "method": "GET", "path": "/v1/ads/audiences/{audienceId}", "tags": ["Ad Audiences"], "summary": "Get audience details", "description": "Returns the local audience record and fresh data from Meta (if available).", "parameters": [{ "name": "audienceId", "in": "path", "required": true, "schemaType": "string" }] }, { "operationId": "addUsersToAdAudience", "method": "POST", "path": "/v1/ads/audiences/{audienceId}/users", "tags": ["Ad Audiences"], "summary": "Add users to audience", "description": "Upload user data to a customer_list audience. Data is SHA256-hashed server-side before sending to the platform. Email is used on every platform; phone is used on Meta only (other platforms ignore it). On TikTok and Pinterest, the first upload also provisions the audience (deferred create). LinkedIn uploads are full-replace. Max 10,000 users per request.", "parameters": [{ "name": "audienceId", "in": "path", "required": true, "schemaType": "string" }], "requestBody": { "required": true, "contentTypes": ["application/json"], "schemaRefs": { "application/json": { "schemaType": "object" } } } }, { "operationId": "updateAdSet", "method": "PUT", "path": "/v1/ads/ad-sets/{adSetId}", "tags": ["Ad Campaigns"], "summary": "Update an ad set (budget, status, and/or bid strategy)", "description": "Ad-set-level writes. Use this for ABO budget updates, ad-set-scoped pause/resume, and bid-strategy edits. At least one of `budget`, `status`, or `bidStrategy` is required. Bid strategy compatibility (per Meta's spec): - `LOWEST_COST_WITHOUT_CAP`: no `bidAmount`, no `roasAverageFloor`. - `LOWEST_COST_WITH_BID_CAP` / `COST_CAP`: `bidAmount` REQUIRED (whole currency units). - `LOWEST_COST_WITH_MIN_ROAS`: `roasAverageFloor` REQUIRED (decimal multiplier, e.g. 2.0 = 2.0x ROAS). When updating `budget`...", "parameters": [{ "name": "adSetId", "in": "path", "required": true, "description": "Platform ad set ID", "schemaType": "string" }], "requestBody": { "required": true, "contentTypes": ["application/json"], "schemaRefs": { "application/json": { "schemaType": "object" } } } }, { "operationId": "updateAdSetStatus", "method": "PUT", "path": "/v1/ads/ad-sets/{adSetId}/status", "tags": ["Ad Campaigns"], "summary": "Pause or resume a single ad set", "description": "Ad-set-scoped pause/resume (doesn't touch sibling ad sets). Thin wrapper over PUT /v1/ads/ad-sets/{adSetId} for callers that only want the status toggle and prefer a symmetric URL to /v1/ads/campaigns/{campaignId}/status.", "parameters": [{ "name": "adSetId", "in": "path", "required": true, "description": "Platform ad set ID", "schemaType": "string" }], "requestBody": { "required": true, "contentTypes": ["application/json"], "schemaRefs": { "application/json": { "schemaType": "object" } } } }, { "operationId": "listAdCampaigns", "method": "GET", "path": "/v1/ads/campaigns", "tags": ["Ad Campaigns"], "summary": "List campaigns", "description": "Returns campaigns as virtual aggregations over ad documents grouped by platform campaign ID. Metrics (spend, impressions, clicks, etc.) are summed across all ads in each campaign. Campaign status is derived from child ad statuses (active > pending_review > paused > error > completed > cancelled > rejected).", "parameters": [{ "name": "page", "in": "query", "required": false, "description": "Page number (1-based)", "schemaType": "integer" }, { "name": "limit", "in": "query", "required": false, "schemaType": "integer" }, { "name": "source", "in": "query", "required": false, "description": "`all` (default) returns both Zernio-created ads and those discovered from the platform's ad manager — matches the web UI's default view. Pass `zernio` to restrict to isExternal=false only. Status is NOT filtered by default — use the `statu...", "schemaType": "string", "schemaEnum": ["zernio", "all"] }, { "name": "platform", "in": "query", "required": false, "schemaType": "string", "schemaEnum": ["facebook", "instagram", "tiktok", "linkedin", "pinterest", "google", "twitter"] }, { "name": "status", "in": "query", "required": false, "description": "Filter by derived campaign status (post-aggregation)", "schemaType": "string", "schemaEnum": ["active", "paused", "pending_review", "rejected", "completed", "cancelled", "error"] }, { "name": "adAccountId", "in": "query", "required": false, "description": "Platform ad account ID (e.g. act_123 for Meta)", "schemaType": "string" }, { "name": "accountId", "in": "query", "required": false, "description": "Social account ID", "schemaType": "string" }, { "name": "profileId", "in": "query", "required": false, "description": "Profile ID", "schemaType": "string" }, { "name": "fromDate", "in": "query", "required": false, "description": "Start of metrics date range (YYYY-MM-DD, inclusive). Defaults to 90 days ago when both date params are omitted.", "schemaType": "string" }, { "name": "toDate", "in": "query", "required": false, "description": "End of metrics date range (YYYY-MM-DD, inclusive). Defaults to today. Max 730-day range.", "schemaType": "string" }] }, { "operationId": "deleteAdCampaign", "method": "DELETE", "path": "/v1/ads/campaigns/{campaignId}", "tags": ["Ad Campaigns"], "summary": "Delete a campaign", "description": "Deletes the whole campaign on the platform, cascading to its ad sets and ads. Locally, all Ad documents for this campaign are marked `status: cancelled`. Meta-only for now. Other platforms return 501 Not Implemented — fall back to DELETE /v1/ads/{adId} per ad in the meantime.", "parameters": [{ "name": "campaignId", "in": "path", "required": true, "description": "Platform campaign ID", "schemaType": "string" }], "requestBody": { "required": true, "contentTypes": ["application/json"], "schemaRefs": { "application/json": { "schemaType": "object" } } } }, { "operationId": "updateAdCampaign", "method": "PUT", "path": "/v1/ads/campaigns/{campaignId}", "tags": ["Ad Campaigns"], "summary": "Update a campaign (budget and/or bid strategy)", "description": "Campaign-level edits. At least one of `budget` or `bidStrategy` is required. - `budget` updates the CBO (Campaign Budget Optimization) budget. For ABO campaigns (where the budget lives on the ad set), use PUT /v1/ads/ad-sets/{adSetId} instead — this endpoint will return 409 with code BUDGET_LEVEL_MISMATCH. - `bidStrategy` sets the campaign-level default bid strategy. Per Meta's spec, `bid_amount` and `bid_constraints` do NOT exist at the campaign level — pass them via PUT /v1/ads/ad-sets/{adSet...", "parameters": [{ "name": "campaignId", "in": "path", "required": true, "description": "Platform campaign ID", "schemaType": "string" }], "requestBody": { "required": true, "contentTypes": ["application/json"], "schemaRefs": { "application/json": { "schemaType": "object" } } } }, { "operationId": "duplicateAdCampaign", "method": "POST", "path": "/v1/ads/campaigns/{campaignId}/duplicate", "tags": ["Ad Campaigns"], "summary": "Duplicate a campaign", "description": "Duplicates a campaign, including its ad sets, ads, creatives, and targeting by default (`deepCopy: true`). The copy is created paused so callers can review before launching. Per-platform implementation: - **Meta** uses the native `POST /{campaign-id}/copies` endpoint. - **TikTok** has no native copy primitive; Zernio walks the source graph (`/v2/campaign/get/`, `/v2/adgroup/get/`, `/v2/ad/get/`) and recreates each entity via the corresponding `/create/` endpoints, carrying over budget / targeti...", "parameters": [{ "name": "campaignId", "in": "path", "required": true, "description": "Source platform campaign ID", "schemaType": "string" }], "requestBody": { "required": true, "contentTypes": ["application/json"], "schemaRefs": { "application/json": { "schemaType": "object" } } } }, { "operationId": "updateAdCampaignStatus", "method": "PUT", "path": "/v1/ads/campaigns/{campaignId}/status", "tags": ["Ad Campaigns"], "summary": "Pause or resume a campaign", "description": "Updates the status of all ads in a campaign. Makes one platform API call (not per-ad) since status cascades through the campaign hierarchy. Ads in terminal statuses (rejected, completed, cancelled) are automatically skipped.", "parameters": [{ "name": "campaignId", "in": "path", "required": true, "description": "Platform campaign ID", "schemaType": "string" }], "requestBody": { "required": true, "contentTypes": ["application/json"], "schemaRefs": { "application/json": { "schemaType": "object" } } } }, { "operationId": "bulkUpdateAdCampaignStatus", "method": "POST", "path": "/v1/ads/campaigns/bulk-status", "tags": ["Ad Campaigns"], "summary": "Pause or resume many campaigns", "description": "Process up to 50 campaigns in one call. Each campaign is updated concurrently and the response contains a per-campaign result so a single bad row does not fail the whole batch.", "parameters": [], "requestBody": { "required": true, "contentTypes": ["application/json"], "schemaRefs": { "application/json": { "schemaType": "object" } } } }, { "operationId": "getAdsTimeline", "method": "GET", "path": "/v1/ads/timeline", "tags": ["Ad Campaigns"], "summary": "Get daily aggregate ad metrics for an account", "description": "Returns daily aggregate metrics across all ads in a SocialAccount as a single time series — one row per calendar day in the requested range. Use this for dashboards that draw a daily-spend or daily-conversions chart, instead of calling `/v1/ads/tree` once per day. `accountId` is required. The lookup is sibling-expanded so passing the `metaads` ID also includes ads under the linked `facebook` / `instagram` posting account (and vice-versa) — same convention as `/v1/ads/tree` and `/v1/ads`. Date r...", "parameters": [{ "name": "accountId", "in": "query", "required": true, "description": "Social account ID. Sibling-expanded to its linked posting↔ads pair.", "schemaType": "string" }, { "name": "adAccountId", "in": "query", "required": false, "description": "Optional platform-native ad account ID (e.g. Meta `act_…`, TikTok advertiser ID). Use when the connection wraps multiple platform ad accounts and the chart should show one only. Note: rows ingested before 2026-05-13 don't carry this column...", "schemaType": "string" }, { "name": "fromDate", "in": "query", "required": false, "description": "Inclusive start of metrics range (YYYY-MM-DD). Defaults to 90 days ago.", "schemaType": "string" }, { "name": "toDate", "in": "query", "required": false, "description": "Inclusive end of metrics range (YYYY-MM-DD). Defaults to today. Max 730-day range.", "schemaType": "string" }, { "name": "platform", "in": "query", "required": false, "description": "Restrict to one platform.", "schemaType": "string", "schemaEnum": ["facebook", "instagram", "tiktok", "linkedin", "pinterest", "google", "twitter"] }] }, { "operationId": "getAdTree", "method": "GET", "path": "/v1/ads/tree", "tags": ["Ad Campaigns"], "summary": "Get campaign tree", "description": "Returns a nested Campaign > Ad Set > Ad hierarchy with rolled-up metrics at each level. Uses a two-stage aggregation: ads are grouped into ad sets, then ad sets into campaigns. Metrics are computed over an optional date range, then rolled up from ad level to ad set and campaign levels. Pagination is at the campaign level. Ads without a campaign or ad set ID are grouped into synthetic \"Ungrouped\" buckets. If no date range is provided, defaults to the last 90 days. Date range is capped at 730 day...", "parameters": [{ "name": "page", "in": "query", "required": false, "description": "Page number (1-based)", "schemaType": "integer" }, { "name": "limit", "in": "query", "required": false, "description": "Campaigns per page", "schemaType": "integer" }, { "name": "source", "in": "query", "required": false, "description": "`all` (default) returns both Zernio-created ads and those discovered from the platform's ad manager — matches the web UI's default view. Pass `zernio` to restrict to isExternal=false only. Status is NOT filtered by default — use the `statu...", "schemaType": "string", "schemaEnum": ["zernio", "all"] }, { "name": "platform", "in": "query", "required": false, "schemaType": "string", "schemaEnum": ["facebook", "instagram", "tiktok", "linkedin", "pinterest", "google", "twitter"] }, { "name": "status", "in": "query", "required": false, "description": "Filter by derived campaign status (post-aggregation)", "schemaType": "string", "schemaEnum": ["active", "paused", "pending_review", "rejected", "completed", "cancelled", "error"] }, { "name": "adAccountId", "in": "query", "required": false, "description": "Platform ad account ID", "schemaType": "string" }, { "name": "accountId", "in": "query", "required": false, "description": "Social account ID", "schemaType": "string" }, { "name": "profileId", "in": "query", "required": false, "description": "Profile ID", "schemaType": "string" }, { "name": "fromDate", "in": "query", "required": false, "description": "Start of metrics date range (YYYY-MM-DD). Defaults to 90 days ago.", "schemaType": "string" }, { "name": "toDate", "in": "query", "required": false, "description": "End of metrics date range (YYYY-MM-DD). Defaults to today. Max 730-day range.", "schemaType": "string" }, { "name": "sort", "in": "query", "required": false, "description": "Campaign-level sort order. `newest` (default) / `oldest` order by the campaign's newest-ad createdAt. `spend_desc` / `spend_asc` order by aggregated spend in the requested date range; campaigns with no spend land at the end.", "schemaType": "string", "schemaEnum": ["newest", "oldest", "spend_desc", "spend_asc"] }] }, { "operationId": "listConversionDestinations", "method": "GET", "path": "/v1/accounts/{accountId}/conversion-destinations", "tags": ["Ads"], "summary": "List destinations for the Conversions API", "description": "Returns the list of pixels (Meta), conversion actions (Google), or conversion rules (LinkedIn) accessible to the connected ads account. Use the returned `id` as `destinationId` when posting to `POST /v1/ads/conversions`. For Google and LinkedIn, each destination's `type` reflects the conversion type (PURCHASE, LEAD, SIGN_UP, etc.) — the event type is locked to the destination. For Meta, `type` is absent: pixels accept any event name per request. For LinkedIn, destinations are returned across ev...", "parameters": [{ "name": "accountId", "in": "path", "required": true, "description": "SocialAccount ID (metaads, googleads, or linkedinads).", "schemaType": "string" }] }, { "operationId": "createConversionDestination", "method": "POST", "path": "/v1/accounts/{accountId}/conversion-destinations", "tags": ["Ads"], "summary": "Create a conversion destination (LinkedIn, Google Ads)", "description": "Create a new conversion destination on the platform. Supported for LinkedIn (conversion rule) and Google Ads (conversion action). Meta manages destinations in its own UI and returns 405. **LinkedIn:** creation is NOT idempotent. A retry creates a second destination. Deduplicate before retrying. **Google Ads:** calling with a name that already exists reuses the existing conversion action transparently (the response is identical to a fresh create). Calling with the same name but a different categ...", "parameters": [{ "name": "accountId", "in": "path", "required": true, "description": "SocialAccount ID (linkedinads or googleads).", "schemaType": "string" }], "requestBody": { "required": true, "contentTypes": ["application/json"], "schemaRefs": { "application/json": { "schemaType": "object" } } } }, { "operationId": "deleteConversionDestination", "method": "DELETE", "path": "/v1/accounts/{accountId}/conversion-destinations/{destinationId}", "tags": ["Ads"], "summary": "Soft-delete a conversion destination", "description": "LinkedIn-only today. LinkedIn does not expose hard-delete on conversion rules — what their UI calls \"delete\" is the same `enabled: false` flip we apply here. The rule remains fetchable via GET with `status: 'inactive'`; the unified discovery endpoint hides it by default. `adAccountId` may be passed as a query parameter (recommended) or as a JSON body field for clients that can send DELETE bodies.", "parameters": [{ "name": "accountId", "in": "path", "required": true, "schemaType": "string" }, { "name": "destinationId", "in": "path", "required": true, "schemaType": "string" }, { "name": "adAccountId", "in": "query", "required": false, "description": "Required as query OR in JSON body.", "schemaType": "string" }] }, { "operationId": "getConversionDestination", "method": "GET", "path": "/v1/accounts/{accountId}/conversion-destinations/{destinationId}", "tags": ["Ads"], "summary": "Fetch a single conversion destination", "description": "LinkedIn-only today. Returns the full destination record for one conversion rule. The `adAccountId` query parameter is required because LinkedIn rules are scoped to a sponsored ad account.", "parameters": [{ "name": "accountId", "in": "path", "required": true, "schemaType": "string" }, { "name": "destinationId", "in": "path", "required": true, "schemaType": "string" }, { "name": "adAccountId", "in": "query", "required": true, "description": "Numeric ID or full `urn:li:sponsoredAccount:{id}` URN.", "schemaType": "string" }] }, { "operationId": "updateConversionDestination", "method": "PATCH", "path": "/v1/accounts/{accountId}/conversion-destinations/{destinationId}", "tags": ["Ads"], "summary": "Update a conversion destination", "description": "Partial-update a conversion rule. LinkedIn-only today. Whitelisted fields: `name`, `enabled`, attribution windows, `valueType`, `value`, `attributionType`. The rule's `type` and parent ad account are intentionally not exposed for update — recreate the rule if those need to change.", "parameters": [{ "name": "accountId", "in": "path", "required": true, "schemaType": "string" }, { "name": "destinationId", "in": "path", "required": true, "schemaType": "string" }], "requestBody": { "required": true, "contentTypes": ["application/json"], "schemaRefs": { "application/json": { "schemaType": "object" } } } }, { "operationId": "removeConversionAssociations", "method": "DELETE", "path": "/v1/accounts/{accountId}/conversion-destinations/{destinationId}/associations", "tags": ["Ads"], "summary": "Remove campaign↔conversion associations", "description": "Remove one or more campaign associations from this conversion rule. Pass `adAccountId` and `campaignIds` as query parameters (`campaignIds` is comma-separated). The route also accepts a JSON body with the same fields for clients that prefer DELETE-with-body, but the documented surface is query-only because some SDK code generators (e.g. Python) collapse query + body parameters with the same name into a single kwarg.", "parameters": [{ "name": "accountId", "in": "path", "required": true, "schemaType": "string" }, { "name": "destinationId", "in": "path", "required": true, "schemaType": "string" }, { "name": "adAccountId", "in": "query", "required": true, "schemaType": "string" }, { "name": "campaignIds", "in": "query", "required": true, "description": "Comma-separated list of campaign IDs.", "schemaType": "string" }] }, { "operationId": "listConversionAssociations", "method": "GET", "path": "/v1/accounts/{accountId}/conversion-destinations/{destinationId}/associations", "tags": ["Ads"], "summary": "List campaigns associated with a conversion destination", "description": "LinkedIn-only today. Returns the campaigns currently associated with this conversion rule. Note that auto-association on rule creation runs once at create time; campaigns created after the rule still need explicit association.", "parameters": [{ "name": "accountId", "in": "path", "required": true, "schemaType": "string" }, { "name": "destinationId", "in": "path", "required": true, "schemaType": "string" }, { "name": "adAccountId", "in": "query", "required": true, "schemaType": "string" }] }, { "operationId": "addConversionAssociations", "method": "POST", "path": "/v1/accounts/{accountId}/conversion-destinations/{destinationId}/associations", "tags": ["Ads"], "summary": "Associate campaigns with a conversion destination", "description": "Associate one or more campaigns with this conversion rule. Returns a per-campaign success/failure result so callers can retry only the rows that failed (e.g. wrong campaign type for the rule's objective).", "parameters": [{ "name": "accountId", "in": "path", "required": true, "schemaType": "string" }, { "name": "destinationId", "in": "path", "required": true, "schemaType": "string" }], "requestBody": { "required": true, "contentTypes": ["application/json"], "schemaRefs": { "application/json": { "schemaType": "object" } } } }, { "operationId": "getConversionMetrics", "method": "GET", "path": "/v1/accounts/{accountId}/conversion-destinations/{destinationId}/metrics", "tags": ["Ads"], "summary": "Fetch attribution metrics for a conversion destination", "description": "LinkedIn-only today. Returns conversion-attribution metrics (`externalWebsiteConversions`, `externalWebsitePostClickConversions`, `externalWebsitePostViewConversions`, `conversionValueInLocalCurrency`, `qualifiedLeads`, `costInLocalCurrency`) bucketed by date. Date-range constraints (passed through from LinkedIn): - `granularity=DAILY` is retained for ~6 months only - `granularity=ALL` with a range > 6 months auto-rounds to month boundaries - `granularity=MONTHLY`/`YEARLY` retains 24 months Thr...", "parameters": [{ "name": "accountId", "in": "path", "required": true, "schemaType": "string" }, { "name": "destinationId", "in": "path", "required": true, "schemaType": "string" }, { "name": "adAccountId", "in": "query", "required": true, "schemaType": "string" }, { "name": "startDate", "in": "query", "required": true, "schemaType": "string" }, { "name": "endDate", "in": "query", "required": false, "schemaType": "string" }, { "name": "granularity", "in": "query", "required": false, "schemaType": "string", "schemaEnum": ["ALL", "DAILY", "MONTHLY", "YEARLY"] }] }, { "operationId": "listAds", "method": "GET", "path": "/v1/ads", "tags": ["Ads"], "summary": "List ads", "description": "Returns a paginated list of ads with metrics computed over an optional date range. Use source=all to include externally-synced ads from platform ad managers. If no date range is provided, defaults to the last 90 days. Date range is capped at 730 days max. To find the Zernio ad behind a comment you see in Meta Business Manager, filter by platformAdId (the Meta ad ID), effectiveObjectStoryId (Facebook), or effectiveInstagramMediaId (Instagram) — those are the post/media the ad's engagement lives ...", "parameters": [{ "name": "page", "in": "query", "required": false, "description": "Page number (1-based)", "schemaType": "integer" }, { "name": "limit", "in": "query", "required": false, "schemaType": "integer" }, { "name": "source", "in": "query", "required": false, "description": "all (default) = Zernio-created + platform-discovered ads. zernio = restrict to Zernio-created only.", "schemaType": "string", "schemaEnum": ["zernio", "all"] }, { "name": "status", "in": "query", "required": false, "schemaType": "string", "schemaEnum": ["active", "paused", "pending_review", "rejected", "completed", "cancelled", "error"] }, { "name": "platform", "in": "query", "required": false, "schemaType": "string", "schemaEnum": ["facebook", "instagram", "tiktok", "linkedin", "pinterest", "google", "twitter"] }, { "name": "accountId", "in": "query", "required": false, "description": "Social account ID", "schemaType": "string" }, { "name": "adAccountId", "in": "query", "required": false, "description": "Platform ad account ID (e.g. act_123 for Meta). Mirrors the same filter on /v1/ads/campaigns and /v1/ads/tree.", "schemaType": "string" }, { "name": "profileId", "in": "query", "required": false, "description": "Profile ID", "schemaType": "string" }, { "name": "campaignId", "in": "query", "required": false, "description": "Platform campaign ID (filter ads within a campaign)", "schemaType": "string" }, { "name": "platformAdId", "in": "query", "required": false, "description": "Meta ad ID. Returns the ad with this platform-side ad ID.", "schemaType": "string" }, { "name": "effectiveObjectStoryId", "in": "query", "required": false, "description": "Facebook `{pageId}_{postId}` of the post the ad's engagement lives on (Meta `effective_object_story_id`). Use to map a Business-Manager-visible post back to the Zernio ad.", "schemaType": "string" }, { "name": "effectiveInstagramMediaId", "in": "query", "required": false, "description": "Instagram media ID of the boosted post (Meta `effective_instagram_media_id`). Use to map a Business-Manager-visible IG post back to the Zernio ad.", "schemaType": "string" }, { "name": "fromDate", "in": "query", "required": false, "description": "Start of metrics date range (YYYY-MM-DD). Defaults to 90 days ago.", "schemaType": "string" }, { "name": "toDate", "in": "query", "required": false, "description": "End of metrics date range (YYYY-MM-DD). Defaults to today. Max 730-day range.", "schemaType": "string" }] }, { "operationId": "deleteAd", "method": "DELETE", "path": "/v1/ads/{adId}", "tags": ["Ads"], "summary": "Cancel an ad", "description": "Cancels the ad on the platform and marks it as cancelled in the database. The ad is preserved for history.", "parameters": [{ "name": "adId", "in": "path", "required": true, "schemaType": "string" }] }, { "operationId": "getAd", "method": "GET", "path": "/v1/ads/{adId}", "tags": ["Ads"], "summary": "Get ad details", "description": "Returns an ad with its creative, targeting, status, and performance metrics. The `{adId}` path segment accepts any identifier dialect Zernio indexes for the ad: - the Zernio internal `_id` (24-char hex) - Meta's numeric `platformAdId` (the value shipped in `comment.received` webhooks as `comment.ad.id`) - the creative's `effective_object_story_id` (`{pageId}_{postId}` shape, Facebook side) - the creative's `effective_instagram_media_id` (Instagram side) Any of the four resolve to the same ad. C...", "parameters": [{ "name": "adId", "in": "path", "required": true, "description": "Zernio `_id` (hex), Meta `platformAdId` (numeric), or one of the creative's effective story/media IDs. See description for details.", "schemaType": "string" }] }, { "operationId": "updateAd", "method": "PUT", "path": "/v1/ads/{adId}", "tags": ["Ads"], "summary": "Update ad", "description": "Patch one or more fields on an ad. Status, budget, targeting, and creative changes are propagated to the platform. Per-platform support: - **Meta** (Facebook + Instagram): all fields supported. - **TikTok**: status, budget, targeting (via `/v2/adgroup/update/`), and creative (via `/v2/ad/update/` patch-style — `headline` is ignored, `body` becomes `ad_text`). - **Pinterest / X / LinkedIn / Google**: status + budget only. Sending `targeting` or `creative` returns 501 with code `unsupported_platf...", "parameters": [{ "name": "adId", "in": "path", "required": true, "schemaType": "string" }], "requestBody": { "required": true, "contentTypes": ["application/json"], "schemaRefs": { "application/json": { "schemaType": "object" } } } }, { "operationId": "getAdAnalytics", "method": "GET", "path": "/v1/ads/{adId}/analytics", "tags": ["Ads"], "summary": "Get ad analytics", "description": "Returns detailed performance analytics for an ad. Includes summary metrics, a daily timeline over the requested date range, and optional demographic breakdowns (Meta and TikTok only). If no date range is provided, defaults to the last 90 days. Date range is capped at 730 days max.", "parameters": [{ "name": "adId", "in": "path", "required": true, "schemaType": "string" }, { "name": "fromDate", "in": "query", "required": false, "description": "Start of date range (YYYY-MM-DD). Defaults to 90 days ago.", "schemaType": "string" }, { "name": "toDate", "in": "query", "required": false, "description": "End of date range (YYYY-MM-DD). Defaults to today. Max 730-day range.", "schemaType": "string" }, { "name": "breakdowns", "in": "query", "required": false, "description": "Comma-separated breakdown dimensions. Meta: age, gender, country, publisher_platform, device_platform, region. TikTok: gender, age, country_code, platform, ac, language.", "schemaType": "string" }] }, { "operationId": "getAdComments", "method": "GET", "path": "/v1/ads/{adId}/comments", "tags": ["Ads"], "summary": "List comments on an ad", "description": "Returns comments on an ad's underlying creative post. Useful for moderating or analyzing engagement on dark posts (ad creatives that never went live organically), which the regular GET /v1/inbox/comments/{postId} endpoint cannot serve because dark posts are not in Zernio's post database. An ad that runs on both Facebook feed and Instagram feed has two separate underlying posts with separate comment threads (the creative's effective_object_story_id and effective_instagram_media_id). Use the `pla...", "parameters": [{ "name": "adId", "in": "path", "required": true, "description": "Internal Zernio ad ID (ObjectId).", "schemaType": "string" }, { "name": "placement", "in": "query", "required": false, "description": "Which side of the ad to return comments for. Omit to default to the Instagram side when present, else Facebook. Returns ad_not_commentable if the ad has no such placement.", "schemaType": "string", "schemaEnum": ["facebook", "instagram"] }, { "name": "limit", "in": "query", "required": false, "schemaType": "integer" }, { "name": "cursor", "in": "query", "required": false, "description": "Pagination cursor from a previous response.", "schemaType": "string" }] }, { "operationId": "getAdTrackingTags", "method": "GET", "path": "/v1/ads/{adId}/tracking-tags", "tags": ["Ads"], "summary": "Read an ad's click-URL tracking tags", "description": "Unified read of the platform's native click-URL tracking params. - Meta (facebook/instagram): the creative's `url_tags` (and template_url_spec). - Google (googleads): the campaign's `trackingUrlTemplate` + `finalUrlSuffix`. Subject to the Google Ads API access-tier daily quota; bulk audits need Standard access. - LinkedIn (linkedinads): the campaign's Dynamic UTM `dynamicValueParameters` + `customValueParameters`. Returns 405 for platforms without a click-URL tracking surface (TikTok, X, Pinter...", "parameters": [{ "name": "adId", "in": "path", "required": true, "description": "Ad id (hex _id, platformAdId, or effective story/media id).", "schemaType": "string" }] }, { "operationId": "updateAdTrackingTags", "method": "PATCH", "path": "/v1/ads/{adId}/tracking-tags", "tags": ["Ads"], "summary": "Set/update an ad's click-URL tracking tags", "description": "Unified update. Send only the fields for the ad's platform: - Meta: `urlTags` (array of {key,value}). Meta creatives are immutable, so this rebuilds the creative and repoints the ad. By DEFAULT we PRESERVE the existing creative verbatim (re-post its object_story_spec + the new url_tags, reusing the image), so you send `urlTags` ALONE — no need to read back headline/body/CTA. `creative` (headline, body, callToAction, linkUrl, imageUrl) is OPTIONAL and only needed to rebuild explicitly, or for SH...", "parameters": [{ "name": "adId", "in": "path", "required": true, "schemaType": "string" }], "requestBody": { "required": true, "contentTypes": ["application/json"], "schemaRefs": { "application/json": { "schemaType": "object" } } } }, { "operationId": "listAdAccounts", "method": "GET", "path": "/v1/ads/accounts", "tags": ["Ads"], "summary": "List ad accounts", "description": "Returns the platform ad accounts available for the given social account (e.g. Meta ad accounts, TikTok advertiser IDs, Google Ads customer IDs). For TikTok agencies: enumerates every advertiser under every Business Center the token can read (paginated server-side), then chunks the lookup against TikTok's `/advertiser/info/` endpoint (which has a per-call cap of ≤100 IDs). Solo advertisers without a BC fall back to the OAuth-time `advertiser_ids` list. Cached for 1h on the SocialAccount; lazy-re...", "parameters": [{ "name": "accountId", "in": "query", "required": true, "description": "Social account ID", "schemaType": "string" }, { "name": "adAccountId", "in": "query", "required": false, "description": "Filter response to a single platform ad account ID (e.g. `act_123` for Meta, advertiser_id for TikTok). Returns at most one item.", "schemaType": "string" }, { "name": "limit", "in": "query", "required": false, "description": "Clamp the returned `accounts[]` length. Useful for typeahead pickers on agency tokens with hundreds of advertisers.", "schemaType": "integer" }] }, { "operationId": "boostPost", "method": "POST", "path": "/v1/ads/boost", "tags": ["Ads"], "summary": "Boost post as ad", "description": "Creates a paid ad campaign from an existing published post. Creates the full platform campaign hierarchy (campaign, ad set, ad).", "parameters": [], "requestBody": { "required": true, "contentTypes": ["application/json"], "schemaRefs": { "application/json": { "schemaType": "object" } } } }, { "operationId": "listAdsBusinessCenters", "method": "GET", "path": "/v1/ads/business-centers", "tags": ["Ads"], "summary": "List TikTok Business Centers", "description": "Returns the TikTok Business Centers (BCs) the connected `tiktokads` account can read. Each BC reports its advertiser count so callers can build agency-style pickers without re-walking `/v1/ads/accounts` per BC. TikTok-only. Solo advertisers (non-agency tokens) return an empty array.", "parameters": [{ "name": "accountId", "in": "query", "required": true, "description": "ID of the `tiktokads` (or parent `tiktok` posting) SocialAccount", "schemaType": "string" }] }, { "operationId": "listAdCatalogs", "method": "GET", "path": "/v1/ads/catalogs", "tags": ["Ads"], "summary": "List Meta product catalogs", "description": "Lists the Meta product catalogs reachable from an ad account (owned + agency-shared catalogs of the ad account's business), for Advantage+ catalog ads (`goal: catalog_sales` on POST /v1/ads/create — e.g. vehicle inventory catalogs). Read-only; uses scopes customers already granted (no reconnect needed). Catalog contents (items, feeds) are managed in Meta Commerce Manager, not through this API.", "parameters": [{ "name": "accountId", "in": "query", "required": true, "description": "A facebook, instagram, or metaads social account ID", "schemaType": "string" }, { "name": "adAccountId", "in": "query", "required": true, "description": "Meta ad account ID (act_...)", "schemaType": "string" }] }, { "operationId": "listAdCatalogProductSets", "method": "GET", "path": "/v1/ads/catalogs/{catalogId}/product-sets", "tags": ["Ads"], "summary": "List a catalog's product sets", "description": "Lists a Meta product catalog's product sets — the unit a catalog ad promotes. Pass the chosen set as `promotedObject.productSetId` on POST /v1/ads/create with `goal: catalog_sales`.", "parameters": [{ "name": "catalogId", "in": "path", "required": true, "description": "Meta product catalog ID (from GET /v1/ads/catalogs)", "schemaType": "string" }, { "name": "accountId", "in": "query", "required": true, "description": "A facebook, instagram, or metaads social account ID", "schemaType": "string" }] }, { "operationId": "sendConversions", "method": "POST", "path": "/v1/ads/conversions", "tags": ["Ads"], "summary": "Send conversion events to an ad platform", "description": "Relay one or more conversion events to the target ad platform's native Conversions API. Platform is inferred from the provided `accountId`. Requires the Ads add-on. Supported platforms: - Meta (`metaads`) via Graph API - Google Ads (`googleads`) via Data Manager API `ingestEvents` - LinkedIn (`linkedinads`) via `/rest/conversionEvents` `destinationId` semantics differ per platform: - Meta: pixel (dataset) ID, e.g. `123456789012345` - Google: conversion action resource name, e.g. `customers/1234...", "parameters": [], "requestBody": { "required": true, "contentTypes": ["application/json"], "schemaRefs": { "application/json": { "schemaType": "object" } } } }, { "operationId": "adjustConversions", "method": "POST", "path": "/v1/ads/conversions/adjustments", "tags": ["Ads"], "summary": "Adjust already-uploaded conversions (Google only)", "description": "Adjust conversions that were previously uploaded via `POST /v1/ads/conversions` — retract them, restate their value, or enhance them with first-party data. Requires the Ads add-on. **Google Ads only.** Google handles adjustments through the classic Google Ads API (`ConversionAdjustmentUploadService`); the Data Manager `ingestEvents` path used for sending conversions is ingest-only. Meta and LinkedIn have no equivalent, so this endpoint returns `405` for those platforms. Adjustment types: - `RET...", "parameters": [], "requestBody": { "required": true, "contentTypes": ["application/json"], "schemaRefs": { "application/json": { "schemaType": "object" } } } }, { "operationId": "getConversionsQuality", "method": "GET", "path": "/v1/ads/conversions/quality", "tags": ["Ads"], "summary": "Read Event Match Quality + coverage for a Meta pixel", "description": "Reads Meta Event Match Quality (EMQ) and pixel↔CAPI event coverage for a pixel/dataset, live from Meta's Dataset Quality API. Web events only (a Meta limitation). Meta-only; other platforms return 405. Requires the Ads add-on.", "parameters": [{ "name": "accountId", "in": "query", "required": true, "description": "SocialAccount _id (must be a metaads account).", "schemaType": "string" }, { "name": "destinationId", "in": "query", "required": true, "description": "Meta pixel/dataset ID.", "schemaType": "string" }] }, { "operationId": "createStandaloneAd", "method": "POST", "path": "/v1/ads/create", "tags": ["Ads"], "summary": "Create standalone ad", "description": "Creates a paid ad with custom creative across Meta, Google Ads, Pinterest, TikTok, X/Twitter, and LinkedIn. Supports three mutually-exclusive request shapes selected by the body, a legacy single-creative shape (all platforms, default), a Meta-only multi-creative shape via the creatives array (one ad set with N ads sharing budget and targeting), and a Meta-only attach shape via adSetId (adds one new ad to an existing ad set). Per-platform required fields, budget minimums, and video-ad rules are ...", "parameters": [{ "name": "Idempotency-Key", "in": "header", "required": false, "description": "Optional client-generated unique key (e.g. a UUID) that makes create retries safe. Same key + same body replays the original response; same key + different body → 422; key still processing → 409.", "schemaType": "string" }], "requestBody": { "required": true, "contentTypes": ["application/json"], "schemaRefs": { "application/json": { "schemaType": "object" } } } }, { "operationId": "createCtwaAd", "method": "POST", "path": "/v1/ads/ctwa", "tags": ["Ads"], "summary": "Create Click-to-WhatsApp ad(s)", "description": "Creates one or more Click-to-WhatsApp (CTWA) ads on Meta under a single campaign and ad set. When tapped, each ad opens a WhatsApp conversation with the business attached to the supplied Facebook Page. The full hierarchy (campaign, ad set, creative(s), ad(s)) is created and activated in one call. The CTA is locked to WHATSAPP_MESSAGE and the destination is hard-coded to api.whatsapp.com/send; Meta resolves the actual WhatsApp number from the Page-to-WA pairing configured in Page settings or Bus...", "parameters": [], "requestBody": { "required": true, "contentTypes": ["application/json"], "schemaRefs": { "application/json": { "schemaType": "object" } } } }, { "operationId": "searchAdInterests", "method": "GET", "path": "/v1/ads/interests", "tags": ["Ads"], "summary": "Search targeting interests (deprecated)", "description": "Deprecated alias for `GET /v1/ads/targeting/search?dimension=interest`. Kept for backward compatibility, it returns the legacy `{ interests: [...] }` shape rather than the normalized `{ results: [...] }`. New integrations should use `GET /v1/ads/targeting/search` with `dimension=interest`.", "parameters": [{ "name": "q", "in": "query", "required": true, "description": "Search query", "schemaType": "string" }, { "name": "accountId", "in": "query", "required": true, "description": "Social account ID", "schemaType": "string" }] }, { "operationId": "listLeadForms", "method": "GET", "path": "/v1/ads/lead-forms", "tags": ["Ads"], "summary": "List Lead Gen (Instant) forms", "description": "Lists the Lead Gen forms owned by the connected Facebook Page. Requires the Ads add-on.", "parameters": [{ "name": "accountId", "in": "query", "required": true, "description": "Connected facebook account id.", "schemaType": "string" }, { "name": "limit", "in": "query", "required": false, "schemaType": "integer" }, { "name": "cursor", "in": "query", "required": false, "schemaType": "string" }] }, { "operationId": "createLeadForm", "method": "POST", "path": "/v1/ads/lead-forms", "tags": ["Ads"], "summary": "Create a Lead Gen (Instant) form", "description": "Creates a Lead Gen form on the connected Facebook Page (POST /{page-id}/leadgen_forms). NOT idempotent — a retry creates a second form. Prefilled question types (EMAIL, PHONE, FULL_NAME, …) must omit label/key; CUSTOM questions require both. Requires the Ads add-on.", "parameters": [], "requestBody": { "required": true, "contentTypes": ["application/json"], "schemaRefs": { "application/json": { "schemaType": "object" } } } }, { "operationId": "archiveLeadForm", "method": "DELETE", "path": "/v1/ads/lead-forms/{formId}", "tags": ["Ads"], "summary": "Archive a Lead Gen form", "description": "Meta has no hard delete for forms; this archives the form (status=ARCHIVED).", "parameters": [{ "name": "formId", "in": "path", "required": true, "schemaType": "string" }, { "name": "accountId", "in": "query", "required": true, "schemaType": "string" }] }, { "operationId": "getLeadForm", "method": "GET", "path": "/v1/ads/lead-forms/{formId}", "tags": ["Ads"], "summary": "Get a single Lead Gen form", "parameters": [{ "name": "formId", "in": "path", "required": true, "schemaType": "string" }, { "name": "accountId", "in": "query", "required": true, "schemaType": "string" }] }, { "operationId": "listFormLeads", "method": "GET", "path": "/v1/ads/lead-forms/{formId}/leads", "tags": ["Ads"], "summary": "List leads for a single form", "description": "Returns leads for one form. Serves persisted leads (ingested via the leadgen webhook) when available, falling back to a live Graph read.", "parameters": [{ "name": "formId", "in": "path", "required": true, "schemaType": "string" }, { "name": "accountId", "in": "query", "required": true, "schemaType": "string" }, { "name": "limit", "in": "query", "required": false, "schemaType": "integer" }, { "name": "cursor", "in": "query", "required": false, "schemaType": "string" }, { "name": "since", "in": "query", "required": false, "description": "Unix seconds.", "schemaType": "integer" }] }, { "operationId": "createTestLead", "method": "POST", "path": "/v1/ads/lead-forms/{formId}/test-leads", "tags": ["Ads"], "summary": "Create a synthetic test lead", "description": "Submits a test lead against the form (POST /{form-id}/test_leads) to exercise retrieval without waiting for real ad impressions. Meta allows one test lead per form at a time.", "parameters": [{ "name": "formId", "in": "path", "required": true, "schemaType": "string" }], "requestBody": { "required": true, "contentTypes": ["application/json"], "schemaRefs": { "application/json": { "schemaType": "object" } } } }, { "operationId": "listLeads", "method": "GET", "path": "/v1/ads/leads", "tags": ["Ads"], "summary": "List submitted leads (cross-form CRM view)", "description": "Returns persisted Meta Lead Gen leads for your team, newest-first, with keyset pagination on `cursor`. Leads are ingested in real time from the `leadgen` webhook. Requires the Ads add-on.", "parameters": [{ "name": "formId", "in": "query", "required": false, "description": "Filter to a single lead form.", "schemaType": "string" }, { "name": "accountId", "in": "query", "required": false, "description": "Filter to a single connected account.", "schemaType": "string" }, { "name": "limit", "in": "query", "required": false, "schemaType": "integer" }, { "name": "since", "in": "query", "required": false, "description": "Unix seconds; only leads created at/after this Meta timestamp.", "schemaType": "integer" }, { "name": "cursor", "in": "query", "required": false, "description": "Keyset cursor from a previous response's pagination.cursor.", "schemaType": "string" }] }, { "operationId": "estimateAdReach", "method": "POST", "path": "/v1/ads/targeting/reach-estimate", "tags": ["Ads"], "summary": "Estimate audience reach", "description": "Returns a normalized pre-flight audience-size estimate for a targeting spec, before any campaign is created. Backed by each platform's native reach API (Meta `delivery_estimate`, LinkedIn `audienceCounts`, X `audience_summary`, Pinterest `audience_sizing`). Platforms without a usable pre-flight reach API (Google Search/Display, TikTok) return `available: false` with no bounds, so clients can hide or grey out the estimate rather than treat the absence as an error.", "parameters": [], "requestBody": { "required": true, "contentTypes": ["application/json"], "schemaRefs": { "application/json": { "schemaType": "object" } } } }, { "operationId": "searchAdTargeting", "method": "GET", "path": "/v1/ads/targeting/search", "tags": ["Ads"], "summary": "Search targeting options", "description": "Resolve a human-readable query into the platform's opaque targeting ids used in the `TargetingSpec` (`countries`/`regions`/`cities`/`zips`/`metros` geo keys, and `interests`/`behaviors` entity ids) on `POST /v1/ads/create`, `POST /v1/ads/targeting/reach-estimate`, and `saved_targeting` audiences. The `dimension` param selects what is searched, `geo` (locations, further scoped by `geoType`), `interest`, `behavior`, or `income`. Availability of each dimension varies by platform (e.g. behaviours a...", "parameters": [{ "name": "accountId", "in": "query", "required": true, "description": "Social account ID (a connected account on the target ad platform).", "schemaType": "string" }, { "name": "q", "in": "query", "required": true, "description": "Search query. For geo, the locality name only (no region/country suffix).", "schemaType": "string" }, { "name": "dimension", "in": "query", "required": false, "description": "What to search. `geo` resolves locations (scope further with `geoType`), `interest`/`behavior` resolve audience entities, `income` resolves income-tier options. Defaults to `interest` for backward compatibility with the deprecated /v1/ads/...", "schemaType": "string", "schemaEnum": ["geo", "interest", "behavior", "income"] }, { "name": "geoType", "in": "query", "required": false, "description": "Only used when `dimension=geo`. The kind of location to resolve. Defaults to `city`.", "schemaType": "string", "schemaEnum": ["country", "region", "city", "zip", "metro"] }, { "name": "countryCode", "in": "query", "required": false, "description": "ISO 3166-1 alpha-2 country code (e.g. NL) to scope a geo search.", "schemaType": "string" }, { "name": "limit", "in": "query", "required": false, "description": "Maximum results to return.", "schemaType": "integer" }] }, { "operationId": "getLinkedInAggregateAnalytics", "method": "GET", "path": "/v1/accounts/{accountId}/linkedin-aggregate-analytics", "tags": ["Analytics"], "summary": "Get LinkedIn aggregate stats", "description": "Returns aggregate analytics across all posts for a LinkedIn personal account. Only includes posts published through Zernio (LinkedIn API limitation). Org accounts should use /v1/analytics instead. Requires r_member_postAnalytics scope. Saves (POST_SAVE) and sends (POST_SEND) are available for personal accounts; organization pages always return 0 for these two metrics because LinkedIn does not expose them on the organization analytics endpoint.", "parameters": [{ "name": "accountId", "in": "path", "required": true, "description": "The ID of the LinkedIn personal account", "schemaType": "string" }, { "name": "aggregation", "in": "query", "required": false, "description": "TOTAL (default, lifetime totals) or DAILY (time series). MEMBERS_REACHED not available with DAILY.", "schemaType": "string", "schemaEnum": ["TOTAL", "DAILY"] }, { "name": "startDate", "in": "query", "required": false, "description": "Start date (YYYY-MM-DD). If omitted, returns lifetime analytics.", "schemaType": "string" }, { "name": "endDate", "in": "query", "required": false, "description": "End date (YYYY-MM-DD, exclusive). Defaults to today if omitted.", "schemaType": "string" }, { "name": "metrics", "in": "query", "required": false, "description": "Comma-separated metrics: IMPRESSION, MEMBERS_REACHED, REACTION, COMMENT, RESHARE, POST_SAVE, POST_SEND. Omit for all.", "schemaType": "string" }] }, { "operationId": "getLinkedInPostAnalytics", "method": "GET", "path": "/v1/accounts/{accountId}/linkedin-post-analytics", "tags": ["Analytics"], "summary": "Get LinkedIn post stats", "description": "Returns analytics for a specific LinkedIn post by URN. Works for both personal and organization accounts. Saves and sends are only populated for personal accounts (LinkedIn does not expose these metrics on the organization analytics endpoint).", "parameters": [{ "name": "accountId", "in": "path", "required": true, "description": "The ID of the LinkedIn account", "schemaType": "string" }, { "name": "urn", "in": "query", "required": true, "description": "The LinkedIn post URN", "schemaType": "string" }] }, { "operationId": "getLinkedInPostReactions", "method": "GET", "path": "/v1/accounts/{accountId}/linkedin-post-reactions", "tags": ["Analytics"], "summary": "Get LinkedIn post reactions", "description": "Returns individual reactions for a specific LinkedIn post, including reactor profiles (name, headline/job title, profile picture, profile URL, reaction type). Only works for organization/company page accounts. LinkedIn restricts reaction data for personal profiles (r_member_social_feed is a closed permission).", "parameters": [{ "name": "accountId", "in": "path", "required": true, "description": "The ID of the LinkedIn organization account", "schemaType": "string" }, { "name": "urn", "in": "query", "required": true, "description": "The LinkedIn post URN", "schemaType": "string" }, { "name": "limit", "in": "query", "required": false, "description": "Maximum number of reactions to return per page", "schemaType": "integer" }, { "name": "cursor", "in": "query", "required": false, "description": "Offset-based pagination start index", "schemaType": "string" }] }, { "operationId": "getAnalytics", "method": "GET", "path": "/v1/analytics", "tags": ["Analytics"], "summary": "Get post analytics", "description": "Returns analytics for posts. With postId, returns a single post. Without it, returns a paginated list with overview stats. Accepts both Zernio Post IDs and External Post IDs (auto-resolved). fromDate defaults to 90 days ago if omitted, max range 366 days. Single post lookups may return 202 (sync pending) or 424 (all platforms failed). For follower stats, use /v1/accounts/follower-stats. LinkedIn personal accounts: Analytics are only available for posts published through Zernio. LinkedIn's API o...", "parameters": [{ "name": "postId", "in": "query", "required": false, "description": "Returns analytics for a single post. Accepts both Zernio Post IDs and External Post IDs. Zernio IDs are auto-resolved to External Post analytics.", "schemaType": "string" }, { "name": "platform", "in": "query", "required": false, "description": "Filter by platform (default \"all\")", "schemaType": "string" }, { "name": "profileId", "in": "query", "required": false, "description": "Filter by profile ID (default \"all\")", "schemaType": "string" }, { "name": "accountId", "in": "query", "required": false, "description": "Filter by social account ID", "schemaType": "string" }, { "name": "source", "in": "query", "required": false, "description": "Filter by post source: late (posted via Zernio API), external (synced from platform), all (default)", "schemaType": "string", "schemaEnum": ["all", "late", "external"] }, { "name": "fromDate", "in": "query", "required": false, "description": "Inclusive lower bound (YYYY-MM-DD). Defaults to 90 days ago if omitted. Max range is 366 days.", "schemaType": "string" }, { "name": "toDate", "in": "query", "required": false, "description": "Inclusive upper bound (YYYY-MM-DD). Defaults to today if omitted.", "schemaType": "string" }, { "name": "limit", "in": "query", "required": false, "description": "Page size (default 50)", "schemaType": "integer" }, { "name": "page", "in": "query", "required": false, "description": "Page number (default 1)", "schemaType": "integer" }, { "name": "sortBy", "in": "query", "required": false, "description": "Sort by date, engagement, or a specific metric", "schemaType": "string", "schemaEnum": ["date", "engagement", "impressions", "reach", "likes", "comments", "shares", "saves", "clicks", "views"] }, { "name": "order", "in": "query", "required": false, "description": "Sort order", "schemaType": "string", "schemaEnum": ["asc", "desc"] }] }, { "operationId": "getBestTimeToPost", "method": "GET", "path": "/v1/analytics/best-time", "tags": ["Analytics"], "summary": "Get best times to post", "description": "Returns the best times to post based on historical engagement data. Groups all published posts by day of week and hour (UTC), calculating average engagement per slot. Use this to auto-schedule posts at optimal times. Requires the Analytics add-on.", "parameters": [{ "name": "platform", "in": "query", "required": false, "description": "Filter by platform (e.g. \"instagram\", \"tiktok\"). Omit for all platforms.", "schemaType": "string" }, { "name": "profileId", "in": "query", "required": false, "description": "Filter by profile ID. Omit for all profiles.", "schemaType": "string" }, { "name": "accountId", "in": "query", "required": false, "description": "Filter by social account ID. Omit for all accounts.", "schemaType": "string" }, { "name": "source", "in": "query", "required": false, "description": "Filter by post origin. \"late\" for posts published via Zernio, \"external\" for posts imported from platforms.", "schemaType": "string", "schemaEnum": ["all", "late", "external"] }] }, { "operationId": "getContentDecay", "method": "GET", "path": "/v1/analytics/content-decay", "tags": ["Analytics"], "summary": "Get content performance decay", "description": "Returns how engagement accumulates over time after a post is published. Each bucket shows what percentage of the post's total engagement had been reached by that time window. Useful for understanding content lifespan (e.g. \"posts reach 78% of total engagement within 24 hours\"). Requires the Analytics add-on.", "parameters": [{ "name": "platform", "in": "query", "required": false, "description": "Filter by platform (e.g. \"instagram\", \"tiktok\"). Omit for all platforms.", "schemaType": "string" }, { "name": "profileId", "in": "query", "required": false, "description": "Filter by profile ID. Omit for all profiles.", "schemaType": "string" }, { "name": "accountId", "in": "query", "required": false, "description": "Filter by social account ID. Omit for all accounts.", "schemaType": "string" }, { "name": "source", "in": "query", "required": false, "description": "Filter by post origin. \"late\" for posts published via Zernio, \"external\" for posts imported from platforms.", "schemaType": "string", "schemaEnum": ["all", "late", "external"] }] }, { "operationId": "getDailyMetrics", "method": "GET", "path": "/v1/analytics/daily-metrics", "tags": ["Analytics"], "summary": "Get daily aggregated metrics", "description": "Returns daily aggregated analytics metrics and a per-platform breakdown. Each day includes post count, platform distribution, and summed metrics (impressions, reach, likes, comments, shares, saves, clicks, views). Defaults to the last 180 days. Requires the Analytics add-on.", "parameters": [{ "name": "platform", "in": "query", "required": false, "description": "Filter by platform (e.g. \"instagram\", \"tiktok\"). Omit for all platforms.", "schemaType": "string" }, { "name": "profileId", "in": "query", "required": false, "description": "Filter by profile ID. Omit for all profiles.", "schemaType": "string" }, { "name": "accountId", "in": "query", "required": false, "description": "Filter by social account ID", "schemaType": "string" }, { "name": "fromDate", "in": "query", "required": false, "description": "Inclusive start date (ISO 8601). Defaults to 180 days ago.", "schemaType": "string" }, { "name": "toDate", "in": "query", "required": false, "description": "Inclusive end date (ISO 8601). Defaults to now.", "schemaType": "string" }, { "name": "source", "in": "query", "required": false, "description": "Filter by post origin. \"late\" for posts published via Zernio, \"external\" for posts imported from platforms.", "schemaType": "string", "schemaEnum": ["all", "late", "external"] }, { "name": "attribution", "in": "query", "required": false, "description": "How each post's engagement is attributed to a day. \"publish\" (default) sums each post's lifetime total on its publish date. \"received\" buckets the per-day increase in engagement by the day it actually arrived (engagement-over-time), so eng...", "schemaType": "string", "schemaEnum": ["publish", "received"] }] }, { "operationId": "getFacebookPageInsights", "method": "GET", "path": "/v1/analytics/facebook/page-insights", "tags": ["Analytics"], "summary": "Get Facebook Page insights", "description": "Returns page-level Facebook insights (media views, views, post engagements, video metrics, follower counts). Response shape matches /v1/analytics/instagram/account-insights so the same client handling works across platforms. Metric names track the current (post-November 2025) Meta Graph API. The legacy page_impressions / page_fans / page_fan_adds / page_fan_removes metrics were deprecated by Meta on November 15, 2025 and are NOT accepted by this endpoint. Use the replacements below. Because Met...", "parameters": [{ "name": "accountId", "in": "query", "required": true, "description": "The Zernio SocialAccount ID for the connected Facebook Page.", "schemaType": "string" }, { "name": "metrics", "in": "query", "required": false, "description": "Comma-separated list of metrics. Defaults to \"page_media_view,page_post_engagements,page_follows,followers_gained,followers_lost\". Live Meta metrics (current names, post-Nov-2025): - page_media_view (replaces deprecated page_impressions) -...", "schemaType": "string" }, { "name": "since", "in": "query", "required": false, "description": "Start date (YYYY-MM-DD). Defaults to 30 days ago.", "schemaType": "string" }, { "name": "until", "in": "query", "required": false, "description": "End date (YYYY-MM-DD). Defaults to today.", "schemaType": "string" }, { "name": "metricType", "in": "query", "required": false, "description": "\"total_value\" (default) returns aggregated totals only. \"time_series\" returns daily values in the \"values\" array.", "schemaType": "string", "schemaEnum": ["time_series", "total_value"] }] }, { "operationId": "getGoogleBusinessPerformance", "method": "GET", "path": "/v1/analytics/googlebusiness/performance", "tags": ["Analytics"], "summary": "Get GBP performance metrics", "description": "Returns daily performance metrics for a Google Business Profile location. Metrics include impressions (Maps/Search, desktop/mobile), website clicks, call clicks, direction requests, conversations, bookings, and food orders. Data may be delayed 2-3 days. Max 18 months of historical data. Requires the Analytics add-on.", "parameters": [{ "name": "accountId", "in": "query", "required": true, "description": "The Zernio SocialAccount ID for the Google Business Profile account.", "schemaType": "string" }, { "name": "metrics", "in": "query", "required": false, "description": "Comma-separated metric names. Defaults to all available metrics. Valid values: BUSINESS_IMPRESSIONS_DESKTOP_MAPS, BUSINESS_IMPRESSIONS_DESKTOP_SEARCH, BUSINESS_IMPRESSIONS_MOBILE_MAPS, BUSINESS_IMPRESSIONS_MOBILE_SEARCH, BUSINESS_CONVERSAT...", "schemaType": "string" }, { "name": "startDate", "in": "query", "required": false, "description": "Start date (YYYY-MM-DD). Defaults to 30 days ago. Max 18 months back.", "schemaType": "string" }, { "name": "endDate", "in": "query", "required": false, "description": "End date (YYYY-MM-DD). Defaults to today.", "schemaType": "string" }] }, { "operationId": "getGoogleBusinessSearchKeywords", "method": "GET", "path": "/v1/analytics/googlebusiness/search-keywords", "tags": ["Analytics"], "summary": "Get GBP search keywords", "description": "Returns search keywords that triggered impressions for a Google Business Profile location. Data is aggregated monthly. Keywords below a minimum impression threshold set by Google are excluded. Max 18 months of historical data. Requires the Analytics add-on.", "parameters": [{ "name": "accountId", "in": "query", "required": true, "description": "The Zernio SocialAccount ID for the Google Business Profile account.", "schemaType": "string" }, { "name": "startMonth", "in": "query", "required": false, "description": "Start month (YYYY-MM). Defaults to 3 months ago.", "schemaType": "string" }, { "name": "endMonth", "in": "query", "required": false, "description": "End month (YYYY-MM). Defaults to current month.", "schemaType": "string" }] }, { "operationId": "getInstagramAccountInsights", "method": "GET", "path": "/v1/analytics/instagram/account-insights", "tags": ["Analytics"], "summary": "Get Instagram insights", "description": "Returns account-level Instagram insights such as reach, views, accounts engaged, and total interactions. These metrics reflect the entire account's performance across all content surfaces (feed, stories, explore, profile), and are fundamentally different from post-level metrics. Data may be delayed up to 48 hours. Max 90 days, defaults to last 30 days. Requires the Analytics add-on.", "parameters": [{ "name": "accountId", "in": "query", "required": true, "description": "The Zernio SocialAccount ID for the Instagram account", "schemaType": "string" }, { "name": "metrics", "in": "query", "required": false, "description": "Comma-separated list of metrics. Defaults to \"reach,views,accounts_engaged,total_interactions\". Valid metrics: reach, views, accounts_engaged, total_interactions, comments, likes, saves, shares, replies, reposts, follows_and_unfollows, pro...", "schemaType": "string" }, { "name": "since", "in": "query", "required": false, "description": "Start date (YYYY-MM-DD). Defaults to 30 days ago.", "schemaType": "string" }, { "name": "until", "in": "query", "required": false, "description": "End date (YYYY-MM-DD). Defaults to today.", "schemaType": "string" }, { "name": "metricType", "in": "query", "required": false, "description": "\"total_value\" (default) returns aggregated totals and supports breakdowns. \"time_series\" returns daily values but only works with the \"reach\" metric.", "schemaType": "string", "schemaEnum": ["time_series", "total_value"] }, { "name": "breakdown", "in": "query", "required": false, "description": "Breakdown dimension (only valid with metricType=total_value). Valid values depend on the metric: media_product_type, follow_type, follower_type, contact_button_type.", "schemaType": "string" }] }, { "operationId": "getInstagramDemographics", "method": "GET", "path": "/v1/analytics/instagram/demographics", "tags": ["Analytics"], "summary": "Get Instagram demographics", "description": "Returns audience demographic insights for an Instagram account, broken down by age, city, country, and/or gender. Requires at least 100 followers. Returns top 45 entries per dimension. Data may be delayed up to 48 hours. Requires the Analytics add-on.", "parameters": [{ "name": "accountId", "in": "query", "required": true, "description": "The Zernio SocialAccount ID for the Instagram account", "schemaType": "string" }, { "name": "metric", "in": "query", "required": false, "description": "\"follower_demographics\" for follower audience data, or \"engaged_audience_demographics\" for engaged viewers.", "schemaType": "string", "schemaEnum": ["follower_demographics", "engaged_audience_demographics"] }, { "name": "breakdown", "in": "query", "required": false, "description": "Comma-separated list of demographic dimensions: age, city, country, gender. Defaults to all four if omitted.", "schemaType": "string" }, { "name": "timeframe", "in": "query", "required": false, "description": "Time period for demographic data. Defaults to \"this_month\".", "schemaType": "string", "schemaEnum": ["this_week", "this_month"] }] }, { "operationId": "getInstagramFollowerHistory", "method": "GET", "path": "/v1/analytics/instagram/follower-history", "tags": ["Analytics"], "summary": "Get Instagram follower history", "description": "Returns a daily running Instagram follower count time series, served from Zernio's cross-platform daily snapshotter. Exists because Meta removed follower_count from the /insights endpoint in Graph API v22+ and never exposed a historical daily series via any public API. Response envelope matches /v1/analytics/instagram/account-insights so the same client handling works. Max 89 days, defaults to last 30 days. Requires the Analytics add-on.", "parameters": [{ "name": "accountId", "in": "query", "required": true, "description": "The Zernio SocialAccount ID for the Instagram account.", "schemaType": "string" }, { "name": "metrics", "in": "query", "required": false, "description": "Comma-separated list. Defaults to \"follower_count,followers_gained,followers_lost\". - follower_count : per-day raw follower count - followers_gained : sum of positive daily deltas - followers_lost : sum of absolute negative daily deltas", "schemaType": "string" }, { "name": "since", "in": "query", "required": false, "description": "Start date (YYYY-MM-DD). Defaults to 30 days ago.", "schemaType": "string" }, { "name": "until", "in": "query", "required": false, "description": "End date (YYYY-MM-DD). Defaults to today.", "schemaType": "string" }, { "name": "metricType", "in": "query", "required": false, "description": "\"total_value\" returns aggregated totals (latest for follower_count, sum for gained/lost). \"time_series\" returns per-day values in the \"values\" array.", "schemaType": "string", "schemaEnum": ["time_series", "total_value"] }] }, { "operationId": "getLinkedInOrgAggregateAnalytics", "method": "GET", "path": "/v1/analytics/linkedin/org-aggregate-analytics", "tags": ["Analytics"], "summary": "Get LinkedIn organization page aggregate analytics", "description": "Returns aggregate analytics for a LinkedIn organization page. Parallel to /v1/accounts/{id}/linkedin-aggregate-analytics (which handles personal accounts only). Backed by LinkedIn's organizationalEntityShareStatistics, organizationalEntityFollowerStatistics, and organizationPageStatistics endpoints. Response shape matches /v1/analytics/instagram/account-insights. Max 89 days, defaults to last 30 days. Requires the Analytics add-on. Scope requirements: r_organization_social, r_organization_follo...", "parameters": [{ "name": "accountId", "in": "query", "required": true, "description": "The Zernio SocialAccount ID for the LinkedIn organization account.", "schemaType": "string" }, { "name": "metrics", "in": "query", "required": false, "description": "Comma-separated list. Defaults to \"impressions,clicks,engagement_rate,organic_followers_gained,followers_gained,followers_lost\". Share statistics (support both total_value and time_series): - impressions - unique_impressions - clicks - lik...", "schemaType": "string" }, { "name": "since", "in": "query", "required": false, "description": "Start date (YYYY-MM-DD). Defaults to 30 days ago.", "schemaType": "string" }, { "name": "until", "in": "query", "required": false, "description": "End date (YYYY-MM-DD). Defaults to today.", "schemaType": "string" }, { "name": "metricType", "in": "query", "required": false, "schemaType": "string", "schemaEnum": ["time_series", "total_value"] }] }, { "operationId": "getPostTimeline", "method": "GET", "path": "/v1/analytics/post-timeline", "tags": ["Analytics"], "summary": "Get post analytics timeline", "description": "Returns a daily timeline of analytics metrics for a specific post, showing how impressions, likes, and other metrics evolved day-by-day since publishing. Each row represents one day of data per platform. For multi-platform Zernio posts, returns separate rows for each platform. Requires the Analytics add-on.", "parameters": [{ "name": "postId", "in": "query", "required": true, "description": "The post to fetch timeline for. Accepts an ExternalPost ID, a platformPostId, or a Zernio Post ID.", "schemaType": "string" }, { "name": "fromDate", "in": "query", "required": false, "description": "Start of date range (ISO 8601). Defaults to 90 days ago.", "schemaType": "string" }, { "name": "toDate", "in": "query", "required": false, "description": "End of date range (ISO 8601). Defaults to now.", "schemaType": "string" }] }, { "operationId": "getPostingFrequency", "method": "GET", "path": "/v1/analytics/posting-frequency", "tags": ["Analytics"], "summary": "Get frequency vs engagement", "description": "Returns the correlation between posting frequency (posts per week) and engagement rate, broken down by platform. Helps find the optimal posting cadence for each platform. Each row represents a specific (platform, posts_per_week) combination with the average engagement rate observed across all weeks matching that frequency. Requires the Analytics add-on.", "parameters": [{ "name": "platform", "in": "query", "required": false, "description": "Filter by platform (e.g. \"instagram\", \"tiktok\"). Omit for all platforms.", "schemaType": "string" }, { "name": "profileId", "in": "query", "required": false, "description": "Filter by profile ID. Omit for all profiles.", "schemaType": "string" }, { "name": "accountId", "in": "query", "required": false, "description": "Filter by social account ID. Omit for all accounts.", "schemaType": "string" }, { "name": "source", "in": "query", "required": false, "description": "Filter by post origin. \"late\" for posts published via Zernio, \"external\" for posts imported from platforms.", "schemaType": "string", "schemaEnum": ["all", "late", "external"] }] }, { "operationId": "getTikTokAccountInsights", "method": "GET", "path": "/v1/analytics/tiktok/account-insights", "tags": ["Analytics"], "summary": "Get TikTok account-level insights", "description": "Returns account-level TikTok insights from /v2/user/info/ (live) plus historical time series joined from Zernio's daily snapshotter (AccountStats). Response shape matches /v1/analytics/instagram/account-insights. Max 89 days, defaults to last 30 days. Requires the Analytics add-on and the user.info.stats scope on the account (412 if missing). Scope intentionally narrow. TikTok's public API exposes only the four counter metrics below. The deep metrics that live in TikTok Studio are NOT available...", "parameters": [{ "name": "accountId", "in": "query", "required": true, "description": "The Zernio SocialAccount ID for the TikTok account.", "schemaType": "string" }, { "name": "metrics", "in": "query", "required": false, "description": "Comma-separated list. Defaults to \"follower_count,likes_count,video_count,followers_gained,followers_lost\". Live from /v2/user/info/ (requires user.info.stats scope): - follower_count (cumulative; time series joined from AccountStats) - fo...", "schemaType": "string" }, { "name": "since", "in": "query", "required": false, "description": "Start date (YYYY-MM-DD). Defaults to 30 days ago.", "schemaType": "string" }, { "name": "until", "in": "query", "required": false, "description": "End date (YYYY-MM-DD). Defaults to today.", "schemaType": "string" }, { "name": "metricType", "in": "query", "required": false, "description": "\"total_value\" returns the latest cumulative counter value. \"time_series\" returns daily values joined from AccountStats snapshots.", "schemaType": "string", "schemaEnum": ["time_series", "total_value"] }] }, { "operationId": "getYouTubeChannelInsights", "method": "GET", "path": "/v1/analytics/youtube/channel-insights", "tags": ["Analytics"], "summary": "Get YouTube channel-level insights", "description": "Returns channel-scoped aggregate metrics from YouTube Analytics API v2. Saves you from looping /v1/analytics/youtube/daily-views over every video when you only need channel totals. Response shape matches /v1/analytics/instagram/account-insights so the same client handling works. Requires yt-analytics.readonly scope (412 with reauthorizeUrl if missing). Data has a 2-3 day delay (endDate is clamped accordingly). Max 89 days, defaults to last 30 days. Requires the Analytics add-on. NOT exposed: im...", "parameters": [{ "name": "accountId", "in": "query", "required": true, "description": "The Zernio SocialAccount ID for the YouTube account.", "schemaType": "string" }, { "name": "metrics", "in": "query", "required": false, "description": "Comma-separated list. Defaults to \"views,estimatedMinutesWatched,subscribersGained,subscribersLost\". Live YouTube Analytics v2 metrics: - views - estimatedMinutesWatched - averageViewDuration (ratio - weighted mean computed across days) - ...", "schemaType": "string" }, { "name": "since", "in": "query", "required": false, "description": "Start date (YYYY-MM-DD). Defaults to 30 days ago.", "schemaType": "string" }, { "name": "until", "in": "query", "required": false, "description": "End date (YYYY-MM-DD). Defaults to today. YouTube Analytics has a 2-3 day delay, so the fetch is internally clamped to 3 days ago; any requested range extending beyond that returns zero values for the tail days. The response's dateRange.un...", "schemaType": "string" }, { "name": "metricType", "in": "query", "required": false, "description": "\"total_value\" (default) returns aggregated totals. \"time_series\" returns per-day values in the \"values\" array.", "schemaType": "string", "schemaEnum": ["time_series", "total_value"] }] }, { "operationId": "getYouTubeDailyViews", "method": "GET", "path": "/v1/analytics/youtube/daily-views", "tags": ["Analytics"], "summary": "Get YouTube daily views", "description": "Returns daily view counts for a YouTube video including views, watch time, and subscriber changes. Requires yt-analytics.readonly scope (re-authorization may be needed). Data has a 2-3 day delay. Max 90 days, defaults to last 30 days.", "parameters": [{ "name": "videoId", "in": "query", "required": true, "description": "The YouTube video ID (e.g., \"dQw4w9WgXcQ\")", "schemaType": "string" }, { "name": "accountId", "in": "query", "required": true, "description": "The Zernio account ID for the YouTube account", "schemaType": "string" }, { "name": "startDate", "in": "query", "required": false, "description": "Start date (YYYY-MM-DD). Defaults to 30 days ago.", "schemaType": "string" }, { "name": "endDate", "in": "query", "required": false, "description": "End date (YYYY-MM-DD). Defaults to 3 days ago (YouTube data latency).", "schemaType": "string" }] }, { "operationId": "getYouTubeDemographics", "method": "GET", "path": "/v1/analytics/youtube/demographics", "tags": ["Analytics"], "summary": "Get YouTube demographics", "description": "Returns audience demographic insights for a YouTube channel, broken down by age, gender, and/or country. Age and gender values are viewer percentages (0-100). Country values are view counts. Data is based on signed-in viewers only, with a 2-3 day delay. Requires the Analytics add-on.", "parameters": [{ "name": "accountId", "in": "query", "required": true, "description": "The Zernio SocialAccount ID for the YouTube account", "schemaType": "string" }, { "name": "breakdown", "in": "query", "required": false, "description": "Comma-separated list of demographic dimensions: age, gender, country. Defaults to all three if omitted.", "schemaType": "string" }, { "name": "startDate", "in": "query", "required": false, "description": "Start date in YYYY-MM-DD format. Defaults to 90 days ago.", "schemaType": "string" }, { "name": "endDate", "in": "query", "required": false, "description": "End date in YYYY-MM-DD format. Defaults to 3 days ago (YouTube data latency).", "schemaType": "string" }] }, { "operationId": "getYouTubeVideoRetention", "method": "GET", "path": "/v1/analytics/youtube/video-retention", "tags": ["Analytics"], "summary": "Get YouTube video retention curve", "description": "Returns the audience retention curve for a single YouTube video, plus the video's duration for rendering the curve on a time axis. The curve has up to 100 points (elapsedVideoTimeRatio 0.01-1.0) aggregated over the whole date range; YouTube does not support per-day retention breakdowns. audienceWatchRatio is the absolute share of viewers watching at that point in the video and can exceed 1 (rewinds and looping, common on Shorts). relativeRetentionPerformance compares against videos of similar l...", "parameters": [{ "name": "videoId", "in": "query", "required": true, "description": "The YouTube video ID (e.g., \"dQw4w9WgXcQ\")", "schemaType": "string" }, { "name": "accountId", "in": "query", "required": true, "description": "The Zernio account ID for the YouTube account", "schemaType": "string" }, { "name": "startDate", "in": "query", "required": false, "description": "Start date (YYYY-MM-DD). Defaults to the video's publish date (lifetime curve).", "schemaType": "string" }, { "name": "endDate", "in": "query", "required": false, "description": "End date (YYYY-MM-DD). Defaults to 3 days ago (YouTube data latency).", "schemaType": "string" }] }, { "operationId": "listApiKeys", "method": "GET", "path": "/v1/api-keys", "tags": ["API Keys"], "summary": "List keys", "description": "Returns all API keys for the authenticated user. Keys are returned with a preview only, not the full key value.", "parameters": [] }, { "operationId": "createApiKey", "method": "POST", "path": "/v1/api-keys", "tags": ["API Keys"], "summary": "Create key", "description": "Creates a new API key with an optional expiry. The full key value is only returned once in the response.", "parameters": [], "requestBody": { "required": true, "contentTypes": ["application/json"], "schemaRefs": { "application/json": { "schemaType": "object" } } } }, { "operationId": "deleteApiKey", "method": "DELETE", "path": "/v1/api-keys/{keyId}", "tags": ["API Keys"], "summary": "Delete key", "description": "Permanently revokes and deletes an API key.", "parameters": [{ "name": "keyId", "in": "path", "required": true, "schemaType": "string" }] }, { "operationId": "listBroadcasts", "method": "GET", "path": "/v1/broadcasts", "tags": ["Broadcasts"], "summary": "List broadcasts", "description": "Returns broadcasts with delivery stats. Filter by status, platform, or profile.", "parameters": [{ "name": "profileId", "in": "query", "required": false, "description": "Filter by profile. Omit to list across all profiles", "schemaType": "string" }, { "name": "status", "in": "query", "required": false, "schemaType": "string", "schemaEnum": ["draft", "scheduled", "sending", "completed", "failed", "cancelled"] }, { "name": "platform", "in": "query", "required": false, "schemaType": "string" }, { "name": "limit", "in": "query", "required": false, "schemaType": "integer" }, { "name": "skip", "in": "query", "required": false, "schemaType": "integer" }] }, { "operationId": "createBroadcast", "method": "POST", "path": "/v1/broadcasts", "tags": ["Broadcasts"], "summary": "Create broadcast draft", "description": "Create a broadcast in draft status. Add recipients and then send or schedule it.", "parameters": [], "requestBody": { "required": true, "contentTypes": ["application/json"], "schemaRefs": { "application/json": { "schemaType": "object" } } } }, { "operationId": "deleteBroadcast", "method": "DELETE", "path": "/v1/broadcasts/{broadcastId}", "tags": ["Broadcasts"], "summary": "Delete broadcast", "description": "Permanently delete a broadcast. Only drafts can be deleted.", "parameters": [{ "name": "broadcastId", "in": "path", "required": true, "schemaType": "string" }] }, { "operationId": "getBroadcast", "method": "GET", "path": "/v1/broadcasts/{broadcastId}", "tags": ["Broadcasts"], "summary": "Get broadcast details", "description": "Returns a broadcast with its full configuration and delivery stats.", "parameters": [{ "name": "broadcastId", "in": "path", "required": true, "schemaType": "string" }] }, { "operationId": "updateBroadcast", "method": "PATCH", "path": "/v1/broadcasts/{broadcastId}", "tags": ["Broadcasts"], "summary": "Update broadcast", "description": "Update a broadcast's name, message, template, or segment filters. Only draft broadcasts can be updated.", "parameters": [{ "name": "broadcastId", "in": "path", "required": true, "schemaType": "string" }], "requestBody": { "required": false, "contentTypes": ["application/json"], "schemaRefs": { "application/json": { "schemaType": "object" } } } }, { "operationId": "cancelBroadcast", "method": "POST", "path": "/v1/broadcasts/{broadcastId}/cancel", "tags": ["Broadcasts"], "summary": "Cancel broadcast", "description": "Cancel a scheduled or in-progress broadcast. Already-sent messages are not affected.", "parameters": [{ "name": "broadcastId", "in": "path", "required": true, "schemaType": "string" }] }, { "operationId": "listBroadcastRecipients", "method": "GET", "path": "/v1/broadcasts/{broadcastId}/recipients", "tags": ["Broadcasts"], "summary": "List broadcast recipients", "description": "Returns recipients for a broadcast with individual delivery status. Filter by status.", "parameters": [{ "name": "broadcastId", "in": "path", "required": true, "schemaType": "string" }, { "name": "status", "in": "query", "required": false, "schemaType": "string", "schemaEnum": ["pending", "sent", "delivered", "read", "failed"] }, { "name": "limit", "in": "query", "required": false, "schemaType": "integer" }, { "name": "skip", "in": "query", "required": false, "schemaType": "integer" }] }, { "operationId": "addBroadcastRecipients", "method": "POST", "path": "/v1/broadcasts/{broadcastId}/recipients", "tags": ["Broadcasts"], "summary": "Add recipients to a broadcast", "description": "Add recipients by contact IDs, raw phone numbers, or from the broadcast's segment filters.", "parameters": [{ "name": "broadcastId", "in": "path", "required": true, "schemaType": "string" }], "requestBody": { "required": true, "contentTypes": ["application/json"], "schemaRefs": { "application/json": { "schemaType": "object" } } } }, { "operationId": "scheduleBroadcast", "method": "POST", "path": "/v1/broadcasts/{broadcastId}/schedule", "tags": ["Broadcasts"], "summary": "Schedule broadcast for later", "description": "Schedule a draft broadcast to be sent at a future date and time.", "parameters": [{ "name": "broadcastId", "in": "path", "required": true, "schemaType": "string" }], "requestBody": { "required": true, "contentTypes": ["application/json"], "schemaRefs": { "application/json": { "schemaType": "object" } } } }, { "operationId": "sendBroadcast", "method": "POST", "path": "/v1/broadcasts/{broadcastId}/send", "tags": ["Broadcasts"], "summary": "Send broadcast now", "description": "Immediately start sending a draft broadcast to its recipients.", "parameters": [{ "name": "broadcastId", "in": "path", "required": true, "schemaType": "string" }] }, { "operationId": "listCommentAutomations", "method": "GET", "path": "/v1/comment-automations", "tags": ["Comment Automations"], "summary": "List comment-to-DM automations", "description": "List all comment-to-DM automations for a profile. Returns automations with their stats.", "parameters": [{ "name": "profileId", "in": "query", "required": false, "description": "Filter by profile. Omit to list across all profiles", "schemaType": "string" }] }, { "operationId": "createCommentAutomation", "method": "POST", "path": "/v1/comment-automations", "tags": ["Comment Automations"], "summary": "Create comment-to-DM automation", "description": "Create a keyword-triggered DM automation on an Instagram or Facebook account. When someone comments a matching keyword (or, with `trigger: story_reply`, replies to your Instagram story with one), they automatically receive a DM. Triggers (`trigger`): * `comment` (default): fires on keyword comments on a post or reel. * `story_reply`: fires when someone replies to your Instagram story with a keyword, and answers them with a DM. Set `platformPostId` to a story media id to scope to one story, or o...", "parameters": [], "requestBody": { "required": true, "contentTypes": ["application/json"], "schemaRefs": { "application/json": { "schemaType": "object" } } } }, { "operationId": "deleteCommentAutomation", "method": "DELETE", "path": "/v1/comment-automations/{automationId}", "tags": ["Comment Automations"], "summary": "Delete automation", "description": "Permanently delete an automation and all its trigger logs.", "parameters": [{ "name": "automationId", "in": "path", "required": true, "schemaType": "string" }] }, { "operationId": "getCommentAutomation", "method": "GET", "path": "/v1/comment-automations/{automationId}", "tags": ["Comment Automations"], "summary": "Get automation details", "description": "Returns an automation with its configuration, stats, and recent trigger logs.", "parameters": [{ "name": "automationId", "in": "path", "required": true, "schemaType": "string" }] }, { "operationId": "updateCommentAutomation", "method": "PATCH", "path": "/v1/comment-automations/{automationId}", "tags": ["Comment Automations"], "summary": "Update automation settings", "description": "Update an automation's keywords, DM message, inline buttons, comment reply, or active status. Pass `buttons: []` to clear all buttons. When `buttons` is non-empty, `dmMessage` (the new one if you're changing it, otherwise the stored one) must be 640 characters or less.", "parameters": [{ "name": "automationId", "in": "path", "required": true, "schemaType": "string" }], "requestBody": { "required": false, "contentTypes": ["application/json"], "schemaRefs": { "application/json": { "schemaType": "object" } } } }, { "operationId": "listCommentAutomationLogs", "method": "GET", "path": "/v1/comment-automations/{automationId}/logs", "tags": ["Comment Automations"], "summary": "List automation logs", "description": "Paginated list of every comment that triggered this automation, with send status and commenter info.", "parameters": [{ "name": "automationId", "in": "path", "required": true, "schemaType": "string" }, { "name": "status", "in": "query", "required": false, "description": "Filter by result status", "schemaType": "string", "schemaEnum": ["sent", "failed", "skipped"] }, { "name": "limit", "in": "query", "required": false, "schemaType": "integer" }, { "name": "skip", "in": "query", "required": false, "schemaType": "integer" }] }, { "operationId": "listInboxComments", "method": "GET", "path": "/v1/inbox/comments", "tags": ["Comments"], "summary": "List commented posts", "description": "Returns posts with comment counts from all connected accounts. Aggregates data across multiple accounts. For users with the Ads add-on (Metronome plans always qualify), the user's Meta ads (boosted/dark posts) are included too. There's one row per (ad, placement-with-comments): an ad that runs on both Facebook feed and Instagram feed produces up to two rows (the Page dark post and the IG media have separate comment threads), each flagged `isAd: true` with `adId` and `placement` (`id` is `{adId}...", "parameters": [{ "name": "profileId", "in": "query", "required": false, "description": "Filter by profile ID", "schemaType": "string" }, { "name": "platform", "in": "query", "required": false, "description": "Filter by platform. `metaads` is a synthetic value meaning the user's ads (boosted/dark posts) only; `facebook`/`instagram` return organic posts only.", "schemaType": "string", "schemaEnum": ["facebook", "instagram", "twitter", "bluesky", "threads", "youtube", "linkedin", "reddit", "metaads"] }, { "name": "minComments", "in": "query", "required": false, "description": "Minimum comment count", "schemaType": "integer" }, { "name": "since", "in": "query", "required": false, "description": "Posts created after this date", "schemaType": "string" }, { "name": "sortBy", "in": "query", "required": false, "description": "Sort field", "schemaType": "string", "schemaEnum": ["date", "comments"] }, { "name": "sortOrder", "in": "query", "required": false, "description": "Sort order", "schemaType": "string", "schemaEnum": ["asc", "desc"] }, { "name": "limit", "in": "query", "required": false, "schemaType": "integer" }, { "name": "cursor", "in": "query", "required": false, "schemaType": "string" }, { "name": "accountId", "in": "query", "required": false, "description": "Filter by specific social account ID", "schemaType": "string" }] }, { "operationId": "deleteInboxComment", "method": "DELETE", "path": "/v1/inbox/comments/{postId}", "tags": ["Comments"], "summary": "Delete comment", "description": "Delete a comment on a post. Supported by Facebook, Instagram, Bluesky, Reddit, YouTube, and LinkedIn. Requires accountId and commentId query parameters.", "parameters": [{ "name": "postId", "in": "path", "required": true, "description": "Zernio post ID or platform-specific post ID. LinkedIn third-party posts accept full activity URN or numeric ID.", "schemaType": "string" }, { "name": "accountId", "in": "query", "required": true, "schemaType": "string" }, { "name": "commentId", "in": "query", "required": true, "schemaType": "string" }] }, { "operationId": "getInboxPostComments", "method": "GET", "path": "/v1/inbox/comments/{postId}", "tags": ["Comments"], "summary": "Get post comments", "description": "Fetch comments for a specific post. Requires accountId query parameter.", "parameters": [{ "name": "postId", "in": "path", "required": true, "description": "Zernio post ID or platform-specific post ID. Zernio IDs are auto-resolved. LinkedIn third-party posts accept full activity URN or numeric ID.", "schemaType": "string" }, { "name": "accountId", "in": "query", "required": true, "schemaType": "string" }, { "name": "subreddit", "in": "query", "required": false, "description": "(Reddit only) Subreddit name", "schemaType": "string" }, { "name": "limit", "in": "query", "required": false, "description": "Maximum number of comments to return", "schemaType": "integer" }, { "name": "cursor", "in": "query", "required": false, "description": "Pagination cursor", "schemaType": "string" }, { "name": "commentId", "in": "query", "required": false, "description": "(Reddit only) Get replies to a specific comment", "schemaType": "string" }] }, { "operationId": "replyToInboxPost", "method": "POST", "path": "/v1/inbox/comments/{postId}", "tags": ["Comments"], "summary": "Reply to comment", "description": "Post a reply to a post or specific comment. Requires accountId in request body.", "parameters": [{ "name": "postId", "in": "path", "required": true, "description": "Zernio post ID or platform-specific post ID. LinkedIn third-party posts accept full activity URN or numeric ID.", "schemaType": "string" }], "requestBody": { "required": true, "contentTypes": ["application/json"], "schemaRefs": { "application/json": { "schemaType": "object" } } } }, { "operationId": "unhideInboxComment", "method": "DELETE", "path": "/v1/inbox/comments/{postId}/{commentId}/hide", "tags": ["Comments"], "summary": "Unhide comment", "description": "Unhide a previously hidden comment. Supported by Facebook, Instagram, Threads, and X/Twitter.", "parameters": [{ "name": "postId", "in": "path", "required": true, "schemaType": "string" }, { "name": "commentId", "in": "path", "required": true, "schemaType": "string" }, { "name": "accountId", "in": "query", "required": true, "schemaType": "string" }] }, { "operationId": "hideInboxComment", "method": "POST", "path": "/v1/inbox/comments/{postId}/{commentId}/hide", "tags": ["Comments"], "summary": "Hide comment", "description": "Hide a comment on a post. Supported by Facebook, Instagram, Threads, and X/Twitter. Hidden comments are only visible to the commenter and page admin. For X/Twitter, the reply must belong to a conversation started by the authenticated user.", "parameters": [{ "name": "postId", "in": "path", "required": true, "schemaType": "string" }, { "name": "commentId", "in": "path", "required": true, "schemaType": "string" }], "requestBody": { "required": true, "contentTypes": ["application/json"], "schemaRefs": { "application/json": { "schemaType": "object" } } } }, { "operationId": "unlikeInboxComment", "method": "DELETE", "path": "/v1/inbox/comments/{postId}/{commentId}/like", "tags": ["Comments"], "summary": "Unlike comment", "description": "Remove a like from a comment. Supported platforms: Facebook, Twitter/X, Bluesky, Reddit. For Bluesky, the likeUri query parameter is required.", "parameters": [{ "name": "postId", "in": "path", "required": true, "schemaType": "string" }, { "name": "commentId", "in": "path", "required": true, "schemaType": "string" }, { "name": "accountId", "in": "query", "required": true, "schemaType": "string" }, { "name": "likeUri", "in": "query", "required": false, "description": "(Bluesky only) The like URI returned when liking", "schemaType": "string" }] }, { "operationId": "likeInboxComment", "method": "POST", "path": "/v1/inbox/comments/{postId}/{commentId}/like", "tags": ["Comments"], "summary": "Like comment", "description": "Like or upvote a comment on a post. Supported platforms: Facebook, Twitter/X, Bluesky, Reddit. For Bluesky, the cid (content identifier) is required in the request body.", "parameters": [{ "name": "postId", "in": "path", "required": true, "schemaType": "string" }, { "name": "commentId", "in": "path", "required": true, "schemaType": "string" }], "requestBody": { "required": true, "contentTypes": ["application/json"], "schemaRefs": { "application/json": { "schemaType": "object" } } } }, { "operationId": "sendPrivateReplyToComment", "method": "POST", "path": "/v1/inbox/comments/{postId}/{commentId}/private-reply", "tags": ["Comments"], "summary": "Send private reply", "description": "Send a private message to the author of a comment. Supported on Instagram and Facebook only. One reply per comment, must be sent within 7 days. Optionally attach interactive elements: `quickReplies` (chips above the keyboard, max 13) or `buttons` (1-3 inline postback/url buttons rendered in the same bubble via Meta's button_template). Buttons are recommended for cold reach since chips do not render in the Instagram Message Requests folder. `quickReplies` and `buttons` are mutually exclusive.", "parameters": [{ "name": "postId", "in": "path", "required": true, "description": "The media/post ID (Instagram media ID or Facebook post ID)", "schemaType": "string" }, { "name": "commentId", "in": "path", "required": true, "description": "The comment ID to send a private reply to", "schemaType": "string" }], "requestBody": { "required": true, "contentTypes": ["application/json"], "schemaRefs": { "application/json": { "schemaType": "object" } } } }, { "operationId": "getFacebookPages", "method": "GET", "path": "/v1/accounts/{accountId}/facebook-page", "tags": ["Connect"], "summary": "List Facebook pages", "description": "Returns all Facebook pages the connected account has access to, including the currently selected page.", "parameters": [{ "name": "accountId", "in": "path", "required": true, "schemaType": "string" }, { "name": "refresh", "in": "query", "required": false, "description": "When true, bypasses the page cache and fetches fresh pages from Meta. Rate-limited server-side to 1 refresh per 60s. Pages no longer accessible to the connected account will be removed from the list on refresh.", "schemaType": "boolean" }] }, { "operationId": "updateFacebookPage", "method": "PUT", "path": "/v1/accounts/{accountId}/facebook-page", "tags": ["Connect"], "summary": "Update Facebook page", "description": "Switch which Facebook Page is active for a connected account.", "parameters": [{ "name": "accountId", "in": "path", "required": true, "schemaType": "string" }], "requestBody": { "required": true, "contentTypes": ["application/json"], "schemaRefs": { "application/json": { "schemaType": "object" } } } }, { "operationId": "getGmbLocations", "method": "GET", "path": "/v1/accounts/{accountId}/gmb-locations", "tags": ["Connect"], "summary": "List GBP locations", "description": "Returns Google Business Profile locations the connected account can access, plus the currently selected location. The list is bounded (see hasMore); for accounts that own many locations, use the search or filter query params to find a specific one instead of loading them all.", "parameters": [{ "name": "accountId", "in": "path", "required": true, "schemaType": "string" }, { "name": "search", "in": "query", "required": false, "description": "Free-text search on the business name, applied server-side by Google. Use for accounts with many locations.", "schemaType": "string" }, { "name": "filter", "in": "query", "required": false, "description": "Raw Google Business Information API filter expression (advanced; takes precedence over search), e.g. storeCode=\"LH279411\".", "schemaType": "string" }] }, { "operationId": "updateGmbLocation", "method": "PUT", "path": "/v1/accounts/{accountId}/gmb-locations", "tags": ["Connect"], "summary": "Update GBP location", "description": "Switch which GBP location is active for a connected account.", "parameters": [{ "name": "accountId", "in": "path", "required": true, "schemaType": "string" }], "requestBody": { "required": true, "contentTypes": ["application/json"], "schemaRefs": { "application/json": { "schemaType": "object" } } } }, { "operationId": "updateLinkedInOrganization", "method": "PUT", "path": "/v1/accounts/{accountId}/linkedin-organization", "tags": ["Connect"], "summary": "Switch LinkedIn account type", "description": "Switch a LinkedIn account between personal profile and organization (company page) posting.", "parameters": [{ "name": "accountId", "in": "path", "required": true, "schemaType": "string" }], "requestBody": { "required": true, "contentTypes": ["application/json"], "schemaRefs": { "application/json": { "schemaType": "object" } } } }, { "operationId": "getLinkedInOrganizations", "method": "GET", "path": "/v1/accounts/{accountId}/linkedin-organizations", "tags": ["Connect"], "summary": "List LinkedIn orgs", "description": "Returns LinkedIn organizations (company pages) the connected account has admin access to.", "parameters": [{ "name": "accountId", "in": "path", "required": true, "schemaType": "string" }] }, { "operationId": "getPinterestBoards", "method": "GET", "path": "/v1/accounts/{accountId}/pinterest-boards", "tags": ["Connect"], "summary": "List Pinterest boards", "description": "Returns the boards available for a connected Pinterest account. Use this to get a board ID when creating a Pinterest post.", "parameters": [{ "name": "accountId", "in": "path", "required": true, "schemaType": "string" }] }, { "operationId": "updatePinterestBoards", "method": "PUT", "path": "/v1/accounts/{accountId}/pinterest-boards", "tags": ["Connect"], "summary": "Set default Pinterest board", "description": "Sets the default board used when publishing pins for this account.", "parameters": [{ "name": "accountId", "in": "path", "required": true, "schemaType": "string" }], "requestBody": { "required": true, "contentTypes": ["application/json"], "schemaRefs": { "application/json": { "schemaType": "object" } } } }, { "operationId": "getRedditFlairs", "method": "GET", "path": "/v1/accounts/{accountId}/reddit-flairs", "tags": ["Connect"], "summary": "List subreddit flairs", "description": "Returns available post flairs for a subreddit. Some subreddits require a flair when posting.", "parameters": [{ "name": "accountId", "in": "path", "required": true, "schemaType": "string" }, { "name": "subreddit", "in": "query", "required": true, "description": "Subreddit name (without \"r/\" prefix) to fetch flairs for", "schemaType": "string" }] }, { "operationId": "getRedditSubreddits", "method": "GET", "path": "/v1/accounts/{accountId}/reddit-subreddits", "tags": ["Connect"], "summary": "List Reddit subreddits", "description": "Returns the subreddits the connected Reddit account can post to. Use this to get a subreddit name when creating a Reddit post.", "parameters": [{ "name": "accountId", "in": "path", "required": true, "schemaType": "string" }] }, { "operationId": "updateRedditSubreddits", "method": "PUT", "path": "/v1/accounts/{accountId}/reddit-subreddits", "tags": ["Connect"], "summary": "Set default subreddit", "description": "Sets the default subreddit used when publishing posts for this Reddit account.", "parameters": [{ "name": "accountId", "in": "path", "required": true, "schemaType": "string" }], "requestBody": { "required": true, "contentTypes": ["application/json"], "schemaRefs": { "application/json": { "schemaType": "object" } } } }, { "operationId": "getYoutubePlaylists", "method": "GET", "path": "/v1/accounts/{accountId}/youtube-playlists", "tags": ["Connect"], "summary": "List YouTube playlists", "description": "Returns the playlists available for a connected YouTube account. Use this to get a playlist ID when creating a YouTube post with the playlistId field.", "parameters": [{ "name": "accountId", "in": "path", "required": true, "schemaType": "string" }] }, { "operationId": "updateYoutubeDefaultPlaylist", "method": "PUT", "path": "/v1/accounts/{accountId}/youtube-playlists", "tags": ["Connect"], "summary": "Set default YouTube playlist", "description": "Sets the default playlist used when publishing videos for this account. When a post does not specify a playlistId, the default playlist is not automatically used (it is stored for client-side convenience).", "parameters": [{ "name": "accountId", "in": "path", "required": true, "schemaType": "string" }], "requestBody": { "required": true, "contentTypes": ["application/json"], "schemaRefs": { "application/json": { "schemaType": "object" } } } }, { "operationId": "getConnectUrl", "method": "GET", "path": "/v1/connect/{platform}", "tags": ["Connect"], "summary": "Get OAuth connect URL", "description": "Initiate an OAuth connection flow. Returns an authUrl to redirect the user to. Standard flow: Zernio hosts the selection UI, then redirects to your redirect_url. Headless mode (headless=true): user is redirected to your redirect_url with OAuth data for custom UI. Use the platform-specific selection endpoints to complete.", "parameters": [{ "name": "platform", "in": "path", "required": true, "description": "Social media platform to connect", "schemaType": "string", "schemaEnum": ["facebook", "instagram", "linkedin", "twitter", "tiktok", "youtube", "threads", "reddit", "pinterest", "bluesky", "googlebusiness", "telegram", "snapchat", "discord", "whatsapp"] }, { "name": "profileId", "in": "query", "required": true, "description": "Your Zernio profile ID (get from /v1/profiles)", "schemaType": "string" }, { "name": "redirect_url", "in": "query", "required": false, "description": "Your custom redirect URL after connection completes. Standard mode appends ?connected={platform}&profileId=X&accountId=Y&username=Z. Headless mode appends OAuth data params for platforms requiring selection (e.g. LinkedIn orgs, Facebook pa...", "schemaType": "string" }, { "name": "headless", "in": "query", "required": false, "description": "When true, the user is redirected to your redirect_url with raw OAuth data (code, state) instead of Zernio's default account selection UI. Use this to build a custom connect experience.", "schemaType": "boolean" }] }, { "operationId": "handleOAuthCallback", "method": "POST", "path": "/v1/connect/{platform}", "tags": ["Connect"], "summary": "Complete OAuth callback", "description": "Exchange the OAuth authorization code for tokens and connect the account to the specified profile.", "parameters": [{ "name": "platform", "in": "path", "required": true, "schemaType": "string" }], "requestBody": { "required": true, "contentTypes": ["application/json"], "schemaRefs": { "application/json": { "schemaType": "object" } } } }, { "operationId": "connectAds", "method": "GET", "path": "/v1/connect/{platform}/ads", "tags": ["Connect"], "summary": "Connect ads for a platform", "description": "Unified ads connection endpoint. Creates a dedicated ads SocialAccount for the specified platform. Same-token platforms (facebook, instagram, linkedin, pinterest): Creates an ads SocialAccount (metaads, linkedinads, pinterestads) with a copied OAuth token from the parent posting account. If the ads account already exists, returns alreadyConnected: true. No extra OAuth needed. Separate-token platforms (tiktok, twitter): Starts the platform-specific marketing API OAuth flow and creates an ads Soc...", "parameters": [{ "name": "platform", "in": "path", "required": true, "description": "Platform to connect ads for. Only platforms with ads support are accepted.", "schemaType": "string", "schemaEnum": ["facebook", "instagram", "linkedin", "tiktok", "twitter", "pinterest", "googleads"] }, { "name": "profileId", "in": "query", "required": true, "description": "Your Zernio profile ID", "schemaType": "string" }, { "name": "accountId", "in": "query", "required": false, "description": "Existing SocialAccount ID. Required for `twitter` (X Ads). Optional for `tiktok` — omit to enter ads-only mode (no TikTok posting account linked; ad creation uses a Brand Identity instead of a TT_USER). Ignored for same-token (`facebook`, ...", "schemaType": "string" }, { "name": "redirect_url", "in": "query", "required": false, "description": "Custom redirect URL after OAuth completes (same-token platforms only)", "schemaType": "string" }, { "name": "headless", "in": "query", "required": false, "description": "Enable headless mode (same-token platforms only)", "schemaType": "boolean" }, { "name": "adAccountId", "in": "query", "required": false, "description": "Scope ad sync to a single platform ad account. Without this param, sync covers every ad account the connected token can see. Supported on `facebook`/`instagram` (Meta, `act_<digits>`), `linkedin` (bare numeric sponsored-account id), `googl...", "schemaType": "string" }, { "name": "adAccountIds", "in": "query", "required": false, "description": "Scope ad sync to multiple platform ad accounts (same platform support and id shapes as `adAccountId`). Repeat the param (`?adAccountIds=act_1&adAccountIds=act_2`) or comma-separate (`?adAccountIds=act_1,act_2`). Persisted server-side; late...", "schemaType": "array" }] }, { "operationId": "connectBlueskyCredentials", "method": "POST", "path": "/v1/connect/bluesky/credentials", "tags": ["Connect"], "summary": "Connect Bluesky account", "description": "Connect a Bluesky account using identifier (handle or email) and an app password. To get your userId for the state parameter, call GET /v1/users which includes a currentUserId field.", "parameters": [], "requestBody": { "required": true, "contentTypes": ["application/json"], "schemaRefs": { "application/json": { "schemaType": "object" } } } }, { "operationId": "listFacebookPages", "method": "GET", "path": "/v1/connect/facebook/select-page", "tags": ["Connect"], "summary": "List Facebook pages", "description": "Returns the list of Facebook Pages the user can manage after OAuth. Extract tempToken and userProfile from the OAuth redirect params and pass them here. Use the X-Connect-Token header if connecting via API key.", "parameters": [{ "name": "profileId", "in": "query", "required": true, "description": "Profile ID from your connection flow", "schemaType": "string" }, { "name": "tempToken", "in": "query", "required": true, "description": "Temporary Facebook access token from the OAuth callback redirect", "schemaType": "string" }] }, { "operationId": "selectFacebookPage", "method": "POST", "path": "/v1/connect/facebook/select-page", "tags": ["Connect"], "summary": "Select Facebook page", "description": "Complete the headless flow by saving the user's selected Facebook page. Pass the userProfile from the OAuth redirect and use X-Connect-Token if connecting via API key.", "parameters": [], "requestBody": { "required": true, "contentTypes": ["application/json"], "schemaRefs": { "application/json": { "schemaType": "object" } } } }, { "operationId": "listGoogleBusinessLocations", "method": "GET", "path": "/v1/connect/googlebusiness/locations", "tags": ["Connect"], "summary": "List GBP locations", "description": "For headless flows. Returns the list of GBP locations the user can manage. Use pendingDataToken (from the OAuth callback redirect) to list locations without consuming the token, so it remains available for select-location. Use X-Connect-Token header if connecting via API key.", "parameters": [{ "name": "profileId", "in": "query", "required": false, "description": "Profile ID from your connection flow. Required for auth validation when provided.", "schemaType": "string" }, { "name": "pendingDataToken", "in": "query", "required": false, "description": "Token from the OAuth callback redirect. Preferred over tempToken because it preserves server-side token storage. One of pendingDataToken or tempToken is required.", "schemaType": "string" }, { "name": "tempToken", "in": "query", "required": false, "description": "Legacy. Direct Google access token. Use pendingDataToken instead when available.", "schemaType": "string" }, { "name": "search", "in": "query", "required": false, "description": "Free-text search on the business name, applied server-side by Google. Use this for accounts that own many locations (the response is bounded, see hasMore) so the user can find a specific location without loading the full list.", "schemaType": "string" }, { "name": "filter", "in": "query", "required": false, "description": "Raw Google Business Information API filter expression (advanced; takes precedence over search). Supports fields such as title, storeCode, storefront_address.postal_code, labels and categories, e.g. storeCode=\"LH279411\". See Google's \"Work ...", "schemaType": "string" }] }, { "operationId": "selectGoogleBusinessLocation", "method": "POST", "path": "/v1/connect/googlebusiness/select-location", "tags": ["Connect"], "summary": "Select GBP location", "description": "Complete the headless GBP flow by saving the user's selected location. The pendingDataToken is returned in your redirect URL after OAuth completes (step=select_location). Tokens and profile data are stored server-side, so only the pendingDataToken is needed here. Use X-Connect-Token header if connecting via API key.", "parameters": [], "requestBody": { "required": true, "contentTypes": ["application/json"], "schemaRefs": { "application/json": { "schemaType": "object" } } } }, { "operationId": "listLinkedInOrganizations", "method": "GET", "path": "/v1/connect/linkedin/organizations", "tags": ["Connect"], "summary": "List LinkedIn orgs", "description": "Fetch full LinkedIn organization details (logos, vanity names, websites) for custom UI. No authentication required, just the tempToken from OAuth.", "parameters": [{ "name": "tempToken", "in": "query", "required": true, "description": "The temporary LinkedIn access token from the OAuth redirect", "schemaType": "string" }, { "name": "orgIds", "in": "query", "required": true, "description": "Comma-separated list of organization IDs to fetch details for (max 100)", "schemaType": "string" }] }, { "operationId": "selectLinkedInOrganization", "method": "POST", "path": "/v1/connect/linkedin/select-organization", "tags": ["Connect"], "summary": "Select LinkedIn org", "description": "Complete the LinkedIn connection flow. Set accountType to \"personal\" or \"organization\" to connect as a company page. Use X-Connect-Token if connecting via API key.", "parameters": [], "requestBody": { "required": true, "contentTypes": ["application/json"], "schemaRefs": { "application/json": { "schemaType": "object" } } } }, { "operationId": "getPendingOAuthData", "method": "GET", "path": "/v1/connect/pending-data", "tags": ["Connect"], "summary": "Get pending OAuth data", "description": "Fetch pending OAuth data for headless mode using the pendingDataToken from the redirect URL. **Scope**: This endpoint is used only for LinkedIn organizations and Snapchat profiles, where the selection list is too large to fit in URL params. WhatsApp, Facebook, Pinterest, Google Business and other platforms pass selection state directly via URL query params on the redirect (`profileId`, `tempToken`, `step`), no pending record is created, so this endpoint will return 404 for those flows. Use the ...", "parameters": [{ "name": "token", "in": "query", "required": true, "description": "The pending data token from the OAuth redirect URL (pendingDataToken parameter)", "schemaType": "string" }] }, { "operationId": "listPinterestBoardsForSelection", "method": "GET", "path": "/v1/connect/pinterest/select-board", "tags": ["Connect"], "summary": "List Pinterest boards", "description": "For headless flows. Returns Pinterest boards the user can post to. Use X-Connect-Token from the redirect URL.", "parameters": [{ "name": "X-Connect-Token", "in": "header", "required": true, "description": "Short-lived connect token from the OAuth redirect", "schemaType": "string" }, { "name": "profileId", "in": "query", "required": true, "description": "Your Zernio profile ID", "schemaType": "string" }, { "name": "tempToken", "in": "query", "required": true, "description": "Temporary Pinterest access token from the OAuth callback redirect", "schemaType": "string" }] }, { "operationId": "selectPinterestBoard", "method": "POST", "path": "/v1/connect/pinterest/select-board", "tags": ["Connect"], "summary": "Select Pinterest board", "description": "Complete the Pinterest connection flow. After OAuth, use this endpoint to save the selected board and complete the account connection. Use the X-Connect-Token header if you initiated the connection via API key.", "parameters": [], "requestBody": { "required": true, "contentTypes": ["application/json"], "schemaRefs": { "application/json": { "schemaType": "object" } } } }, { "operationId": "listSnapchatProfiles", "method": "GET", "path": "/v1/connect/snapchat/select-profile", "tags": ["Connect"], "summary": "List Snapchat profiles", "description": "For headless flows. Returns Snapchat Public Profiles the user can post to. Use X-Connect-Token from the redirect URL.", "parameters": [{ "name": "X-Connect-Token", "in": "header", "required": true, "description": "Short-lived connect token from the OAuth redirect", "schemaType": "string" }, { "name": "profileId", "in": "query", "required": true, "description": "Your Zernio profile ID", "schemaType": "string" }, { "name": "tempToken", "in": "query", "required": true, "description": "Temporary Snapchat access token from the OAuth callback redirect", "schemaType": "string" }] }, { "operationId": "selectSnapchatProfile", "method": "POST", "path": "/v1/connect/snapchat/select-profile", "tags": ["Connect"], "summary": "Select Snapchat profile", "description": "Complete the Snapchat connection flow by saving the selected Public Profile. Snapchat requires a Public Profile to publish content. Use X-Connect-Token if connecting via API key.", "parameters": [{ "name": "X-Connect-Token", "in": "header", "required": false, "description": "Short-lived connect token from the OAuth redirect (for API users)", "schemaType": "string" }], "requestBody": { "required": true, "contentTypes": ["application/json"], "schemaRefs": { "application/json": { "schemaType": "object" } } } }, { "operationId": "getTelegramConnectStatus", "method": "GET", "path": "/v1/connect/telegram", "tags": ["Connect"], "summary": "Generate Telegram code", "description": "Generate an access code (valid 15 minutes) for connecting a Telegram channel or group. Add the bot as admin, then send the code + @yourchannel to the bot. Poll PATCH /v1/connect/telegram to check status.", "parameters": [{ "name": "profileId", "in": "query", "required": true, "description": "The profile ID to connect the Telegram account to", "schemaType": "string" }] }, { "operationId": "completeTelegramConnect", "method": "PATCH", "path": "/v1/connect/telegram", "tags": ["Connect"], "summary": "Check Telegram status", "description": "Poll this endpoint to check if a Telegram access code has been used to connect a channel/group. Recommended polling interval: 3 seconds. Status values: pending (waiting for user), connected (channel/group linked), expired (generate a new code).", "parameters": [{ "name": "code", "in": "query", "required": true, "description": "The access code to check status for", "schemaType": "string" }] }, { "operationId": "initiateTelegramConnect", "method": "POST", "path": "/v1/connect/telegram", "tags": ["Connect"], "summary": "Connect Telegram directly", "description": "Connect a Telegram channel/group directly using the chat ID. Alternative to the access code flow. The bot must already be an admin in the channel/group.", "parameters": [], "requestBody": { "required": true, "contentTypes": ["application/json"], "schemaRefs": { "application/json": { "schemaType": "object" } } } }, { "operationId": "configureTikTokAdsBrandIdentity", "method": "PATCH", "path": "/v1/connect/tiktok-ads", "tags": ["Connect"], "summary": "Configure TikTok Ads Brand Identity", "description": "Set or update the Brand Identity (display name + avatar) for a `tiktokads` SocialAccount. TikTok requires every ad to carry an `identity_id + identity_type` pair. The Brand Identity is the CUSTOMIZED_USER alternative to attributing ads to a real @username (TT_USER). This route uploads the supplied image to TikTok, creates the identity via `/v2/identity/create/`, and caches the resulting `identity_id` on the account so subsequent `POST /v1/ads/create` calls can opt into it via `identityType: 'CU...", "parameters": [], "requestBody": { "required": true, "contentTypes": ["application/json"], "schemaRefs": { "application/json": { "schemaType": "object" } } } }, { "operationId": "connectWhatsAppCredentials", "method": "POST", "path": "/v1/connect/whatsapp/credentials", "tags": ["Connect"], "summary": "Connect WhatsApp via credentials", "description": "Connect a WhatsApp Business Account by providing Meta credentials directly. This is the headless alternative to the Embedded Signup browser flow. To get the required credentials: 1. Go to Meta Business Suite (business.facebook.com) 2. Create or select a WhatsApp Business Account 3. In Business Settings > System Users, create a System User 4. Assign it the whatsapp_business_management and whatsapp_business_messaging permissions 5. Generate a permanent access token 6. Get the WABA ID from WhatsAp...", "parameters": [], "requestBody": { "required": true, "contentTypes": ["application/json"], "schemaRefs": { "application/json": { "schemaType": "object" } } } }, { "operationId": "listWhatsAppPhoneNumbers", "method": "GET", "path": "/v1/connect/whatsapp/select-phone-number", "tags": ["Connect"], "summary": "List WhatsApp phone numbers for selection", "description": "Fetch the WhatsApp phone numbers available across the user's WhatsApp Business Accounts (WABAs) after a headless OAuth flow. WhatsApp OAuth grants access at the WABA level. When a connected WABA has 2 or more phone numbers, you must call this endpoint to list them and then `POST /v1/connect/whatsapp/select-phone-number` to bind one to the Zernio profile. Single-phone WABAs auto-complete during the OAuth callback and never reach this endpoint. Use the `profileId` and `tempToken` returned in the ...", "parameters": [{ "name": "profileId", "in": "query", "required": true, "description": "The Zernio profile ID from the headless redirect", "schemaType": "string" }, { "name": "tempToken", "in": "query", "required": true, "description": "The temporary access token from the headless redirect", "schemaType": "string" }, { "name": "X-Connect-Token", "in": "header", "required": false, "description": "Alternative auth for API users' end customers (used when the bearer token is scoped to a different user)", "schemaType": "string" }] }, { "operationId": "completeWhatsAppPhoneSelection", "method": "POST", "path": "/v1/connect/whatsapp/select-phone-number", "tags": ["Connect"], "summary": "Complete WhatsApp phone number selection", "description": "Bind a specific WhatsApp phone number to the Zernio profile after the user picks one from `listWhatsAppPhoneNumbers`. Exchanges the short-lived OAuth token for a long-lived token, subscribes the WABA to webhooks, and creates the SocialAccount.", "parameters": [{ "name": "X-Connect-Token", "in": "header", "required": false, "description": "Alternative auth for API users' end customers", "schemaType": "string" }], "requestBody": { "required": true, "contentTypes": ["application/json"], "schemaRefs": { "application/json": { "schemaType": "object" } } } }, { "operationId": "listContacts", "method": "GET", "path": "/v1/contacts", "tags": ["Contacts"], "summary": "List contacts", "description": "List and search contacts for a profile. Supports filtering by tags, platform, subscription status, and full-text search.", "parameters": [{ "name": "profileId", "in": "query", "required": false, "description": "Filter by profile. Omit to list across all profiles", "schemaType": "string" }, { "name": "search", "in": "query", "required": false, "schemaType": "string" }, { "name": "tag", "in": "query", "required": false, "schemaType": "string" }, { "name": "platform", "in": "query", "required": false, "schemaType": "string", "schemaEnum": ["instagram", "facebook", "telegram", "twitter", "bluesky", "reddit", "whatsapp"] }, { "name": "isSubscribed", "in": "query", "required": false, "schemaType": "string", "schemaEnum": ["true", "false"] }, { "name": "limit", "in": "query", "required": false, "schemaType": "integer" }, { "name": "skip", "in": "query", "required": false, "schemaType": "integer" }] }, { "operationId": "createContact", "method": "POST", "path": "/v1/contacts", "tags": ["Contacts"], "summary": "Create contact", "description": "Create a new contact. Optionally create a platform channel in the same request by providing accountId, platform, and platformIdentifier.", "parameters": [], "requestBody": { "required": true, "contentTypes": ["application/json"], "schemaRefs": { "application/json": { "schemaType": "object" } } } }, { "operationId": "deleteContact", "method": "DELETE", "path": "/v1/contacts/{contactId}", "tags": ["Contacts"], "summary": "Delete contact", "description": "Permanently deletes a contact and all associated channels.", "parameters": [{ "name": "contactId", "in": "path", "required": true, "schemaType": "string" }] }, { "operationId": "getContact", "method": "GET", "path": "/v1/contacts/{contactId}", "tags": ["Contacts"], "summary": "Get contact", "description": "Returns a contact with all associated messaging channels.", "parameters": [{ "name": "contactId", "in": "path", "required": true, "schemaType": "string" }] }, { "operationId": "updateContact", "method": "PATCH", "path": "/v1/contacts/{contactId}", "tags": ["Contacts"], "summary": "Update contact", "description": "Update one or more fields on a contact. Only provided fields are changed.", "parameters": [{ "name": "contactId", "in": "path", "required": true, "schemaType": "string" }], "requestBody": { "required": false, "contentTypes": ["application/json"], "schemaRefs": { "application/json": { "schemaType": "object" } } } }, { "operationId": "getContactChannels", "method": "GET", "path": "/v1/contacts/{contactId}/channels", "tags": ["Contacts"], "summary": "List channels for a contact", "description": "Returns all messaging channels linked to a contact (e.g. Instagram DM, Telegram, WhatsApp).", "parameters": [{ "name": "contactId", "in": "path", "required": true, "schemaType": "string" }] }, { "operationId": "bulkCreateContacts", "method": "POST", "path": "/v1/contacts/bulk", "tags": ["Contacts"], "summary": "Bulk create contacts", "description": "Import up to 1000 contacts at a time. Skips duplicates.", "parameters": [], "requestBody": { "required": true, "contentTypes": ["application/json"], "schemaRefs": { "application/json": { "schemaType": "object" } } } }, { "operationId": "clearContactFieldValue", "method": "DELETE", "path": "/v1/contacts/{contactId}/fields/{slug}", "tags": ["Custom Fields"], "summary": "Clear custom field value", "description": "Remove a custom field value from a contact. The field definition is not affected.", "parameters": [{ "name": "contactId", "in": "path", "required": true, "schemaType": "string" }, { "name": "slug", "in": "path", "required": true, "schemaType": "string" }] }, { "operationId": "setContactFieldValue", "method": "PUT", "path": "/v1/contacts/{contactId}/fields/{slug}", "tags": ["Custom Fields"], "summary": "Set custom field value", "description": "Set or overwrite a custom field value on a contact. The value type must match the field definition.", "parameters": [{ "name": "contactId", "in": "path", "required": true, "schemaType": "string" }, { "name": "slug", "in": "path", "required": true, "schemaType": "string" }], "requestBody": { "required": true, "contentTypes": ["application/json"], "schemaRefs": { "application/json": { "schemaType": "object" } } } }, { "operationId": "listCustomFields", "method": "GET", "path": "/v1/custom-fields", "tags": ["Custom Fields"], "summary": "List custom field definitions", "description": "Returns all custom field definitions. Optionally filter by profile.", "parameters": [{ "name": "profileId", "in": "query", "required": false, "description": "Filter by profile. Omit to list across all profiles", "schemaType": "string" }] }, { "operationId": "createCustomField", "method": "POST", "path": "/v1/custom-fields", "tags": ["Custom Fields"], "summary": "Create custom field", "description": "Create a new custom field definition. Supported types are text, number, date, boolean, and select.", "parameters": [], "requestBody": { "required": true, "contentTypes": ["application/json"], "schemaRefs": { "application/json": { "schemaType": "object" } } } }, { "operationId": "deleteCustomField", "method": "DELETE", "path": "/v1/custom-fields/{fieldId}", "tags": ["Custom Fields"], "summary": "Delete custom field", "description": "Delete a custom field definition and remove its values from all contacts.", "parameters": [{ "name": "fieldId", "in": "path", "required": true, "schemaType": "string" }] }, { "operationId": "updateCustomField", "method": "PATCH", "path": "/v1/custom-fields/{fieldId}", "tags": ["Custom Fields"], "summary": "Update custom field", "description": "Update a custom field definition. The field type cannot be changed after creation.", "parameters": [{ "name": "fieldId", "in": "path", "required": true, "schemaType": "string" }], "requestBody": { "required": false, "contentTypes": ["application/json"], "schemaRefs": { "application/json": { "schemaType": "object" } } } }, { "operationId": "getDiscordChannels", "method": "GET", "path": "/v1/accounts/{accountId}/discord-channels", "tags": ["Discord"], "summary": "List Discord guild channels", "description": "Returns the text, announcement, and forum channels in the connected Discord guild. Use this to discover available channels when switching the connected channel via PATCH /v1/accounts/{accountId}/discord-settings.", "parameters": [{ "name": "accountId", "in": "path", "required": true, "schemaType": "string" }] }, { "operationId": "getDiscordSettings", "method": "GET", "path": "/v1/accounts/{accountId}/discord-settings", "tags": ["Discord"], "summary": "Get Discord account settings", "description": "Returns the current Discord account settings including webhook identity (display name and avatar), connected channel, and guild information.", "parameters": [{ "name": "accountId", "in": "path", "required": true, "schemaType": "string" }] }, { "operationId": "updateDiscordSettings", "method": "PATCH", "path": "/v1/accounts/{accountId}/discord-settings", "tags": ["Discord"], "summary": "Update Discord settings", "description": "Update Discord account settings. Supports two operations (can be combined): 1. **Webhook identity** - Set the default display name and avatar that appear as the message author on every post. These are account-level defaults; individual posts can override them via platformSpecificData.webhookUsername / webhookAvatarUrl. 2. **Switch channel** - Move the connection to a different channel in the same guild. A new webhook is automatically created in the target channel.", "parameters": [{ "name": "accountId", "in": "path", "required": true, "schemaType": "string" }], "requestBody": { "required": true, "contentTypes": ["application/json"], "schemaRefs": { "application/json": { "schemaType": "object" } } } }, { "operationId": "listDiscordPinnedMessages", "method": "GET", "path": "/v1/discord/channels/{channelId}/pins", "tags": ["Discord"], "summary": "List pinned messages in a Discord channel", "description": "Returns the channel's pinned messages, sorted most-recently-pinned first. Discord caps a channel at 50 pinned messages and returns the full list unpaginated. Bot needs READ_MESSAGE_HISTORY in the channel (granted by default BOT_PERMISSIONS).", "parameters": [{ "name": "channelId", "in": "path", "required": true, "description": "Discord channel snowflake.", "schemaType": "string" }, { "name": "accountId", "in": "query", "required": true, "description": "SocialAccount _id of any Discord account in the same guild.", "schemaType": "string" }] }, { "operationId": "unpinDiscordMessage", "method": "DELETE", "path": "/v1/discord/channels/{channelId}/pins/{messageId}", "tags": ["Discord"], "summary": "Unpin a Discord message", "description": "Unpin a message. Same MANAGE_MESSAGES permission requirement as pin. Idempotent — unpinning a non-pinned message is a 204 no-op.", "parameters": [{ "name": "channelId", "in": "path", "required": true, "schemaType": "string" }, { "name": "messageId", "in": "path", "required": true, "schemaType": "string" }, { "name": "accountId", "in": "query", "required": true, "schemaType": "string" }] }, { "operationId": "pinDiscordMessage", "method": "PUT", "path": "/v1/discord/channels/{channelId}/pins/{messageId}", "tags": ["Discord"], "summary": "Pin a Discord message", "description": "Pin a specific message in a channel. Path shape mirrors Discord's own API (`PUT /channels/{cid}/pins/{mid}`). Idempotent — re-pinning an already-pinned message is a 204 no-op. Constraints: - Bot needs MANAGE_MESSAGES in the channel. - 50-pin cap per channel — hitting it returns 400 (Discord-side). Caller should unpin one first.", "parameters": [{ "name": "channelId", "in": "path", "required": true, "schemaType": "string" }, { "name": "messageId", "in": "path", "required": true, "schemaType": "string" }, { "name": "accountId", "in": "query", "required": true, "schemaType": "string" }] }, { "operationId": "sendDiscordDirectMessage", "method": "POST", "path": "/v1/discord/dms", "tags": ["Discord"], "summary": "Send a Discord Direct Message", "description": "Send a 1:1 Direct Message from the bot to a Discord user (by snowflake ID). Supports the same payload shape as channel posts — content, embeds, media attachments, and TTS. Constraints (Discord platform limits): - The bot can only DM users it shares at least one guild with. - If the recipient has DMs disabled for non-friends, Discord returns 403 (surfaces as a 502 platform error). - `content` capped at 2,000 chars. - At least one of `content`, `embeds`, or `attachments` is required. - The recipi...", "parameters": [], "requestBody": { "required": true, "contentTypes": ["application/json"], "schemaRefs": { "application/json": { "schemaType": "object" } } } }, { "operationId": "listDiscordScheduledEvents", "method": "GET", "path": "/v1/discord/guilds/{guildId}/events", "tags": ["Discord"], "summary": "List Discord scheduled events", "description": "Return all scheduled events in the guild. Events are distinct from messages — they appear in the server's Events panel and Discord auto-notifies interested members ahead of start time. Pass `withUserCount=true` to include `user_count` (number of members who RSVP'd) on each event. Useful for surfacing engagement.", "parameters": [{ "name": "guildId", "in": "path", "required": true, "schemaType": "string" }, { "name": "accountId", "in": "query", "required": true, "schemaType": "string" }, { "name": "withUserCount", "in": "query", "required": false, "description": "Include user_count on each event.", "schemaType": "boolean" }] }, { "operationId": "createDiscordScheduledEvent", "method": "POST", "path": "/v1/discord/guilds/{guildId}/events", "tags": ["Discord"], "summary": "Create a Discord scheduled event", "description": "Create a guild scheduled event. Three event types, selected via the discriminator on `entity.type`: - `external` — off-platform (Zoom, in-person, livestream). Requires both `location` and `endsAt`. Most common type for scheduler integrations. - `voice` — hosted in a Discord voice channel. Requires `channelId`. - `stage` — hosted in a Discord stage channel. Requires `channelId`. Bot needs MANAGE_EVENTS in the guild. Existing installs (pre-events PR) need a re-invite OR a server admin manually gr...", "parameters": [{ "name": "guildId", "in": "path", "required": true, "schemaType": "string" }], "requestBody": { "required": true, "contentTypes": ["application/json"], "schemaRefs": { "application/json": { "schemaType": "object" } } } }, { "operationId": "deleteDiscordScheduledEvent", "method": "DELETE", "path": "/v1/discord/guilds/{guildId}/events/{eventId}", "tags": ["Discord"], "summary": "Delete a Discord scheduled event", "description": "Hard-delete an event. Use PATCH with `status: 'cancelled'` instead if you want the event preserved in the guild's history.", "parameters": [{ "name": "guildId", "in": "path", "required": true, "schemaType": "string" }, { "name": "eventId", "in": "path", "required": true, "schemaType": "string" }, { "name": "accountId", "in": "query", "required": true, "schemaType": "string" }] }, { "operationId": "getDiscordScheduledEvent", "method": "GET", "path": "/v1/discord/guilds/{guildId}/events/{eventId}", "tags": ["Discord"], "summary": "Get a Discord scheduled event", "parameters": [{ "name": "guildId", "in": "path", "required": true, "schemaType": "string" }, { "name": "eventId", "in": "path", "required": true, "schemaType": "string" }, { "name": "accountId", "in": "query", "required": true, "schemaType": "string" }] }, { "operationId": "updateDiscordScheduledEvent", "method": "PATCH", "path": "/v1/discord/guilds/{guildId}/events/{eventId}", "tags": ["Discord"], "summary": "Update a Discord scheduled event", "description": "Patch any subset of fields. Passing `status: 'cancelled'` is how you cancel an event — Discord doesn't have a dedicated cancel endpoint, it's a status transition. Most status transitions Discord enforces (you can't go SCHEDULED → COMPLETED directly). The common consumer case is SCHEDULED → CANCELED.", "parameters": [{ "name": "guildId", "in": "path", "required": true, "schemaType": "string" }, { "name": "eventId", "in": "path", "required": true, "schemaType": "string" }], "requestBody": { "required": true, "contentTypes": ["application/json"], "schemaRefs": { "application/json": { "schemaType": "object" } } } }, { "operationId": "listDiscordGuildMembers", "method": "GET", "path": "/v1/discord/guilds/{guildId}/members", "tags": ["Discord"], "summary": "List Discord guild members", "description": "Cursor-paginated list of guild members. Returns Discord's raw member objects so callers can build community-ops automation (e.g. \"add role to all members joined in the last 7 days\") on the actual platform shape. **Important:** this endpoint requires the privileged \"Server Members Intent\" enabled on the Discord app (Developer Portal → Bot tab → toggle \"Server Members Intent\" ON, then Save). Without it, Discord returns an empty array with no error. Verify the intent is enabled before relying on t...", "parameters": [{ "name": "guildId", "in": "path", "required": true, "schemaType": "string" }, { "name": "accountId", "in": "query", "required": true, "schemaType": "string" }, { "name": "limit", "in": "query", "required": false, "description": "Page size (1-1000).", "schemaType": "integer" }, { "name": "after", "in": "query", "required": false, "description": "Snowflake of the last member from the previous page.", "schemaType": "string" }] }, { "operationId": "removeDiscordMemberRole", "method": "DELETE", "path": "/v1/discord/guilds/{guildId}/members/{userId}/roles/{roleId}", "tags": ["Discord"], "summary": "Remove a role from a guild member", "description": "Remove one role from one member. Idempotent — removing a role the member doesn't have returns 204 no-op. Same permission + hierarchy constraints as the PUT counterpart.", "parameters": [{ "name": "guildId", "in": "path", "required": true, "schemaType": "string" }, { "name": "userId", "in": "path", "required": true, "schemaType": "string" }, { "name": "roleId", "in": "path", "required": true, "schemaType": "string" }, { "name": "accountId", "in": "query", "required": true, "schemaType": "string" }] }, { "operationId": "addDiscordMemberRole", "method": "PUT", "path": "/v1/discord/guilds/{guildId}/members/{userId}/roles/{roleId}", "tags": ["Discord"], "summary": "Assign a role to a guild member", "description": "Assign one role to one member. Idempotent on Discord's side — re-running on a member who already has the role is a 204 no-op. Path shape mirrors Discord's own API (`PUT /guilds/{guild}/members/{user}/roles/{role}`) for zero-translation mental mapping. Bot needs MANAGE_ROLES permission in the guild AND its highest role must be above the target role (Discord hierarchy rule). The `@everyone` role (where roleId == guildId) cannot be assigned.", "parameters": [{ "name": "guildId", "in": "path", "required": true, "schemaType": "string" }, { "name": "userId", "in": "path", "required": true, "description": "Discord user snowflake to assign the role to.", "schemaType": "string" }, { "name": "roleId", "in": "path", "required": true, "schemaType": "string" }, { "name": "accountId", "in": "query", "required": true, "schemaType": "string" }] }, { "operationId": "listDiscordGuildRoles", "method": "GET", "path": "/v1/discord/guilds/{guildId}/roles", "tags": ["Discord"], "summary": "List Discord guild roles", "description": "Returns all roles in a Discord guild. Useful for building role-mention pickers, role-permission UIs, or finding the role ID before calling the role-assign endpoint. Roles are returned unordered — sort client-side by `position` if you need Discord's UI ordering. Caller must pass `accountId` of a Discord SocialAccount bound to this guild (route verifies team access + guild match).", "parameters": [{ "name": "guildId", "in": "path", "required": true, "description": "Discord guild snowflake ID", "schemaType": "string" }, { "name": "accountId", "in": "query", "required": true, "description": "SocialAccount _id of the Discord account bound to this guild", "schemaType": "string" }] }, { "operationId": "getGmbAttributeMetadata", "method": "GET", "path": "/v1/accounts/{accountId}/gmb-attribute-metadata", "tags": ["GMB Attributes"], "summary": "Get attribute metadata", "description": "Returns metadata about which Google Business Profile attributes are available for a location or business category. Use this endpoint to discover valid attribute names, value types, and allowed enum values before reading or writing via gmb-attributes. Two mutually exclusive query modes: **Location mode**: pass `locationId` (or rely on the account's stored `selectedLocationId`). Google returns attributes valid for that specific location. **Category mode**: pass `categoryName` (must start with `ca...", "parameters": [{ "name": "accountId", "in": "path", "required": true, "schemaType": "string" }, { "name": "locationId", "in": "query", "required": false, "description": "GBP location ID (e.g. \"6257659026299438786\"). If omitted, uses the account's stored selectedLocationId. Mutually exclusive with categoryName.", "schemaType": "string" }, { "name": "categoryName", "in": "query", "required": false, "description": "Category resource name, must start with \"categories/\" (e.g. \"categories/gcid:plumber\"). Required together with regionCode. Mutually exclusive with locationId.", "schemaType": "string" }, { "name": "regionCode", "in": "query", "required": false, "description": "BCP-47 region code (e.g. \"US\", \"ES\"). Required when categoryName is provided.", "schemaType": "string" }, { "name": "languageCode", "in": "query", "required": false, "description": "BCP-47 language code for display names (e.g. \"en\", \"es\"). Optional when categoryName is provided. Omitted from the Google call when not supplied.", "schemaType": "string" }, { "name": "pageSize", "in": "query", "required": false, "description": "Maximum number of attribute metadata items to return. Google defaults to 200.", "schemaType": "integer" }, { "name": "pageToken", "in": "query", "required": false, "description": "Pagination token from a previous response's nextPageToken field.", "schemaType": "string" }] }, { "operationId": "getGoogleBusinessAttributes", "method": "GET", "path": "/v1/accounts/{accountId}/gmb-attributes", "tags": ["GMB Attributes"], "summary": "Get attributes", "description": "Returns GBP location attributes (amenities, services, accessibility, payment types). Available attributes vary by business category.", "parameters": [{ "name": "accountId", "in": "path", "required": true, "schemaType": "string" }, { "name": "locationId", "in": "query", "required": false, "description": "Override which location to query. If omitted, uses the account's selected location. Use GET /gmb-locations to list valid IDs.", "schemaType": "string" }] }, { "operationId": "updateGoogleBusinessAttributes", "method": "PUT", "path": "/v1/accounts/{accountId}/gmb-attributes", "tags": ["GMB Attributes"], "summary": "Update attributes", "description": "Updates location attributes (amenities, services, etc.). The attributeMask specifies which attributes to update (comma-separated).", "parameters": [{ "name": "accountId", "in": "path", "required": true, "schemaType": "string" }, { "name": "locationId", "in": "query", "required": false, "description": "Override which location to target. If omitted, uses the account's selected location. Use GET /gmb-locations to list valid IDs.", "schemaType": "string" }], "requestBody": { "required": true, "contentTypes": ["application/json"], "schemaRefs": { "application/json": { "schemaType": "object" } } } }, { "operationId": "getGoogleBusinessFoodMenus", "method": "GET", "path": "/v1/accounts/{accountId}/gmb-food-menus", "tags": ["GMB Food Menus"], "summary": "Get food menus", "description": "Returns food menus for a GBP location including sections, items, pricing, and dietary info. Only for locations with food menu support.", "parameters": [{ "name": "accountId", "in": "path", "required": true, "description": "The Zernio account ID (from /v1/accounts)", "schemaType": "string" }, { "name": "locationId", "in": "query", "required": false, "description": "Override which location to query. If omitted, uses the account's selected location. Use GET /gmb-locations to list valid IDs.", "schemaType": "string" }] }, { "operationId": "updateGoogleBusinessFoodMenus", "method": "PUT", "path": "/v1/accounts/{accountId}/gmb-food-menus", "tags": ["GMB Food Menus"], "summary": "Update food menus", "description": "Updates food menus for a GBP location. Send the full menus array. Use updateMask for partial updates.", "parameters": [{ "name": "accountId", "in": "path", "required": true, "description": "The Zernio account ID (from /v1/accounts)", "schemaType": "string" }, { "name": "locationId", "in": "query", "required": false, "description": "Override which location to target. If omitted, uses the account's selected location. Use GET /gmb-locations to list valid IDs.", "schemaType": "string" }], "requestBody": { "required": true, "contentTypes": ["application/json"], "schemaRefs": { "application/json": { "schemaType": "object" } } } }, { "operationId": "getGoogleBusinessLocationDetails", "method": "GET", "path": "/v1/accounts/{accountId}/gmb-location-details", "tags": ["GMB Location Details"], "summary": "Get location details", "description": "Returns detailed GBP location info (hours, description, phone, website, categories, services). Use readMask to request specific fields.", "parameters": [{ "name": "accountId", "in": "path", "required": true, "description": "The Zernio account ID (from /v1/accounts)", "schemaType": "string" }, { "name": "locationId", "in": "query", "required": false, "description": "Override which location to query. If omitted, uses the account's selected location. Use GET /gmb-locations to list valid IDs.", "schemaType": "string" }, { "name": "readMask", "in": "query", "required": false, "description": "Comma-separated fields to return. Available: name, title, phoneNumbers, categories, storefrontAddress, websiteUri, regularHours, specialHours, serviceArea, serviceItems, profile, openInfo, metadata, moreHours. `title` and `metadata` are al...", "schemaType": "string" }] }, { "operationId": "updateGoogleBusinessLocationDetails", "method": "PUT", "path": "/v1/accounts/{accountId}/gmb-location-details", "tags": ["GMB Location Details"], "summary": "Update location details", "description": "Updates GBP location details. The updateMask field is required and specifies which fields to update. This endpoint proxies Google's Business Information API locations.patch, so any valid updateMask field is supported. Common fields: regularHours, specialHours, profile.description, websiteUri, phoneNumbers, categories, serviceItems.", "parameters": [{ "name": "accountId", "in": "path", "required": true, "description": "The Zernio account ID (from /v1/accounts)", "schemaType": "string" }, { "name": "locationId", "in": "query", "required": false, "description": "Override which location to target. If omitted, uses the account's selected location. Use GET /gmb-locations to list valid IDs.", "schemaType": "string" }], "requestBody": { "required": true, "contentTypes": ["application/json"], "schemaRefs": { "application/json": { "schemaType": "object" } } } }, { "operationId": "deleteGoogleBusinessMedia", "method": "DELETE", "path": "/v1/accounts/{accountId}/gmb-media", "tags": ["GMB Media"], "summary": "Delete photo", "description": "Deletes a photo or media item from a GBP location.", "parameters": [{ "name": "accountId", "in": "path", "required": true, "schemaType": "string" }, { "name": "locationId", "in": "query", "required": false, "description": "Override which location to target. If omitted, uses the account's selected location. Use GET /gmb-locations to list valid IDs.", "schemaType": "string" }, { "name": "mediaId", "in": "query", "required": true, "description": "The media item ID to delete", "schemaType": "string" }] }, { "operationId": "listGoogleBusinessMedia", "method": "GET", "path": "/v1/accounts/{accountId}/gmb-media", "tags": ["GMB Media"], "summary": "List media", "description": "Lists media items (photos) for a Google Business Profile location. Returns photo URLs, descriptions, categories, and metadata.", "parameters": [{ "name": "accountId", "in": "path", "required": true, "schemaType": "string" }, { "name": "locationId", "in": "query", "required": false, "description": "Override which location to query. If omitted, uses the account's selected location. Use GET /gmb-locations to list valid IDs.", "schemaType": "string" }, { "name": "pageSize", "in": "query", "required": false, "description": "Number of items to return (max 100)", "schemaType": "integer" }, { "name": "pageToken", "in": "query", "required": false, "description": "Pagination token from previous response", "schemaType": "string" }] }, { "operationId": "createGoogleBusinessMedia", "method": "POST", "path": "/v1/accounts/{accountId}/gmb-media", "tags": ["GMB Media"], "summary": "Upload photo", "description": "Creates a media item (photo) for a location from a publicly accessible URL. Categories determine where the photo appears: COVER, PROFILE, LOGO, EXTERIOR, INTERIOR, FOOD_AND_DRINK, MENU, PRODUCT, TEAMS, ADDITIONAL.", "parameters": [{ "name": "accountId", "in": "path", "required": true, "schemaType": "string" }, { "name": "locationId", "in": "query", "required": false, "description": "Override which location to target. If omitted, uses the account's selected location. Use GET /gmb-locations to list valid IDs.", "schemaType": "string" }], "requestBody": { "required": true, "contentTypes": ["application/json"], "schemaRefs": { "application/json": { "schemaType": "object" } } } }, { "operationId": "deleteGoogleBusinessPlaceAction", "method": "DELETE", "path": "/v1/accounts/{accountId}/gmb-place-actions", "tags": ["GMB Place Actions"], "summary": "Delete action link", "description": "Deletes a place action link (e.g. booking or ordering URL) from a GBP location.", "parameters": [{ "name": "accountId", "in": "path", "required": true, "schemaType": "string" }, { "name": "locationId", "in": "query", "required": false, "description": "Override which location to target. If omitted, uses the account's selected location. Use GET /gmb-locations to list valid IDs.", "schemaType": "string" }, { "name": "name", "in": "query", "required": true, "description": "The resource name of the place action link (e.g. locations/123/placeActionLinks/456)", "schemaType": "string" }] }, { "operationId": "listGoogleBusinessPlaceActions", "method": "GET", "path": "/v1/accounts/{accountId}/gmb-place-actions", "tags": ["GMB Place Actions"], "summary": "List action links", "description": "Lists place action links for a Google Business Profile location. Place actions are the booking, ordering, and reservation buttons that appear on your listing.", "parameters": [{ "name": "accountId", "in": "path", "required": true, "schemaType": "string" }, { "name": "locationId", "in": "query", "required": false, "description": "Override which location to query. If omitted, uses the account's selected location. Use GET /gmb-locations to list valid IDs.", "schemaType": "string" }, { "name": "pageSize", "in": "query", "required": false, "schemaType": "integer" }, { "name": "pageToken", "in": "query", "required": false, "schemaType": "string" }] }, { "operationId": "updateGoogleBusinessPlaceAction", "method": "PATCH", "path": "/v1/accounts/{accountId}/gmb-place-actions", "tags": ["GMB Place Actions"], "summary": "Update action link", "description": "Updates a place action link (change URL or action type). Only the fields included in the request body will be updated.", "parameters": [{ "name": "accountId", "in": "path", "required": true, "schemaType": "string" }, { "name": "locationId", "in": "query", "required": false, "description": "Override which location to target. If omitted, uses the account's selected location.", "schemaType": "string" }], "requestBody": { "required": true, "contentTypes": ["application/json"], "schemaRefs": { "application/json": { "schemaType": "object" } } } }, { "operationId": "createGoogleBusinessPlaceAction", "method": "POST", "path": "/v1/accounts/{accountId}/gmb-place-actions", "tags": ["GMB Place Actions"], "summary": "Create action link", "description": "Creates a place action link for a location. Available action types: APPOINTMENT, ONLINE_APPOINTMENT, DINING_RESERVATION, FOOD_ORDERING, FOOD_DELIVERY, FOOD_TAKEOUT, SHOP_ONLINE.", "parameters": [{ "name": "accountId", "in": "path", "required": true, "schemaType": "string" }, { "name": "locationId", "in": "query", "required": false, "description": "Override which location to target. If omitted, uses the account's selected location. Use GET /gmb-locations to list valid IDs.", "schemaType": "string" }], "requestBody": { "required": true, "contentTypes": ["application/json"], "schemaRefs": { "application/json": { "schemaType": "object" } } } }, { "operationId": "getGoogleBusinessReviews", "method": "GET", "path": "/v1/accounts/{accountId}/gmb-reviews", "tags": ["GMB Reviews"], "summary": "Get reviews", "description": "Returns reviews for a GBP account including ratings, comments, and owner replies. Use nextPageToken for pagination.", "parameters": [{ "name": "accountId", "in": "path", "required": true, "description": "The Zernio account ID (from /v1/accounts)", "schemaType": "string" }, { "name": "locationId", "in": "query", "required": false, "description": "Override which location to query. If omitted, uses the account's selected location. Use GET /gmb-locations to list valid IDs.", "schemaType": "string" }, { "name": "pageSize", "in": "query", "required": false, "description": "Number of reviews to fetch per page (max 50)", "schemaType": "integer" }, { "name": "pageToken", "in": "query", "required": false, "description": "Pagination token from previous response", "schemaType": "string" }] }, { "operationId": "deleteGoogleBusinessReviewReply", "method": "DELETE", "path": "/v1/accounts/{accountId}/gmb-reviews/{reviewId}/reply", "tags": ["GMB Reviews"], "summary": "Delete a review reply", "description": "Removes the business owner reply from a Google Business review. The review itself remains.", "parameters": [{ "name": "accountId", "in": "path", "required": true, "description": "The Zernio account ID (from /v1/accounts)", "schemaType": "string" }, { "name": "reviewId", "in": "path", "required": true, "description": "The review ID portion (e.g. \"AIe9_BGx1234567890\"), not the full resource name", "schemaType": "string" }] }, { "operationId": "replyToGoogleBusinessReview", "method": "POST", "path": "/v1/accounts/{accountId}/gmb-reviews/{reviewId}/reply", "tags": ["GMB Reviews"], "summary": "Reply to a review", "description": "Posts (or updates) the business owner reply to a Google Business review. The reply is associated with the account's currently selected location (set via /v1/accounts/{accountId}/gmb-locations). Calling this endpoint a second time on the same review overwrites the previous reply (PUT semantics on Google's side).", "parameters": [{ "name": "accountId", "in": "path", "required": true, "description": "The Zernio account ID (from /v1/accounts)", "schemaType": "string" }, { "name": "reviewId", "in": "path", "required": true, "description": "The review ID portion (e.g. \"AIe9_BGx1234567890\"), not the full resource name", "schemaType": "string" }], "requestBody": { "required": true, "contentTypes": ["application/json"], "schemaRefs": { "application/json": { "schemaType": "object" } } } }, { "operationId": "batchGetGoogleBusinessReviews", "method": "POST", "path": "/v1/accounts/{accountId}/gmb-reviews/batch", "tags": ["GMB Reviews"], "summary": "Batch get reviews", "description": "Fetches reviews across multiple locations in a single request. More efficient than calling GET /gmb-reviews per location for multi-location businesses. Returns a flat list of individual reviews, each tagged with its review resource name. Note: this endpoint does not return aggregate metrics (averageRating / totalReviewCount). For those, use the single-location GET /gmb-reviews endpoint.", "parameters": [{ "name": "accountId", "in": "path", "required": true, "schemaType": "string" }], "requestBody": { "required": true, "contentTypes": ["application/json"], "schemaRefs": { "application/json": { "schemaType": "object" } } } }, { "operationId": "getGoogleBusinessServices", "method": "GET", "path": "/v1/accounts/{accountId}/gmb-services", "tags": ["GMB Services"], "summary": "Get services", "description": "Gets the services offered by a Google Business Profile location. Returns an array of service items (structured or free-form with optional price).", "parameters": [{ "name": "accountId", "in": "path", "required": true, "schemaType": "string" }, { "name": "locationId", "in": "query", "required": false, "description": "Override which location to query. If omitted, uses the account's selected location.", "schemaType": "string" }] }, { "operationId": "updateGoogleBusinessServices", "method": "PUT", "path": "/v1/accounts/{accountId}/gmb-services", "tags": ["GMB Services"], "summary": "Replace services", "description": "Replaces the entire service list for a location. Google's API requires full replacement; individual item updates are not supported. Each service can be structured (using a predefined serviceTypeId) or free-form (custom label).", "parameters": [{ "name": "accountId", "in": "path", "required": true, "schemaType": "string" }, { "name": "locationId", "in": "query", "required": false, "description": "Override which location to target. If omitted, uses the account's selected location.", "schemaType": "string" }], "requestBody": { "required": true, "contentTypes": ["application/json"], "schemaRefs": { "application/json": { "schemaType": "object" } } } }, { "operationId": "getGoogleBusinessVerifications", "method": "GET", "path": "/v1/accounts/{accountId}/gmb-verifications", "tags": ["GMB Verifications"], "summary": "Get verification state", "description": "Returns the location's Voice of Merchant state plus its verification history. `voiceOfMerchantState.hasVoiceOfMerchant` tells you whether the listing is verified and published; when it is false, `verify` reports whether a verification is already pending. Each entry in `verifications` has a `state` of PENDING, COMPLETED, or FAILED.", "parameters": [{ "name": "accountId", "in": "path", "required": true, "description": "The Zernio account ID (from /v1/accounts)", "schemaType": "string" }, { "name": "locationId", "in": "query", "required": false, "description": "Override which location to query. If omitted, uses the account's selected location. Use GET /gmb-locations to list valid IDs.", "schemaType": "string" }] }, { "operationId": "startGoogleBusinessVerification", "method": "POST", "path": "/v1/accounts/{accountId}/gmb-verifications", "tags": ["GMB Verifications"], "summary": "Start a verification", "description": "Starts a verification for the location. This is a mutating action: depending on `method`, Google mails a postcard, places a call, or sends an SMS/email to the business. Submit the resulting code with POST /gmb-verifications/{verificationId}/complete. Use POST /gmb-verifications/options first to discover which methods are eligible.", "parameters": [{ "name": "accountId", "in": "path", "required": true, "description": "The Zernio account ID (from /v1/accounts)", "schemaType": "string" }, { "name": "locationId", "in": "query", "required": false, "description": "Override which location to target. If omitted, uses the account's selected location.", "schemaType": "string" }], "requestBody": { "required": true, "contentTypes": ["application/json"], "schemaRefs": { "application/json": { "schemaType": "object" } } } }, { "operationId": "completeGoogleBusinessVerification", "method": "POST", "path": "/v1/accounts/{accountId}/gmb-verifications/{verificationId}/complete", "tags": ["GMB Verifications"], "summary": "Complete a verification", "description": "Completes a PENDING verification by submitting the PIN/code Google sent the business (postcard code, SMS PIN, etc.). On success the verification moves to COMPLETED.", "parameters": [{ "name": "accountId", "in": "path", "required": true, "description": "The Zernio account ID (from /v1/accounts)", "schemaType": "string" }, { "name": "verificationId", "in": "path", "required": true, "description": "The last segment of a verification `name` from GET /gmb-verifications.", "schemaType": "string" }, { "name": "locationId", "in": "query", "required": false, "description": "Override which location to target. If omitted, uses the account's selected location.", "schemaType": "string" }], "requestBody": { "required": true, "contentTypes": ["application/json"], "schemaRefs": { "application/json": { "schemaType": "object" } } } }, { "operationId": "fetchGoogleBusinessVerificationOptions", "method": "POST", "path": "/v1/accounts/{accountId}/gmb-verifications/options", "tags": ["GMB Verifications"], "summary": "Fetch verification options", "description": "Reports the verification methods Google currently offers for the location. Non-mutating (nothing is sent to the business). `languageCode` is required; service-area (\"CUSTOMER_LOCATION_ONLY\") businesses also require `context.address`, otherwise Google returns 400.", "parameters": [{ "name": "accountId", "in": "path", "required": true, "description": "The Zernio account ID (from /v1/accounts)", "schemaType": "string" }, { "name": "locationId", "in": "query", "required": false, "description": "Override which location to query. If omitted, uses the account's selected location.", "schemaType": "string" }], "requestBody": { "required": true, "contentTypes": ["application/json"], "schemaRefs": { "application/json": { "schemaType": "object" } } } }, { "operationId": "listInboxConversationAnalytics", "method": "GET", "path": "/v1/analytics/inbox/conversations", "tags": ["Inbox Analytics"], "summary": "List conversations with inbox analytics", "description": "Per-conversation listing with per-row totals + first/last message timestamps. The inbox analog of GET /v1/analytics (posts listing) — same filter shape, same pagination, same sort/order semantics. Use as the entry point for the per-conversation analytics drawer at /v1/analytics/inbox/conversations/{conversationId}. Rows are enriched with the conversation's participant info (`participantName`, `participantUsername`, `participantPicture`) and last-message preview by joining the Conversation docum...", "parameters": [{ "name": "fromDate", "in": "query", "required": true, "schemaType": "string" }, { "name": "toDate", "in": "query", "required": false, "schemaType": "string" }, { "name": "profileId", "in": "query", "required": false, "schemaType": "string" }, { "name": "platform", "in": "query", "required": false, "schemaType": "string" }, { "name": "accountId", "in": "query", "required": false, "schemaType": "string" }, { "name": "source", "in": "query", "required": false, "schemaType": "string" }, { "name": "limit", "in": "query", "required": false, "schemaType": "integer" }, { "name": "page", "in": "query", "required": false, "schemaType": "integer" }, { "name": "sortBy", "in": "query", "required": false, "schemaType": "string", "schemaEnum": ["lastMessageAt", "firstMessageAt", "totalMessages", "received", "sent", "read", "failed"] }, { "name": "order", "in": "query", "required": false, "schemaType": "string", "schemaEnum": ["asc", "desc"] }] }, { "operationId": "getInboxConversationAnalytics", "method": "GET", "path": "/v1/analytics/inbox/conversations/{conversationId}", "tags": ["Inbox Analytics"], "summary": "Get analytics for a single conversation", "description": "Per-conversation inbox analytics. The inbox analog of /v1/analytics/post-timeline — one conversation, daily totals, source mix. The {conversationId} path param accepts EITHER the Mongo `_id` of the Conversation document OR its `platformConversationId` (the same identity used by metadata.conversationId at ingest time). Ownership is verified in MongoDB against the caller's team before the Tinybird query fires. Max date range is 365 days.", "parameters": [{ "name": "conversationId", "in": "path", "required": true, "description": "Mongo _id or platformConversationId.", "schemaType": "string" }, { "name": "fromDate", "in": "query", "required": true, "schemaType": "string" }, { "name": "toDate", "in": "query", "required": false, "schemaType": "string" }] }, { "operationId": "getInboxHeatmap", "method": "GET", "path": "/v1/analytics/inbox/heatmap", "tags": ["Inbox Analytics"], "summary": "Get inbox day-of-week × hour-of-day heatmap", "description": "Day-of-week × hour-of-day breakdown of inbox messages. Buckets are sparse — only cells with at least one event are returned; clients zero-fill the rest to render the full 7×24 grid. The `dow` field follows ClickHouse's `toDayOfWeek` convention (1 = Monday … 7 = Sunday). Max date range is 365 days.", "parameters": [{ "name": "fromDate", "in": "query", "required": true, "schemaType": "string" }, { "name": "toDate", "in": "query", "required": false, "schemaType": "string" }, { "name": "profileId", "in": "query", "required": false, "schemaType": "string" }, { "name": "platform", "in": "query", "required": false, "schemaType": "string" }, { "name": "accountId", "in": "query", "required": false, "schemaType": "string" }, { "name": "source", "in": "query", "required": false, "schemaType": "string" }, { "name": "action", "in": "query", "required": false, "description": "Narrow to a single event type. \"all\" or omitted means no filter.", "schemaType": "string", "schemaEnum": ["message.received", "message.sent", "message.read", "all"] }] }, { "operationId": "getInboxResponseTime", "method": "GET", "path": "/v1/analytics/inbox/response-time", "tags": ["Inbox Analytics"], "summary": "Get inbox response-time stats", "description": "Time-to-first-response stats. Pairs each received message with the next sent message in the same conversation and reports the delta as both summary statistics and a fixed-bucket histogram suited for the analytics page's TTR chart. `sampleSize` reflects only conversations that received AND got a reply in the window — received-but-never-answered conversations are excluded. Compare against /v1/analytics/inbox/volume's `summary.received` to compute reply rate. Max date range is 365 days.", "parameters": [{ "name": "fromDate", "in": "query", "required": true, "schemaType": "string" }, { "name": "toDate", "in": "query", "required": false, "schemaType": "string" }, { "name": "profileId", "in": "query", "required": false, "schemaType": "string" }, { "name": "platform", "in": "query", "required": false, "schemaType": "string" }, { "name": "accountId", "in": "query", "required": false, "schemaType": "string" }] }, { "operationId": "getInboxSourceBreakdown", "method": "GET", "path": "/v1/analytics/inbox/source-breakdown", "tags": ["Inbox Analytics"], "summary": "Get inbox source breakdown", "description": "Breakdown of inbox messages by their lineage source (the `metadata.source` field set at ingest time: human / workflow / sequence / broadcast / comment_automation / api / contact / platform). Each source row also carries a per-platform sub-split. Max date range is 365 days.", "parameters": [{ "name": "fromDate", "in": "query", "required": true, "schemaType": "string" }, { "name": "toDate", "in": "query", "required": false, "schemaType": "string" }, { "name": "profileId", "in": "query", "required": false, "schemaType": "string" }, { "name": "platform", "in": "query", "required": false, "schemaType": "string" }, { "name": "accountId", "in": "query", "required": false, "schemaType": "string" }] }, { "operationId": "getInboxTopAccounts", "method": "GET", "path": "/v1/analytics/inbox/top-accounts", "tags": ["Inbox Analytics"], "summary": "Get top accounts by inbox volume", "description": "Leaderboard of social accounts by inbox message volume. Decorates each row with display labels from the live SocialAccount record (so the UI shows username + displayName, not just an ID). Accounts that no longer map to a SocialAccount surface as \"(disconnected)\" so the row stays visible. Max date range is 365 days.", "parameters": [{ "name": "fromDate", "in": "query", "required": true, "schemaType": "string" }, { "name": "toDate", "in": "query", "required": false, "schemaType": "string" }, { "name": "profileId", "in": "query", "required": false, "schemaType": "string" }, { "name": "platform", "in": "query", "required": false, "schemaType": "string" }, { "name": "source", "in": "query", "required": false, "schemaType": "string" }, { "name": "limit", "in": "query", "required": false, "description": "Cap on returned rows. Lower than the posting listing's 100 because each row triggers a SocialAccount Mongo lookup.", "schemaType": "integer" }] }, { "operationId": "getInboxVolume", "method": "GET", "path": "/v1/analytics/inbox/volume", "tags": ["Inbox Analytics"], "summary": "Get inbox messaging volume", "description": "Daily inbox messaging volume + breakdowns. Folds the raw messaging events into three projections so the client can render the volume chart, KPI strip, and per-platform stacked bar from a single call. Max date range is 365 days.", "parameters": [{ "name": "fromDate", "in": "query", "required": true, "description": "Inclusive lower bound (YYYY-MM-DD). Required.", "schemaType": "string" }, { "name": "toDate", "in": "query", "required": false, "description": "Inclusive upper bound (YYYY-MM-DD). Defaults to today.", "schemaType": "string" }, { "name": "profileId", "in": "query", "required": false, "schemaType": "string" }, { "name": "platform", "in": "query", "required": false, "description": "Filter by single platform (facebook, instagram, twitter, etc.).", "schemaType": "string" }, { "name": "accountId", "in": "query", "required": false, "schemaType": "string" }, { "name": "source", "in": "query", "required": false, "description": "Filter by metadata.source lineage (human, workflow, sequence, broadcast, comment_automation, api, contact, platform).", "schemaType": "string" }] }, { "operationId": "listInstagramStories", "method": "GET", "path": "/v1/accounts/{accountId}/instagram/stories", "tags": ["Instagram"], "summary": "List active Instagram stories", "description": "Returns the IG Business/Creator account's currently-active stories. Meta keeps stories live for 24h; expired stories are not returned. Limitations propagated from Meta (these are NOT bugs): - 24h window only - Live videos excluded - Reshared stories not returned - `mediaUrl` may be null if Meta flagged the story for copyright - `caption`, `likeCount`, `commentsCount` do not apply to story media", "parameters": [{ "name": "accountId", "in": "path", "required": true, "description": "The Instagram account ID", "schemaType": "string" }] }, { "operationId": "getInstagramStoryInsights", "method": "GET", "path": "/v1/accounts/{accountId}/instagram/stories/{storyId}/insights", "tags": ["Instagram"], "summary": "Get Instagram story insights", "description": "Returns metrics for a single story. The `source` field discriminates between three states: - `live` — fetched from Meta in real time (story is still active) - `cached` — fetched from a persisted `story_insights` webhook payload (story has expired but we received its final-state metrics from Meta) - `unavailable` — story has expired and we never received its webhook payload (for example, the account connected after the story expired) Field semantics follow Meta's API. Counts below 5 may be retur...", "parameters": [{ "name": "accountId", "in": "path", "required": true, "description": "The Instagram account ID", "schemaType": "string" }, { "name": "storyId", "in": "path", "required": true, "description": "The Instagram media ID of the story.", "schemaType": "string" }] }, { "operationId": "createInviteToken", "method": "POST", "path": "/v1/invite/tokens", "tags": ["Invites"], "summary": "Create invite token", "description": "Generate a secure invite link to grant team members access to your profiles. Invites expire after 7 days and are single-use.", "parameters": [], "requestBody": { "required": true, "contentTypes": ["application/json"], "schemaRefs": { "application/json": { "schemaType": "object" } } } }, { "operationId": "getLinkedInMentions", "method": "GET", "path": "/v1/accounts/{accountId}/linkedin-mentions", "tags": ["LinkedIn Mentions"], "summary": "Resolve LinkedIn mention", "description": "Converts a LinkedIn profile or company URL to a URN for @mentions in posts. How to use LinkedIn @mentions (2-step workflow): 1. Call this endpoint with the LinkedIn profile/company URL to get the mention URN and format. 2. Embed the returned mentionFormat (e.g. @[Vincent Jong](urn:li:person:xxx)) directly in your post's content field. Example: - Resolve: GET /v1/accounts/{id}/linkedin-mentions?url=linkedin.com/in/vincentjong&displayName=Vincent Jong - Returns: mentionFormat: \"@[Vincent Jong](ur...", "parameters": [{ "name": "accountId", "in": "path", "required": true, "description": "The LinkedIn account ID", "schemaType": "string" }, { "name": "url", "in": "query", "required": true, "description": "LinkedIn profile URL, company URL, or vanity name.", "schemaType": "string" }, { "name": "displayName", "in": "query", "required": false, "description": "Exact display name as shown on LinkedIn. Required for person mentions to be clickable. Optional for org mentions.", "schemaType": "string" }] }, { "operationId": "listLogs", "method": "GET", "path": "/v1/logs", "tags": ["Logs"], "summary": "List activity logs", "description": "Unified logs endpoint. Returns logs for publishing, connections, webhooks, and messaging. Filter by type, platform, status, and time range. Logs are retained for 90 days.", "parameters": [{ "name": "type", "in": "query", "required": false, "description": "Log category to query", "schemaType": "string", "schemaEnum": ["publishing", "connections", "webhooks", "messaging"] }, { "name": "status", "in": "query", "required": false, "description": "Filter by status", "schemaType": "string", "schemaEnum": ["success", "failed", "pending", "skipped", "all"] }, { "name": "platform", "in": "query", "required": false, "description": "Filter by platform", "schemaType": "string", "schemaEnum": ["tiktok", "instagram", "whatsapp", "facebook", "youtube", "linkedin", "twitter", "threads", "pinterest", "reddit", "bluesky", "googlebusiness", "telegram", "snapchat", "all"] }, { "name": "action", "in": "query", "required": false, "description": "Filter by action (e.g., post.published, message.sent, account.connected, webhook.delivered)", "schemaType": "string" }, { "name": "search", "in": "query", "required": false, "description": "Free-text search across log fields", "schemaType": "string" }, { "name": "days", "in": "query", "required": false, "description": "Number of days to look back (max 90)", "schemaType": "integer" }, { "name": "limit", "in": "query", "required": false, "description": "Maximum number of logs to return (max 100)", "schemaType": "integer" }, { "name": "skip", "in": "query", "required": false, "description": "Number of logs to skip (for pagination)", "schemaType": "integer" }] }, { "operationId": "getMediaPresignedUrl", "method": "POST", "path": "/v1/media/presign", "tags": ["Media"], "summary": "Get upload URL", "description": "Get a presigned URL to upload files directly to cloud storage (up to 5GB). Returns an uploadUrl and publicUrl. PUT your file to the uploadUrl, then use the publicUrl in your posts.", "parameters": [], "requestBody": { "required": true, "contentTypes": ["application/json"], "schemaRefs": { "application/json": { "schemaType": "object" } } } }, { "operationId": "listInboxConversations", "method": "GET", "path": "/v1/inbox/conversations", "tags": ["Messages"], "summary": "List conversations", "description": "Fetch conversations (DMs) from all connected messaging accounts in a single API call. Supports filtering by profile and platform. Results are aggregated and deduplicated. Supported platforms: Facebook, Instagram, Twitter/X, Bluesky, Reddit, Telegram. Twitter/X limitation: X has replaced traditional DMs with encrypted \"X Chat\" for many accounts. Messages sent or received through encrypted X Chat are not accessible via X's API (the /2/dm_events endpoint only returns legacy unencrypted DMs). This ...", "parameters": [{ "name": "profileId", "in": "query", "required": false, "description": "Filter by profile ID", "schemaType": "string" }, { "name": "platform", "in": "query", "required": false, "description": "Filter by platform", "schemaType": "string", "schemaEnum": ["facebook", "instagram", "twitter", "bluesky", "reddit", "telegram"] }, { "name": "status", "in": "query", "required": false, "description": "Filter by conversation status", "schemaType": "string", "schemaEnum": ["active", "archived"] }, { "name": "sortOrder", "in": "query", "required": false, "description": "Sort order by updated time", "schemaType": "string", "schemaEnum": ["asc", "desc"] }, { "name": "limit", "in": "query", "required": false, "description": "Maximum number of conversations to return", "schemaType": "integer" }, { "name": "cursor", "in": "query", "required": false, "description": "Pagination cursor for next page", "schemaType": "string" }, { "name": "accountId", "in": "query", "required": false, "description": "Filter by specific social account ID", "schemaType": "string" }] }, { "operationId": "createInboxConversation", "method": "POST", "path": "/v1/inbox/conversations", "tags": ["Messages"], "summary": "Create conversation (send a WhatsApp template)", "description": "Initiate a new direct message conversation with a specified user. If a conversation already exists with the recipient, the message is added to the existing thread. Supported platforms: X/Twitter, Bluesky, Reddit, and WhatsApp. Other platforms return PLATFORM_NOT_SUPPORTED. WhatsApp: this is the endpoint for sending an approved template message to a phone number. Provide templateName, templateLanguage, and templateParams (body variable values), with the recipient phone in participantId. A templa...", "parameters": [], "requestBody": { "required": true, "contentTypes": ["application/json", "multipart/form-data"], "schemaRefs": { "application/json": { "schemaType": "object" }, "multipart/form-data": { "schemaType": "object" } } } }, { "operationId": "getInboxConversation", "method": "GET", "path": "/v1/inbox/conversations/{conversationId}", "tags": ["Messages"], "summary": "Get conversation", "description": "Retrieve details and metadata for a specific conversation. Requires accountId query parameter.", "parameters": [{ "name": "conversationId", "in": "path", "required": true, "description": "The conversation ID (id field from list conversations endpoint). This is the platform-specific conversation identifier, not an internal database ID.", "schemaType": "string" }, { "name": "accountId", "in": "query", "required": true, "description": "The social account ID", "schemaType": "string" }] }, { "operationId": "updateInboxConversation", "method": "PUT", "path": "/v1/inbox/conversations/{conversationId}", "tags": ["Messages"], "summary": "Update conversation status", "description": "Archive or activate a conversation. Requires accountId in request body.", "parameters": [{ "name": "conversationId", "in": "path", "required": true, "description": "The conversation ID (id field from list conversations endpoint). This is the platform-specific conversation identifier, not an internal database ID.", "schemaType": "string" }], "requestBody": { "required": true, "contentTypes": ["application/json"], "schemaRefs": { "application/json": { "schemaType": "object" } } } }, { "operationId": "getInboxConversationMessages", "method": "GET", "path": "/v1/inbox/conversations/{conversationId}/messages", "tags": ["Messages"], "summary": "List messages", "description": "Fetch messages for a specific conversation, with cursor-based pagination and ordering control. Pagination: pass `pagination.nextCursor` from a prior response back as the `cursor` query param to fetch the next page. The cursor is opaque; do not parse or construct it client-side. Sort order: defaults to `asc` (oldest first, chat style). For the \"show me the latest messages\" pattern, pass `?sortOrder=desc&limit=N`. Twitter, Instagram, Telegram, WhatsApp and Reddit honor the requested order from th...", "parameters": [{ "name": "conversationId", "in": "path", "required": true, "description": "The conversation ID (id field from list conversations endpoint). This is the platform-specific conversation identifier, not an internal database ID.", "schemaType": "string" }, { "name": "accountId", "in": "query", "required": true, "description": "Social account ID", "schemaType": "string" }, { "name": "limit", "in": "query", "required": false, "description": "Number of messages to return per page. Default 100, max 100.", "schemaType": "integer" }, { "name": "cursor", "in": "query", "required": false, "description": "Opaque pagination cursor. Pass `pagination.nextCursor` from a prior response.", "schemaType": "string" }, { "name": "sortOrder", "in": "query", "required": false, "description": "Order of returned messages. Default `asc` (oldest first, chat style). Twitter, Instagram, Telegram, WhatsApp and Reddit honor this order across cursor pages. For Facebook and Bluesky, only intra-page ordering is affected — pages always wal...", "schemaType": "string", "schemaEnum": ["asc", "desc"] }] }, { "operationId": "sendInboxMessage", "method": "POST", "path": "/v1/inbox/conversations/{conversationId}/messages", "tags": ["Messages"], "summary": "Send message", "description": "Send a message in a conversation. Supports text, attachments, quick replies, buttons, templates, and message tags. Attachment and interactive message support varies by platform. WhatsApp template messages: to send an approved template into this conversation (required when the 24-hour customer-service window is closed), use the `template` field with a single element carrying the template reference: `{ \"elements\": [{ \"name\": ..., \"language\": ..., \"components\": [...] }] }`. See the `template` fiel...", "parameters": [{ "name": "conversationId", "in": "path", "required": true, "description": "The conversation ID (id field from list conversations endpoint). This is the platform-specific conversation identifier, not an internal database ID.", "schemaType": "string" }], "requestBody": { "required": true, "contentTypes": ["application/json", "multipart/form-data"], "schemaRefs": { "application/json": { "schemaType": "object" }, "multipart/form-data": { "schemaType": "object" } } } }, { "operationId": "deleteInboxMessage", "method": "DELETE", "path": "/v1/inbox/conversations/{conversationId}/messages/{messageId}", "tags": ["Messages"], "summary": "Delete message", "description": "Delete a message from a conversation. Platform support varies: - Telegram: Full delete (bot's own messages anytime, others if admin) - X/Twitter: Full delete (own DM events only) - Bluesky: Delete for self only (recipient still sees it) - Reddit: Delete from sender's view only - Facebook, Instagram, WhatsApp: Not supported (returns 400)", "parameters": [{ "name": "conversationId", "in": "path", "required": true, "description": "The conversation ID", "schemaType": "string" }, { "name": "messageId", "in": "path", "required": true, "description": "The platform message ID to delete", "schemaType": "string" }, { "name": "accountId", "in": "query", "required": true, "description": "Social account ID", "schemaType": "string" }] }, { "operationId": "editInboxMessage", "method": "PATCH", "path": "/v1/inbox/conversations/{conversationId}/messages/{messageId}", "tags": ["Messages"], "summary": "Edit message", "description": "Edit the text and/or reply markup of a previously sent Telegram message. Only supported for Telegram. Returns 400 for other platforms.", "parameters": [{ "name": "conversationId", "in": "path", "required": true, "description": "The conversation ID", "schemaType": "string" }, { "name": "messageId", "in": "path", "required": true, "description": "The Telegram message ID to edit", "schemaType": "string" }], "requestBody": { "required": true, "contentTypes": ["application/json"], "schemaRefs": { "application/json": { "schemaType": "object" } } } }, { "operationId": "removeMessageReaction", "method": "DELETE", "path": "/v1/inbox/conversations/{conversationId}/messages/{messageId}/reactions", "tags": ["Messages"], "summary": "Remove reaction", "description": "Remove a reaction from a message. Platform support: - Telegram: Send empty reaction array to clear - WhatsApp: Send empty emoji to remove - All others: Returns 400 (not supported)", "parameters": [{ "name": "conversationId", "in": "path", "required": true, "description": "The conversation ID", "schemaType": "string" }, { "name": "messageId", "in": "path", "required": true, "description": "The platform message ID", "schemaType": "string" }, { "name": "accountId", "in": "query", "required": true, "description": "Social account ID", "schemaType": "string" }] }, { "operationId": "addMessageReaction", "method": "POST", "path": "/v1/inbox/conversations/{conversationId}/messages/{messageId}/reactions", "tags": ["Messages"], "summary": "Add reaction", "description": "Add an emoji reaction to a message. Platform support: - Telegram: Supports a subset of Unicode emoji reactions - WhatsApp: Supports any standard emoji (one reaction per message per sender) - All others: Returns 400 (not supported)", "parameters": [{ "name": "conversationId", "in": "path", "required": true, "description": "The conversation ID", "schemaType": "string" }, { "name": "messageId", "in": "path", "required": true, "description": "The platform message ID to react to", "schemaType": "string" }], "requestBody": { "required": true, "contentTypes": ["application/json"], "schemaRefs": { "application/json": { "schemaType": "object" } } } }, { "operationId": "markConversationRead", "method": "POST", "path": "/v1/inbox/conversations/{conversationId}/read", "tags": ["Messages"], "summary": "Mark a conversation as read", "description": "Marks all unread incoming messages in the conversation as read. For WhatsApp, this also sends read receipts (blue ticks) to the contact, EXCEPT on coexistence accounts (where the WhatsApp Business app on the customer's phone owns read state and we never override it). This is the explicit, human-driven counterpart to `GET .../messages`, which is side-effect-free and does NOT mark anything read. Call this when a user actually views the conversation.", "parameters": [{ "name": "conversationId", "in": "path", "required": true, "description": "The conversation ID", "schemaType": "string" }], "requestBody": { "required": true, "contentTypes": ["application/json"], "schemaRefs": { "application/json": { "schemaType": "object" } } } }, { "operationId": "sendTypingIndicator", "method": "POST", "path": "/v1/inbox/conversations/{conversationId}/typing", "tags": ["Messages"], "summary": "Send typing indicator", "description": "Show a typing indicator in a conversation. Platform support: - Facebook Messenger: Shows \"Page is typing...\" for 20 seconds - Telegram: Shows \"Bot is typing...\" for 5 seconds - WhatsApp: Shows \"typing...\" for up to 25 seconds. Requires a recent inbound message in the conversation (Meta references the inbound message id) and also marks that message as read as a side-effect. - All others: Returns 200 but no-op (platform doesn't support it) Typing indicators are best-effort. The endpoint always re...", "parameters": [{ "name": "conversationId", "in": "path", "required": true, "description": "The conversation ID", "schemaType": "string" }], "requestBody": { "required": true, "contentTypes": ["application/json"], "schemaRefs": { "application/json": { "schemaType": "object" } } } }, { "operationId": "uploadMediaDirect", "method": "POST", "path": "/v1/media/upload-direct", "tags": ["Messages"], "summary": "Upload media file", "description": "Upload a media file using API key authentication and get back a publicly accessible URL. The URL can be used as attachmentUrl when sending inbox messages. Files are stored in temporary storage and auto-delete after 7 days. Maximum file size is 25MB. Unlike /v1/media/upload (which uses upload tokens for end-user flows), this endpoint uses standard Bearer token authentication for programmatic use.", "parameters": [], "requestBody": { "required": true, "contentTypes": ["multipart/form-data"], "schemaRefs": { "multipart/form-data": { "schemaType": "object" } } } }, { "operationId": "listPosts", "method": "GET", "path": "/v1/posts", "tags": ["Posts"], "summary": "List posts", "description": "Returns a paginated list of posts. Published posts include platformPostUrl with the public URL on each platform.", "parameters": [{ "name": "page", "in": "query", "required": false, "description": "Page number (1-based)", "schemaType": "integer" }, { "name": "limit", "in": "query", "required": false, "description": "Page size", "schemaType": "integer" }, { "name": "status", "in": "query", "required": false, "schemaType": "string", "schemaEnum": ["draft", "scheduled", "published", "failed"] }, { "name": "platform", "in": "query", "required": false, "schemaType": "string" }, { "name": "profileId", "in": "query", "required": false, "schemaType": "string" }, { "name": "createdBy", "in": "query", "required": false, "schemaType": "string" }, { "name": "dateFrom", "in": "query", "required": false, "schemaType": "string" }, { "name": "dateTo", "in": "query", "required": false, "schemaType": "string" }, { "name": "includeHidden", "in": "query", "required": false, "schemaType": "boolean" }, { "name": "search", "in": "query", "required": false, "description": "Search posts by text content.", "schemaType": "string" }, { "name": "sortBy", "in": "query", "required": false, "description": "Sort order for results.", "schemaType": "string", "schemaEnum": ["scheduled-desc", "scheduled-asc", "created-desc", "created-asc", "status", "platform"] }, { "name": "accountId", "in": "query", "required": false, "description": "Filter posts to those published via a specific social account (24-char hex ObjectId).", "schemaType": "string" }] }, { "operationId": "createPost", "method": "POST", "path": "/v1/posts", "tags": ["Posts"], "summary": "Create post", "description": "Create and optionally publish a post. Immediate posts (`publishNow: true`) include `platformPostUrl` in the response. Content is optional when media is attached or all platforms have `customContent`. See each platform's schema for media constraints. ## Idempotency Two layers of duplicate-protection apply, so safe-to-retry callers (network blips, n8n / Zapier retries, etc.) don't accidentally double-post. **1. Same-request idempotency (5-minute window).** Pass an `x-request-id` header to mark a ...", "parameters": [{ "name": "x-request-id", "in": "header", "required": false, "description": "Optional client-generated request identifier for safe retry (idempotency). When two requests carry the same value, the second is treated as a retry of the first and returns the original post (HTTP 200) instead of creating a duplicate. Wind...", "schemaType": "string" }], "requestBody": { "required": true, "contentTypes": ["application/json"], "schemaRefs": { "application/json": { "schemaType": "object" } } } }, { "operationId": "deletePost", "method": "DELETE", "path": "/v1/posts/{postId}", "tags": ["Posts"], "summary": "Delete post", "description": "Delete a draft or scheduled post from Zernio. Published posts cannot be deleted; use the Unpublish endpoint instead. Upload quota is automatically refunded.", "parameters": [{ "name": "postId", "in": "path", "required": true, "schemaType": "string" }] }, { "operationId": "getPost", "method": "GET", "path": "/v1/posts/{postId}", "tags": ["Posts"], "summary": "Get post", "description": "Fetch a single post by ID. For published posts, this returns platformPostUrl for each platform.", "parameters": [{ "name": "postId", "in": "path", "required": true, "schemaType": "string" }] }, { "operationId": "updatePost", "method": "PUT", "path": "/v1/posts/{postId}", "tags": ["Posts"], "summary": "Update post", "description": "Update an existing post. Draft, scheduled, failed, partial, and cancelled posts can be edited. Published posts can only have their recycling config updated.", "parameters": [{ "name": "postId", "in": "path", "required": true, "schemaType": "string" }], "requestBody": { "required": true, "contentTypes": ["application/json"], "schemaRefs": { "application/json": { "schemaType": "object" } } } }, { "operationId": "editPost", "method": "POST", "path": "/v1/posts/{postId}/edit", "tags": ["Posts"], "summary": "Edit published post", "description": "Edit a published post on a social media platform. Currently only supported for X (Twitter). Requirements: - Connected X account must have an active X Premium subscription - Must be within 1 hour of original publish time - Maximum 5 edits per tweet (enforced by X) - Text-only edits (media changes are not supported) The post record in Zernio is updated with the new content and edit history.", "parameters": [{ "name": "postId", "in": "path", "required": true, "schemaType": "string" }], "requestBody": { "required": true, "contentTypes": ["application/json"], "schemaRefs": { "application/json": { "schemaType": "object" } } } }, { "operationId": "retryPost", "method": "POST", "path": "/v1/posts/{postId}/retry", "tags": ["Posts"], "summary": "Retry failed post", "description": "Immediately retries publishing a failed post. Returns the updated post with its new status.", "parameters": [{ "name": "postId", "in": "path", "required": true, "schemaType": "string" }] }, { "operationId": "unpublishPost", "method": "POST", "path": "/v1/posts/{postId}/unpublish", "tags": ["Posts"], "summary": "Unpublish post", "description": "Deletes a published post from the specified platform. The post record in Zernio is kept but its status is updated to cancelled. Not supported on Instagram, TikTok, or Snapchat. Threaded posts delete all items. YouTube deletion is permanent.", "parameters": [{ "name": "postId", "in": "path", "required": true, "schemaType": "string" }], "requestBody": { "required": true, "contentTypes": ["application/json"], "schemaRefs": { "application/json": { "schemaType": "object" } } } }, { "operationId": "updatePostMetadata", "method": "POST", "path": "/v1/posts/{postId}/update-metadata", "tags": ["Posts"], "summary": "Update post metadata", "description": "Updates metadata of a published video on the specified platform without re-uploading. Currently only supported for YouTube. At least one updatable field is required. Two modes: 1. Post-based (video published through Zernio): pass the Zernio postId in the URL and platform in the body. 2. Direct video ID (video uploaded outside Zernio, e.g. directly to YouTube): use _ as the postId, and pass videoId + accountId + platform in the body. The accountId is the Zernio social account ID for the connecte...", "parameters": [{ "name": "postId", "in": "path", "required": true, "description": "Zernio post ID, or \"_\" when using direct video ID mode", "schemaType": "string" }], "requestBody": { "required": true, "contentTypes": ["application/json"], "schemaRefs": { "application/json": { "schemaType": "object" } } } }, { "operationId": "bulkUploadPosts", "method": "POST", "path": "/v1/posts/bulk-upload", "tags": ["Posts"], "summary": "Bulk upload from CSV", "description": "Create multiple posts by uploading a CSV file. Use dryRun=true to validate without creating posts.", "parameters": [{ "name": "dryRun", "in": "query", "required": false, "schemaType": "boolean" }], "requestBody": { "required": true, "contentTypes": ["multipart/form-data"], "schemaRefs": { "multipart/form-data": { "schemaType": "object" } } } }, { "operationId": "listProfiles", "method": "GET", "path": "/v1/profiles", "tags": ["Profiles"], "summary": "List profiles", "description": "Returns profiles sorted by creation date. Use includeOverLimit=true to include profiles that exceed the plan limit.", "parameters": [{ "name": "includeOverLimit", "in": "query", "required": false, "description": "When true, includes over-limit profiles (marked with isOverLimit: true).", "schemaType": "boolean" }] }, { "operationId": "createProfile", "method": "POST", "path": "/v1/profiles", "tags": ["Profiles"], "summary": "Create profile", "description": "Creates a new profile with a name, optional description, and color.", "parameters": [], "requestBody": { "required": true, "contentTypes": ["application/json"], "schemaRefs": { "application/json": { "schemaType": "object" } } } }, { "operationId": "deleteProfile", "method": "DELETE", "path": "/v1/profiles/{profileId}", "tags": ["Profiles"], "summary": "Delete profile", "description": "Permanently deletes a profile by ID.", "parameters": [{ "name": "profileId", "in": "path", "required": true, "schemaType": "string" }] }, { "operationId": "getProfile", "method": "GET", "path": "/v1/profiles/{profileId}", "tags": ["Profiles"], "summary": "Get profile", "description": "Returns a single profile by ID, including its name, color, and default status.", "parameters": [{ "name": "profileId", "in": "path", "required": true, "schemaType": "string" }] }, { "operationId": "updateProfile", "method": "PUT", "path": "/v1/profiles/{profileId}", "tags": ["Profiles"], "summary": "Update profile", "description": "Updates a profile's name, description, color, or default status.", "parameters": [{ "name": "profileId", "in": "path", "required": true, "schemaType": "string" }], "requestBody": { "required": true, "contentTypes": ["application/json"], "schemaRefs": { "application/json": { "schemaType": "object" } } } }, { "operationId": "getNextQueueSlot", "method": "GET", "path": "/v1/queue/next-slot", "tags": ["Queue"], "summary": "Get next available slot", "description": "Returns the next available queue slot for preview purposes. To create a queue post, use POST /v1/posts with queuedFromProfile instead of scheduledFor.", "parameters": [{ "name": "profileId", "in": "query", "required": true, "schemaType": "string" }, { "name": "queueId", "in": "query", "required": false, "description": "Specific queue ID (optional, defaults to profile's default queue)", "schemaType": "string" }] }, { "operationId": "previewQueue", "method": "GET", "path": "/v1/queue/preview", "tags": ["Queue"], "summary": "Preview upcoming slots", "description": "Returns the next N upcoming queue slot times for a profile as ISO datetime strings.", "parameters": [{ "name": "profileId", "in": "query", "required": true, "schemaType": "string" }, { "name": "queueId", "in": "query", "required": false, "description": "Filter by specific queue ID. Omit to use the default queue.", "schemaType": "string" }, { "name": "count", "in": "query", "required": false, "schemaType": "integer" }] }, { "operationId": "deleteQueueSlot", "method": "DELETE", "path": "/v1/queue/slots", "tags": ["Queue"], "summary": "Delete schedule", "description": "Delete a queue from a profile. Requires queueId to specify which queue to delete. If deleting the default queue, another queue will be promoted to default.", "parameters": [{ "name": "profileId", "in": "query", "required": true, "schemaType": "string" }, { "name": "queueId", "in": "query", "required": true, "description": "Queue ID to delete", "schemaType": "string" }] }, { "operationId": "listQueueSlots", "method": "GET", "path": "/v1/queue/slots", "tags": ["Queue"], "summary": "List schedules", "description": "Returns queue schedules for a profile. Use all=true for all queues, or queueId for a specific one. Defaults to the default queue.", "parameters": [{ "name": "profileId", "in": "query", "required": true, "description": "Profile ID to get queues for", "schemaType": "string" }, { "name": "queueId", "in": "query", "required": false, "description": "Specific queue ID to retrieve (optional)", "schemaType": "string" }, { "name": "all", "in": "query", "required": false, "description": "Set to 'true' to list all queues for the profile", "schemaType": "string", "schemaEnum": ["true"] }] }, { "operationId": "createQueueSlot", "method": "POST", "path": "/v1/queue/slots", "tags": ["Queue"], "summary": "Create schedule", "description": "Create an additional queue for a profile. The first queue created becomes the default. Subsequent queues are non-default unless explicitly set.", "parameters": [], "requestBody": { "required": true, "contentTypes": ["application/json"], "schemaRefs": { "application/json": { "schemaType": "object" } } } }, { "operationId": "updateQueueSlot", "method": "PUT", "path": "/v1/queue/slots", "tags": ["Queue"], "summary": "Update schedule", "description": "Create a new queue or update an existing one. Without queueId, creates/updates the default queue. With queueId, updates a specific queue. With setAsDefault=true, makes this queue the default for the profile.", "parameters": [], "requestBody": { "required": true, "contentTypes": ["application/json"], "schemaRefs": { "application/json": { "schemaType": "object" } } } }, { "operationId": "getRedditFeed", "method": "GET", "path": "/v1/reddit/feed", "tags": ["Reddit Search"], "summary": "Get subreddit feed", "description": "Fetch posts from a subreddit feed. Supports sorting, time filtering, and cursor-based pagination.", "parameters": [{ "name": "accountId", "in": "query", "required": true, "schemaType": "string" }, { "name": "subreddit", "in": "query", "required": false, "schemaType": "string" }, { "name": "sort", "in": "query", "required": false, "schemaType": "string", "schemaEnum": ["hot", "new", "top", "rising"] }, { "name": "limit", "in": "query", "required": false, "schemaType": "integer" }, { "name": "after", "in": "query", "required": false, "schemaType": "string" }, { "name": "t", "in": "query", "required": false, "schemaType": "string", "schemaEnum": ["hour", "day", "week", "month", "year", "all"] }] }, { "operationId": "searchReddit", "method": "GET", "path": "/v1/reddit/search", "tags": ["Reddit Search"], "summary": "Search posts", "description": "Search Reddit posts using a connected account. Optionally scope to a specific subreddit.", "parameters": [{ "name": "accountId", "in": "query", "required": true, "schemaType": "string" }, { "name": "subreddit", "in": "query", "required": false, "schemaType": "string" }, { "name": "q", "in": "query", "required": true, "schemaType": "string" }, { "name": "restrict_sr", "in": "query", "required": false, "schemaType": "string", "schemaEnum": ["0", "1"] }, { "name": "sort", "in": "query", "required": false, "schemaType": "string", "schemaEnum": ["relevance", "hot", "top", "new", "comments"] }, { "name": "limit", "in": "query", "required": false, "schemaType": "integer" }, { "name": "after", "in": "query", "required": false, "schemaType": "string" }] }, { "operationId": "listInboxReviews", "method": "GET", "path": "/v1/inbox/reviews", "tags": ["Reviews"], "summary": "List reviews", "description": "Fetch reviews from all connected Facebook Pages and Google Business accounts. Aggregates data with filtering and sorting options. Supported platforms: Facebook, Google Business.", "parameters": [{ "name": "profileId", "in": "query", "required": false, "schemaType": "string" }, { "name": "platform", "in": "query", "required": false, "schemaType": "string", "schemaEnum": ["facebook", "googlebusiness"] }, { "name": "minRating", "in": "query", "required": false, "schemaType": "integer" }, { "name": "maxRating", "in": "query", "required": false, "schemaType": "integer" }, { "name": "hasReply", "in": "query", "required": false, "description": "Filter by reply status", "schemaType": "boolean" }, { "name": "sortBy", "in": "query", "required": false, "schemaType": "string", "schemaEnum": ["date", "rating"] }, { "name": "sortOrder", "in": "query", "required": false, "schemaType": "string", "schemaEnum": ["asc", "desc"] }, { "name": "limit", "in": "query", "required": false, "schemaType": "integer" }, { "name": "cursor", "in": "query", "required": false, "schemaType": "string" }, { "name": "accountId", "in": "query", "required": false, "description": "Filter by specific social account ID", "schemaType": "string" }] }, { "operationId": "deleteInboxReviewReply", "method": "DELETE", "path": "/v1/inbox/reviews/{reviewId}/reply", "tags": ["Reviews"], "summary": "Delete review reply", "description": "Delete a reply to a review (Google Business only). Requires accountId in request body.", "parameters": [{ "name": "reviewId", "in": "path", "required": true, "schemaType": "string" }], "requestBody": { "required": true, "contentTypes": ["application/json"], "schemaRefs": { "application/json": { "schemaType": "object" } } } }, { "operationId": "replyToInboxReview", "method": "POST", "path": "/v1/inbox/reviews/{reviewId}/reply", "tags": ["Reviews"], "summary": "Reply to review", "description": "Post a reply to a review. Requires accountId in request body.", "parameters": [{ "name": "reviewId", "in": "path", "required": true, "description": "Review ID (URL-encoded for Google Business)", "schemaType": "string" }], "requestBody": { "required": true, "contentTypes": ["application/json"], "schemaRefs": { "application/json": { "schemaType": "object" } } } }, { "operationId": "listSequences", "method": "GET", "path": "/v1/sequences", "tags": ["Sequences"], "summary": "List sequences", "description": "Returns sequences with enrollment stats. Filter by status, platform, or profile.", "parameters": [{ "name": "profileId", "in": "query", "required": false, "description": "Filter by profile. Omit to list across all profiles", "schemaType": "string" }, { "name": "status", "in": "query", "required": false, "schemaType": "string", "schemaEnum": ["draft", "active", "paused"] }, { "name": "limit", "in": "query", "required": false, "schemaType": "integer" }, { "name": "skip", "in": "query", "required": false, "schemaType": "integer" }] }, { "operationId": "createSequence", "method": "POST", "path": "/v1/sequences", "tags": ["Sequences"], "summary": "Create sequence", "description": "Create a multi-step messaging sequence. Each step has a delay and a message or WhatsApp template.", "parameters": [], "requestBody": { "required": true, "contentTypes": ["application/json"], "schemaRefs": { "application/json": { "schemaType": "object" } } } }, { "operationId": "deleteSequence", "method": "DELETE", "path": "/v1/sequences/{sequenceId}", "tags": ["Sequences"], "summary": "Delete sequence", "description": "Permanently delete a sequence. Active enrollments are stopped.", "parameters": [{ "name": "sequenceId", "in": "path", "required": true, "schemaType": "string" }] }, { "operationId": "getSequence", "method": "GET", "path": "/v1/sequences/{sequenceId}", "tags": ["Sequences"], "summary": "Get sequence with steps", "description": "Returns a sequence with all its steps and enrollment stats.", "parameters": [{ "name": "sequenceId", "in": "path", "required": true, "schemaType": "string" }] }, { "operationId": "updateSequence", "method": "PATCH", "path": "/v1/sequences/{sequenceId}", "tags": ["Sequences"], "summary": "Update sequence", "description": "Update a sequence's name, steps, or exit conditions. Steps can only be modified while the sequence is draft or paused.", "parameters": [{ "name": "sequenceId", "in": "path", "required": true, "schemaType": "string" }], "requestBody": { "required": false, "contentTypes": ["application/json"], "schemaRefs": { "application/json": { "schemaType": "object" } } } }, { "operationId": "activateSequence", "method": "POST", "path": "/v1/sequences/{sequenceId}/activate", "tags": ["Sequences"], "summary": "Activate sequence", "description": "Start a draft or paused sequence. The sequence must have at least one step.", "parameters": [{ "name": "sequenceId", "in": "path", "required": true, "schemaType": "string" }] }, { "operationId": "enrollContacts", "method": "POST", "path": "/v1/sequences/{sequenceId}/enroll", "tags": ["Sequences"], "summary": "Enroll contacts in a sequence", "description": "Enroll one or more contacts into a sequence. Contacts already enrolled are skipped.", "parameters": [{ "name": "sequenceId", "in": "path", "required": true, "schemaType": "string" }], "requestBody": { "required": true, "contentTypes": ["application/json"], "schemaRefs": { "application/json": { "schemaType": "object" } } } }, { "operationId": "unenrollContact", "method": "DELETE", "path": "/v1/sequences/{sequenceId}/enroll/{contactId}", "tags": ["Sequences"], "summary": "Unenroll contact", "description": "Remove a contact from a sequence. No further messages will be sent to this contact.", "parameters": [{ "name": "sequenceId", "in": "path", "required": true, "schemaType": "string" }, { "name": "contactId", "in": "path", "required": true, "schemaType": "string" }] }, { "operationId": "listSequenceEnrollments", "method": "GET", "path": "/v1/sequences/{sequenceId}/enrollments", "tags": ["Sequences"], "summary": "List enrollments for a sequence", "description": "Returns enrolled contacts with their progress, status, and next scheduled step.", "parameters": [{ "name": "sequenceId", "in": "path", "required": true, "schemaType": "string" }, { "name": "status", "in": "query", "required": false, "schemaType": "string", "schemaEnum": ["active", "completed", "exited", "paused"] }, { "name": "limit", "in": "query", "required": false, "schemaType": "integer" }, { "name": "skip", "in": "query", "required": false, "schemaType": "integer" }] }, { "operationId": "pauseSequence", "method": "POST", "path": "/v1/sequences/{sequenceId}/pause", "tags": ["Sequences"], "summary": "Pause sequence", "description": "Pause an active sequence. Enrolled contacts stop receiving messages until the sequence is reactivated.", "parameters": [{ "name": "sequenceId", "in": "path", "required": true, "schemaType": "string" }] }, { "operationId": "listTrackingTags", "method": "GET", "path": "/v1/accounts/{accountId}/tracking-tags", "tags": ["Tracking Tags"], "summary": "List tracking tags (Meta Pixels)", "description": "Returns the tracking tags (Meta Pixels) the connected ads account can see. Pass `?adAccountId=act_...` to scope the list to a single ad account; omit it to list every pixel reachable by the token (the name is then suffixed with the ad account it was discovered on, for disambiguation). The list view omits `code` — call `getTrackingTag` for the install snippet and full detail. Meta only today (platform `metaads`); other platforms return 405. The `accountId` must be the Meta *ads* SocialAccount cr...", "parameters": [{ "name": "accountId", "in": "path", "required": true, "description": "Meta ads SocialAccount id (platform `metaads`).", "schemaType": "string" }, { "name": "adAccountId", "in": "query", "required": false, "description": "Optional. Scope to one ad account, e.g. `act_123456789`.", "schemaType": "string" }] }, { "operationId": "createTrackingTag", "method": "POST", "path": "/v1/accounts/{accountId}/tracking-tags", "tags": ["Tracking Tags"], "summary": "Create a tracking tag (Meta Pixel)", "description": "Creates a Meta Pixel on the given ad account (`POST /act_{id}/adspixels` — `name` is the only input). Returns the created tag including its install `code`. The pixel is owned by the Business Manager that owns the ad account; a pixel created on a personal (non-BM) ad account ends up with `ownerBusinessId: null` and can't be shared with other ad accounts. Creating a pixel does NOT install it — install the returned `code` snippet on the site, or send events server-side via `POST /v1/ads/conversion...", "parameters": [{ "name": "accountId", "in": "path", "required": true, "description": "Meta ads SocialAccount id (platform `metaads`).", "schemaType": "string" }], "requestBody": { "required": true, "contentTypes": ["application/json"], "schemaRefs": { "application/json": { "schemaType": "object" } } } }, { "operationId": "getTrackingTag", "method": "GET", "path": "/v1/accounts/{accountId}/tracking-tags/{tagId}", "tags": ["Tracking Tags"], "summary": "Fetch a single tracking tag (Meta Pixel)", "description": "Returns the full tag record including the base-code `code` snippet, `lastFiredTime`, `ownerBusinessId`, `isUnavailable`, etc. Meta only (platform `metaads`); other platforms return 405.", "parameters": [{ "name": "accountId", "in": "path", "required": true, "schemaType": "string" }, { "name": "tagId", "in": "path", "required": true, "description": "Pixel id.", "schemaType": "string" }] }, { "operationId": "updateTrackingTag", "method": "PATCH", "path": "/v1/accounts/{accountId}/tracking-tags/{tagId}", "tags": ["Tracking Tags"], "summary": "Update a tracking tag (Meta Pixel)", "description": "Partial-update a pixel. Whitelisted fields: `name` (rename), `enableAutomaticMatching`, `automaticMatchingFields`, `firstPartyCookieStatus`, `dataUseSetting`. At least one is required. Returns the re-fetched canonical tag. Meta only (platform `metaads`); other platforms return 405. There is no DELETE — Meta has no API to delete a pixel. To stop using one, unshare it from your ad accounts (`DELETE .../tracking-tags/{tagId}/shared-accounts`) or disable it in Events Manager.", "parameters": [{ "name": "accountId", "in": "path", "required": true, "schemaType": "string" }, { "name": "tagId", "in": "path", "required": true, "description": "Pixel id.", "schemaType": "string" }], "requestBody": { "required": true, "contentTypes": ["application/json"], "schemaRefs": { "application/json": { "schemaType": "object" } } } }, { "operationId": "removeTrackingTagSharedAccount", "method": "DELETE", "path": "/v1/accounts/{accountId}/tracking-tags/{tagId}/shared-accounts", "tags": ["Tracking Tags"], "summary": "Stop sharing a tracking tag with an ad account", "description": "`adAccountId` may be passed as a query parameter (recommended) or as a JSON body field for clients that can send DELETE bodies. Meta only (platform `metaads`); other platforms return 405.", "parameters": [{ "name": "accountId", "in": "path", "required": true, "schemaType": "string" }, { "name": "tagId", "in": "path", "required": true, "description": "Pixel id.", "schemaType": "string" }, { "name": "adAccountId", "in": "query", "required": false, "description": "Ad account to unshare, e.g. `act_123456789`. May also be sent in the JSON body.", "schemaType": "string" }] }, { "operationId": "listTrackingTagSharedAccounts", "method": "GET", "path": "/v1/accounts/{accountId}/tracking-tags/{tagId}/shared-accounts", "tags": ["Tracking Tags"], "summary": "List ad accounts a tracking tag is shared with", "description": "Meta only (platform `metaads`); other platforms return 405.", "parameters": [{ "name": "accountId", "in": "path", "required": true, "schemaType": "string" }, { "name": "tagId", "in": "path", "required": true, "description": "Pixel id.", "schemaType": "string" }] }, { "operationId": "addTrackingTagSharedAccount", "method": "POST", "path": "/v1/accounts/{accountId}/tracking-tags/{tagId}/shared-accounts", "tags": ["Tracking Tags"], "summary": "Share a tracking tag with an ad account", "description": "Shares the pixel with another ad account so campaigns/audiences in that account can use it. Requires that you administer both the pixel's owning Business Manager and the target ad account; a pixel on a personal (non-BM) ad account can't be shared (Meta will reject the call). Meta only (platform `metaads`); other platforms return 405.", "parameters": [{ "name": "accountId", "in": "path", "required": true, "schemaType": "string" }, { "name": "tagId", "in": "path", "required": true, "description": "Pixel id.", "schemaType": "string" }], "requestBody": { "required": true, "contentTypes": ["application/json"], "schemaRefs": { "application/json": { "schemaType": "object" } } } }, { "operationId": "getTrackingTagStats", "method": "GET", "path": "/v1/accounts/{accountId}/tracking-tags/{tagId}/stats", "tags": ["Tracking Tags"], "summary": "Aggregated event stats for a tracking tag (Meta Pixel)", "description": "Returns aggregated event counts for the pixel (`GET /{pixel_id}/stats`). Rows are passed through from Meta as-is — their shape depends on the `aggregation` requested. Meta only (platform `metaads`); other platforms return 405.", "parameters": [{ "name": "accountId", "in": "path", "required": true, "schemaType": "string" }, { "name": "tagId", "in": "path", "required": true, "description": "Pixel id.", "schemaType": "string" }, { "name": "aggregation", "in": "query", "required": false, "description": "Aggregation dimension. Defaults to `event`.", "schemaType": "string", "schemaEnum": ["event", "host", "url", "url_by_rule", "pixel_fire", "device_type", "device_os", "browser_type", "had_pii", "custom_data_field", "match_keys", "event_source", "event_detection_method", "event_processing_results", "event_total_counts", "event_value_count"] }, { "name": "startTime", "in": "query", "required": false, "description": "Unix seconds lower bound.", "schemaType": "integer" }, { "name": "endTime", "in": "query", "required": false, "description": "Unix seconds upper bound.", "schemaType": "integer" }] }, { "operationId": "removeBookmark", "method": "DELETE", "path": "/v1/twitter/bookmark", "tags": ["Twitter Engagement"], "summary": "Remove bookmark", "description": "Remove a bookmark from a tweet.", "parameters": [{ "name": "accountId", "in": "query", "required": true, "schemaType": "string" }, { "name": "tweetId", "in": "query", "required": true, "description": "The ID of the tweet to unbookmark", "schemaType": "string" }] }, { "operationId": "bookmarkPost", "method": "POST", "path": "/v1/twitter/bookmark", "tags": ["Twitter Engagement"], "summary": "Bookmark a tweet", "description": "Bookmark a tweet by ID. Requires the bookmark.write OAuth scope. Rate limit: 50 requests per 15-min window.", "parameters": [], "requestBody": { "required": true, "contentTypes": ["application/json"], "schemaRefs": { "application/json": { "schemaType": "object" } } } }, { "operationId": "unfollowUser", "method": "DELETE", "path": "/v1/twitter/follow", "tags": ["Twitter Engagement"], "summary": "Unfollow a user", "description": "Unfollow a user on X/Twitter.", "parameters": [{ "name": "accountId", "in": "query", "required": true, "schemaType": "string" }, { "name": "targetUserId", "in": "query", "required": true, "description": "The Twitter ID of the user to unfollow", "schemaType": "string" }] }, { "operationId": "followUser", "method": "POST", "path": "/v1/twitter/follow", "tags": ["Twitter Engagement"], "summary": "Follow a user", "description": "Follow a user on X/Twitter. Requires the follows.write OAuth scope. For protected accounts, a follow request is sent instead (pending_follow will be true).", "parameters": [], "requestBody": { "required": true, "contentTypes": ["application/json"], "schemaRefs": { "application/json": { "schemaType": "object" } } } }, { "operationId": "undoRetweet", "method": "DELETE", "path": "/v1/twitter/retweet", "tags": ["Twitter Engagement"], "summary": "Undo retweet", "description": "Undo a retweet (un-repost a tweet).", "parameters": [{ "name": "accountId", "in": "query", "required": true, "schemaType": "string" }, { "name": "tweetId", "in": "query", "required": true, "description": "The ID of the original tweet to un-retweet", "schemaType": "string" }] }, { "operationId": "retweetPost", "method": "POST", "path": "/v1/twitter/retweet", "tags": ["Twitter Engagement"], "summary": "Retweet a post", "description": "Retweet (repost) a tweet by ID. Rate limit: 50 requests per 15-min window. Shares the 300/3hr creation limit with tweet creation.", "parameters": [], "requestBody": { "required": true, "contentTypes": ["application/json"], "schemaRefs": { "application/json": { "schemaType": "object" } } } }, { "operationId": "getXApiPricing", "method": "GET", "path": "/v1/billing/x-pricing", "tags": ["Usage"], "summary": "Get X/Twitter API pricing table", "description": "Returns Zernio's canonical X/Twitter API pricing table. Each X action has its own Metronome product and its own rate, and Zernio passes X API costs through at exact rates with zero markup. The response is identical for every authenticated user (pricing is universal), so it is safe to cache on the client for the duration of a billing period. To compute your own per-operation spend, pair this endpoint with `GET /v1/usage-stats` — that endpoint returns `usage.xApiCallsByOperation` keyed by the sam...", "parameters": [] }, { "operationId": "getUsageStats", "method": "GET", "path": "/v1/usage-stats", "tags": ["Usage"], "summary": "Get plan and usage stats", "description": "Returns the current plan name, billing period, plan limits, and usage counts. The response shape depends on the account's `billingSystem`: * Stripe users: per-period `usage.uploads` / `usage.profiles` counters. * Metronome (usage-based) users: `usage.connectedAccounts`, `usage.xApiCallsByOperation` (per-operation X API call counts — resolve keys via `GET /v1/billing/x-pricing`), plus a `spend` block with `currentPeriodCents`, `xSpendCents`, and `xSpendLimitCents`. The legacy `usage.xApiCalls` 3...", "parameters": [{ "name": "reconcile", "in": "query", "required": false, "description": "For Stripe subscription users, `true` forces a subscription reconciliation pass even when cached plan data looks complete. Omit the parameter, or pass `false`, to use the default first-time-only reconciliation behavior. Invalid boolean val...", "schemaType": "boolean" }] }, { "operationId": "listUsers", "method": "GET", "path": "/v1/users", "tags": ["Users"], "summary": "List users", "description": "Returns all users in the workspace including roles and profile access. Also returns the currentUserId of the caller.", "parameters": [] }, { "operationId": "getUser", "method": "GET", "path": "/v1/users/{userId}", "tags": ["Users"], "summary": "Get user", "description": "Returns a single user's details by ID, including name, email, and role.", "parameters": [{ "name": "userId", "in": "path", "required": true, "schemaType": "string" }] }, { "operationId": "validateMedia", "method": "POST", "path": "/v1/tools/validate/media", "tags": ["Validate"], "summary": "Validate media URL", "description": "Check if a media URL is accessible and return metadata (content type, file size) plus per-platform size limit comparisons. Performs a HEAD request (with GET fallback) to detect content type and size. Rejects private/localhost URLs for SSRF protection. Platform limits are sourced from each platform's actual upload constraints.", "parameters": [], "requestBody": { "required": true, "contentTypes": ["application/json"], "schemaRefs": { "application/json": { "schemaType": "object" } } } }, { "operationId": "validatePostLength", "method": "POST", "path": "/v1/tools/validate/post-length", "tags": ["Validate"], "summary": "Validate character count", "description": "Check weighted character count per platform and whether the text is within each platform's limit. Twitter/X uses weighted counting (URLs = 23 chars via t.co, emojis = 2 chars). All other platforms use plain character length. Returns counts and limits for all 15 supported platform variants.", "parameters": [], "requestBody": { "required": true, "contentTypes": ["application/json"], "schemaRefs": { "application/json": { "schemaType": "object" } } } }, { "operationId": "validatePost", "method": "POST", "path": "/v1/tools/validate/post", "tags": ["Validate"], "summary": "Validate post content", "description": "Dry-run the full post validation pipeline without publishing. Catches issues like missing media for Instagram/TikTok/YouTube, hashtag limits, invalid thread formats, Facebook Reel requirements, and character limit violations. Accepts the same body as POST /v1/posts. Does NOT validate accounts, process media, or track usage. This is content-only validation. Returns errors for failures and warnings for near-limit content (>90% of character limit).", "parameters": [], "requestBody": { "required": true, "contentTypes": ["application/json"], "schemaRefs": { "application/json": { "schemaType": "object" } } } }, { "operationId": "validateSubreddit", "method": "GET", "path": "/v1/tools/validate/subreddit", "tags": ["Validate"], "summary": "Check subreddit existence", "description": "Check if a subreddit exists and return basic info (title, subscriber count, NSFW status, post types allowed). When accountId is provided, uses authenticated Reddit OAuth API with automatic token refresh (recommended). Falls back to Reddit's public JSON API, which may be unreliable from server IPs. Returns exists: false for private, banned, or nonexistent subreddits.", "parameters": [{ "name": "name", "in": "query", "required": true, "description": "Subreddit name (with or without \"r/\" prefix)", "schemaType": "string" }, { "name": "accountId", "in": "query", "required": false, "description": "Reddit social account ID for authenticated lookup (recommended for reliable results)", "schemaType": "string" }] }, { "operationId": "getWebhookLogs", "method": "GET", "path": "/v1/webhooks/logs", "tags": ["Webhooks"], "summary": "List webhook delivery logs", "description": "Retrieve recorded webhook delivery attempts for the authenticated user, most recent first. Logs are retained for 30 days. Supports filtering by status, event type, webhook ID, and event ID, plus offset-based pagination.", "parameters": [{ "name": "limit", "in": "query", "required": false, "description": "Maximum number of logs to return", "schemaType": "integer" }, { "name": "skip", "in": "query", "required": false, "description": "Number of logs to skip (offset-based pagination)", "schemaType": "integer" }, { "name": "status", "in": "query", "required": false, "description": "Filter by delivery outcome", "schemaType": "string", "schemaEnum": ["success", "failed"] }, { "name": "event", "in": "query", "required": false, "description": "Filter by event type (e.g. post.published)", "schemaType": "string" }, { "name": "webhookId", "in": "query", "required": false, "description": "Filter by webhook configuration ID", "schemaType": "string" }, { "name": "eventId", "in": "query", "required": false, "description": "Filter by stable webhook event ID", "schemaType": "string" }] }, { "operationId": "deleteWebhookSettings", "method": "DELETE", "path": "/v1/webhooks/settings", "tags": ["Webhooks"], "summary": "Delete webhook", "description": "Permanently delete a webhook configuration.", "parameters": [{ "name": "id", "in": "query", "required": true, "description": "Webhook ID to delete", "schemaType": "string" }] }, { "operationId": "getWebhookSettings", "method": "GET", "path": "/v1/webhooks/settings", "tags": ["Webhooks"], "summary": "List webhooks", "description": "Retrieve all configured webhooks for the authenticated user. Supports up to 10 webhooks per user.", "parameters": [] }, { "operationId": "createWebhookSettings", "method": "POST", "path": "/v1/webhooks/settings", "tags": ["Webhooks"], "summary": "Create webhook", "description": "Create a new webhook configuration. Maximum 10 webhooks per user. `name`, `url` and `events` are required. `url` must be a valid URL and `events` must contain at least one event. Whitespace is trimmed from `url` before validation. Webhooks are automatically disabled after 10 consecutive delivery failures.", "parameters": [], "requestBody": { "required": true, "contentTypes": ["application/json"], "schemaRefs": { "application/json": { "schemaType": "object" } } } }, { "operationId": "updateWebhookSettings", "method": "PUT", "path": "/v1/webhooks/settings", "tags": ["Webhooks"], "summary": "Update webhook", "description": "Update an existing webhook configuration. All fields except `_id` are optional; only provided fields will be updated. When provided, `name` must be 1-50 characters, `url` must be a valid URL, and `events` must contain at least one event. Whitespace is trimmed from `url` before validation. Webhooks are automatically disabled after 10 consecutive delivery failures.", "parameters": [], "requestBody": { "required": true, "contentTypes": ["application/json"], "schemaRefs": { "application/json": { "schemaType": "object" } } } }, { "operationId": "testWebhook", "method": "POST", "path": "/v1/webhooks/test", "tags": ["Webhooks"], "summary": "Send test webhook", "description": "Send a test webhook to verify your endpoint is configured correctly. The test payload includes event: \"webhook.test\" to distinguish it from real events.", "parameters": [], "requestBody": { "required": true, "contentTypes": ["application/json"], "schemaRefs": { "application/json": { "schemaType": "object" } } } }, { "operationId": "getWhatsAppCallPermissions", "method": "GET", "path": "/v1/whatsapp/call-permissions", "tags": ["WhatsApp Calling"], "summary": "Check call permission for a consumer", "description": "Returns the permission state and the list of available actions for a given consumer wa_id (e.g. `start_call`, `send_call_permission_request`). Use this before placing a call to decide whether to prompt for consent first.", "parameters": [{ "name": "accountId", "in": "query", "required": true, "schemaType": "string" }, { "name": "to", "in": "query", "required": true, "description": "Consumer wa_id (E.164", "schemaType": "string" }] }, { "operationId": "getWhatsAppCallingConfig", "method": "GET", "path": "/v1/whatsapp/calling", "tags": ["WhatsApp Calling"], "summary": "Get calling config for an account", "description": "Returns the local calling configuration snapshot for the connected WhatsApp account: whether calling is enabled, the forward-to destination URI, recording opt-in state, the WhatsAppPhoneNumber doc id (use as `{id}` on the calling-config write endpoints) and whether SIP digest credentials are stored (the encrypted password itself is never returned).", "parameters": [{ "name": "accountId", "in": "query", "required": true, "description": "WhatsApp social account ID", "schemaType": "string" }] }, { "operationId": "listWhatsAppCalls", "method": "GET", "path": "/v1/whatsapp/calls", "tags": ["WhatsApp Calling"], "summary": "List call history for an account", "description": "Compact history listing for a single connected account. Results are scoped to the resolved SocialAccount; profile-scoped team members cannot read calls on sibling accounts.", "parameters": [{ "name": "accountId", "in": "query", "required": true, "schemaType": "string" }, { "name": "status", "in": "query", "required": false, "schemaType": "string", "schemaEnum": ["ringing", "answered", "ended", "failed"] }, { "name": "direction", "in": "query", "required": false, "schemaType": "string", "schemaEnum": ["inbound", "outbound"] }, { "name": "since", "in": "query", "required": false, "schemaType": "string" }, { "name": "until", "in": "query", "required": false, "schemaType": "string" }, { "name": "limit", "in": "query", "required": false, "schemaType": "integer" }] }, { "operationId": "initiateWhatsAppCall", "method": "POST", "path": "/v1/whatsapp/calls", "tags": ["WhatsApp Calling"], "summary": "Initiate outbound call", "description": "Initiates an outbound Business-Initiated Call. The Telnyx-side SIP leg is originated server-side (Option B: SIP-first). Telnyx INVITEs Meta directly over TLS:5061 with the SIP digest credentials we captured at calling-enablement time). No client-side SDP is required; pass only `accountId` and `to`. To send the consumer the call-consent prompt instead of placing a call, pass `action: \"send_call_permission_request\"` (+ optional `bodyText`). The consumer must tap Allow in WhatsApp before `start_ca...", "parameters": [], "requestBody": { "required": true, "contentTypes": ["application/json"], "schemaRefs": { "application/json": { "schemaType": "object" } } } }, { "operationId": "getWhatsAppCall", "method": "GET", "path": "/v1/whatsapp/calls/{callId}", "tags": ["WhatsApp Calling"], "summary": "Get a single call", "parameters": [{ "name": "callId", "in": "path", "required": true, "schemaType": "string" }, { "name": "accountId", "in": "query", "required": true, "schemaType": "string" }] }, { "operationId": "getWhatsAppCallEstimate", "method": "GET", "path": "/v1/whatsapp/calls/estimate", "tags": ["WhatsApp Calling"], "summary": "Estimate per-minute cost for a destination", "description": "Returns a zero-markup estimated cost for an outbound call to the given destination, broken down by Meta + Telnyx + recording line items. Costs are pass-through, no margin applied.", "parameters": [{ "name": "accountId", "in": "query", "required": true, "schemaType": "string" }, { "name": "to", "in": "query", "required": true, "schemaType": "string" }, { "name": "minutes", "in": "query", "required": false, "schemaType": "integer" }, { "name": "recording", "in": "query", "required": false, "schemaType": "boolean" }] }, { "operationId": "disableWhatsAppCalling", "method": "DELETE", "path": "/v1/whatsapp/phone-numbers/{id}/calling", "tags": ["WhatsApp Calling"], "summary": "Disable calling on a number", "description": "Disable calling. Sends calling.status=DISABLED to Meta (best-effort) and flips the local `callingEnabled` flag off. forwardTo and SIP creds are preserved so a re-enable does not lose the destination.", "parameters": [{ "name": "id", "in": "path", "required": true, "schemaType": "string" }, { "name": "accountId", "in": "query", "required": true, "schemaType": "string" }] }, { "operationId": "updateWhatsAppCalling", "method": "PATCH", "path": "/v1/whatsapp/phone-numbers/{id}/calling", "tags": ["WhatsApp Calling"], "summary": "Update calling config", "description": "Update fields on an already-enabled number. Only fields present in the body are written; `undefined` leaves the stored value alone, explicit `null` clears a nullable field. No Meta side effect, this only changes local routing state consumed by the Telnyx webhook handler.", "parameters": [{ "name": "id", "in": "path", "required": true, "schemaType": "string" }], "requestBody": { "required": true, "contentTypes": ["application/json"], "schemaRefs": { "application/json": { "schemaType": "object" } } } }, { "operationId": "enableWhatsAppCalling", "method": "POST", "path": "/v1/whatsapp/phone-numbers/{id}/calling", "tags": ["WhatsApp Calling"], "summary": "Enable calling on a number", "description": "Enable WhatsApp Business Calling on a connected number. Configures Meta calling.status=ENABLED with our Telnyx SIP endpoint, fetches and stores the Meta-issued SIP password (encrypted), and snapshots the customer's forward-to destination.", "parameters": [{ "name": "id", "in": "path", "required": true, "description": "WhatsAppPhoneNumber Mongo ID", "schemaType": "string" }], "requestBody": { "required": true, "contentTypes": ["application/json"], "schemaRefs": { "application/json": { "schemaType": "object" } } } }, { "operationId": "listWhatsAppFlowResponses", "method": "GET", "path": "/v1/whatsapp/flow-responses", "tags": ["WhatsApp Flows"], "summary": "List flow responses", "description": "List the responses customers submitted when completing a flow (parsed from the nfm_reply messages received via webhook), newest first. Scope to a single flow with `flowId` — this matches responses whose flow_token carries the `<flowId>:` prefix that Zernio stamps on auto-generated tokens at send time. Responses sent with a custom integrator-supplied flow_token are not attributed to a flow.", "parameters": [{ "name": "accountId", "in": "query", "required": true, "description": "WhatsApp social account ID", "schemaType": "string" }, { "name": "flowId", "in": "query", "required": false, "description": "Scope to responses for this flow", "schemaType": "string" }, { "name": "limit", "in": "query", "required": false, "description": "Max responses to return", "schemaType": "integer" }] }, { "operationId": "listWhatsAppFlows", "method": "GET", "path": "/v1/whatsapp/flows", "tags": ["WhatsApp Flows"], "summary": "List flows", "description": "List all WhatsApp Flows for the Business Account (WABA) associated with the given account.", "parameters": [{ "name": "accountId", "in": "query", "required": true, "description": "WhatsApp social account ID", "schemaType": "string" }] }, { "operationId": "createWhatsAppFlow", "method": "POST", "path": "/v1/whatsapp/flows", "tags": ["WhatsApp Flows"], "summary": "Create flow", "description": "Create a new WhatsApp Flow in DRAFT status. Optionally clone an existing flow. After creating, upload a Flow JSON definition, then publish to make it sendable.", "parameters": [], "requestBody": { "required": true, "contentTypes": ["application/json"], "schemaRefs": { "application/json": { "schemaType": "object" } } } }, { "operationId": "deleteWhatsAppFlow", "method": "DELETE", "path": "/v1/whatsapp/flows/{flowId}", "tags": ["WhatsApp Flows"], "summary": "Delete flow", "description": "Delete a DRAFT flow. This is irreversible. Only flows in DRAFT status can be deleted.", "parameters": [{ "name": "flowId", "in": "path", "required": true, "description": "Flow ID", "schemaType": "string" }, { "name": "accountId", "in": "query", "required": true, "description": "WhatsApp social account ID", "schemaType": "string" }] }, { "operationId": "getWhatsAppFlow", "method": "GET", "path": "/v1/whatsapp/flows/{flowId}", "tags": ["WhatsApp Flows"], "summary": "Get flow", "description": "Get details for a specific flow, including status, categories, validation errors, and preview URL.", "parameters": [{ "name": "flowId", "in": "path", "required": true, "description": "Flow ID", "schemaType": "string" }, { "name": "accountId", "in": "query", "required": true, "description": "WhatsApp social account ID", "schemaType": "string" }, { "name": "fields", "in": "query", "required": false, "description": "Comma-separated fields to return (default: id,name,status,categories,validation_errors,json_version,preview,data_api_version,endpoint_uri)", "schemaType": "string" }] }, { "operationId": "updateWhatsAppFlow", "method": "PATCH", "path": "/v1/whatsapp/flows/{flowId}", "tags": ["WhatsApp Flows"], "summary": "Update flow", "description": "Update metadata (name, categories) of a DRAFT flow. Published flows are immutable.", "parameters": [{ "name": "flowId", "in": "path", "required": true, "description": "Flow ID", "schemaType": "string" }], "requestBody": { "required": true, "contentTypes": ["application/json"], "schemaRefs": { "application/json": { "schemaType": "object" } } } }, { "operationId": "deprecateWhatsAppFlow", "method": "POST", "path": "/v1/whatsapp/flows/{flowId}/deprecate", "tags": ["WhatsApp Flows"], "summary": "Deprecate flow", "description": "Deprecate a PUBLISHED flow. This is irreversible. Deprecated flows cannot be sent or opened, but existing active sessions may continue until they complete.", "parameters": [{ "name": "flowId", "in": "path", "required": true, "description": "Flow ID", "schemaType": "string" }], "requestBody": { "required": true, "contentTypes": ["application/json"], "schemaRefs": { "application/json": { "schemaType": "object" } } } }, { "operationId": "getWhatsAppFlowJson", "method": "GET", "path": "/v1/whatsapp/flows/{flowId}/json", "tags": ["WhatsApp Flows"], "summary": "Get flow JSON asset", "description": "Get the flow JSON asset metadata, including a temporary download URL for the Flow JSON file.", "parameters": [{ "name": "flowId", "in": "path", "required": true, "description": "Flow ID", "schemaType": "string" }, { "name": "accountId", "in": "query", "required": true, "description": "WhatsApp social account ID", "schemaType": "string" }] }, { "operationId": "uploadWhatsAppFlowJson", "method": "PUT", "path": "/v1/whatsapp/flows/{flowId}/json", "tags": ["WhatsApp Flows"], "summary": "Upload flow JSON", "description": "Upload or update the Flow JSON for a DRAFT flow. The Flow JSON defines all screens, components (text inputs, dropdowns, date pickers, etc.), and navigation. Meta validates the JSON on upload and returns any validation errors. See: https://developers.facebook.com/docs/whatsapp/flows/reference/flowjson", "parameters": [{ "name": "flowId", "in": "path", "required": true, "description": "Flow ID", "schemaType": "string" }], "requestBody": { "required": true, "contentTypes": ["application/json"], "schemaRefs": { "application/json": { "schemaType": "object" } } } }, { "operationId": "getWhatsAppFlowPreview", "method": "GET", "path": "/v1/whatsapp/flows/{flowId}/preview", "tags": ["WhatsApp Flows"], "summary": "Get flow preview URL", "description": "Get Meta's public web-preview URL for a flow (drafts included), embeddable as an interactive iframe. The link is reused across calls (valid ~30 days); pass invalidate=true to mint a fresh one (the previous link stops working).", "parameters": [{ "name": "flowId", "in": "path", "required": true, "description": "Flow ID", "schemaType": "string" }, { "name": "accountId", "in": "query", "required": true, "description": "WhatsApp social account ID", "schemaType": "string" }, { "name": "invalidate", "in": "query", "required": false, "description": "Mint a fresh preview link (default false)", "schemaType": "boolean" }] }, { "operationId": "publishWhatsAppFlow", "method": "POST", "path": "/v1/whatsapp/flows/{flowId}/publish", "tags": ["WhatsApp Flows"], "summary": "Publish flow", "description": "Publish a DRAFT flow. This is irreversible. Once published, the flow and its JSON become immutable and the flow can be sent to users. To update a published flow, create a new flow (optionally cloning this one via cloneFlowId).", "parameters": [{ "name": "flowId", "in": "path", "required": true, "description": "Flow ID", "schemaType": "string" }], "requestBody": { "required": true, "contentTypes": ["application/json"], "schemaRefs": { "application/json": { "schemaType": "object" } } } }, { "operationId": "listWhatsAppFlowVersions", "method": "GET", "path": "/v1/whatsapp/flows/{flowId}/versions", "tags": ["WhatsApp Flows"], "summary": "List flow versions", "description": "List the flow's version history (the clone lineage Zernio tracks, since Meta has no native versioning), newest version first. Each entry is enriched with the version's live name and status from Meta. A flow with no lineage returns just itself as version 1.", "parameters": [{ "name": "flowId", "in": "path", "required": true, "description": "Flow ID", "schemaType": "string" }, { "name": "accountId", "in": "query", "required": true, "description": "WhatsApp social account ID", "schemaType": "string" }] }, { "operationId": "sendWhatsAppFlowMessage", "method": "POST", "path": "/v1/whatsapp/flows/send", "tags": ["WhatsApp Flows"], "summary": "Send flow message", "description": "Send a published flow as an interactive message with a CTA button. When the recipient taps the button, the flow opens natively in WhatsApp. Flow responses are received via webhooks.", "parameters": [], "requestBody": { "required": true, "contentTypes": ["application/json"], "schemaRefs": { "application/json": { "schemaType": "object" } } } }, { "operationId": "getWhatsAppNumberInfo", "method": "GET", "path": "/v1/whatsapp/number-info", "tags": ["WhatsApp Phone Numbers"], "summary": "Get number status", "description": "Live snapshot of a connected number straight from Meta: the phone-number node (display number, display name + approval, quality rating, messaging-limit tier, throughput, official-business badge, connection status, health_status) and its owning WhatsApp Business Account (name, business verification, timezone, health_status). Fetched live because Meta updates quality/tier/name/health over time; the call also refreshes the cached values shown on the connection card.", "parameters": [{ "name": "accountId", "in": "query", "required": true, "description": "WhatsApp social account ID", "schemaType": "string" }] }, { "operationId": "getWhatsAppPhoneNumbers", "method": "GET", "path": "/v1/whatsapp/phone-numbers", "tags": ["WhatsApp Phone Numbers"], "summary": "List phone numbers", "description": "List all WhatsApp phone numbers purchased by the authenticated user. By default, released numbers are excluded. Connected (bring-your-own) numbers are returned in the separate `connected` array — they are not billed and have no provisioning lifecycle.", "parameters": [{ "name": "status", "in": "query", "required": false, "description": "Filter by status (by default excludes released numbers). NOTE: `status=pending_regulatory` returns the \"provisioning\" view — numbers still in review PLUS recently-declined (last 30 days) ones, so a failed registration surfaces (with `regul...", "schemaType": "string", "schemaEnum": ["provisioning", "verifying", "pending_payment", "pending_regulatory", "regulatory_declined", "active", "suspended", "releasing", "released"] }, { "name": "profileId", "in": "query", "required": false, "description": "Filter by profile", "schemaType": "string" }] }, { "operationId": "getWhatsAppNumberRemediation", "method": "GET", "path": "/v1/whatsapp/phone-numbers/{id}/remediate", "tags": ["WhatsApp Phone Numbers"], "summary": "Get the declined requirements to fix", "description": "For a number in `regulatory_declined`, returns ONLY the requirements the reviewer flagged declined, as a form spec (same shape as the KYC form GET). The customer fixes just those — Telnyx supports correcting a declined requirement group and re-submitting it (no new number/group). Falls back to the full spec if the provider exposes no per-requirement flags.", "parameters": [{ "name": "id", "in": "path", "required": true, "description": "WhatsAppPhoneNumber id.", "schemaType": "string" }] }, { "operationId": "remediateWhatsAppNumber", "method": "POST", "path": "/v1/whatsapp/phone-numbers/{id}/remediate", "tags": ["WhatsApp Phone Numbers"], "summary": "Fix a declined number and re-submit", "description": "Submit corrected values/documents for the declined requirement(s). We PATCH them onto the SAME requirement group and re-submit it for approval; the number goes `regulatory_declined` → `pending_regulatory`. No new number and no new billing. Body shape matches the KYC submit (values / documents / address) — send only the corrected fields.", "parameters": [{ "name": "id", "in": "path", "required": true, "schemaType": "string" }], "requestBody": { "required": true, "contentTypes": ["application/json"], "schemaRefs": { "application/json": { "schemaType": "object" } } } }, { "operationId": "releaseWhatsAppPhoneNumber", "method": "DELETE", "path": "/v1/whatsapp/phone-numbers/{phoneNumberId}", "tags": ["WhatsApp Phone Numbers"], "summary": "Release phone number", "description": "Release a purchased phone number. This will: 1. Disconnect any linked WhatsApp social account 2. Decrement the Stripe subscription quantity (or cancel if last number) 3. Release the number from Telnyx 4. Mark the number as released", "parameters": [{ "name": "phoneNumberId", "in": "path", "required": true, "description": "Phone number record ID", "schemaType": "string" }] }, { "operationId": "getWhatsAppPhoneNumber", "method": "GET", "path": "/v1/whatsapp/phone-numbers/{phoneNumberId}", "tags": ["WhatsApp Phone Numbers"], "summary": "Get phone number", "description": "Retrieve the current status of a purchased phone number. Poll this to track Meta pre-verification (US sync path) and, for regulated (Tier 3/4) numbers, the async lifecycle: pending_regulatory → active (or regulatory_declined). When a regulated number has an Onfido ID step, `onfidoVerificationUrl` appears here once the order is placed — forward it to the end user. (Or subscribe to the whatsapp.number.* webhooks instead of polling.)", "parameters": [{ "name": "phoneNumberId", "in": "path", "required": true, "description": "Phone number record ID", "schemaType": "string" }] }, { "operationId": "checkWhatsAppNumberAvailability", "method": "GET", "path": "/v1/whatsapp/phone-numbers/availability", "tags": ["WhatsApp Phone Numbers"], "summary": "Check a country's availability + address constraint", "description": "Pre-purchase check, so you can warn BEFORE a customer invests in KYC (regulated review is async, 1-3 days). Tells you whether we have deliverable inventory, and what address the customer needs: - `addressConstraint: geo` → the registered address MUST be in one of the returned `areas` (the only place we have stock). A different-area address passes pre-approval but the number can never be assigned. - `addressConstraint: country` → any in-country address works. - `addressConstraint: none` → field-...", "parameters": [{ "name": "country", "in": "query", "required": true, "description": "ISO-2 country code.", "schemaType": "string" }] }, { "operationId": "searchAvailableWhatsAppNumbers", "method": "GET", "path": "/v1/whatsapp/phone-numbers/available", "tags": ["WhatsApp Phone Numbers"], "summary": "Search available numbers to purchase", "description": "Search the provider's inventory for numbers available to purchase in a country (default US). Optional filters narrow the results. The country must be offerable (see GET /v1/whatsapp/phone-numbers/countries).", "parameters": [{ "name": "country", "in": "query", "required": false, "schemaType": "string" }, { "name": "type", "in": "query", "required": false, "description": "Number type; defaults to the country's WhatsApp-safe type", "schemaType": "string" }, { "name": "prefix", "in": "query", "required": false, "description": "Area code", "schemaType": "string" }, { "name": "locality", "in": "query", "required": false, "description": "City", "schemaType": "string" }, { "name": "contains", "in": "query", "required": false, "description": "Pattern to match within the number", "schemaType": "string" }, { "name": "limit", "in": "query", "required": false, "schemaType": "integer" }] }, { "operationId": "listWhatsAppNumberCountries", "method": "GET", "path": "/v1/whatsapp/phone-numbers/countries", "tags": ["WhatsApp Phone Numbers"], "summary": "List offerable number countries", "description": "The WhatsApp number countries available to purchase, each with its flat monthly price (cents), regulatory tier, whether it needs end-user KYC (Tier 3/4), and whether outbound calling is available (not BIC-blocked). Drives the country picker. Tier-4 countries appear only when enabled.", "parameters": [] }, { "operationId": "getWhatsAppNumberKycForm", "method": "GET", "path": "/v1/whatsapp/phone-numbers/kyc", "tags": ["WhatsApp Phone Numbers"], "summary": "Get regulated-number KYC form spec", "description": "For a Tier 3/4 country, the fields the end customer must provide (Telnyx regulatory requirements) before a number can be ordered: text, date, address, or file (document) per requirement.", "parameters": [{ "name": "country", "in": "query", "required": true, "schemaType": "string" }, { "name": "profileId", "in": "query", "required": true, "schemaType": "string" }] }, { "operationId": "submitWhatsAppNumberKyc", "method": "POST", "path": "/v1/whatsapp/phone-numbers/kyc", "tags": ["WhatsApp Phone Numbers"], "summary": "Submit regulated-number KYC", "description": "Submit the end customer's KYC (textual values, uploaded documents, address) for a Tier 3/4 country. Documents are streamed straight to the number provider and are not stored by Zernio. Builds + submits a regulatory requirement group and claims a pending_regulatory slot; the number is ordered + activated once the provider approves (asynchronous). A customer may hold several same-country numbers in review at once; a double-submit of the SAME attempt is deduped via `submissionId`. For an ID-card d...", "parameters": [], "requestBody": { "required": true, "contentTypes": ["application/json"], "schemaRefs": { "application/json": { "schemaType": "object" } } } }, { "operationId": "uploadWhatsAppNumberKycDocument", "method": "POST", "path": "/v1/whatsapp/phone-numbers/kyc/upload-document", "tags": ["WhatsApp Phone Numbers"], "summary": "Upload a single regulated-number KYC document", "description": "Upload ONE document and get back its provider document id, to reference from POST /v1/whatsapp/phone-numbers/kyc via `documents[].documentId`. Send the RAW file bytes as the request body (not base64); put the filename in the `X-Filename` header. Uploading documents one-per-request keeps each request under the ~4.5MB body limit. The document streams straight to the number provider and is not stored by Zernio.", "parameters": [{ "name": "X-Filename", "in": "header", "required": true, "description": "URL-encoded original filename.", "schemaType": "string" }], "requestBody": { "required": true, "contentTypes": ["application/octet-stream"], "schemaRefs": { "application/octet-stream": { "schemaType": "string" } } } }, { "operationId": "validateWhatsAppNumberKycAddress", "method": "POST", "path": "/v1/whatsapp/phone-numbers/kyc/validate-address", "tags": ["WhatsApp Phone Numbers"], "summary": "Pre-validate a regulated-number KYC address (Tier 4)", "description": "Optional early check for the address step of a Tier 4 (end-user identity) registration: validates a postal address for deliverability BEFORE the full KYC submit, so it can be corrected before any documents are uploaded. The full submit (POST /v1/whatsapp/phone-numbers/kyc) re-validates the address, so this call is purely a fast feedback path and skipping it is safe. Only the postal address is sent (no documents, no gov-ID fields). A region (`administrative_area`) is required by the validator; w...", "parameters": [], "requestBody": { "required": true, "contentTypes": ["application/json"], "schemaRefs": { "application/json": { "schemaType": "object" } } } }, { "operationId": "purchaseWhatsAppPhoneNumber", "method": "POST", "path": "/v1/whatsapp/phone-numbers/purchase", "tags": ["WhatsApp Phone Numbers"], "summary": "Purchase phone number", "description": "Initiate purchasing a WhatsApp phone number. Payment-first flow: the user does not pick a specific number. The system either creates a Stripe Checkout Session (first number) or increments the existing subscription quantity and provisions inline (subsequent numbers). Requires a paid plan. The maximum number of phone numbers is determined by the user's plan.", "parameters": [], "requestBody": { "required": true, "contentTypes": ["application/json"], "schemaRefs": { "application/json": { "schemaType": "object" } } } }, { "operationId": "listWhatsAppSandboxSessions", "method": "GET", "path": "/v1/whatsapp/sandbox/sessions", "tags": ["WhatsApp Sandbox"], "summary": "List your sandbox sessions", "description": "Returns all of the authenticated user's non-expired sandbox sessions (pending + active) plus the sandbox phone number. In practice there is at most one session per user since the sandbox is one-phone-per-user; the array shape is preserved for forward compatibility.", "parameters": [] }, { "operationId": "createWhatsAppSandboxSession", "method": "POST", "path": "/v1/whatsapp/sandbox/sessions", "tags": ["WhatsApp Sandbox"], "summary": "Start a sandbox activation for a phone", "description": "Creates (or refreshes) a pending sandbox session for the given phone and immediately fires the verified sandbox template from the shared sandbox number to that phone. The session activates when the phone owner replies to that WhatsApp message — the reply itself is proof of ownership. One phone per user: if the caller already has a non-expired session for a DIFFERENT phone, the request is rejected with `invalid_field_value` (the message names the existing phone so it can be revoked first). Re-cr...", "parameters": [], "requestBody": { "required": true, "contentTypes": ["application/json"], "schemaRefs": { "application/json": { "schemaType": "object" } } } }, { "operationId": "deleteWhatsAppSandboxSession", "method": "DELETE", "path": "/v1/whatsapp/sandbox/sessions/{sessionId}", "tags": ["WhatsApp Sandbox"], "summary": "Revoke a sandbox session", "description": "Hard-deletes the session. The user loses the ability to send to that phone via the sandbox until they re-activate it. Existing conversations and messages already exchanged with that phone are untouched — revocation only blocks FUTURE sends. Sessions belonging to other users cannot be revoked; the response is the same 400 as \"session not found\" so existence isn't leaked.", "parameters": [{ "name": "sessionId", "in": "path", "required": true, "description": "The session id returned by POST /v1/whatsapp/sandbox/sessions.", "schemaType": "string" }] }, { "operationId": "getWhatsAppLibraryTemplate", "method": "GET", "path": "/v1/whatsapp/template-library", "tags": ["WhatsApp Templates"], "summary": "Look up a library template", "description": "Look up a single pre-approved Template Library template by its exact name, to introspect its structure before importing it. Most importantly it returns the template's `buttons`: a library template with `URL` / `PHONE_NUMBER` buttons must be created with a matching `library_template_button_inputs` array (see Create Template), or Meta rejects it. Use this to discover which inputs to collect.", "parameters": [{ "name": "accountId", "in": "query", "required": true, "description": "WhatsApp social account ID", "schemaType": "string" }, { "name": "name", "in": "query", "required": true, "description": "Exact library template name", "schemaType": "string" }] }, { "operationId": "unblockWhatsAppUsers", "method": "DELETE", "path": "/v1/whatsapp/block-users", "tags": ["WhatsApp"], "summary": "Unblock users", "description": "Unblock one or more previously blocked WhatsApp users on this number. Up to 1,000 users per request; per-user failures are reported in `failed` without failing the rest of the batch.", "parameters": [], "requestBody": { "required": true, "contentTypes": ["application/json"], "schemaRefs": { "application/json": { "schemaType": "object" } } } }, { "operationId": "getWhatsAppBlockedUsers", "method": "GET", "path": "/v1/whatsapp/block-users", "tags": ["WhatsApp"], "summary": "List blocked users", "description": "List the WhatsApp users blocked on this number. Cursor-paginated; pass `nextCursor` back as `after` to fetch the next page. The blocklist holds up to 64,000 users.", "parameters": [{ "name": "accountId", "in": "query", "required": true, "description": "WhatsApp social account ID", "schemaType": "string" }, { "name": "limit", "in": "query", "required": false, "description": "Page size.", "schemaType": "integer" }, { "name": "after", "in": "query", "required": false, "description": "Cursor from a previous response's `nextCursor`.", "schemaType": "string" }] }, { "operationId": "blockWhatsAppUsers", "method": "POST", "path": "/v1/whatsapp/block-users", "tags": ["WhatsApp"], "summary": "Block users", "description": "Block one or more WhatsApp users on this number. Blocked users cannot message your number or see that you are online, and your sends to them return an error. Meta constraints, surfaced per-user in `failed` (the request itself still succeeds for the rest of the batch): - Only users who messaged your business within the last 24 hours can be blocked (failures outside the window report \"Re-engagement required\"). - Up to 1,000 users per request; the blocklist caps at 64,000. - Other WhatsApp Busines...", "parameters": [], "requestBody": { "required": true, "contentTypes": ["application/json"], "schemaRefs": { "application/json": { "schemaType": "object" } } } }, { "operationId": "getWhatsAppBlockStatus", "method": "GET", "path": "/v1/whatsapp/block-users/status", "tags": ["WhatsApp"], "summary": "Check if a user is blocked", "description": "Definitive blocked-state lookup for a single contact. Meta exposes no membership endpoint, so this reads Zernio's blocklist mirror (kept in sync by the block/unblock endpoints; the first call per account backfills the mirror from Meta's full list). Constant-time regardless of blocklist size.", "parameters": [{ "name": "accountId", "in": "query", "required": true, "schemaType": "string" }, { "name": "user", "in": "query", "required": true, "description": "Consumer wa_id or E.164 phone (leading + optional)", "schemaType": "string" }] }, { "operationId": "getWhatsAppBusinessProfile", "method": "GET", "path": "/v1/whatsapp/business-profile", "tags": ["WhatsApp"], "summary": "Get business profile", "description": "Retrieve the WhatsApp Business profile for the account (about, address, description, email, websites, etc.).", "parameters": [{ "name": "accountId", "in": "query", "required": true, "description": "WhatsApp social account ID", "schemaType": "string" }] }, { "operationId": "updateWhatsAppBusinessProfile", "method": "POST", "path": "/v1/whatsapp/business-profile", "tags": ["WhatsApp"], "summary": "Update business profile", "description": "Update the WhatsApp Business profile. All fields are optional; only provided fields will be updated. Constraints: about max 139 chars, description max 512 chars, max 2 websites.", "parameters": [], "requestBody": { "required": true, "contentTypes": ["application/json"], "schemaRefs": { "application/json": { "schemaType": "object" } } } }, { "operationId": "getWhatsAppDisplayName", "method": "GET", "path": "/v1/whatsapp/business-profile/display-name", "tags": ["WhatsApp"], "summary": "Get display name status", "description": "Fetch the current display name and its Meta review status for a WhatsApp Business account. Display name changes require Meta approval and can take 1-3 business days.", "parameters": [{ "name": "accountId", "in": "query", "required": true, "description": "WhatsApp social account ID", "schemaType": "string" }] }, { "operationId": "updateWhatsAppDisplayName", "method": "POST", "path": "/v1/whatsapp/business-profile/display-name", "tags": ["WhatsApp"], "summary": "Request display name change", "description": "Submit a display name change request for the WhatsApp Business account. The new name must follow WhatsApp naming guidelines (3-512 characters, must represent your business). Changes require Meta review and approval, which typically takes 1-3 business days.", "parameters": [], "requestBody": { "required": true, "contentTypes": ["application/json"], "schemaRefs": { "application/json": { "schemaType": "object" } } } }, { "operationId": "uploadWhatsAppProfilePhoto", "method": "POST", "path": "/v1/whatsapp/business-profile/photo", "tags": ["WhatsApp"], "summary": "Upload profile picture", "description": "Upload a new profile picture for the WhatsApp Business Profile. Uses Meta's resumable upload API under the hood: creates an upload session, uploads the image bytes, then updates the business profile with the resulting handle.", "parameters": [], "requestBody": { "required": true, "contentTypes": ["multipart/form-data"], "schemaRefs": { "multipart/form-data": { "schemaType": "object" } } } }, { "operationId": "listWhatsAppConversions", "method": "GET", "path": "/v1/whatsapp/conversions", "tags": ["WhatsApp", "Ads"], "summary": "List recent WhatsApp conversion events", "description": "Returns the most recent conversion events sent through `POST /v1/whatsapp/conversions` for the given WhatsApp account. Sourced from delivery logs (Axiom `late` dataset), so the visible window is bounded by log retention (about 30 days). Useful for rendering a \"recent activity\" panel on the conversions setup tab without standing up a parallel persistence layer. Per-event payload mirrors the structured log we write on every successful send: `eventName`, `conversationId`, `eventsReceived`, `events...", "parameters": [{ "name": "accountId", "in": "query", "required": true, "description": "WhatsApp social account ID", "schemaType": "string" }, { "name": "limit", "in": "query", "required": false, "description": "Max events to return (1-200, default 50).", "schemaType": "integer" }] }, { "operationId": "sendWhatsAppConversion", "method": "POST", "path": "/v1/whatsapp/conversions", "tags": ["WhatsApp", "Ads"], "summary": "Send WhatsApp conversion event", "description": "Forward a WhatsApp Business Messaging conversion event (`LeadSubmitted`, `Purchase`, `AddToCart`, `InitiateCheckout`, `ViewContent`) to Meta's Conversions API with `action_source = business_messaging` and `messaging_channel = whatsapp`. The endpoint looks up the originating CTWA click ID (`ctwa_clid`) captured on the first inbound message of the conversation and replays it on every event so Meta can attribute the conversion back to the Click-to-WhatsApp ad that drove the chat. Configuration pre...", "parameters": [], "requestBody": { "required": true, "contentTypes": ["application/json"], "schemaRefs": { "application/json": { "schemaType": "object" } } } }, { "operationId": "getWhatsAppDataset", "method": "GET", "path": "/v1/whatsapp/dataset", "tags": ["WhatsApp"], "summary": "Get CTWA conversions dataset", "description": "Returns the Meta Click-to-WhatsApp conversions dataset currently linked to the WhatsApp account, if one has been provisioned. Reads only from the stored `metadata.metaCapiDatasetId` — never hits Meta, never creates a dataset. Use this to detect whether `POST /v1/whatsapp/conversions` is configured for an account.", "parameters": [{ "name": "accountId", "in": "query", "required": true, "description": "WhatsApp social account ID", "schemaType": "string" }] }, { "operationId": "createWhatsAppDataset", "method": "POST", "path": "/v1/whatsapp/dataset", "tags": ["WhatsApp"], "summary": "Provision CTWA conversions dataset", "description": "Creates (or fetches, if one already exists) the Meta dataset that Click-to-WhatsApp ad events are reported against via the Conversions API, and persists its ID on the account as `metadata.metaCapiDatasetId`. The call is GET-first idempotent — a WABA can only own one CTWA dataset, so a second call after a successful provision is a safe no-op that returns the same ID with `created: false`. Requires the connected WhatsApp account's token to carry the `whatsapp_business_manage_events` permission. I...", "parameters": [], "requestBody": { "required": true, "contentTypes": ["application/json"], "schemaRefs": { "application/json": { "schemaType": "object" } } } }, { "operationId": "getWhatsAppTemplates", "method": "GET", "path": "/v1/whatsapp/templates", "tags": ["WhatsApp"], "summary": "List templates", "description": "List all message templates for the WhatsApp Business Account (WABA) associated with the given account. Templates are fetched directly from the WhatsApp Cloud API.", "parameters": [{ "name": "accountId", "in": "query", "required": true, "description": "WhatsApp social account ID", "schemaType": "string" }] }, { "operationId": "createWhatsAppTemplate", "method": "POST", "path": "/v1/whatsapp/templates", "tags": ["WhatsApp"], "summary": "Create template", "description": "Create a new message template. Supports two modes: Custom template: Provide components with your own content. Submitted to Meta for review (can take up to 24h). Library template: Provide library_template_name instead of components to use a pre-built template from Meta's template library. Library templates are pre-approved (no review wait). You can optionally customize parameters and buttons via library_template_body_inputs and library_template_button_inputs. Browse available library templates a...", "parameters": [], "requestBody": { "required": true, "contentTypes": ["application/json"], "schemaRefs": { "application/json": { "schemaType": "object" } } } }, { "operationId": "deleteWhatsAppTemplate", "method": "DELETE", "path": "/v1/whatsapp/templates/{templateName}", "tags": ["WhatsApp"], "summary": "Delete template", "description": "Permanently delete a message template by name.", "parameters": [{ "name": "templateName", "in": "path", "required": true, "description": "Template name", "schemaType": "string" }, { "name": "accountId", "in": "query", "required": true, "description": "WhatsApp social account ID", "schemaType": "string" }] }, { "operationId": "getWhatsAppTemplate", "method": "GET", "path": "/v1/whatsapp/templates/{templateName}", "tags": ["WhatsApp"], "summary": "Get template", "description": "Retrieve a single message template by name.", "parameters": [{ "name": "templateName", "in": "path", "required": true, "description": "Template name", "schemaType": "string" }, { "name": "accountId", "in": "query", "required": true, "description": "WhatsApp social account ID", "schemaType": "string" }] }, { "operationId": "updateWhatsAppTemplate", "method": "PATCH", "path": "/v1/whatsapp/templates/{templateName}", "tags": ["WhatsApp"], "summary": "Update template", "description": "Update a message template's components. Only certain fields can be updated depending on the template's current approval state. Approved templates can only have components updated.", "parameters": [{ "name": "templateName", "in": "path", "required": true, "description": "Template name", "schemaType": "string" }], "requestBody": { "required": true, "contentTypes": ["application/json"], "schemaRefs": { "application/json": { "schemaType": "object" } } } }, { "operationId": "listWhatsAppGroupChats", "method": "GET", "path": "/v1/whatsapp/wa-groups", "tags": ["WhatsApp"], "summary": "List active groups", "description": "List active WhatsApp group chats for a business phone number. These are actual WhatsApp group conversations on the platform. Not available on [Coexistence](/platforms/whatsapp/connection#whatsapp-business-app-coexistence) numbers. Requires a Cloud API-only number.", "parameters": [{ "name": "accountId", "in": "query", "required": true, "description": "WhatsApp social account ID", "schemaType": "string" }, { "name": "limit", "in": "query", "required": false, "description": "Max groups to return", "schemaType": "integer" }, { "name": "after", "in": "query", "required": false, "description": "Pagination cursor", "schemaType": "string" }] }, { "operationId": "createWhatsAppGroupChat", "method": "POST", "path": "/v1/whatsapp/wa-groups", "tags": ["WhatsApp"], "summary": "Create group", "description": "Create a new WhatsApp group chat. Returns the group ID and optionally an invite link. Not available on [Coexistence](/platforms/whatsapp/connection#whatsapp-business-app-coexistence) numbers. Requires a Cloud API-only number.", "parameters": [], "requestBody": { "required": true, "contentTypes": ["application/json"], "schemaRefs": { "application/json": { "schemaType": "object" } } } }, { "operationId": "deleteWhatsAppGroupChat", "method": "DELETE", "path": "/v1/whatsapp/wa-groups/{groupId}", "tags": ["WhatsApp"], "summary": "Delete group", "description": "Delete a WhatsApp group and remove all participants. Not available on [Coexistence](/platforms/whatsapp/connection#whatsapp-business-app-coexistence) numbers. Requires a Cloud API-only number.", "parameters": [{ "name": "groupId", "in": "path", "required": true, "description": "Group ID", "schemaType": "string" }, { "name": "accountId", "in": "query", "required": true, "description": "WhatsApp social account ID", "schemaType": "string" }] }, { "operationId": "getWhatsAppGroupChat", "method": "GET", "path": "/v1/whatsapp/wa-groups/{groupId}", "tags": ["WhatsApp"], "summary": "Get group info", "description": "Retrieve metadata about a WhatsApp group including subject, description, participants, and settings. Not available on [Coexistence](/platforms/whatsapp/connection#whatsapp-business-app-coexistence) numbers. Requires a Cloud API-only number.", "parameters": [{ "name": "groupId", "in": "path", "required": true, "description": "Group ID", "schemaType": "string" }, { "name": "accountId", "in": "query", "required": true, "description": "WhatsApp social account ID", "schemaType": "string" }] }, { "operationId": "updateWhatsAppGroupChat", "method": "POST", "path": "/v1/whatsapp/wa-groups/{groupId}", "tags": ["WhatsApp"], "summary": "Update group settings", "description": "Update the subject, description, or join approval mode of a WhatsApp group. Not available on [Coexistence](/platforms/whatsapp/connection#whatsapp-business-app-coexistence) numbers. Requires a Cloud API-only number.", "parameters": [{ "name": "groupId", "in": "path", "required": true, "description": "Group ID", "schemaType": "string" }, { "name": "accountId", "in": "query", "required": true, "description": "WhatsApp social account ID", "schemaType": "string" }], "requestBody": { "required": true, "contentTypes": ["application/json"], "schemaRefs": { "application/json": { "schemaType": "object" } } } }, { "operationId": "createWhatsAppGroupInviteLink", "method": "POST", "path": "/v1/whatsapp/wa-groups/{groupId}/invite-link", "tags": ["WhatsApp"], "summary": "Create invite link", "description": "Create a new invite link for a WhatsApp group. The previous link is revoked. Not available on [Coexistence](/platforms/whatsapp/connection#whatsapp-business-app-coexistence) numbers. Requires a Cloud API-only number.", "parameters": [{ "name": "groupId", "in": "path", "required": true, "description": "Group ID", "schemaType": "string" }, { "name": "accountId", "in": "query", "required": true, "description": "WhatsApp social account ID", "schemaType": "string" }] }, { "operationId": "rejectWhatsAppGroupJoinRequests", "method": "DELETE", "path": "/v1/whatsapp/wa-groups/{groupId}/join-requests", "tags": ["WhatsApp"], "summary": "Reject join requests", "description": "Reject pending join requests for a WhatsApp group. Not available on [Coexistence](/platforms/whatsapp/connection#whatsapp-business-app-coexistence) numbers. Requires a Cloud API-only number.", "parameters": [{ "name": "groupId", "in": "path", "required": true, "description": "Group ID", "schemaType": "string" }, { "name": "accountId", "in": "query", "required": true, "description": "WhatsApp social account ID", "schemaType": "string" }], "requestBody": { "required": true, "contentTypes": ["application/json"], "schemaRefs": { "application/json": { "schemaType": "object" } } } }, { "operationId": "listWhatsAppGroupJoinRequests", "method": "GET", "path": "/v1/whatsapp/wa-groups/{groupId}/join-requests", "tags": ["WhatsApp"], "summary": "List join requests", "description": "List pending join requests for a WhatsApp group (only for groups with approval_required mode). Not available on [Coexistence](/platforms/whatsapp/connection#whatsapp-business-app-coexistence) numbers. Requires a Cloud API-only number.", "parameters": [{ "name": "groupId", "in": "path", "required": true, "description": "Group ID", "schemaType": "string" }, { "name": "accountId", "in": "query", "required": true, "description": "WhatsApp social account ID", "schemaType": "string" }] }, { "operationId": "approveWhatsAppGroupJoinRequests", "method": "POST", "path": "/v1/whatsapp/wa-groups/{groupId}/join-requests", "tags": ["WhatsApp"], "summary": "Approve join requests", "description": "Approve pending join requests for a WhatsApp group. Not available on [Coexistence](/platforms/whatsapp/connection#whatsapp-business-app-coexistence) numbers. Requires a Cloud API-only number.", "parameters": [{ "name": "groupId", "in": "path", "required": true, "description": "Group ID", "schemaType": "string" }, { "name": "accountId", "in": "query", "required": true, "description": "WhatsApp social account ID", "schemaType": "string" }], "requestBody": { "required": true, "contentTypes": ["application/json"], "schemaRefs": { "application/json": { "schemaType": "object" } } } }, { "operationId": "removeWhatsAppGroupParticipants", "method": "DELETE", "path": "/v1/whatsapp/wa-groups/{groupId}/participants", "tags": ["WhatsApp"], "summary": "Remove participants", "description": "Remove participants from a WhatsApp group. Not available on [Coexistence](/platforms/whatsapp/connection#whatsapp-business-app-coexistence) numbers. Requires a Cloud API-only number.", "parameters": [{ "name": "groupId", "in": "path", "required": true, "description": "Group ID", "schemaType": "string" }, { "name": "accountId", "in": "query", "required": true, "description": "WhatsApp social account ID", "schemaType": "string" }], "requestBody": { "required": true, "contentTypes": ["application/json"], "schemaRefs": { "application/json": { "schemaType": "object" } } } }, { "operationId": "addWhatsAppGroupParticipants", "method": "POST", "path": "/v1/whatsapp/wa-groups/{groupId}/participants", "tags": ["WhatsApp"], "summary": "Add participants", "description": "Add participants to a WhatsApp group. Maximum 8 participants per request. Not available on [Coexistence](/platforms/whatsapp/connection#whatsapp-business-app-coexistence) numbers. Requires a Cloud API-only number.", "parameters": [{ "name": "groupId", "in": "path", "required": true, "description": "Group ID", "schemaType": "string" }, { "name": "accountId", "in": "query", "required": true, "description": "WhatsApp social account ID", "schemaType": "string" }], "requestBody": { "required": true, "contentTypes": ["application/json"], "schemaRefs": { "application/json": { "schemaType": "object" } } } }, { "operationId": "listWorkflows", "method": "GET", "path": "/v1/workflows", "tags": ["Workflows"], "summary": "List workflows", "description": "Returns workflows with run stats. Filter by status or profile.", "parameters": [{ "name": "profileId", "in": "query", "required": false, "description": "Filter by profile. Omit to list across all profiles", "schemaType": "string" }, { "name": "status", "in": "query", "required": false, "schemaType": "string", "schemaEnum": ["draft", "active", "paused"] }, { "name": "limit", "in": "query", "required": false, "schemaType": "integer" }, { "name": "skip", "in": "query", "required": false, "schemaType": "integer" }] }, { "operationId": "createWorkflow", "method": "POST", "path": "/v1/workflows", "tags": ["Workflows"], "summary": "Create workflow", "description": "Create a branching conversation workflow (draft) from a node/edge graph. Created in `draft` status; activate it to start matching inbound messages. The graph is validated structurally; completeness (a trigger node + reachable entry) is required at activation.", "parameters": [], "requestBody": { "required": true, "contentTypes": ["application/json"], "schemaRefs": { "application/json": { "schemaType": "object" } } } }, { "operationId": "deleteWorkflow", "method": "DELETE", "path": "/v1/workflows/{workflowId}", "tags": ["Workflows"], "summary": "Delete workflow", "description": "Permanently delete a workflow and all of its executions.", "parameters": [{ "name": "workflowId", "in": "path", "required": true, "schemaType": "string" }] }, { "operationId": "getWorkflow", "method": "GET", "path": "/v1/workflows/{workflowId}", "tags": ["Workflows"], "summary": "Get workflow with graph", "description": "Returns a workflow including its full node/edge graph and run stats.", "parameters": [{ "name": "workflowId", "in": "path", "required": true, "schemaType": "string" }] }, { "operationId": "updateWorkflow", "method": "PATCH", "path": "/v1/workflows/{workflowId}", "tags": ["Workflows"], "summary": "Update workflow", "description": "Update name, description, the graph, or reassign to a different account. The graph can only be modified while the workflow is draft or paused. Account swaps re-validate the graph against the new platform (so e.g. moving from WhatsApp to Facebook surfaces a `start_call` node as an error instead of silently saving an unrunnable graph).", "parameters": [{ "name": "workflowId", "in": "path", "required": true, "schemaType": "string" }], "requestBody": { "required": false, "contentTypes": ["application/json"], "schemaRefs": { "application/json": { "schemaType": "object" } } } }, { "operationId": "activateWorkflow", "method": "POST", "path": "/v1/workflows/{workflowId}/activate", "tags": ["Workflows"], "summary": "Activate workflow", "description": "Validate the graph is runnable and set the workflow live. Once active, matching inbound messages start executions. Idempotent.", "parameters": [{ "name": "workflowId", "in": "path", "required": true, "schemaType": "string" }] }, { "operationId": "duplicateWorkflow", "method": "POST", "path": "/v1/workflows/{workflowId}/duplicate", "tags": ["Workflows"], "summary": "Duplicate a workflow", "description": "Create an independent copy of a workflow's graph, name, description, and account binding. The copy is created in `draft` status with fresh execution counters and a new id — execution history is NOT copied. Useful for branching off a known-good workflow before making experimental edits.", "parameters": [{ "name": "workflowId", "in": "path", "required": true, "schemaType": "string" }] }, { "operationId": "listWorkflowExecutions", "method": "GET", "path": "/v1/workflows/{workflowId}/executions", "tags": ["Workflows"], "summary": "List workflow runs", "description": "Returns recent executions (runs) with their status, current node, and accumulated variables.", "parameters": [{ "name": "workflowId", "in": "path", "required": true, "schemaType": "string" }, { "name": "status", "in": "query", "required": false, "schemaType": "string", "schemaEnum": ["running", "waiting", "completed", "exited", "failed"] }, { "name": "limit", "in": "query", "required": false, "schemaType": "integer" }, { "name": "skip", "in": "query", "required": false, "schemaType": "integer" }] }, { "operationId": "triggerWorkflow", "method": "POST", "path": "/v1/workflows/{workflowId}/executions", "tags": ["Workflows"], "summary": "Manually start a workflow run", "description": "Kick off a run without waiting for an inbound message (useful for testing). Target an existing conversation by `conversationId`, or — WhatsApp only — a phone number via `to` (a conversation is found or created). `text` seeds the run's `lastMessage` variable. The graph must be runnable.", "parameters": [{ "name": "workflowId", "in": "path", "required": true, "schemaType": "string" }], "requestBody": { "required": true, "contentTypes": ["application/json"], "schemaRefs": { "application/json": { "schemaType": "object" } } } }, { "operationId": "listWorkflowExecutionEvents", "method": "GET", "path": "/v1/workflows/{workflowId}/executions/{executionId}/events", "tags": ["Workflows"], "summary": "Get an execution's timeline", "description": "Returns the per-step run-log for a single workflow execution: trigger fired, each node visited, edge handles taken, errors, and durations. Backed by Tinybird (90-day retention). Used by the Runs UI drawer to render the timeline.", "parameters": [{ "name": "workflowId", "in": "path", "required": true, "schemaType": "string" }, { "name": "executionId", "in": "path", "required": true, "schemaType": "string" }] }, { "operationId": "pauseWorkflow", "method": "POST", "path": "/v1/workflows/{workflowId}/pause", "tags": ["Workflows"], "summary": "Pause workflow", "description": "Stop matching new inbound messages. In-flight executions continue to completion. Idempotent.", "parameters": [{ "name": "workflowId", "in": "path", "required": true, "schemaType": "string" }] }, { "operationId": "listWorkflowVersions", "method": "GET", "path": "/v1/workflows/{workflowId}/versions", "tags": ["Workflows"], "summary": "List a workflow's version history", "description": "Returns the snapshot history. A new version is recorded automatically before every PATCH to `nodes` / `edges` / `entryNodeId`, and explicitly when a previous version is restored. Lightweight list — call `getWorkflowVersion` for the full snapshot graph.", "parameters": [{ "name": "workflowId", "in": "path", "required": true, "schemaType": "string" }] }, { "operationId": "getWorkflowVersion", "method": "GET", "path": "/v1/workflows/{workflowId}/versions/{version}", "tags": ["Workflows"], "summary": "Get a specific workflow version", "description": "Returns the full snapshot for a single historical version, including the graph.", "parameters": [{ "name": "workflowId", "in": "path", "required": true, "schemaType": "string" }, { "name": "version", "in": "path", "required": true, "schemaType": "integer" }] }, { "operationId": "restoreWorkflowVersion", "method": "POST", "path": "/v1/workflows/{workflowId}/versions/{version}/restore", "tags": ["Workflows"], "summary": "Restore a previous workflow version", "description": "Replace the current graph with the named version's snapshot. Before the swap, the current graph is itself snapshotted as a new version, so a restore is reversible. The workflow must be in `draft` or `paused` status (same gate as a normal graph edit). The returned workflow carries `restoredFromVersion` so the UI can surface which version was rolled back to.", "parameters": [{ "name": "workflowId", "in": "path", "required": true, "schemaType": "string" }, { "name": "version", "in": "path", "required": true, "schemaType": "integer" }] }];