zhyper docs

API reference

URETILMIS SAYFA — elle duzenlemeyin. Kaynak: apps/api/openapi.json.

zhyper API v1.0.0

Each section lists a tag as a table, then repeats every operation below the table with its full description, its parameters, and the top-level fields of its JSON request body. Read the description, not only the summary: a summary names what an operation does, while a scope limit — test mode only, one platform only, one ad provider only — is stated in the description.

Field-level detail below the first level of a request body, and the response schemas, are not reproduced here. The document itself is the contract and it is public: GET /v1/openapi.json on the API returns it, and the marketing site serves the same document at /openapi.json.

Sections

Every operation the API exposes appears below, grouped by tag. A tag with no hand-written guide page is still complete here: this table is the index for the whole surface.

SectionOperations
account-groups6
account-settings6
accounts50
ad-targeting5
ads113
analytics24
apikeys3
audit-log1
auth9
billing3
blogs10
broadcasts10
comment-automations6
comments11
connect45
contacts9
custom-fields5
discord23
gmb-attributes3
gmb-media3
gmb-place-actions4
gmb-reviews5
gmb-services2
gmb-verifications4
health1
inbox15
inbox-analytics7
incoming1
instagram4
invites1
logs1
media5
mentions2
messages1
phone-numbers13
platforms1
posts17
profiles6
queue6
queues5
reddit2
reviews3
sequences10
sms13
status1
team7
twitter-engagement8
usage2
users2
validate5
webhooks12
whatsapp40
whatsapp-calling16
whatsapp-flows13
whatsapp-phone-numbers16
whatsapp-sandbox3
workflows14

Total: 613

account-groups

OperationMethodPathSummary
listAccountGroupsGET/v1/account-groupsList account groups
createAccountGroupPOST/v1/account-groupsCreate an account group
updateAccountGroupPUT/v1/account-groups/{groupId}Update an account group
patchAccountGroupPATCH/v1/account-groups/{groupId}Update an account group (legacy PATCH)
deleteAccountGroupDELETE/v1/account-groups/{groupId}Delete an account group
getAccountGroupGET/v1/account-groups/{id}Retrieve an account group

listAccountGroupsGET /v1/account-groups

Parameters:

  • sort (query)
  • cursor (query)
  • limit (query)

createAccountGroupPOST /v1/account-groups

Request body (CreateAccountGroupDto): name (string, required), accountIds (array of string, required), profileId (string)

updateAccountGroupPUT /v1/account-groups/{groupId}

Parameters:

  • groupId (path, required)

Request body (UpdateAccountGroupDto): name (string), accountIds (array of string)

patchAccountGroupPATCH /v1/account-groups/{groupId}

Parameters:

  • groupId (path, required)

Request body (UpdateAccountGroupDto): name (string), accountIds (array of string)

deleteAccountGroupDELETE /v1/account-groups/{groupId}

Parameters:

  • groupId (path, required)

getAccountGroupGET /v1/account-groups/{id}

Parameters:

  • id (path, required)

account-settings

OperationMethodPathSummary
getInstagramIceBreakersGET/v1/accounts/{accountId}/instagram-ice-breakersGet IG ice breakers
setInstagramIceBreakersPUT/v1/accounts/{accountId}/instagram-ice-breakersSet IG ice breakers
deleteInstagramIceBreakersDELETE/v1/accounts/{accountId}/instagram-ice-breakersDelete IG ice breakers
getMessengerMenuGET/v1/accounts/{accountId}/messenger-menuGet Messenger menu
setMessengerMenuPUT/v1/accounts/{accountId}/messenger-menuSet Messenger menu
deleteMessengerMenuDELETE/v1/accounts/{accountId}/messenger-menuDelete Messenger menu

getInstagramIceBreakersGET /v1/accounts/{accountId}/instagram-ice-breakers

Get the ice breaker configuration for an Instagram account.

Parameters:

  • accountId (path, required)

setInstagramIceBreakersPUT /v1/accounts/{accountId}/instagram-ice-breakers

Set ice breakers for an Instagram account. Max 4 ice breakers, question max 80 chars.

Parameters:

  • accountId (path, required)

Request body (SetInstagramIceBreakersDto): ice_breakers (array of InstagramIceBreakerDto, required)

deleteInstagramIceBreakersDELETE /v1/accounts/{accountId}/instagram-ice-breakers

Removes the ice breaker questions from an Instagram account's Messenger experience.

Parameters:

  • accountId (path, required)

getMessengerMenuGET /v1/accounts/{accountId}/messenger-menu

Get the Messenger persistent menu for a Facebook Page account.

Parameters:

  • accountId (path, required)

setMessengerMenuPUT /v1/accounts/{accountId}/messenger-menu

Replace the Messenger persistent menu of a Facebook Page. Max 3 top-level items, max 5 nested items.

Parameters:

  • accountId (path, required)

Request body (SetFacebookMessengerMenuDto): persistent_menu (array of FacebookMessengerMenuItemDto, required)

deleteMessengerMenuDELETE /v1/accounts/{accountId}/messenger-menu

Removes the persistent menu from a Facebook Page's Messenger experience.

Parameters:

  • accountId (path, required)

accounts

OperationMethodPathSummary
listAccountsGET/v1/accountsList connected accounts
updateAccountPUT/v1/accounts/{accountId}Update an account identity or X capabilities
moveAccountToProfilePATCH/v1/accounts/{accountId}Move an account to another profile
deleteAccountDELETE/v1/accounts/{accountId}Disconnect an account
getBlueskySettingsGET/v1/accounts/{accountId}/bluesky-settingsGet Bluesky account settings
updateBlueskySettingsPATCH/v1/accounts/{accountId}/bluesky-settingsUpdate Bluesky account settings
getDiscordChannelsGET/v1/accounts/{accountId}/discord-channelsList writable Discord guild channels
getInstagramFollowStatusGET/v1/accounts/{accountId}/follow-status/{userId}Check whether an Instagram user follows the account
getGoogleBusinessFoodMenusGET/v1/accounts/{accountId}/gmb-food-menusGet Google Business food menus
updateGoogleBusinessFoodMenusPUT/v1/accounts/{accountId}/gmb-food-menusUpdate Google Business food menus
getGoogleBusinessLocationDetailsGET/v1/accounts/{accountId}/gmb-location-detailsGet Google Business location details
updateGoogleBusinessLocationDetailsPUT/v1/accounts/{accountId}/gmb-location-detailsUpdate Google Business location details
getGoogleBusinessAccountPerformanceLegacyGET/v1/accounts/{accountId}/google-business-performanceRead Google Business daily performance metrics
listGoogleBusinessPostsGET/v1/accounts/{accountId}/google-business-postsList Google Business local posts
getGoogleBusinessPostGET/v1/accounts/{accountId}/google-business-posts/{postId}Get a Google Business local post
listGoogleBusinessReviewsGET/v1/accounts/{accountId}/google-business-reviewsList Google Business reviews
getGoogleBusinessReviewNativeGET/v1/accounts/{accountId}/google-business-reviews/{reviewId}Get a Google Business review
upsertGoogleBusinessReviewReplyLegacyPUT/v1/accounts/{accountId}/google-business-reviews/{reviewId}/replyCreate or replace a Google Business review reply
deleteGoogleBusinessReviewReplyLegacyDELETE/v1/accounts/{accountId}/google-business-reviews/{reviewId}/replyDelete a Google Business review reply
listGoogleBusinessSearchKeywordsGET/v1/accounts/{accountId}/google-business-search-keywordsList Google Business monthly search keywords
getAccountHealthGET/v1/accounts/{accountId}/healthRetrieve the stored health record for an account
getInstagramPublishingLimitGET/v1/accounts/{accountId}/instagram/publishing-limitGet the current Instagram publishing limit
listPinterestBoardSectionsGET/v1/accounts/{accountId}/pinterest-board-sectionsList sections of the selected Pinterest board
getPinterestBoardsGET/v1/accounts/{accountId}/pinterest-boardsList Pinterest boards
updatePinterestBoardsPUT/v1/accounts/{accountId}/pinterest-boardsSet the default Pinterest board
createPinterestBoardPOST/v1/accounts/{accountId}/pinterest-boardsCreate a Pinterest board
getRedditFlairsGET/v1/accounts/{accountId}/reddit-flairsList selectable Reddit link flairs
getRedditSubredditsGET/v1/accounts/{accountId}/reddit-subredditsDiscover Reddit subreddits
updateRedditSubredditsPUT/v1/accounts/{accountId}/reddit-subredditsSet the Reddit default subreddit
listSlackMembersGET/v1/accounts/{accountId}/slack-membersList Slack workspace members
getSlackSettingsGET/v1/accounts/{accountId}/slack-settingsGet Slack account settings
updateSlackSettingsPATCH/v1/accounts/{accountId}/slack-settingsUpdate Slack account settings
getTelegramCommandsGET/v1/accounts/{accountId}/telegram-commandsGet Telegram bot commands
setTelegramCommandsPUT/v1/accounts/{accountId}/telegram-commandsSet Telegram bot commands
deleteTelegramCommandsDELETE/v1/accounts/{accountId}/telegram-commandsDelete Telegram bot commands
listThreadsPostConversationGET/v1/accounts/{accountId}/threads-posts/{postId}/conversationList a flattened Threads reply conversation
listThreadsPendingRepliesGET/v1/accounts/{accountId}/threads-posts/{postId}/pending-repliesList pending or ignored replies awaiting Threads approval
listThreadsPostRepliesGET/v1/accounts/{accountId}/threads-posts/{postId}/repliesList immediate replies to a Threads post or reply
listThreadsAuthoredRepliesGET/v1/accounts/{accountId}/threads-repliesList replies authored by the connected Threads profile
decideThreadsPendingReplyPOST/v1/accounts/{accountId}/threads-replies/{replyId}/approvalApprove or ignore a pending Threads reply
hideThreadsReplyPUT/v1/accounts/{accountId}/threads-replies/{replyId}/hiddenHide a Threads reply
unhideThreadsReplyDELETE/v1/accounts/{accountId}/threads-replies/{replyId}/hiddenUnhide a Threads reply
getTikTokCreatorInfoGET/v1/accounts/{accountId}/tiktok/creator-infoGet current TikTok creator posting constraints
getYoutubeCaptionsGET/v1/accounts/{accountId}/youtube-captionsGet a YouTube video transcript
getYoutubePlaylistsGET/v1/accounts/{accountId}/youtube-playlistsList playlists owned by a connected YouTube channel
updateYoutubeDefaultPlaylistPUT/v1/accounts/{accountId}/youtube-playlistsSet the default YouTube playlist preference
getAccountGET/v1/accounts/{id}Retrieve an account
disconnectAccountPOST/v1/accounts/{id}/actions/disconnectDisconnect an account
getFollowerStatsGET/v1/accounts/follower-statsGet stored follower statistics
getAllAccountsHealthGET/v1/accounts/healthSummarise stored health for every account in scope

listAccountsGET /v1/accounts

Cursor pagination remains available to existing clients. Supplying page and limit together selects 1-based offset pagination and adds total/page metadata.

Parameters:

  • status (query)
  • platform (query)
  • profileId (query)
  • sort (query)
  • page (query) — 1-based offset page. Requires an explicit limit and cannot be combined with cursor.
  • cursor (query)
  • limit (query)

updateAccountPUT /v1/accounts/{accountId}

Updates durable displayName/username fields and, for X accounts only, opt-in background analytics or inbox reads.

Parameters:

  • accountId (path, required)

Request body (UpdateAccountDto): displayName (string), username (string), xCapabilities (UpdateAccountXCapabilitiesDto)

moveAccountToProfilePATCH /v1/accounts/{accountId}

Parameters:

  • accountId (path, required)

Request body (MoveAccountToProfileDto): profileId (string, required)

deleteAccountDELETE /v1/accounts/{accountId}

Parameters:

  • accountId (path, required)

getBlueskySettingsGET /v1/accounts/{accountId}/bluesky-settings

Parameters:

  • accountId (path, required)

updateBlueskySettingsPATCH /v1/accounts/{accountId}/bluesky-settings

Parameters:

  • accountId (path, required)

Request body (UpdateBlueskySettingsDto): defaultLangs (array of string, required)

getDiscordChannelsGET /v1/accounts/{accountId}/discord-channels

Lists writable text, announcement and forum channels in the connected Discord guild.

Parameters:

  • accountId (path, required)

getInstagramFollowStatusGET /v1/accounts/{accountId}/follow-status/{userId}

Resolve a messaging-scoped Instagram user profile and follow relationship. Unresolvable users return 200 with null relationship fields and an unavailableReason.

Parameters:

  • accountId (path, required)
  • userId (path, required)
  • refresh (query) — Bypass the brief cached result and query Meta again.

getGoogleBusinessFoodMenusGET /v1/accounts/{accountId}/gmb-food-menus

Returns the complete FoodMenus resource for the selected or explicitly targeted accessible location.

TEST MODE ONLY TODAY. No Google Business adapter is composed into the live registry, so a Google Business account cannot be connected in live mode and this operation fails closed there rather than returning partial data. Setting a live-mode environment flag does not change this. Use test mode for this vertical.

Parameters:

  • accountId (path, required)
  • locationId (query) — Optional accessible Google location id; defaults to the selected location.

updateGoogleBusinessFoodMenusPUT /v1/accounts/{accountId}/gmb-food-menus

Replaces the atomic menus collection with an exact desired state. An empty menus array clears the resource.

TEST MODE ONLY TODAY. No Google Business adapter is composed into the live registry, so a Google Business account cannot be connected in live mode and this operation fails closed there rather than returning partial data. Setting a live-mode environment flag does not change this. Use test mode for this vertical.

Parameters:

  • accountId (path, required)
  • locationId (query) — Optional accessible Google location id; defaults to the selected location.

Request body (GoogleBusinessFoodMenusUpdateInputDto): menus (array of GoogleBusinessFoodMenuDto, required), updateMask (enum: menus)

getGoogleBusinessLocationDetailsGET /v1/accounts/{accountId}/gmb-location-details

Returns the selected location with Zernio-compatible readMask filtering. A supplied locationId targets another location accessible to the same Google account.

TEST MODE ONLY TODAY. No Google Business adapter is composed into the live registry, so a Google Business account cannot be connected in live mode and this operation fails closed there rather than returning partial data. Setting a live-mode environment flag does not change this. Use test mode for this vertical.

Parameters:

  • accountId (path, required)
  • readMask (query)
  • locationId (query) — Optional accessible Google location id; defaults to the selected location.

updateGoogleBusinessLocationDetailsPUT /v1/accounts/{accountId}/gmb-location-details

Writes an exact desired state for the seven safe Zernio location fields. Omitting a field named by updateMask clears it; fields outside the mask are rejected.

TEST MODE ONLY TODAY. No Google Business adapter is composed into the live registry, so a Google Business account cannot be connected in live mode and this operation fails closed there rather than returning partial data. Setting a live-mode environment flag does not change this. Use test mode for this vertical.

Parameters:

  • accountId (path, required)
  • locationId (query) — Optional accessible Google location id; defaults to the selected location.

Request body (GoogleBusinessLocationUpdateInputDto): updateMask (string, required), regularHours (GoogleBusinessLocationHoursDto), specialHours (GoogleBusinessLocationSpecialHoursDto), profile (GoogleBusinessLocationProfileDto), websiteUri (string), phoneNumbers (GoogleBusinessLocationPhoneNumbersDto), categories (GoogleBusinessLocationPatchCategoriesDto), serviceItems (array of GoogleBusinessLocationServiceItemDto)

getGoogleBusinessAccountPerformanceLegacyGET /v1/accounts/{accountId}/google-business-performance

Reads exact int64 daily values for the selected location. The date range defaults to the latest 30 days and is limited to the latest 18 calendar months.

TEST MODE ONLY TODAY. No Google Business adapter is composed into the live registry, so a Google Business account cannot be connected in live mode and this operation fails closed there rather than returning partial data. Setting a live-mode environment flag does not change this. Use test mode for this vertical.

Parameters:

  • accountId (path, required)
  • endDate (query)
  • startDate (query)
  • metrics (query)

listGoogleBusinessPostsGET /v1/accounts/{accountId}/google-business-posts

Pages provider LocalPosts for the exact connected location. The opaque cursor is bound to the account identity, credential revision, mode, and page size.

TEST MODE ONLY TODAY. No Google Business adapter is composed into the live registry, so a Google Business account cannot be connected in live mode and this operation fails closed there rather than returning partial data. Setting a live-mode environment flag does not change this. Use test mode for this vertical.

Parameters:

  • accountId (path, required)
  • cursor (query)
  • limit (query)

getGoogleBusinessPostGET /v1/accounts/{accountId}/google-business-posts/{postId}

TEST MODE ONLY TODAY. No Google Business adapter is composed into the live registry, so a Google Business account cannot be connected in live mode and this operation fails closed there rather than returning partial data. Setting a live-mode environment flag does not change this. Use test mode for this vertical.

Parameters:

  • accountId (path, required)
  • postId (path, required)

listGoogleBusinessReviewsGET /v1/accounts/{accountId}/google-business-reviews

Pages provider reviews for the exact connected location. The opaque cursor is also bound to orderBy and page size.

TEST MODE ONLY TODAY. No Google Business adapter is composed into the live registry, so a Google Business account cannot be connected in live mode and this operation fails closed there rather than returning partial data. Setting a live-mode environment flag does not change this. Use test mode for this vertical.

Parameters:

  • accountId (path, required)
  • orderBy (query)
  • cursor (query)
  • limit (query)

getGoogleBusinessReviewNativeGET /v1/accounts/{accountId}/google-business-reviews/{reviewId}

TEST MODE ONLY TODAY. No Google Business adapter is composed into the live registry, so a Google Business account cannot be connected in live mode and this operation fails closed there rather than returning partial data. Setting a live-mode environment flag does not change this. Use test mode for this vertical.

Parameters:

  • accountId (path, required)
  • reviewId (path, required)

upsertGoogleBusinessReviewReplyLegacyPUT /v1/accounts/{accountId}/google-business-reviews/{reviewId}/reply

Sets the exact desired owner reply. Replaying the same PUT body is safe.

TEST MODE ONLY TODAY. No Google Business adapter is composed into the live registry, so a Google Business account cannot be connected in live mode and this operation fails closed there rather than returning partial data. Setting a live-mode environment flag does not change this. Use test mode for this vertical.

Parameters:

  • accountId (path, required)
  • reviewId (path, required)

Request body (GoogleBusinessReviewReplyInputDto): comment (string, required)

deleteGoogleBusinessReviewReplyLegacyDELETE /v1/accounts/{accountId}/google-business-reviews/{reviewId}/reply

Deletes the current owner reply. Replaying this DELETE is safe.

TEST MODE ONLY TODAY. No Google Business adapter is composed into the live registry, so a Google Business account cannot be connected in live mode and this operation fails closed there rather than returning partial data. Setting a live-mode environment flag does not change this. Use test mode for this vertical.

Parameters:

  • accountId (path, required)
  • reviewId (path, required)

listGoogleBusinessSearchKeywordsGET /v1/accounts/{accountId}/google-business-search-keywords

Pages exact and privacy-thresholded keyword impressions for the selected location. The month range defaults to three months ago through the current month.

TEST MODE ONLY TODAY. No Google Business adapter is composed into the live registry, so a Google Business account cannot be connected in live mode and this operation fails closed there rather than returning partial data. Setting a live-mode environment flag does not change this. Use test mode for this vertical.

Parameters:

  • accountId (path, required)
  • cursor (query)
  • limit (query)
  • endMonth (query)
  • startMonth (query)

getAccountHealthGET /v1/accounts/{accountId}/health

Reads the health row already stored for this account. zhyper does NOT contact the platform while serving this request, so the answer is exactly as old as lastCheckedAt says; nextCheckAt is when the background account-maintenance job is due to refresh it. An account that has no stored health row answers 404 account_not_found rather than an empty health object, so a 404 here does not prove the account is gone. The response carries connection state only - there is no issues or recommendations list on this endpoint; GET /v1/accounts/health is the one that carries those.

Parameters:

  • accountId (path, required)

getInstagramPublishingLimitGET /v1/accounts/{accountId}/instagram/publishing-limit

Reads the provider-owned rolling publishing quota for one active Instagram account.

Parameters:

  • accountId (path, required)

listPinterestBoardSectionsGET /v1/accounts/{accountId}/pinterest-board-sections

The account identifies its selected board; callers cannot override it with a boardId query parameter. Provider bookmarks are wrapped in an opaque, account- and credential-revision-bound cursor.

Parameters:

  • accountId (path, required)
  • cursor (query) — Opaque cursor returned by this exact account endpoint.
  • limit (query)

getPinterestBoardsGET /v1/accounts/{accountId}/pinterest-boards

Returns the boards available for a connected Pinterest account. Use this to get a board ID when creating a Pinterest post.

Parameters:

  • accountId (path, required)

updatePinterestBoardsPUT /v1/accounts/{accountId}/pinterest-boards

Verifies the selected board against the connected Pinterest user and stores its canonical provider name as an explicit durable preference.

Parameters:

  • accountId (path, required)

Request body (SetPinterestDefaultBoardDto): defaultBoardId (string, required), defaultBoardName (string)

createPinterestBoardPOST /v1/accounts/{accountId}/pinterest-boards

Creates one board for the Pinterest user connected through accountId. The returned board.id can be used immediately as a Pinterest post options.boardId without reconnecting or changing the account default. Pinterest does not expose an idempotency key for this write; clients must not automatically replay an ambiguous response.

Parameters:

  • accountId (path, required)

Request body (PinterestCreateBoardDto): name (string, required), description (string), privacy (PinterestBoardPrivacy)

getRedditFlairsGET /v1/accounts/{accountId}/reddit-flairs

Returns the caller-selectable link-flair templates for one subreddit using the connected Reddit account.

Parameters:

  • accountId (path, required)
  • subreddit (query, required)

getRedditSubredditsGET /v1/accounts/{accountId}/reddit-subreddits

With query, preserves bounded autocomplete suggestions. Without query, pages the connected account's official contributor listing and includes its explicit durable default.

Parameters:

  • accountId (path, required)
  • query (query)
  • cursor (query) — Opaque no-query contributor discovery cursor.
  • limit (query) — No-query contributor discovery only.

updateRedditSubredditsPUT /v1/accounts/{accountId}/reddit-subreddits

Stores an explicit account- and provider-identity-bound default. Retries with the same value are idempotent.

Parameters:

  • accountId (path, required)

Request body (SetRedditDefaultSubredditDto): defaultSubreddit (string, required)

listSlackMembersGET /v1/accounts/{accountId}/slack-members

Lists human members of the connected Slack workspace that can receive a DM. Omit query for an unfiltered list. Empty local matches can still carry a continuation cursor while later Slack pages remain.

Parameters:

  • accountId (path, required)
  • cursor (query) — Opaque cursor returned by this endpoint; never a raw Slack cursor.
  • limit (query)
  • query (query) — Optional case-insensitive display-name and handle filter.

getSlackSettingsGET /v1/accounts/{accountId}/slack-settings

Returns the connected Slack channel details and default per-message identity. The app's own Slack profile remains workspace-global.

Parameters:

  • accountId (path, required)

updateSlackSettingsPATCH /v1/accounts/{accountId}/slack-settings

Sets or clears the default message identity. Empty strings clear fields; per-post username/iconUrl values override these defaults.

Parameters:

  • accountId (path, required)

Request body (UpdateSlackSettingsDto): defaultUsername (string), defaultIconUrl (string)

getTelegramCommandsGET /v1/accounts/{accountId}/telegram-commands

Returns the default, language-neutral commands configured for the bot. Commands are bot-global even though the path selects one connected chat account.

Parameters:

  • accountId (path, required)

setTelegramCommandsPUT /v1/accounts/{accountId}/telegram-commands

Replaces the bot-global default command list. Replaying the exact request is safe.

Parameters:

  • accountId (path, required)

Request body (SetTelegramCommandsDto): commands (array of TelegramCommandDto, required)

deleteTelegramCommandsDELETE /v1/accounts/{accountId}/telegram-commands

Clears the bot-global default command list. Replaying the request is safe.

Parameters:

  • accountId (path, required)

listThreadsPostConversationGET /v1/accounts/{accountId}/threads-posts/{postId}/conversation

Returns all visible top-level and nested replies. Meta accepts only a root-level Thread ID on this surface.

Parameters:

  • accountId (path, required)
  • postId (path, required)
  • reverse (query)
  • cursor (query)
  • limit (query)

listThreadsPendingRepliesGET /v1/accounts/{accountId}/threads-posts/{postId}/pending-replies

Pages Meta pending_replies for an owned Thread created with reply approvals enabled. Omit approvalStatus to include both pending and ignored replies.

Parameters:

  • accountId (path, required)
  • postId (path, required)
  • approvalStatus (query)
  • reverse (query)
  • cursor (query)
  • limit (query)

listThreadsPostRepliesGET /v1/accounts/{accountId}/threads-posts/{postId}/replies

Returns only immediate child replies. Use hasReplies and call this endpoint with a reply ID to traverse deeper levels.

Parameters:

  • accountId (path, required)
  • postId (path, required)
  • reverse (query)
  • cursor (query)
  • limit (query)

listThreadsAuthoredRepliesGET /v1/accounts/{accountId}/threads-replies

Pages Meta /me/replies for the exact connected profile. This is distinct from replies received under one owned Thread.

Parameters:

  • accountId (path, required)
  • cursor (query)
  • limit (query)

decideThreadsPendingReplyPOST /v1/accounts/{accountId}/threads-replies/{replyId}/approval

Dispatches Meta manage_pending_reply once. Meta does not document idempotency, so clients must not automatically replay an ambiguous response. Ignored replies remain in the queue and can later be approved.

Parameters:

  • accountId (path, required)
  • replyId (path, required)

Request body (ThreadsPendingReplyDecisionDto): decision (enum: approve, ignore, required)

hideThreadsReplyPUT /v1/accounts/{accountId}/threads-replies/{replyId}/hidden

Hides a top-level reply under a Thread owned by the connected profile. Nested replies follow the top-level state. Replaying this exact PUT is safe.

Parameters:

  • accountId (path, required)
  • replyId (path, required)

unhideThreadsReplyDELETE /v1/accounts/{accountId}/threads-replies/{replyId}/hidden

Restores a previously hidden top-level reply and its nested replies. Replaying this exact DELETE is safe.

Parameters:

  • accountId (path, required)
  • replyId (path, required)

getTikTokCreatorInfoGET /v1/accounts/{accountId}/tiktok/creator-info

Reads the provider-owned visibility, duration and interaction constraints for one active TikTok account.

Parameters:

  • accountId (path, required)
  • mediaType (query) — Shapes the applicable interaction settings: photo responses omit duet, stitch and video-duration controls.

getYoutubeCaptionsGET /v1/accounts/{accountId}/youtube-captions

Returns an owned video caption track as plain text plus either timed cues or SRT. Uploaded tracks take precedence over auto-generated tracks. Provider track metadata is revalidated on every request; refresh=true also re-downloads the transcript.

Parameters:

  • accountId (path, required)
  • refresh (query)
  • format (query)
  • language (query)
  • videoId (query, required)

getYoutubePlaylistsGET /v1/accounts/{accountId}/youtube-playlists

Returns the complete bounded directory of playlists owned by one active YouTube account and its explicit client-side default preference.

Parameters:

  • accountId (path, required)

updateYoutubeDefaultPlaylistPUT /v1/accounts/{accountId}/youtube-playlists

Stores an explicit account- and channel-identity-bound client preference. Publishing does not apply this default automatically.

Parameters:

  • accountId (path, required)

Request body (SetYoutubeDefaultPlaylistDto): defaultPlaylistId (string, required), defaultPlaylistName (string)

getAccountGET /v1/accounts/{id}

Parameters:

  • id (path, required)

disconnectAccountPOST /v1/accounts/{id}/actions/disconnect

Parameters:

  • id (path, required)

getFollowerStatsGET /v1/accounts/follower-stats

Returns only real daily follower observations persisted by account maintenance. Accounts without observations in the requested period are omitted.

Parameters:

  • granularity (query)
  • toDate (query)
  • fromDate (query)
  • profileId (query)
  • accountIds (query) — Comma-separated account IDs.

getAllAccountsHealthGET /v1/accounts/health

The summary covers the WHOLE scope, never the returned page; the scope itself is echoed in scope so a filtered total is never mistaken for the fleet. Only limit, cursor, profileId and platform are accepted — any other query parameter (status included) is rejected with 400 unknown_parameter instead of being ignored. Filter by connection status with GET /v1/accounts.

Parameters:

  • platform (query)
  • profileId (query)
  • cursor (query)
  • limit (query)

ad-targeting

OperationMethodPathSummary
searchAdInterestsGET/v1/ads/interestsSearch Meta interests (deprecated alias)
getLinkedInBidPricingPOST/v1/ads/targeting/bid-pricingSuggested LinkedIn bid and budget bounds
estimateAdReachPOST/v1/ads/targeting/reach-estimateEstimate how many people a targeting spec would reach
searchAdTargetingGET/v1/ads/targeting/searchSearch the platform for targeting entries
getLinkedInSupplyForecastPOST/v1/ads/targeting/supply-forecastForecast LinkedIn ad supply

searchAdInterestsGET /v1/ads/interests

Backward-compatible alias for targeting/search?dimension=interest. It discovers the connected Meta ad accounts and returns the legacy { interests } envelope.

Parameters:

  • accountId (query, required)
  • q (query, required)

getLinkedInBidPricingPOST /v1/ads/targeting/bid-pricing

Request body (LinkedInBidPricingRequestDto): accountId (string, required), adAccountId (string, required), spec (AdTargetingSpecDto, required), campaignType (enum: TEXT_AD, SPONSORED_UPDATES, SPONSORED_INMAILS), bidType (enum: CPM, CPC, CPV), matchType (enum: EXACT, AUDIENCE_EXPANDED), currency (string), objectiveType (string), optimizationTargetType (string), dailyBudget (number)

estimateAdReachPOST /v1/ads/targeting/reach-estimate

Returns the platform's own estimate of the audience a TargetingSpec would reach, WITHOUT creating anything and without spending budget.

IT IS A RANGE, NOT A NUMBER. lower and upper bound a MONTHLY ACTIVE audience; the platform deliberately does not commit to a single figure, so this API does not average them into one.

optimizationGoal IS REQUIRED HERE, and that deviates from the published contract, which calls it optional and says to omit it for the platform default. THE PLATFORM HAS NO SUCH DEFAULT -- the parameter is mandatory on its edge -- and choosing one on your behalf would silently change the audience size you are shown.

daily AND currency ARE ALWAYS null. The platform removed the services behind the daily figure and states no replacement exists, and nothing in this request or response is monetary, so there is nothing to denominate. Both fields are kept so a client generated from the contract still compiles.

CHECK estimateReady BEFORE TRUSTING THE BOUNDS: the platform answers 200 with estimateReady: false while it is still computing a very new audience, and the bounds may be null in that state.

Only served for Meta today. Another platform answers 501 -- the request is valid, the capability is what is missing.

Request body (EstimateAdReachDto): accountId (string, required), adAccountId (string, required), spec (AdTargetingSpecDto, required), optimizationGoal (string, required), platform (string)

searchAdTargetingGET /v1/ads/targeting/search

Resolves free text into the opaque ids a TargetingSpec needs: locations, interests, behaviors and income tiers, read live from the platform.

ONE ENDPOINT, TWO PLATFORM EDGES. dimension=geo searches the platform's location catalogue, which is NOT scoped to an ad account; the other dimensions search inside one ad account. That split is why adAccountId is required for everything except geo.

adAccountId IS NOT IN THE PUBLISHED CONTRACT FOR THIS OPERATION and is required here anyway for non-geo searches. It cannot be derived from accountId: one social account can reach many ad accounts. Get it from GET /v1/ads/accounts. The sibling reach-estimate operation requires it for the same reason.

THERE IS NO PAGING. Raise limit (up to 100) or narrow q; there is no cursor to follow.

Only served for Meta today. Another platform answers 501 -- the request is valid, the capability is what is missing.

Parameters:

  • platform (query) — Defaults to 'facebook'. Only Meta is served today; anything else is a 501 rather than a silent empty answer.
  • limit (query) — Maximum results to return. There is no paging, so this is the only control over how much comes back.
  • adAccountId (query) — Ad account to search inside, in act_<id> form. REQUIRED for every dimension except geo, because the platform serves interest, behavior and income search under an ad account. IGNORED for geo: that edge is not account scoped, so the value is dropped rather than rejected. Get the id from GET /v1/ads/accounts.
  • countryCode (query) — ISO 3166-1 alpha-2 country code to restrict results to. Two LETTERS: two digits are the right length and still not a country.
  • geoType (query) — Narrows a geo search to one kind of location. ONLY USED WHEN dimension=geo -- sending it with any other dimension is a 400 rather than a silently ignored field, because ignoring it would let you believe you had narrowed a search that was never narrowed. OMITTING IT SEARCHES ALL LOCATION TYPES. The contract documents city as the default; this API does not substitute one, because a default it applied itself would silently override the platform's own.
  • dimension (query) — What kind of targeting entry to search for. Defaults to interest for backward compatibility with the deprecated /v1/ads/interests alias. Anything other than geo requires adAccountId.
  • q (query, required) — Search query. For geo, the locality name only (no region or country suffix) -- send Paris, not Paris, France. It is NOT trimmed: multi-word localities are legitimate.
  • accountId (query, required) — Social account id (a connected account on the target ad platform). It is what resolves the platform token.

getLinkedInSupplyForecastPOST /v1/ads/targeting/supply-forecast

Request body (LinkedInSupplyForecastRequestDto): accountId (string, required), adAccountId (string, required), spec (AdTargetingSpecDto, required), campaignType (enum: SPONSORED_UPDATES, SPONSORED_INMAILS, DYNAMIC), timeRangeStart (number, required), timeRangeEnd (number, required), objectiveType (string), optimizationTarget (string), dailyBudget (number), totalBudget (number), currency (string), competingBid (LinkedInCompetingBidDto), enableAudienceNetwork (boolean), enableAudienceExpansion (boolean), connectedTelevisionOnly (boolean)

ads

OperationMethodPathSummary
listConversionDestinationsGET/v1/accounts/{accountId}/conversion-destinationsList conversion destinations
createConversionDestinationPOST/v1/accounts/{accountId}/conversion-destinationsCreate a conversion destination
getConversionDestinationGET/v1/accounts/{accountId}/conversion-destinations/{destinationId}Get a conversion destination
updateConversionDestinationPATCH/v1/accounts/{accountId}/conversion-destinations/{destinationId}Update a conversion destination
deleteConversionDestinationDELETE/v1/accounts/{accountId}/conversion-destinations/{destinationId}Delete a conversion destination
listConversionAssociationsGET/v1/accounts/{accountId}/conversion-destinations/{destinationId}/associationsList associated campaigns
addConversionAssociationsPOST/v1/accounts/{accountId}/conversion-destinations/{destinationId}/associationsAssociate campaigns
removeConversionAssociationsDELETE/v1/accounts/{accountId}/conversion-destinations/{destinationId}/associationsRemove associated campaigns
getConversionMetricsGET/v1/accounts/{accountId}/conversion-destinations/{destinationId}/metricsGet attribution metrics
listCustomConversionsGET/v1/accounts/{accountId}/custom-conversionsList the custom conversions on an ad account
createCustomConversionPOST/v1/accounts/{accountId}/custom-conversionsCreate a custom conversion, or reuse the matching one
listTrackingTagsGET/v1/accounts/{accountId}/tracking-tagsList the tracking tags on an ad account
createTrackingTagPOST/v1/accounts/{accountId}/tracking-tagsCreate a tracking tag
getTrackingTagGET/v1/accounts/{accountId}/tracking-tags/{tagId}Read one tracking tag
updateTrackingTagPATCH/v1/accounts/{accountId}/tracking-tags/{tagId}Update a tracking tag
listTrackingTagSharedAccountsGET/v1/accounts/{accountId}/tracking-tags/{tagId}/shared-accountsList the ad accounts a tracking tag is shared with
addTrackingTagSharedAccountPOST/v1/accounts/{accountId}/tracking-tags/{tagId}/shared-accountsShare a tracking tag with an ad account
removeTrackingTagSharedAccountDELETE/v1/accounts/{accountId}/tracking-tags/{tagId}/shared-accountsStop sharing a tracking tag with an ad account
getTrackingTagStatsGET/v1/accounts/{accountId}/tracking-tags/{tagId}/statsRead tracking tag activity statistics
listAdsGET/v1/adsList ad documents with metrics over a date range
updateAdPUT/v1/ads/{adId}Update a single ad
deleteAdDELETE/v1/ads/{adId}Delete a single ad -- permanently
getAdGET/v1/ads/{adId}Get one ad by any identifier dialect it is indexed under
getAdAnalyticsGET/v1/ads/{adId}/analyticsOne ad's performance: totals plus a day-by-day series
getAdCommentsGET/v1/ads/{adId}/commentsRead comments on the post behind an ad
duplicateAdPOST/v1/ads/{adId}/duplicateDuplicate a single ad
getAdMediaGET/v1/ads/{adId}/mediaResolve every media asset used by an ad
getAdPreviewsGET/v1/ads/{adId}/previewRender an existing ad as it appears in a placement
updateAdStatusPUT/v1/ads/{adId}/statusPause or resume a single ad
getAdTrackingTagsGET/v1/ads/{adId}/tracking-tagsRead an ad's click-URL tracking parameters
updateAdTrackingTagsPATCH/v1/ads/{adId}/tracking-tagsSet an ad's click-URL tracking parameters
listAdAccountsGET/v1/ads/accountsList the ad accounts reachable from a connected account
updateAdAccountPATCH/v1/ads/accountsSet the default DSA beneficiary and payor on an ad account
getAdAccountFinanceGET/v1/ads/accounts/financeRead the balance, spend and funding source of an ad account
getAdsActivityLogGET/v1/ads/activityList the change log of an ad account
updateAdSetPUT/v1/ads/ad-sets/{adSetId}Update an ad set
deleteAdSetDELETE/v1/ads/ad-sets/{adSetId}Delete an ad set
getAdSetDetailsGET/v1/ads/ad-sets/{adSetId}Live ad-set details incl. learning phase
duplicateAdSetPOST/v1/ads/ad-sets/{adSetId}/duplicateDuplicate an ad set
updateAdSetStatusPUT/v1/ads/ad-sets/{adSetId}/statusPause or resume a single ad set
listAdAudiencesGET/v1/ads/audiencesList custom audiences in an ad account
createAdAudiencePOST/v1/ads/audiencesCreate a custom audience
getAdAudienceGET/v1/ads/audiences/{audienceId}Read a custom audience
updateAdAudiencePUT/v1/ads/audiences/{audienceId}Rename or re-describe a custom audience
deleteAdAudienceDELETE/v1/ads/audiences/{audienceId}Delete a custom audience
replaceAdAudienceCompaniesPOST/v1/ads/audiences/{audienceId}/companiesReplace the companies in a LinkedIn audience
addUsersToAdAudiencePOST/v1/ads/audiences/{audienceId}/usersAdd members to a customer-list audience
removeAdAudienceUsersDELETE/v1/ads/audiences/{audienceId}/usersRemove members from a customer-list audience
boostPostPOST/v1/ads/boostPromote an existing Page post with a new campaign
listAdsBusinessCentersGET/v1/ads/business-centersList TikTok Business Centers
listMetaBusinessesGET/v1/ads/businessesList the Meta Business Manager portfolios the connected user belongs to
createCallAdPOST/v1/ads/callCreate an ad whose button places a phone call
listAdCampaignsGET/v1/ads/campaignsList ad campaigns as aggregations over ad documents
createAdCampaignPOST/v1/ads/campaignsCreate a standalone campaign (ODAX shell, no budget)
deleteAdCampaignDELETE/v1/ads/campaigns/{campaignId}Delete a campaign
updateAdCampaignPUT/v1/ads/campaigns/{campaignId}Rename a campaign
getCampaignAnalyticsGET/v1/ads/campaigns/{campaignId}/analyticsCampaign totals plus a day-by-day timeline
attachCampaignAssetsPOST/v1/ads/campaigns/{campaignId}/assetsAttach assets to a Google Search campaign
duplicateAdCampaignPOST/v1/ads/campaigns/{campaignId}/duplicateDuplicate a campaign
updateAdCampaignStatusPUT/v1/ads/campaigns/{campaignId}/statusPause or resume a campaign
bulkUpdateAdCampaignStatusPOST/v1/ads/campaigns/bulk-statusPause or resume many campaigns
listAdCatalogsGET/v1/ads/catalogsList the product catalogs an ad account can advertise from
listAdCatalogProductSetsGET/v1/ads/catalogs/{catalogId}/product-setsList the product sets in a product catalog
sendConversionsPOST/v1/ads/conversionsSend conversion events to Meta
adjustConversionsPOST/v1/ads/conversions/adjustmentsAdjust Google Ads conversions
getConversionsQualityGET/v1/ads/conversions/qualityRead how well your conversion events match real people
createStandaloneAdPOST/v1/ads/createCreate a campaign, ad set and ad in one call
listAdCreativesGET/v1/ads/creativesThe ad account's creative library
createAdCreativePOST/v1/ads/creativesCreate a standalone creative
getAdCreativeGET/v1/ads/creatives/{creativeId}One creative's details
updateAdCreativePUT/v1/ads/creatives/{creativeId}Rename a creative
deleteAdCreativeDELETE/v1/ads/creatives/{creativeId}Delete a creative from the library
createCtwaAdPOST/v1/ads/ctwaCreate click-to-WhatsApp ads
getDsaDefaultsGET/v1/ads/dsa-defaultsRead the DSA defaults set on an ad account
getDsaRecommendationsGET/v1/ads/dsa-recommendationsSuggest legal entity names for an ad account
listHighDemandPeriodsGET/v1/ads/high-demand-periodsList the scheduled high demand periods on a campaign or ad set
createHighDemandPeriodPOST/v1/ads/high-demand-periodsSchedule a high demand period on a campaign or ad set
listAdImagesGET/v1/ads/imagesAd image library
uploadAdImagePOST/v1/ads/imagesUpload an ad image from base64
queryAdInsightsGET/v1/ads/insightsFlexible live insights query
createAdInsightsReportPOST/v1/ads/insights/reportsSubmit an async insights report run
getAdInsightsReportGET/v1/ads/insights/reports/{reportRunId}Poll an async insights report run
listAdKeywordsGET/v1/ads/keywordsList Google Search keywords live
generateKeywordHistoricalMetricsPOST/v1/ads/keywords/historical-metricsGenerate Google keyword historical metrics
generateKeywordIdeasPOST/v1/ads/keywords/ideasGenerate Google Keyword Planner ideas
listAdLabelsGET/v1/ads/labelsList the ad labels in an ad account
listLeadFormsGET/v1/ads/lead-formsList lead forms
createLeadFormPOST/v1/ads/lead-formsCreate a lead form
getLeadFormGET/v1/ads/lead-forms/{formId}Get a lead form
archiveLeadFormDELETE/v1/ads/lead-forms/{formId}Archive a lead form
listFormLeadsGET/v1/ads/lead-forms/{formId}/leadsList submitted leads for one Meta form
createTestLeadPOST/v1/ads/lead-forms/{formId}/test-leadsCreate one Meta test lead
listLeadsGET/v1/ads/leadsList submitted Meta leads
listLocalServicesLeadsGET/v1/ads/local-services/leadsList Google Local Services Ads leads
listLocalServicesLeadConversationsGET/v1/ads/local-services/leads/{leadId}/conversationsList conversations for a Google Local Services lead
createMessagingAdPOST/v1/ads/messagingCreate ads that open a chat thread
generateAdPreviewsPOST/v1/ads/previewRender a creative as it would look, before any ad exists
createRfPredictionPOST/v1/ads/rf-predictionsCreate a Reach & Frequency prediction
getRfPredictionGET/v1/ads/rf-predictions/{predictionId}Read a Reach & Frequency prediction
cancelRfReservationDELETE/v1/ads/rf-predictions/{predictionId}Cancel a Reach & Frequency reservation
reserveRfPredictionPOST/v1/ads/rf-predictions/{predictionId}/reserveReserve a Reach & Frequency prediction
getAdsSearchTermsGET/v1/ads/search-termsRead Google Ads search terms live
listAdStudiesGET/v1/ads/studiesList the A/B studies in an ad account
getAdsTimelineGET/v1/ads/timelineGet daily account metrics
getAdTreeGET/v1/ads/treeCampaign > ad set > ad hierarchy with rolled-up metrics
listValueRuleSetsGET/v1/ads/value-rule-setsList value rule sets in an ad account
createValueRuleSetPOST/v1/ads/value-rule-setsCreate a value rule set
getValueRuleSetGET/v1/ads/value-rule-sets/{valueRuleSetId}Read a value rule set
updateValueRuleSetPUT/v1/ads/value-rule-sets/{valueRuleSetId}Replace a value rule set
deleteValueRuleSetDELETE/v1/ads/value-rule-sets/{valueRuleSetId}Delete a value rule set
uploadAdVideoPOST/v1/ads/videosUpload a video into a Meta ad account library
listAdVideosGET/v1/ads/videosList Meta ad videos
deleteAdVideoDELETE/v1/ads/videos/{videoId}Delete a video through the account-scoped Meta edge

listConversionDestinationsGET /v1/accounts/{accountId}/conversion-destinations

Lists the destinations you can send conversions to. For Meta a destination is a PIXEL, and destinations[].id is the id the shipped conversion surfaces already take -- GET /v1/ads/conversions/quality?destinationId=... above all. The same identifier is accepted by POST /v1/ads/conversions.

THE LIST SPANS EVERY AD ACCOUNT THE CONNECTED ACCOUNT REACHES, because this operation takes no scoping parameter. Under the hood that is one ad-account discovery call plus one call per ad account, and each of those counts against the account's hourly platform budget. An account reaching more than 25 ad accounts is answered 400 rather than a silently shortened list; use GET /v1/accounts/{accountId}/tracking-tags?adAccountId=act_... to walk it one ad account at a time.

A PIXEL SHARED WITH SEVERAL AD ACCOUNTS IS LISTED ONCE -- results are deduplicated by id. Per ad account only the first page of pixels is read; the tracking-tag endpoint above is the paged view.

type IS NEVER SET HERE and that follows the contract: it is present only where the platform locks the event type to the destination (Google conversion actions, LinkedIn conversion rules). A Meta pixel accepts any event name per request.

AN EMPTY LIST IS A NORMAL ANSWER -- an account with no destinations has nothing to report, and that is not an error.

Google Ads conversion actions and LinkedIn conversion rules are also served by their provider adapters. LinkedIn discovery spans the sponsored ad accounts available to the authenticated member; each result includes its adAccountId. Other account platforms receive a 400 platform-mismatch response.

Parameters:

  • accountId (path, required) — The connected Zhyper account id. It resolves the provider credential; it is not the provider ad account or destination id.

createConversionDestinationPOST /v1/accounts/{accountId}/conversion-destinations

Creates an offline conversion action on Google Ads or a conversion rule on LinkedIn. Provider-specific fields are rejected when used with the other platform. Meta pixels use the tracking-tags endpoint.

Parameters:

  • accountId (path, required)

Request body (CreateConversionDestinationRequestDto): adAccountId (string, required), name (string, required), type (string, required), attributionType (enum: LAST_TOUCH_BY_CAMPAIGN, LAST_TOUCH_BY_CONVERSION), postClickAttributionWindowSize (enum: 1, 7, 30, 90, 365), viewThroughAttributionWindowSize (enum: 1, 7, 30, 90, 365), valueType (enum: DYNAMIC, FIXED, NO_VALUE), value (ConversionDestinationValueDto), autoAssociationType (enum: ALL_CAMPAIGNS, OBJECTIVE_BASED, NONE), countingType (enum: MANY_PER_CLICK, ONE_PER_CLICK), primaryForGoal (boolean)

getConversionDestinationGET /v1/accounts/{accountId}/conversion-destinations/{destinationId}

LinkedIn-only. Fetches one conversion rule within its sponsored ad account.

Parameters:

  • accountId (path, required)
  • destinationId (path, required)
  • adAccountId (query, required) — Numeric id or full urn:li:sponsoredAccount:{id} URN.

updateConversionDestinationPATCH /v1/accounts/{accountId}/conversion-destinations/{destinationId}

LinkedIn-only partial update. At least one mutable field is required.

Parameters:

  • accountId (path, required)
  • destinationId (path, required)

Request body (UpdateConversionDestinationRequestDto): adAccountId (string, required), name (string), enabled (boolean), attributionType (enum: LAST_TOUCH_BY_CAMPAIGN, LAST_TOUCH_BY_CONVERSION), postClickAttributionWindowSize (enum: 1, 7, 30, 90, 365), viewThroughAttributionWindowSize (enum: 1, 7, 30, 90, 365), valueType (enum: DYNAMIC, FIXED, NO_VALUE), value (ConversionDestinationValueDto)

deleteConversionDestinationDELETE /v1/accounts/{accountId}/conversion-destinations/{destinationId}

LinkedIn-only soft delete. The provider rule is retained with enabled=false.

Parameters:

  • accountId (path, required)
  • destinationId (path, required)
  • adAccountId (query)

listConversionAssociationsGET /v1/accounts/{accountId}/conversion-destinations/{destinationId}/associations

Parameters:

  • accountId (path, required)
  • destinationId (path, required)
  • adAccountId (query, required)

addConversionAssociationsPOST /v1/accounts/{accountId}/conversion-destinations/{destinationId}/associations

Parameters:

  • accountId (path, required)
  • destinationId (path, required)

Request body (ConversionAssociationBatchRequestDto): adAccountId (string, required), campaignIds (array of string, required)

removeConversionAssociationsDELETE /v1/accounts/{accountId}/conversion-destinations/{destinationId}/associations

The documented surface accepts adAccountId and comma-separated campaignIds in query.

Parameters:

  • accountId (path, required)
  • destinationId (path, required)
  • adAccountId (query, required)
  • campaignIds (query, required)

getConversionMetricsGET /v1/accounts/{accountId}/conversion-destinations/{destinationId}/metrics

LinkedIn conversion-pivot adAnalytics metrics.

Parameters:

  • accountId (path, required)
  • destinationId (path, required)
  • adAccountId (query, required)
  • startDate (query, required)
  • endDate (query)
  • granularity (query)

listCustomConversionsGET /v1/accounts/{accountId}/custom-conversions

Lists the custom conversions an ad account owns, INCLUDING ARCHIVED ONES -- isArchived is the only thing that tells them apart, and filtering them out here would close the only surface on which an archived conversion is visible at all.

data[].id is exactly the value adSet.promotedObject.customConversionId wants on the ad-creating endpoints, with no conversion.

AN EMPTY LIST IS A NORMAL ANSWER -- an ad account with no custom conversions has nothing to report, and that is not an error.

Only served for Meta today. Another platform answers 501, because the request and its method are both fine and the capability is what is missing.

Parameters:

  • accountId (path, required) — The connected Meta account id. It is what resolves the platform token; it is NOT the ad account and NOT the pixel.
  • after (query) — Cursor from paging.after of the previous page. Forwarded to the platform OPAQUELY -- no shape is imposed on it here, because the platform is the one that mints it.
  • limit (query) — Page size. NOT IN THE TRACKED CONTRACT, which declares no paging for this operation -- the platform edge pages anyway, and hiding that would silently truncate your list.
  • platform (query) — Defaults to 'facebook'. Only Meta is served today; anything else is a 501 rather than a silent empty answer. NOT IN THE PUBLISHED CONTRACT for these operations -- it is accepted because every sibling ads operation carries it, so the same query shape works across the vertical.
  • adAccountId (query, required) — The act_<n> ad account whose custom conversions are listed.

createCustomConversionPOST /v1/accounts/{accountId}/custom-conversions

Creates a custom conversion on an ad account, or RETURNS THE EXISTING ONE when a non-archived conversion already has the same name on the same pixelId. A reused conversion answers 200; a newly created one answers 201, and reused in the body says which happened.

THE REUSE IS THIS API'S, NOT THE PLATFORM'S. The platform create is not idempotent, so a retried request would otherwise mint a duplicate carrying none of the original's optimisation history. That is also why no Idempotency-Key is accepted: the reuse key -- name plus pixelId -- already makes a retry safe, and the tracked contract declares no header here.

THE REUSE SCAN READS ONE PAGE. An ad account holding more custom conversions than 200 can therefore hold a match the scan does not see, and this call would then create a second conversion with the same name. The platform offers no server-side name filter, so a full scan would cost a number of calls that grows with your account and cannot be priced as one operation. List first if your account is that large.

rule is forwarded VERBATIM in the platform's own grammar -- not translated, not validated here. A rule the platform refuses comes back as a 400 carrying its reason.

Only served for Meta today; another platform answers 501.

Parameters:

  • accountId (path, required) — The connected Meta account id. It is what resolves the platform token; it is NOT the ad account and NOT the pixel.
  • platform (query) — Defaults to 'facebook'. Only Meta is served today; anything else is a 501 rather than a silent empty answer. NOT IN THE PUBLISHED CONTRACT for these operations -- it is accepted because every sibling ads operation carries it, so the same query shape works across the vertical.

Request body (CreateCustomConversionDto): adAccountId (string, required), name (string, required), pixelId (string, required), customEventType (string, required), rule (object, required)

listTrackingTagsGET /v1/accounts/{accountId}/tracking-tags

Lists the tracking tags (Meta calls them pixels) an ad account owns.

THIS IS THE OPERATION THAT MAKES THREE OTHERS USABLE. tags[].id is exactly the value getConversionsQuality wants as destinationId, createAdAudience wants as pixelId, and an ad's promotedObject.pixelId needs -- with NO conversion: all of them validate the same numeric shape. Before this endpoint there was no way to discover that id through this API at all.

ownerAdAccountId ON EVERY ROW tells you which ad account each tag belongs to -- the ambiguity getConversionsQuality warns about in its own error text.

code IS ALWAYS NULL HERE: the install snippet is kilobytes per row, so the list edge does not fetch it. Read a single tag to get it.

AN EMPTY LIST IS A NORMAL ANSWER -- an ad account with no tags has nothing to report, and that is not an error.

Only served for Meta today. Another platform answers 501 -- NOT the 405 the contract declares, because the request and its method are both fine and the capability is what is missing.

Parameters:

  • accountId (path, required) — Social account id -- a connected Meta account. It is what resolves the platform token; it is NOT the ad account and NOT the tag.
  • after (query) — Cursor from paging.after of the previous page. Forwarded to the platform OPAQUELY -- no shape is imposed on it here, because the platform is the one that mints it. It requires adAccountId; an omitted-account response is complete and always returns a null cursor.
  • limit (query) — With adAccountId this is the response page size. Without adAccountId it is the provider page size used while the bounded directory is fully drained; it does not truncate the aggregate result.
  • platform (query) — Defaults to 'facebook'. Only Meta is served today; anything else is a 501 rather than a silent empty answer. NOT IN THE PUBLISHED CONTRACT for these operations -- it is accepted because every sibling ads operation carries it, so the same query shape works across the vertical.
  • adAccountId (query) — The act_<n> ad account whose tags are listed. Omit it to aggregate every tag reachable by the connected token. Accounts are processed in deterministic id order, duplicate tag ids are returned once, and each provider page receives its own rate-limit admission. The complete directory is bounded to 25 ad accounts, 50 provider pages and 5000 unique tags. A larger directory answers 400 (too_many_ad_accounts or facebook_tracking_tag_directory_page_limit_exceeded) instead of returning a truncated success.

createTrackingTagPOST /v1/accounts/{accountId}/tracking-tags

Creates a tracking tag (Meta: a pixel) in the ad account and returns it, INCLUDING the install code.

THIS OPERATION IS NOT IDEMPOTENT and the cost of a retry is PERMANENT: the same body sent twice creates TWO tags, and META HAS NO API TO DELETE A TAG -- the extra one stays on the account forever. No Idempotency-Key is accepted, because the contract this API tracks declares one explicitly for other write endpoints and nothing here; the silence is a choice, not an omission. Do not auto-retry on timeout: read the list first and check whether the tag landed.

CREATING IS NOT INSTALLING. The returned tag has installed: false until the snippet is actually on your site and the first event arrives; lastFiredTime is what that flag is derived from.

name IS THE ONLY INPUT THE PLATFORM TAKES on this edge. Matching keys, cookie behaviour and data-use settings are set afterwards with the update operation.

The platform enforces its own ceiling on how many tags a business may hold, and an ad account outside a Business Manager cannot hold one at all; this API does not pre-check either -- they come back as a 400 from the platform.

Parameters:

  • accountId (path, required) — Social account id -- a connected Meta account. It is what resolves the platform token; it is NOT the ad account and NOT the tag.
  • platform (query) — Defaults to 'facebook'. Only Meta is served today; anything else is a 501 rather than a silent empty answer. NOT IN THE PUBLISHED CONTRACT for these operations -- it is accepted because every sibling ads operation carries it, so the same query shape works across the vertical.

Request body (CreateTrackingTagDto): adAccountId (string, required), name (string, required)

getTrackingTagGET /v1/accounts/{accountId}/tracking-tags/{tagId}

Reads one tag live from the platform.

THIS IS THE ONLY OPERATION THAT RETURNS code. The list view leaves it null on purpose -- the install snippet is kilobytes of JavaScript and fetching it for every row would bloat the list for nothing.

A malformed or unreachable tag answers 400, NOT 404, for the reason given on tagId.

Parameters:

  • accountId (path, required) — Social account id -- a connected Meta account. It is what resolves the platform token; it is NOT the ad account and NOT the tag.
  • tagId (path, required) — The tag id, as a numeric platform (pixel) id. A malformed id answers 400, not 404: the platform reports an unreachable tag with one error that cannot be told apart from "no permission" or "account not enabled for pixel management", and naming one of the three would state a fact this API never observed.
  • platform (query) — Defaults to 'facebook'. Only Meta is served today; anything else is a 501 rather than a silent empty answer. NOT IN THE PUBLISHED CONTRACT for these operations -- it is accepted because every sibling ads operation carries it, so the same query shape works across the vertical.

updateTrackingTagPATCH /v1/accounts/{accountId}/tracking-tags/{tagId}

Updates a tag and returns its re-fetched canonical state. AT LEAST ONE FIELD IS REQUIRED -- an empty body answers 400.

FOUR OF THE FIVE WRITABLE FIELDS CANNOT BE READ BACK. name shows up on the returned tag; enableAutomaticMatching, automaticMatchingFields, firstPartyCookieStatus and dataUseSetting do NOT, because the contract this API tracks does not publish them on the tag. The platform does store them -- keep your own record of what you set, because this API cannot show it to you.

automaticMatchingFields IS A FULL REPLACE, not an add: send the complete set you want. Combined with the paragraph above, a partial array is a loss you can neither undo nor see.

THERE IS NO DELETE OPERATION IN THIS FAMILY and there will not be one: the platform has no API to delete a tag. Retiring one means removing the snippet from your site.

Parameters:

  • accountId (path, required) — Social account id -- a connected Meta account. It is what resolves the platform token; it is NOT the ad account and NOT the tag.
  • tagId (path, required) — The tag id, as a numeric platform (pixel) id. A malformed id answers 400, not 404: the platform reports an unreachable tag with one error that cannot be told apart from "no permission" or "account not enabled for pixel management", and naming one of the three would state a fact this API never observed.
  • platform (query) — Defaults to 'facebook'. Only Meta is served today; anything else is a 501 rather than a silent empty answer. NOT IN THE PUBLISHED CONTRACT for these operations -- it is accepted because every sibling ads operation carries it, so the same query shape works across the vertical.

Request body (UpdateTrackingTagDto): name (string), enableAutomaticMatching (boolean), automaticMatchingFields (array of enum: em, ph, fn, ln, ge, db, ct, st, zp, country, external_id), firstPartyCookieStatus (enum: empty, first_party_cookie_disabled, first_party_cookie_enabled), dataUseSetting (enum: advertising_and_analytics, analytics_only, empty)

listTrackingTagSharedAccountsGET /v1/accounts/{accountId}/tracking-tags/{tagId}/shared-accounts

Lists the ad accounts the tag has been shared with.

THIS COSTS TWO PLATFORM CALLS, not one. The platform requires the owning Business Manager id on this edge and the contract this API tracks carries no field for it, so the tag is read first to resolve it. A tag on a personal (non-BM) ad account has no owning business and answers 400 -- the same condition that makes sharing impossible.

AN EMPTY LIST IS A NORMAL ANSWER, and there is no paging: the platform returns the whole set.

Parameters:

  • accountId (path, required) — Social account id -- a connected Meta account. It is what resolves the platform token; it is NOT the ad account and NOT the tag.
  • tagId (path, required) — The tag id, as a numeric platform (pixel) id. A malformed id answers 400, not 404: the platform reports an unreachable tag with one error that cannot be told apart from "no permission" or "account not enabled for pixel management", and naming one of the three would state a fact this API never observed.
  • platform (query) — Defaults to 'facebook'. Only Meta is served today; anything else is a 501 rather than a silent empty answer. NOT IN THE PUBLISHED CONTRACT for these operations -- it is accepted because every sibling ads operation carries it, so the same query shape works across the vertical.

addTrackingTagSharedAccountPOST /v1/accounts/{accountId}/tracking-tags/{tagId}/shared-accounts

Shares the tag with another ad account so campaigns there can optimise against it.

YOU MUST MANAGE BOTH SIDES: the tag's Business Manager and the target ad account. A tag on a personal (non-BM) ad account cannot be shared at all -- it has no owning business, and that answers 400.

THIS COSTS TWO PLATFORM CALLS for the reason given on the list operation.

ok IS ALWAYS TRUE IN THIS BODY: a share that failed comes back as an error envelope instead, so it is not a status you need to branch on. The tag itself is NOT returned -- read it separately if you need it.

Parameters:

  • accountId (path, required) — Social account id -- a connected Meta account. It is what resolves the platform token; it is NOT the ad account and NOT the tag.
  • tagId (path, required) — The tag id, as a numeric platform (pixel) id. A malformed id answers 400, not 404: the platform reports an unreachable tag with one error that cannot be told apart from "no permission" or "account not enabled for pixel management", and naming one of the three would state a fact this API never observed.
  • platform (query) — Defaults to 'facebook'. Only Meta is served today; anything else is a 501 rather than a silent empty answer. NOT IN THE PUBLISHED CONTRACT for these operations -- it is accepted because every sibling ads operation carries it, so the same query shape works across the vertical.

Request body (ShareTrackingTagDto): adAccountId (string, required)

removeTrackingTagSharedAccountDELETE /v1/accounts/{accountId}/tracking-tags/{tagId}/shared-accounts

Removes an ad account from the tag's shared list. The tag itself is NOT deleted -- the platform has no API for that.

adAccountId MAY BE SENT AS A QUERY PARAMETER (recommended) OR AS A JSON BODY FIELD. Exactly one channel must carry it: sending neither is a 400, and sending both is a 400 too rather than a silent choice between two values you may not have meant to be the same.

THIS COSTS TWO PLATFORM CALLS for the reason given on the list operation.

ANSWERS 204 WITH NO BODY.

Parameters:

  • accountId (path, required) — Social account id -- a connected Meta account. It is what resolves the platform token; it is NOT the ad account and NOT the tag.
  • tagId (path, required) — The tag id, as a numeric platform (pixel) id. A malformed id answers 400, not 404: the platform reports an unreachable tag with one error that cannot be told apart from "no permission" or "account not enabled for pixel management", and naming one of the three would state a fact this API never observed.
  • platform (query) — Defaults to 'facebook'. Only Meta is served today; anything else is a 501 rather than a silent empty answer. NOT IN THE PUBLISHED CONTRACT for these operations -- it is accepted because every sibling ads operation carries it, so the same query shape works across the vertical.
  • adAccountId (query) — The act_<n> ad account to stop sharing with. MARKED OPTIONAL because it may travel in the JSON body instead -- but one of the two channels must carry it.

Request body (ShareTrackingTagDto): adAccountId (string, required)

getTrackingTagStatsGET /v1/accounts/{accountId}/tracking-tags/{tagId}/stats

Reads what the tag has been recording, grouped by one dimension.

ROWS ARE PASSED THROUGH FROM THE PLATFORM AS-IS. Their keys depend on aggregation, and this API does not reshape or validate them -- imposing a shape on sixteen dimensions nobody here has observed would reject real data. Read them defensively.

aggregation, startTime and endTime in the response ECHO YOUR REQUEST; they are not platform fields. That is what saves you from tracking which window you asked for.

AN EMPTY rows LIST IS A NORMAL ANSWER -- a tag that recorded nothing in the window has nothing to report.

THIS IS AGGREGATE DATA, not per-person records: every dimension is a grouping key. had_pii counts whether personal information was present, it does not carry any.

Parameters:

  • accountId (path, required) — Social account id -- a connected Meta account. It is what resolves the platform token; it is NOT the ad account and NOT the tag.
  • tagId (path, required) — The tag id, as a numeric platform (pixel) id. A malformed id answers 400, not 404: the platform reports an unreachable tag with one error that cannot be told apart from "no permission" or "account not enabled for pixel management", and naming one of the three would state a fact this API never observed.
  • platform (query) — Defaults to 'facebook'. Only Meta is served today; anything else is a 501 rather than a silent empty answer. NOT IN THE PUBLISHED CONTRACT for these operations -- it is accepted because every sibling ads operation carries it, so the same query shape works across the vertical.
  • endTime (query) — Window end, unix seconds. Must not be earlier than startTime.
  • startTime (query) — Window start, unix seconds. Omit both times to let the platform pick the window.
  • aggregation (query) — The dimension to group by. It also decides the SHAPE of every row, which is why it is echoed back in the response.

listAdsGET /v1/ads

Returns synced ad documents with metrics aggregated over the requested range. The coverage block reports how much of that range is actually backed by synced data, so partial totals cannot be mistaken for complete ones.

Parameters:

  • limit (query)
  • page (query)
  • toDate (query)
  • fromDate (query)
  • source (query)
  • status (query)
  • effectiveInstagramMediaId (query)
  • effectiveObjectStoryId (query)
  • platformAdId (query) — The platform's own ad id, e.g. the value in a comment webhook.
  • campaignId (query)
  • adAccountId (query)
  • profileId (query)
  • accountId (query)

updateAdPUT /v1/ads/{adId}

Meta supports status, parent-ad-set budget, targeting, creative replacement and rename. Budget and targeting intentionally write the parent ad set, so they affect sibling ads; the local sibling documents are refreshed together. Targeting is merged over the last discovery snapshot so omitted Meta fields are preserved.

Creative replacement creates a new immutable creative and then repoints the ad. The old creative remains for reporting. Because creative creation is not idempotent, Idempotency-Key is required whenever the creative field is present; an ambiguous provider outcome is fenced instead of retried.

Moving an ad with adSetId is still rejected: Meta does not permit it. Google/LinkedIn/TikTok lifecycle writes remain explicit 501 gates until a real provider adapter primitive exists.

Parameters:

  • adId (path, required) — The platform's own ad id.
  • Idempotency-Key (header) — Client-generated unique key. Required when replacing creative; optional for the idempotent fields.

Request body (UpdateAdDto): status (enum: active, paused), budget (UpdateAdBudgetDto), targeting (UpdateAdTargetingDto), creative (UpdateAdReplacementCreativeDto), name (string), platform (string)

deleteAdDELETE /v1/ads/{adId}

Deletes ONLY this ad; its ad set, campaign and sibling ads are untouched.

THIS CANNOT BE UNDONE. On the platform a deleted ad cannot be moved back to any other state. It also is not the same as archiving, which this API does not offer: archiving is capped per ad account while deletion is not, so putting both behind one endpoint would quietly consume a limited resource.

The ad keeps existing on the platform in a deleted state: it stays readable by its own id, disappears from listings, and can still accrue metrics for a while after its last delivery. The stored document is marked cancelled here, because the sync pass will never see the ad again and the row would otherwise look active forever.

Deleting an already-deleted ad succeeds rather than erroring, so a retry after a dropped connection is safe.

Parameters:

  • adId (path, required) — The platform's own ad id.
  • Idempotency-Key (header) — Client-generated unique key that makes retries safe.
  • platform (query) — Inferred from the stored ad; only needed when one platform ad id exists on more than one platform for this tenant.

getAdGET /v1/ads/{adId}

The path segment accepts the zhyper ad id, the platform ad id, the creative's effective object story id, or its Instagram media id. All four resolve to the same ad, so the caller needs no translation step.

Parameters:

  • adId (path, required) — Zhyper ad id, platform ad id, <pageId>_<postId> object story id, or Instagram media id.
  • toDate (query)
  • fromDate (query)

getAdAnalyticsGET /v1/ads/{adId}/analytics

Reads stored metrics; the platform is not called.

What this adds over GET /v1/ads/{adId}, which already returns the ad and its totals, is the DAILY series -- that is the whole reason the endpoint exists rather than a second view of the same numbers.

Days with no delivery are omitted rather than zero-filled: a stored zero and an unsynced day are different facts, and coverage reports how much of the range is actually backed by synced data, so partial totals cannot be mistaken for complete ones.

Demographic breakdowns are NOT served and the parameter is not accepted: they cannot be derived from stored data, and accepting a parameter that is then ignored would let a caller believe they got what they asked for.

Parameters:

  • adId (path, required) — The platform's own ad id.
  • toDate (query)
  • fromDate (query)

getAdCommentsGET /v1/ads/{adId}/comments

Returns comments on the post an ad's creative points at, read LIVE.

THIS IS THE ONLY WAY TO SEE DARK-POST COMMENTS. An ad creative often points at a post that was never published organically, so it does not exist in the post surfaces at all; the identifier comes from the ad record.

AN AD CAN HAVE TWO COMMENT THREADS. One running on both the Facebook and the Instagram side has two separate underlying posts. placement picks one; omitted, the Instagram side is returned when it exists, otherwise Facebook. meta.placement always says which one you got. Asking for a placement the ad does not have is refused rather than quietly switched -- a silent switch would have you read one platform's comments as the other's.

THE INSTAGRAM SIDE READS THROUGH THE CONNECTED INSTAGRAM ACCOUNT that owns the media, so it costs one extra platform call to find out which account that is. When no connected account can read it, the call returns ads_connection_required (422) and the Facebook side, if any, is still readable with ?placement=facebook.

The path segment accepts any of the four identifier dialects that GET /v1/ads/{adId} accepts -- unlike the write endpoints, which take the platform ad id only.

Parameters:

  • adId (path, required) — zhyper ad id, platform ad id, object story id, or instagram media id.
  • cursor (query) — The pagination.cursor from a previous response.
  • limit (query)
  • placement (query) — Which side to read. Omit to get the Instagram side when it exists.

duplicateAdPOST /v1/ads/{adId}/duplicate

Copies one ad through the platform's own duplication edge. The copy is PAUSED by default so nobody spends money on a configuration they have not reviewed.

adSetId puts the copy under a different ad set. That is also the only way to move an ad between ad sets at all: the platform refuses to reparent an existing one.

Three fields the sibling ad-set endpoint takes are NOT accepted here, because the platform does not offer them for an ad: deepCopy (an ad has no children), and startTime/endTime (scheduling lives on the ad set).

Sending the same request twice creates TWO copies -- the platform does not de-duplicate this edge, so Idempotency-Key is load-bearing here rather than decorative.

Nothing is written locally: the copy (and its freshly built creative) only truly exists on the platform, and the discovery pass brings it back.

Parameters:

  • adId (path, required) — The platform's own ad id.
  • Idempotency-Key (header) — Client-generated unique key that makes retries safe. Strongly recommended on this endpoint: without it a retry duplicates twice.

Request body (DuplicateAdDto): platform (enum: facebook, instagram), adSetId (string), statusOption (enum: ACTIVE, PAUSED, INHERITED_FROM_SOURCE), renameStrategy (enum: DEEP_RENAME, ONLY_TOP_LEVEL_RENAME, NO_RENAME), renamePrefix (string), renameSuffix (string), syncAfter (boolean)

getAdMediaGET /v1/ads/{adId}/media

Normalizes single-image, video, carousel, Reels/Story and dynamic creative assets live from a connected Facebook Marketing API account. Instagram credentials are intentionally not inferred as Facebook Graph credentials. Returned signed URLs are short-lived and are never cached.

Parameters:

  • adId (path, required) — Zhyper ad id, platform ad id, object-story id or Instagram media id.

getAdPreviewsGET /v1/ads/{adId}/preview

Returns the platform's own <iframe> snippet per requested placement, embeddable as-is.

THE IFRAME EXPIRES 24 HOURS after it is issued. It is meant to be rendered now, not stored -- a snippet kept longer renders an empty frame.

The platform renders ONE format per call, so asking for N formats spends N calls from the account's rate window. That is why the list is capped at 10.

A format that this creative cannot be rendered in comes back with html: null rather than an error -- not every creative type exists in every placement, and asking is legitimate.

Format names are NOT validated against a pinned list: the platform keeps adding placements, so its own error is the authority on which ones exist. Unknown names surface the platform's message verbatim.

Parameters:

  • adId (path, required) — The platform's own ad id.
  • formats (query) — Comma-separated placement format names, one preview per format (max 10, no repeats).

updateAdStatusPUT /v1/ads/{adId}/status

Ad-scoped pause/resume. It touches ONLY this ad -- never its parent ad set or campaign -- so sibling ads keep running. That is the whole reason it exists next to the ad-set and campaign endpoints.

The path segment takes the platform ad id. Unlike GET /v1/ads/{adId} it does NOT accept the other identifier dialects: those are not unique to one ad, and a write must not have an ambiguous target.

An ad whose stored status is terminal (rejected, completed, cancelled) is skipped and the platform is not called at all.

The stored ad document keeps its previous status until the next sync discovery pass. This is deliberate: the platform puts a resumed ad back through review, so writing active here would publish a status the platform has not confirmed.

Parameters:

  • adId (path, required) — The platform's own ad id.
  • Idempotency-Key (header) — Client-generated unique key that makes retries safe.

Request body (UpdateAdStatusDto): status (enum: active, paused, required), platform (string)

getAdTrackingTagsGET /v1/ads/{adId}/tracking-tags

Returns the click-URL tracking parameters the platform has stored for this ad, read LIVE -- nothing is cached locally.

THE TAGS BELONG TO THE CREATIVE, NOT THE AD. On Meta url_tags is a creative field, so two ads that share one creative report the same tags, and changing them for one would change them for the other. creativeId is returned so a caller can see whether such a share exists.

urlTags comes back as the platform's own string, not as a parsed key/value map: it may carry dynamic macros and may repeat a key, and both survive verbatim.

PATCH /v1/ads/{adId}/tracking-tags rebuilds the immutable creative and repoints this ad. Read the PATCH warning before using it: the new creative returns to platform review and does not keep the old creative's likes/comments.

Parameters:

  • adId (path, required) — The platform's own ad id.

updateAdTrackingTagsPATCH /v1/ads/{adId}/tracking-tags

Meta creatives are immutable. This operation reads the current object_story_spec, creates a replacement creative carrying the new url_tags, and repoints only this ad. Placement-customized, dark, or otherwise non-rebuildable creatives return 422 before any write.

REVIEW/ENGAGEMENT SIDE EFFECT: the replacement creative re-enters platform review and does not inherit the old creative's accumulated likes/comments. A retry can otherwise leave an extra creative, so send an Idempotency-Key.

Parameters:

  • adId (path, required) — The platform's own ad id.
  • Idempotency-Key (header) — Client-generated key that prevents duplicate replacement creatives.

Request body (UpdateAdTrackingTagsDto): urlTags (array of AdTrackingTagPairDto, required)

listAdAccountsGET /v1/ads/accounts

Enumerates the platform ad accounts that the connected social account token can reach. Ad accounts are read live from the platform on every call; none of this is stored.

Parameters:

  • limit (query) — Maximum number of ad accounts to return.
  • adAccountId (query) — Filter the response to a single platform ad account id; returns at most one item.
  • accountId (query, required) — The connected social account whose token resolves the ad accounts.

updateAdAccountPATCH /v1/ads/accounts

Writes the default DSA beneficiary and payor to the ad account (EU DSA, Article 26). Set them once and every EU-targeted call to /v1/ads/create, /v1/ads/boost and /v1/ads/ctwa on that ad account may omit dsaBeneficiary/dsaPayor: the platform fills them in.

THE VALUES GO STRAIGHT TO THE PLATFORM AND NOTHING IS STORED HERE. Defaults already set in the platform's ads manager work identically. THIS API NEVER GUESSES THEM: beneficiary and payor are legal disclosures shown to EU users, so the entity names must be given explicitly. Use GET /v1/ads/dsa-recommendations to offer suggestions in your UI -- and let a human choose.

IF defaultDsaPayor IS OMITTED, THE BENEFICIARY IS WRITTEN AS THE PAYOR TOO, which covers the common case where one entity both benefits from and pays for the ads. Omitting it does NOT mean "leave the existing payor alone", and null is rejected: there is no operation for clearing a disclosure.

The response is RE-READ from the platform after the write, not echoed back from the request, so what you get is what the ad account now holds.

No Idempotency-Key is accepted; the published contract declares none here while declaring one explicitly for other write endpoints. Sending the same body twice is harmless: it creates no object, it drives two fields to a stated end state.

Only served for Meta ad accounts; another platform answers 501.

Request body (UpdateAdAccountDsaDefaultsDto): accountId (string, required), adAccountId (string, required), defaultDsaBeneficiary (string, required), defaultDsaPayor (string), platform (string)

getAdAccountFinanceGET /v1/ads/accounts/finance

Reads the money side of an ad account live from the platform: balance, lifetime spend, spending cap and funding source.

THE ANSWER IS FLAT. There is no data/paging envelope and no paging, because this reads one account rather than a list.

EVERY AMOUNT IS IN WHOLE UNITS of currency (the platform keeps them in minor units and they are converted here using its own per-currency offset, so zero-decimal currencies are not off by a factor of a hundred).

spendCap IS NULL WHEN THERE IS NO CAP -- the platform expresses that as 0, and a zero cap would read as "every delivery blocked". fundingSource is null when none is attached, and its type is an integer code the contract declares no enum for.

balance and amountSpent come back null when the platform did not report them. THAT IS NOT ZERO: an unknown amount and a zero amount are different facts, and this API will not turn one into the other.

This does NOT overlap GET /v1/ads/accounts, which carries the account's identity, status and budget fields and no money at all.

Only served for Meta ad accounts. Another platform answers 501 -- the request is valid, the capability is what is missing.

Parameters:

  • platform (query) — Defaults to 'facebook'. Only Meta is served today.
  • adAccountId (query, required) — Platform ad account id (act_<n>) whose figures are read.
  • accountId (query, required) — Social account id used to resolve the platform token.

getAdsActivityLogGET /v1/ads/activity

Reads the ad account's change log -- who changed what, and when -- live from the platform.

ROWS ARE FORWARDED VERBATIM: keys are the ones the platform sent, nothing is added, renamed, dropped or coerced. Read them against the platform's own field reference and treat every field as optional. extra_data is a JSON STRING holding the before/after values, not an object, and it is forwarded as the string it is.

objectId FILTERS THE RETURNED PAGE, NOT THE QUERY. The platform offers no per-object filter on this edge, so the filter runs on the page that came back: combine it with after to walk the history of one campaign, ad set or ad. A page whose rows all fail the filter comes back empty WITH a cursor -- that is not the end of the data.

since/until are calendar days and DO go to the platform. Their order is not enforced: an inverted range simply comes back empty.

This endpoint pages up to 200 rows at a time, twice its labels and studies siblings.

Only served for Meta ad accounts. Another platform answers 501 -- the request is valid, the capability is what is missing.

Parameters:

  • platform (query) — Defaults to 'facebook'. Only Meta is served today; anything else is a 501 rather than a silent empty answer.
  • objectId (query) — Keep only rows whose object is this campaign, ad set or ad. IT FILTERS THE RETURNED PAGE, not the query, so page through the history rather than expecting one call to find every match. No id format is imposed: the value never leaves this API.
  • until (query) — Newest calendar day to include (YYYY-MM-DD).
  • since (query) — Oldest calendar day to include (YYYY-MM-DD).
  • after (query) — Opaque cursor from a previous page.
  • limit (query) — Rows per page.
  • adAccountId (query, required) — Platform ad account id (act_<n>) whose change log is read.
  • accountId (query, required) — Social account id used to resolve the platform token.

updateAdSetPUT /v1/ads/ad-sets/{adSetId}

Ad-set-level writes: ABO budget, delivery status, rename and bid strategy. At least one updatable field is required.

MONEY IS TAKEN IN WHOLE CURRENCY UNITS (19.99 = $19.99) and converted to the integer minor-unit value Meta expects using the platform's own published currency offset. More decimal places than the currency supports is a 400, not a silent rounding. If the ad account currency is not in the platform's published table the amount is REFUSED rather than guessed.

Bid strategy rules: LOWEST_COST_WITH_BID_CAP and COST_CAP require bidAmount; LOWEST_COST_WITHOUT_CAP forbids it. LOWEST_COST_WITH_MIN_ROAS requires roasAverageFloor; its decimal multiplier is sent as bid_constraints.roas_average_floor multiplied by 10,000.

platformSpecificData updates Meta optimization/billing, schedule and promoted-object settings through the same real ad-set primitive used at creation. Unknown nested fields are rejected; campaign-objective compatibility remains Meta's live decision and its error is passed through. Phone/SMS delivery settings are intentionally out of scope.

If the parent campaign uses campaign budget optimisation, a budget write is rejected by Meta and its error is passed through: this API stores no campaign row to preflight the budget level from, and guessing would be worse than the platform's own answer.

Nothing is written to stored ad documents here -- budget and bid live per AD in this store, not per ad set, so the correct row set is ambiguous; the next sync pass reads the real values back.

VALUE RULE SETS. valueRuleSetId attaches or replaces the set that adjusts this ad set's bids; sending a different id replaces the previous association, and there is no separate replace call. To DETACH, send valueRulesApplied: false and OMIT valueRuleSetId -- the two together are a 400 mutually_exclusive_fields, because Meta attaches the set whenever value_rule_set_id is present and the detach would silently leave the bid adjustments live. Only ad sets on LOWEST_COST_WITHOUT_CAP or COST_CAP are eligible and Meta enforces that server-side. The sets themselves live at /v1/ads/value-rule-sets.

Parameters:

  • adSetId (path, required) — The platform's own ad set id.
  • Idempotency-Key (header) — Client-generated unique key that makes retries safe.

Request body (UpdateAdSetDto): platform (string, required), budget (AdSetBudgetDto), status (enum: active, paused), name (string), bidStrategy (enum: LOWEST_COST_WITHOUT_CAP, LOWEST_COST_WITH_BID_CAP, COST_CAP, LOWEST_COST_WITH_MIN_ROAS), bidAmount (number), roasAverageFloor (number), platformSpecificData (AdSetPlatformSpecificDataDto), valueRuleSetId (string), valueRulesApplied (boolean)

deleteAdSetDELETE /v1/ads/ad-sets/{adSetId}

Deletes the provider ad-set node without deleting its campaign. Every stored ad document under that ad set is then marked cancelled; adCount reports only rows changed by this request. Provider capability gaps are returned as 501.

Parameters:

  • adSetId (path, required) — The platform's own ad set id.
  • Idempotency-Key (header) — Client-generated unique key that makes retries safe.

getAdSetDetailsGET /v1/ads/ad-sets/{adSetId}

Reads the ad set LIVE from the platform and returns the body verbatim -- no key is added, renamed or dropped.

The default projection includes learning_stage_info (the learning-phase block), delivery settings, budgets, schedule and targeting. Note that Meta OMITS learning_stage_info.status on PAUSED ad sets, so treat that key as optional; its absence is not an error and is not a sign of missing data.

fields overrides the projection and supports nested {} sub-field selection. It is passed through to the platform, so an unknown field surfaces as the platform's own 400 rather than being silently dropped.

accountId is REQUIRED and is a social account id, not an ad account id: it is what resolves the token. Nothing is read from local storage here, which is deliberate -- an ad set created moments ago has no local row yet, and requiring one would 404 exactly the ad sets a caller is most likely to be looking at.

Parameters:

  • adSetId (path, required) — The platform's own ad set id.
  • fields (query) — Comma-separated Graph field override; supports nested {} projections.
  • platform (query) — Defaults to 'facebook'. Only Meta is served today; anything else is a 501 rather than a silent empty answer.
  • accountId (query, required) — Social account id used to resolve the platform token.

duplicateAdSetPOST /v1/ads/ad-sets/{adSetId}/duplicate

Duplicates the ad set through the platform's own copy edge, including its ads and creatives by default. The source graph is NOT walked and recreated: doing that would need a mapping for every field, and every field the mapping missed would be dropped in silence.

The copy is created PAUSED by default so a caller can review before it spends.

startTime and endTime ARE accepted here -- unlike the campaign duplicate endpoint, which rejects them by name because scheduling lives on the ad set, not the campaign. They must carry a UTC offset: without one the platform reads them in the AD ACCOUNT's timezone, which this endpoint cannot show you, and delivery would shift by hours.

campaignId retargets the copy into another campaign; omitted, the copy stays under the source's own campaign.

Nothing is written to stored ad documents -- the real shape of the copied subtree is only known to the platform. syncAfter instead pulls the account's next discovery pass forward. A discovery of failed does NOT mean the copy failed.

Retries are NOT deduplicated by the platform: the same call twice leaves TWO copies. Send an Idempotency-Key.

Parameters:

  • adSetId (path, required) — Source platform ad set id.
  • Idempotency-Key (header) — Client-generated unique key. Strongly recommended here: this operation is not idempotent on the platform side.

Request body (DuplicateAdSetDto): platform (enum: facebook, instagram, required), campaignId (string), deepCopy (boolean), statusOption (enum: ACTIVE, PAUSED, INHERITED_FROM_SOURCE), startTime (string), endTime (string), renameStrategy (enum: DEEP_RENAME, ONLY_TOP_LEVEL_RENAME, NO_RENAME), renamePrefix (string), renameSuffix (string), syncAfter (boolean)

updateAdSetStatusPUT /v1/ads/ad-sets/{adSetId}/status

Ad-set-scoped pause/resume. It does NOT touch sibling ad sets, which is the whole reason this endpoint exists next to the campaign one: pausing the campaign would stop every ad set under it, including the ones the caller wants to keep spending.

Ads in a terminal status (rejected, completed, cancelled) are skipped and counted. When no ad is actionable the platform is not called at all and the response carries message.

The stored ad documents keep their previous status until the next sync discovery pass; updated describes what the cascade covers on the platform, not rows changed here.

Parameters:

  • adSetId (path, required) — The platform's own ad set id.
  • Idempotency-Key (header) — Client-generated unique key that makes retries safe.

Request body (UpdateAdSetStatusDto): status (enum: active, paused, required), platform (string, required)

listAdAudiencesGET /v1/ads/audiences

Lists the ad account's custom audiences, read live from the platform. Nothing is stored locally: this API keeps NO local audience record, so platformAudienceId here is the id every other operation on this resource takes in its path.

limit and after are an ADDITION to the contract this API tracks, which returns a flat array. The platform pages this edge, so a flat array would mean either a silently truncated first page or an unbounded number of internal calls; the bounds match the sibling GET /v1/ads/images so the surface stays consistent with itself.

type FILTERS THE RETURNED PAGE. The platform edge takes no subtype filter, so filtering happens after the page is read: a filtered page can be SHORTER than limit while more pages remain. Paging still advances correctly, because the cursor is the platform's own. The four types this API does not implement answer 501 rather than an empty list.

Parameters:

  • after (query) — Cursor from paging.after of the previous page.
  • limit (query)
  • type (query) — Restrict the page to one audience type. Filtering is applied to the page after it is read, so a filtered page may be shorter than limit.
  • platform (query) — Defaults to 'facebook'. Only Meta is served today; anything else is a 501 rather than a silent empty list.
  • adAccountId (query, required) — Platform ad account id (act_<n>) whose audiences are listed.
  • accountId (query, required) — Social account id used to resolve the platform token.

createAdAudiencePOST /v1/ads/audiences

Creates a custom audience on the platform and returns it. No local record is kept; the returned platformAudienceId is the id every other operation on this resource takes.

THIS OPERATION IS NOT IDEMPOTENT and must never be auto-retried: the same body sent twice creates TWO audiences. Send an Idempotency-Key if a retry is possible on your side; it is honoured here.

ALL EIGHT contract types are RECOGNISED. The four this API does not implement -- company_list, engagement and website_retargeting (LinkedIn audiences; this API serves ads through its Meta adapter) and saved_targeting (a stored targeting spec consumed by an operation this API does not serve) -- answer 501 WITH THE REASON, never 400: a real type must stay distinguishable from a typo.

Type-specific fields are OPTIONAL here and validated for SHAPE only. Which combination a given type requires is the platform's rule, and enumerating it here would mean rejecting a combination the platform accepts.

Parameters:

  • Idempotency-Key (header) — Client-generated unique key. Strongly recommended: creation is not idempotent on the platform and a repeated body creates a second audience.

Request body (CreateAdAudienceDto): accountId (string, required), adAccountId (string, required), name (string, required), type (enum: customer_list, company_list, engagement, meta_engagement, website, website_retargeting, lookalike, saved_targeting, required), description (string), customerFileSource (string), pixelId (string), retentionDays (integer), engagementSource (enum: page, instagram, video), sourceId (string), event (string), sourceAudienceId (string), country (string), ratio (number), rule (object), platform (string)

getAdAudienceGET /v1/ads/audiences/{audienceId}

Reads one custom audience live from the platform.

{audienceId} IS THE PLATFORM ID. This API keeps no local audience record -- like the sibling creative, image and document endpoints, and unlike the contract it otherwise tracks, which mints a local id and stores the platform id beside it. There is one id space here, not two.

Membership is never returned: size is a count, and the platform does not hand hashed rows back either.

Parameters:

  • audienceId (path, required) — The platform's own audience id.
  • platform (query) — Defaults to 'facebook'. Only Meta is served today.
  • accountId (query, required) — Social account id used to resolve the platform token.

updateAdAudiencePUT /v1/ads/audiences/{audienceId}

Updates the audience name and/or description on the platform.

AT LEAST ONE of name and description is required. An empty update is rejected rather than sent: it would spend a platform call, change nothing, and answer 200 to a caller who believes something changed.

Membership is NOT edited here -- use the /users sub-resource.

Parameters:

  • audienceId (path, required) — The platform's own audience id.

Request body (UpdateAdAudienceDto): accountId (string, required), name (string), description (string), platform (string)

deleteAdAudienceDELETE /v1/ads/audiences/{audienceId}

Deletes the audience on the platform. Nothing local is removed, because nothing local was ever written.

Parameters:

  • audienceId (path, required) — The platform's own audience id.

Request body (DeleteAdAudienceDto): accountId (string, required), platform (string)

replaceAdAudienceCompaniesPOST /v1/ads/audiences/{audienceId}/companies

Publishes the LinkedIn company-list replacement contract. This release has no real LinkedIn Ads audience primitive, so valid requests return a typed 501 and never pretend provider success.

Parameters:

  • audienceId (path, required)

Request body (ReplaceAdAudienceCompaniesDto): companies (array of AdAudienceCompanyDto, required)

addUsersToAdAudiencePOST /v1/ads/audiences/{audienceId}/users

Uploads member rows to a customer-list audience.

SEND RAW IDENTIFIERS, NOT HASHES. Each row is normalised and SHA-256 hashed inside this system; the platform only ever receives the hash, and so does every log, error message and audit record here -- which is to say none of them receives anything at all.

EMAIL ONLY IN THIS RELEASE. The contract this API tracks also allows phone; a row carrying one is REJECTED rather than accepted and dropped. The platform requires a country code on every number, this API has no way to learn yours, and assuming one would hash numbers for the wrong country that then match nothing without reporting any error. Rejecting says so; dropping would not.

The answer carries COUNTS ONLY. numInvalid says how many rows had no usable identifier; WHICH row, and why, is deliberately not reported, because reporting it would put a real identifier into an API response and from there into a log. Rows are counted rather than rejected so one bad row cannot fail the other 9999.

Repeating an upload is safe: the platform reconciles members by hash.

Parameters:

  • audienceId (path, required) — The platform's own audience id.

Request body (AdAudienceUsersDto): accountId (string, required), users (array of AdAudienceUserDto, required), platform (string)

removeAdAudienceUsersDELETE /v1/ads/audiences/{audienceId}/users

Removes member rows from a customer-list audience. Rows are sent RAW and hashed here, exactly as on the add operation -- the same identifier produces the same hash, which is how the platform finds the member to drop.

THIS OPERATION IS AN ADDITION to the contract this API tracks, whose /users sub-resource is POST-only. It exists because the platform terms this endpoint operates under impose an ongoing suppression duty: a person who asks to be removed must be removable. An add-only surface cannot satisfy the terms it accepts.

Identifiers travel in the BODY rather than the query string: a removal can carry as many rows as an upload, and putting real identifiers in a request line would write them into every access log on the way.

EMAIL ONLY IN THIS RELEASE, for the same reason as the add operation. The answer carries COUNTS ONLY.

Parameters:

  • audienceId (path, required) — The platform's own audience id.

Request body (AdAudienceUsersDto): accountId (string, required), users (array of AdAudienceUserDto, required), platform (string)

boostPostPOST /v1/ads/boost

Builds a whole campaign hierarchy in ONE call, or supplies adSetId to add creatives and ads under an existing Meta ad set while preserving its learning history. adSetId cannot be combined with campaign or adSet. A new campaign is created PAUSED and only flipped to ACTIVE as the LAST step. On the existing-ad-set path every new ad is created PAUSED and only flipped after ALL requested ads exist; the parent campaign/ad set is never paused because doing so could interrupt unrelated delivery.

PARTIAL FAILURE LEAVES WHAT IT BUILT. There is no compensating delete: if a later step fails, the campaign and ad set that already exist stay on the platform, paused, and the error carries them in details.created as {kind, platformId} entries. That is deliberate -- a surviving ad set is a HANDLE, not litter, and deleting it would close the documented way to continue.

The objects materialize in /v1/ads/tree on the next sync discovery pass, so no row is written locally here.

The creative is built from an EXISTING Page post (objectStoryId), so no headline, body or image is accepted here -- the post already carries them. The post must belong to the Page selected on the account.

The post is NOT resolved for you: this endpoint takes an <pageId>_<postId> value directly. Listing promotable posts and resolving connected Instagram accounts are separate reads that are not wired to this call.

Parameters:

  • Idempotency-Key (header) — Client-generated unique key that makes retries safe. The same key with the same body replays the original 201 without calling the platform again; the same key with a different body is a 422, and a key still being processed is a 409. A request whose worst-case duration is longer than the replay lease is REFUSED with 422 rather than accepted, because a retry could otherwise start a second chain while the first is still running. Send fewer ads to fit, or send no key at all and accept that a retry duplicates the whole hierarchy.

Request body (BoostPostDto): accountId (string, required), adAccountId (string, required), campaign (AdChainCampaignDto), adSet (AdChainAdSetDto), adSetId (string), ads (array of AdChainBoostAdDto, required), status (enum: ACTIVE, PAUSED)

listAdsBusinessCentersGET /v1/ads/business-centers

Reads one bounded TikTok page live from the dedicated tiktokads account. Zhyper does not copy a posting token or fan out into advertiser assets; advertiserCount is therefore null.

Parameters:

  • limit (query)
  • page (query)
  • accountId (query, required)

listMetaBusinessesGET /v1/ads/businesses

Enumerates the Meta Business Manager portfolios reachable from the connected social account token. The listing is token-scoped, so no ad account is involved, and it is read live from Meta on every call; none of this is stored.

Parameters:

  • after (query) — Opaque cursor taken from the previous page's paging.after. Absent on the first page.
  • limit (query) — Maximum number of businesses to return.
  • accountId (query, required) — The connected social account whose token resolves the businesses.

createCallAdPOST /v1/ads/call

Builds a whole campaign hierarchy in ONE call, or supplies adSetId to add creatives and ads under an existing Meta ad set while preserving its learning history. adSetId cannot be combined with campaign or adSet. A new campaign is created PAUSED and only flipped to ACTIVE as the LAST step. On the existing-ad-set path every new ad is created PAUSED and only flipped after ALL requested ads exist; the parent campaign/ad set is never paused because doing so could interrupt unrelated delivery.

PARTIAL FAILURE LEAVES WHAT IT BUILT. There is no compensating delete: if a later step fails, the campaign and ad set that already exist stay on the platform, paused, and the error carries them in details.created as {kind, platformId} entries. That is deliberate -- a surviving ad set is a HANDLE, not litter, and deleting it would close the documented way to continue.

The objects materialize in /v1/ads/tree on the next sync discovery pass, so no row is written locally here.

The creative must carry callToAction: "CALL_NOW" and a phoneNumber in E.164 form. THAT NUMBER IS THE ADVERTISER OWN LINE -- the number the button dials -- not an end user number, and it is never stored here.

linkUrl stays a separate https:// landing page: the platform keeps the tap target and the link as two different fields, and this API does not fold one into the other.

Parameters:

  • Idempotency-Key (header) — Client-generated unique key that makes retries safe. The same key with the same body replays the original 201 without calling the platform again; the same key with a different body is a 422, and a key still being processed is a 409. A request whose worst-case duration is longer than the replay lease is REFUSED with 422 rather than accepted, because a retry could otherwise start a second chain while the first is still running. Send fewer ads to fit, or send no key at all and accept that a retry duplicates the whole hierarchy.

Request body (CreateCallAdDto): accountId (string, required), adAccountId (string, required), campaign (AdChainCampaignDto), adSet (AdChainAdSetDto), adSetId (string), ads (array of AdChainAdDto, required), status (enum: ACTIVE, PAUSED)

listAdCampaignsGET /v1/ads/campaigns

Campaigns are not stored: they are virtual aggregations over ad documents grouped by platform campaign id, with metrics summed over the requested date range and status derived from child ad statuses. The coverage block reports how much of the requested range is actually backed by synced data, so partial totals cannot be mistaken for complete ones.

Parameters:

  • limit (query)
  • page (query)
  • toDate (query) — End of the metrics range (inclusive). Defaults to today (UTC).
  • fromDate (query) — Start of the metrics range (inclusive). Defaults to 90 days before toDate.
  • source (query) — all returns both locally created ads and those discovered from the platform's ad manager; zernio returns only locally created ones.
  • status (query) — Filter by derived campaign status (applied AFTER aggregation).
  • adAccountId (query) — Platform ad account id, e.g. act_123 for Meta.
  • profileId (query)
  • accountId (query) — Restrict to campaigns whose ads resolve through this social account.

createAdCampaignPOST /v1/ads/campaigns

Creates the campaign shell without its first ad set or ad. The campaign materializes in /v1/ads/tree on the next sync discovery pass, so no row is written locally here.

Campaign-level (CBO) budgeting is NOT accepted: budgetAmount, budgetType, bidStrategy, bidAmount and roasAverageFloor are rejected rather than ignored. Use ABO instead -- each ad set carries its own budget.

An Idempotency-Key header is accepted: a retry carrying the same key replays the first response instead of creating a second campaign.

Parameters:

  • Idempotency-Key (header) — Client-generated unique key that makes retries safe.

Request body (CreateAdCampaignDto): accountId (string, required), adAccountId (string, required), name (string, required), goal (enum: engagement, page_likes, traffic, awareness, lead_generation, lead_conversion, conversions, app_promotion, catalog_sales, required), specialAdCategories (array of enum: HOUSING, EMPLOYMENT, CREDIT, ISSUES_ELECTIONS_POLITICS, FINANCIAL_PRODUCTS_SERVICES, ONLINE_GAMBLING_AND_GAMING), status (enum: ACTIVE, PAUSED)

deleteAdCampaignDELETE /v1/ads/campaigns/{campaignId}

Deletes the campaign on the platform, which cascades to its ad sets and ads, then marks every stored ad document for it cancelled.

A campaign the platform reports as already gone counts as deleted: delete is idempotent, and answering "failed" to a retry after a partial failure would leave the caller unable to finish.

An EMPTY campaign (zero ads) has no stored document to resolve a token from, so send accountId to delete one. It is ignored when the campaign has ads.

Parameters:

  • campaignId (path, required) — The platform's own campaign id.
  • Idempotency-Key (header) — Client-generated unique key that makes retries safe.

Request body (DeleteAdCampaignDto): platform (enum: facebook, instagram, google, required), accountId (string)

updateAdCampaignPUT /v1/ads/campaigns/{campaignId}

Renames the campaign on the platform and refreshes the name stored on its ad documents.

platform is REQUIRED -- platform campaign ids are not globally unique -- and only Meta (facebook, instagram) is served: anything else is a 501 before the platform is called.

Beyond platform only name is accepted. accountId, budget, bidStrategy, bidAmount, roasAverageFloor and platformSpecificData are REJECTED with a per-field reason rather than dropped -- campaign-level (CBO) budgeting is not wired on this edge, the bid fields are an ad-set concern on Meta, and the empty-campaign accountId escape is not wired here yet (it is on DELETE).

Parameters:

  • campaignId (path, required) — The platform's own campaign id.
  • Idempotency-Key (header) — Client-generated unique key that makes retries safe.

Request body (UpdateAdCampaignDto): platform (enum: facebook, instagram, required), name (string, required)

getCampaignAnalyticsGET /v1/ads/campaigns/{campaignId}/analytics

Summary metrics for one campaign and a daily series summed across the campaign's ads, both over the requested range. Days with no delivery are omitted rather than zero-filled; coverage says which part of the range is actually backed by synced data. Demographic breakdowns are NOT returned -- they require live per-dimension calls to the platform and are not derivable from stored data.

Parameters:

  • campaignId (path, required) — The platform's own campaign id.
  • toDate (query)
  • fromDate (query)

attachCampaignAssetsPOST /v1/ads/campaigns/{campaignId}/assets

Parameters:

  • campaignId (path, required)
  • Idempotency-Key (header, required) — Required. Ambiguous multi-call outcomes are fenced for 24 hours.

Request body (GoogleAdsAttachCampaignAssetsRequestDto): accountId (string, required), customerId (string), sitelinks (array of GoogleAdsSitelinkDto), callouts (array of string), structuredSnippets (array of GoogleAdsStructuredSnippetDto)

duplicateAdCampaignPOST /v1/ads/campaigns/{campaignId}/duplicate

Copies the campaign with Meta's own /copies edge, including its ad sets, ads, creatives and targeting when deepCopy is true.

The copy is created PAUSED by default. ACTIVE -- or INHERITED_FROM_SOURCE over an active source -- starts a SECOND front of spend the moment the clone goes live.

No local row is written: the copied hierarchy only truly exists on the platform, so syncAfter brings the owning account's next discovery run forward instead of inventing one. startTime, endTime and raw are NOT part of this implementation; the DTO says why.

Parameters:

  • campaignId (path, required) — Source platform campaign id.
  • Idempotency-Key (header) — Client-generated unique key that makes retries safe. Strongly recommended here: the copy edge does NOT deduplicate, so a retried call without a key creates a second copy.

Request body (DuplicateAdCampaignDto): platform (enum: facebook, instagram, required), deepCopy (boolean), statusOption (enum: ACTIVE, PAUSED, INHERITED_FROM_SOURCE), renameStrategy (enum: DEEP_RENAME, ONLY_TOP_LEVEL_RENAME, NO_RENAME), renamePrefix (string), renameSuffix (string), syncAfter (boolean)

updateAdCampaignStatusPUT /v1/ads/campaigns/{campaignId}/status

Sets the delivery status of every non-terminal ad in the campaign with ONE platform call, because status cascades through the campaign hierarchy. Ads in a terminal status (rejected, completed, cancelled) are skipped and counted, with a reason per status.

When no ad is actionable the platform is not called at all and the response carries message.

The stored ad documents keep their previous status until the next sync discovery pass; updated describes what the cascade covers on the platform, not rows changed here.

Parameters:

  • campaignId (path, required) — The platform's own campaign id.
  • Idempotency-Key (header) — Client-generated unique key that makes retries safe.

Request body (UpdateAdCampaignStatusDto): status (enum: active, paused, required), platform (string, required)

bulkUpdateAdCampaignStatusPOST /v1/ads/campaigns/bulk-status

Up to 50 campaigns in one call. A row that fails does NOT fail the batch: its result carries an error code and totals.failed counts it. Rows run concurrently but with a bounded fan-out -- 50 calls at once would exhaust the account's own outbound window and return rate limits the caller would misread as platform refusals.

A repeated (platform, campaign) pair is rejected up front rather than run twice or silently collapsed.

Parameters:

  • Idempotency-Key (header) — Client-generated unique key that makes retries safe.

Request body (BulkUpdateAdCampaignStatusDto): status (enum: active, paused, required), campaigns (array of BulkAdCampaignItemDto, required)

listAdCatalogsGET /v1/ads/catalogs

Reads the product catalogs behind an ad account, live from the platform.

CATALOGS DO NOT LIVE ON THE AD ACCOUNT. They hang off the BUSINESS that owns it, so this endpoint resolves the business first and then reads both the catalogs it owns and the ones a client granted it. The two are merged and de-duplicated by id, because the contract does not separate them. An ad account with no business behind it is an error rather than an empty list: "you have no catalogs" and "I could not look" send a customer to two opposite places.

THE LIST IS COMPLETE OR IT IS AN ERROR. There is no limit, no cursor and no paging object: the platform pages this data internally and this API walks it to the end. When a list is larger than that walk can carry, the answer is a named 502 rather than a first page that would look complete.

name and productCount CAN COME BACK NULL. The platform does not send them on every row and this API will not invent a zero count or an empty name -- an unknown size and an empty catalog are different facts.

Only served for Meta ad accounts. Another platform answers 501 -- the request is valid, the capability is what is missing.

Parameters:

  • platform (query) — Defaults to 'facebook'. Only Meta is served today; anything else is a 501 rather than a silent empty answer.
  • adAccountId (query, required) — Platform ad account id (act_<n>) whose business owns the catalogs.
  • accountId (query, required) — Social account id used to resolve the platform token.

listAdCatalogProductSetsGET /v1/ads/catalogs/{catalogId}/product-sets

Reads the product sets in one catalog, live from the platform. A product set is the slice of a catalog an ad promotes.

adAccountId IS NOT ACCEPTED HERE, and that is not an omission: the catalog id is globally addressable on the platform and the catalog itself hangs off a business rather than an ad account. THIS ENDPOINT THEREFORE AUTHORISES DIFFERENTLY from GET /v1/ads/catalogs -- that one narrows through an ad account, this one asks only what the token's catalog access allows. Sending adAccountId is a 400 rather than a silently ignored parameter, because accepting it would promise a narrowing that does not happen.

THE LIST IS COMPLETE OR IT IS AN ERROR. There is no limit, no cursor and no paging object: the platform pages this data internally and this API walks it to the end. When a list is larger than that walk can carry, the answer is a named 502 rather than a first page that would look complete.

A bad or unreachable catalogId answers 400, NOT 404. The platform reports "no such node" and "you may not see this node" with one indistinguishable error, and naming one of them would state a fact this API never observed.

Only served for Meta ad accounts. Another platform answers 501 -- the request is valid, the capability is what is missing.

Parameters:

  • catalogId (path, required) — The platform's own product catalog id, as returned by GET /v1/ads/catalogs. No act_ prefix: a catalog is not an ad account.
  • platform (query) — Defaults to 'facebook'. Only Meta is served today.
  • accountId (query, required) — Social account id used to resolve the platform token.

sendConversionsPOST /v1/ads/conversions

Sends one bounded batch to the connected Meta account. eventId is the provider deduplication key. Plaintext identity fields are accepted only so the adapter can normalize and hash them; they are never echoed in an error or persisted in the Idempotency-Key replay request.

Parameters:

  • Idempotency-Key (header, required) — Required client-generated retry key for this exact conversion batch.

Request body (SendAdConversionsDto): accountId (string, required), adAccountId (string, required), destinationId (string, required), events (array of AdConversionEventDto, required), testCode (string), consent (AdConversionConsentDto)

adjustConversionsPOST /v1/ads/conversions/adjustments

Plaintext email and phone identifiers are normalized and hashed in memory by the adapter. Replay state stores only derived SHA-256 digests, not plaintext; those digests are still treated as personal data and are not claimed to be anonymous.

Parameters:

  • Idempotency-Key (header, required) — Required. Ambiguous provider outcomes are fenced for 24 hours.

Request body (GoogleAdsConversionAdjustmentsRequestDto): accountId (string, required), destinationId (string, required), adjustments (array of GoogleAdsConversionAdjustmentDto, required)

getConversionsQualityGET /v1/ads/conversions/quality

Scores the conversion events you send to a pixel/dataset by how well the platform could match them to a real person.

WHY IT MATTERS: unmatched events fail SILENTLY. The platform accepts them, answers 200, and the only symptom is campaign optimisation that quietly underperforms. This operation is what makes that visible.

READ matchKeys BEFORE compositeScore: the composite number tells you there is a problem, the per-key coverage tells you WHICH customer information you are failing to send.

AN EMPTY rows LIST IS A NORMAL ANSWER -- a dataset that has received no scoreable events yet has nothing to report, and that is not an error.

OWNERSHIP OF destinationId IS NOT VERIFIED against the account: the platform answers for any dataset the connected token can reach. Proving ownership would cost a second platform call.

Only served for Meta today. Another platform answers 501 -- NOT the 405 the contract declares, because the request and its method are both fine and the capability is what is missing.

Parameters:

  • platform (query) — Defaults to 'facebook'. Only Meta is served today; anything else is a 501 rather than a silent empty answer. NOT IN THE PUBLISHED CONTRACT for this operation -- it is accepted because every sibling ads operation carries it, so the same query shape works across the vertical.
  • destinationId (query, required) — The pixel or dataset whose event match quality you want, as a numeric platform id. It cannot be derived from accountId: one account can see several datasets, and choosing one for you would report scores for a dataset you did not ask about. An act_<id> ad account id is rejected -- a dataset is a different kind of object.
  • accountId (query, required) — Social account id -- a connected Meta account. It is what resolves the platform token; it is NOT the pixel or dataset.

createStandaloneAdPOST /v1/ads/create

Builds a whole campaign hierarchy in ONE call, or supplies adSetId to add creatives and ads under an existing Meta ad set while preserving its learning history. adSetId cannot be combined with campaign or adSet. A new campaign is created PAUSED and only flipped to ACTIVE as the LAST step. On the existing-ad-set path every new ad is created PAUSED and only flipped after ALL requested ads exist; the parent campaign/ad set is never paused because doing so could interrupt unrelated delivery.

PARTIAL FAILURE LEAVES WHAT IT BUILT. There is no compensating delete: if a later step fails, the campaign and ad set that already exist stay on the platform, paused, and the error carries them in details.created as {kind, platformId} entries. That is deliberate -- a surviving ad set is a HANDLE, not litter, and deleting it would close the documented way to continue.

The objects materialize in /v1/ads/tree on the next sync discovery pass, so no row is written locally here.

Parameters:

  • Idempotency-Key (header) — Client-generated unique key that makes retries safe. The same key with the same body replays the original 201 without calling the platform again; the same key with a different body is a 422, and a key still being processed is a 409. A request whose worst-case duration is longer than the replay lease is REFUSED with 422 rather than accepted, because a retry could otherwise start a second chain while the first is still running. Send fewer ads to fit, or send no key at all and accept that a retry duplicates the whole hierarchy.

Request body (CreateStandaloneAdDto): accountId (string, required), adAccountId (string, required), campaign (AdChainCampaignDto), adSet (AdChainAdSetDto), adSetId (string), ads (array of AdChainAdDto, required), status (enum: ACTIVE, PAUSED)

listAdCreativesGET /v1/ads/creatives

Lists the creative library LIVE from the platform; rows are returned verbatim -- no key is added, renamed or dropped. Nothing is stored locally: a cached copy of this library would show creatives that no longer exist the moment it went stale.

The default projection covers id, name, status, object type, thumbnail, object_story_spec / asset_feed_spec and url_tags. fields overrides it and is passed through, so an unknown field surfaces as the platform's own 400 rather than being silently dropped.

Any id returned here is reusable when creating an ad.

paging.after is derived from the presence of the platform's next link, not from its cursor block: the platform returns a cursor on the LAST page too, and following that one would page forever.

Parameters:

  • after (query)
  • limit (query)
  • fields (query)
  • platform (query) — Defaults to 'facebook'. Only Meta is served today; anything else is a 501 rather than a silent empty list.
  • adAccountId (query, required) — Platform ad account id (act_<n>) whose library is read.
  • accountId (query, required) — Social account id used to resolve the platform token.

createAdCreativePOST /v1/ads/creatives

Creates a creative in the library WITHOUT an ad. The returned id is reusable when creating an ad.

Provide EXACTLY ONE of imageUrl, imageHash or carouselCards. imageUrl needs no separate upload step: the platform fetches the URL and saves it into the ad account's image library itself.

The Page that acts as the story actor is resolved from accountId -- it is never taken from the body, because accepting one would be an attempt to post as a Page the caller may not hold. When the account carries no Page, the answer is 422.

Every link must be https. Carousel cards carry their own call to action whose destination is the card's own linkUrl; the platform requires those two to match, so it is not accepted separately.

This operation is NOT idempotent on the platform: the same body sent twice leaves TWO creatives. An Idempotency-Key header is therefore honoured here even though the contract does not declare one.

Parameters:

  • Idempotency-Key (header) — Client-generated unique key. Strongly recommended: this operation is not idempotent on the platform side.

Request body (CreateAdCreativeDto): accountId (string, required), adAccountId (string, required), headline (string, required), body (string, required), linkUrl (string, required), description (string), callToAction (enum: LEARN_MORE, SHOP_NOW, SIGN_UP, BOOK_TRAVEL, CONTACT_US, DOWNLOAD, GET_OFFER, GET_QUOTE, SUBSCRIBE, WATCH_MORE, ADD_TO_CART, APPLY_NOW, BOOK_NOW, BUY_TICKETS, DONATE, DONATE_NOW, GET_DIRECTIONS, GET_SHOWTIMES, LISTEN_NOW, ORDER_NOW, PLAY_GAME, REQUEST_TIME, SEE_MENU, START_ORDER, INSTALL_MOBILE_APP, USE_APP, REGISTER, JOIN, ATTEND, REQUEST_DEMO, VIEW_QUOTE, APPLY, SEE_MORE, BUY_NOW, WHATSAPP_MESSAGE, MESSAGE_PAGE, CALL_NOW), imageUrl (string), imageHash (string), carouselCards (array of AdCreativeCardDto), urlTags (string), name (string), platform (string)

getAdCreativeGET /v1/ads/creatives/{creativeId}

Reads one creative LIVE from the platform and returns it verbatim. fields overrides the default projection and is passed through.

Parameters:

  • creativeId (path, required) — The platform's own creative id.
  • fields (query)
  • platform (query)
  • accountId (query, required) — Social account id used to resolve the platform token.

updateAdCreativePUT /v1/ads/creatives/{creativeId}

Renames a creative. name is the only field this endpoint writes.

CONTENT cannot be changed on a creative -- the platform's own update parameter list carries no content fields, so to change what an ad shows you create a NEW creative (POST /v1/ads/creatives) and swap it onto the ad. Its delivery status is technically writable on the platform but is not opened here; that would be an additive change.

Leading and trailing whitespace is rejected rather than trimmed: the platform trims silently, so an untrimmed name would read back different from what was sent.

Nothing is stored locally; the response echoes the name that was written, because the platform's mutation response carries no state.

Parameters:

  • creativeId (path, required) — The platform's own creative id.
  • Idempotency-Key (header) — Client-generated unique key that makes retries safe.

Request body (UpdateAdCreativeDto): accountId (string, required), name (string, required), platform (string)

deleteAdCreativeDELETE /v1/ads/creatives/{creativeId}

Deletes a creative. The platform only allows deleting creatives that NO ad references; when one does, its refusal is passed through verbatim rather than rewritten. That is deliberate: this API does not know which ad holds the reference, and the platform message may carry that detail while ours could not.

Unlike campaign deletion, an "already gone" answer is NOT treated as success here -- the platform returns 400 both for a missing creative and for one still in use, and calling the second case a success would report a LIVE creative as deleted.

Parameters:

  • creativeId (path, required) — The platform's own creative id.
  • Idempotency-Key (header) — Client-generated unique key that makes retries safe.
  • platform (query)
  • accountId (query, required) — Social account id used to resolve the platform token.

createCtwaAdPOST /v1/ads/ctwa

Builds a whole campaign hierarchy in ONE call, or supplies adSetId to add creatives and ads under an existing Meta ad set while preserving its learning history. adSetId cannot be combined with campaign or adSet. A new campaign is created PAUSED and only flipped to ACTIVE as the LAST step. On the existing-ad-set path every new ad is created PAUSED and only flipped after ALL requested ads exist; the parent campaign/ad set is never paused because doing so could interrupt unrelated delivery.

PARTIAL FAILURE LEAVES WHAT IT BUILT. There is no compensating delete: if a later step fails, the campaign and ad set that already exist stay on the platform, paused, and the error carries them in details.created as {kind, platformId} entries. That is deliberate -- a surviving ad set is a HANDLE, not litter, and deleting it would close the documented way to continue.

The objects materialize in /v1/ads/tree on the next sync discovery pass, so no row is written locally here.

The request body and the platform call chain are the SAME as /v1/ads/messaging. The endpoint stays separate because the two are different customer acts and must remain distinguishable in the ad account's outbound rate-limit window; they are priced under separate operation names.

Parameters:

  • Idempotency-Key (header) — Client-generated unique key that makes retries safe. The same key with the same body replays the original 201 without calling the platform again; the same key with a different body is a 422, and a key still being processed is a 409. A request whose worst-case duration is longer than the replay lease is REFUSED with 422 rather than accepted, because a retry could otherwise start a second chain while the first is still running. Send fewer ads to fit, or send no key at all and accept that a retry duplicates the whole hierarchy.

Request body (CreateCtwaAdDto): accountId (string, required), adAccountId (string, required), campaign (AdChainCampaignDto), adSet (AdChainAdSetDto), adSetId (string), ads (array of AdChainAdDto, required), status (enum: ACTIVE, PAUSED)

getDsaDefaultsGET /v1/ads/dsa-defaults

Reads the default DSA beneficiary and payor from the ad account, live from the platform (EU DSA, Article 26). NOTHING IS STORED HERE, so defaults set in the platform's own ads manager read back identically and a value changed there is never stale in this answer.

Either field comes back null when no default is set. That is not an error: defaults are optional, and without them every EU-targeted /v1/ads/create, /v1/ads/boost and /v1/ads/ctwa call must carry dsaBeneficiary/dsaPayor itself.

Only served for Meta ad accounts. Another platform answers 501 -- the request is valid, the capability is what is missing.

Parameters:

  • platform (query) — Defaults to 'facebook'. Only Meta is served today; anything else is a 501 rather than a silent empty answer.
  • adAccountId (query, required) — Platform ad account id (act_<n>) whose defaults are read.
  • accountId (query, required) — Social account id used to resolve the platform token.

getDsaRecommendationsGET /v1/ads/dsa-recommendations

Returns the legal entity names the platform suggests for this ad account's DSA disclosures.

THE LIST IS NEVER APPLIED FOR YOU. Beneficiary and payor are legal disclosures shown to EU users, so this API will not pick one: the entries come back as a FLAT, UNLABELLED list -- nothing says which one is a beneficiary and which is a payor -- and choosing the right entity is your user's decision. Offer them in your UI and send the chosen one to PATCH /v1/ads/accounts.

AN EMPTY LIST IS A NORMAL ANSWER, not an error and not a 404: accounts with little activity simply have nothing to suggest.

Only served for Meta ad accounts; another platform answers 501.

Parameters:

  • platform (query) — Defaults to 'facebook'. Only Meta is served today.
  • adAccountId (query, required) — Platform ad account id (act_<n>) the suggestions are read for.
  • accountId (query, required) — Social account id used to resolve the platform token.

listHighDemandPeriodsGET /v1/ads/high-demand-periods

Reads the budget schedules -- the windows where a campaign or ad set spends more than its usual budget -- live from the platform.

THERE IS NO adAccountId ON THIS ENDPOINT, and that is not an omission: the platform hangs these schedules off the CAMPAIGN and AD SET nodes, not off the ad account. Send EXACTLY ONE of campaignId / adSetId; both, or neither, is a 400.

ROWS COME BACK VERBATIM. data[] carries the platform's own rows with its own snake_case keys (budget_value, budget_value_type, time_start, time_end, recurrence_type); nothing is renamed to camelCase, added or dropped. The key set is the platform's, so treat every field as optional.

PAGING IS THE PLATFORM'S CURSOR, forwarded opaquely. Send paging.after back as after to continue; null means the page was the last one.

Only served for Meta today. Another platform answers 501 -- the request is valid, the capability is what is missing.

Parameters:

  • platform (query) — Defaults to 'facebook'. Only Meta is served today; anything else is a 501 rather than a silent empty answer.
  • after (query) — Cursor from paging.after of the previous page.
  • limit (query) — Rows per page.
  • adSetId (query) — Ad set to read the schedules of. Exactly one of campaignId / adSetId.
  • campaignId (query) — Campaign to read the schedules of. Exactly one of campaignId / adSetId.
  • accountId (query, required) — Social account id used to resolve the platform token.

createHighDemandPeriodPOST /v1/ads/high-demand-periods

Creates a budget schedule and returns its id.

THERE IS NO adAccountId ON THIS ENDPOINT, and that is not an omission: the platform hangs these schedules off the CAMPAIGN and AD SET nodes, not off the ad account. Send EXACTLY ONE of campaignId / adSetId; both, or neither, is a 400.

budgetValue MEANS TWO DIFFERENT THINGS, and budgetValueType is what decides which. With ABSOLUTE it is an amount in the ad account currency in WHOLE units (50 = 50.00) and currency is REQUIRED. With MULTIPLIER it is a factor of the existing budget (2 = double it), NOT a currency amount, and currency is IGNORED -- send it and it is dropped rather than forwarded. Do NOT pre-convert to minor units: the conversion happens once, downstream, and a converted amount would scale the budget by a hundred.

A CAMPAIGN CANNOT MIX ABSOLUTE AND MULTIPLIER across its schedules. This API does NOT pre-check that -- the platform rejects the second type and its message is forwarded verbatim. A local check would have to read the campaign's other schedules first, which costs an extra call and still races two concurrent creates.

timeStart / timeEnd are Unix SECONDS on a 15-minute boundary (:00, :15, :30, :45) and timeEnd must be strictly after timeStart. The remaining window rules -- sitting inside the campaign's run dates, minimum lead time, no overlap -- are the platform's, and its message is forwarded verbatim.

THIS OPERATION IS NOT IDEMPOTENT: the same body sent twice creates TWO schedules and raises the budget twice. No Idempotency-Key is accepted, because the contract this API tracks declares none for this resource while declaring one explicitly for other write endpoints -- the silence is a choice, not an omission. Do not auto-retry a create.

Only served for Meta today. Another platform answers 501 -- the request is valid, the capability is what is missing.

Request body (CreateAdHighDemandPeriodDto): accountId (string, required), campaignId (string), adSetId (string), budgetValue (number, required), budgetValueType (enum: ABSOLUTE, MULTIPLIER, required), timeStart (number, required), timeEnd (number, required), recurrenceType (enum: ONE_TIME, WEEKLY, MONTHLY), currency (string)

listAdImagesGET /v1/ads/images

Lists the ad account's image library LIVE from the platform; rows are returned verbatim -- no key is added, renamed or dropped. Nothing is stored locally: a cached copy would show images that no longer exist the moment it went stale.

The default projection covers hash, url, name, dimensions and status. fields overrides it and is passed through, so an unknown field surfaces as the platform's own 400 rather than being silently dropped.

Any hash returned here is reusable wherever the platform accepts an image hash -- e.g. imageHash on POST /v1/ads/creatives.

Parameters:

  • after (query) — Cursor from paging.after of the previous page.
  • limit (query)
  • fields (query)
  • platform (query) — Defaults to 'facebook'. Only Meta is served today; anything else is a 501 rather than a silent empty list.
  • adAccountId (query, required) — Platform ad account id (act_<n>) whose library is read.
  • accountId (query, required) — Social account id used to resolve the platform token.

uploadAdImagePOST /v1/ads/images

Uploads raw image bytes into the ad account's image library -- for callers whose creatives are not hosted at a public URL. Returns the image hash (the platform identifier for the asset) and the platform-hosted url.

Max 31457280 bytes DECODED. imageBase64 takes raw base64 or a full data URL; the data:image/...;base64, prefix is stripped before decoding. The payload is validated strictly and a malformed one is rejected here, naming the field -- a lenient base64 decoder drops invalid characters silently and would upload corrupted bytes instead.

filename needs a filename extension (the platform rejects sample and sample.tmp); it defaults to ad_image.jpg.

Whether the platform de-duplicates identical bytes is NOT documented, so this operation is treated as NOT idempotent: an Idempotency-Key header is honoured here even though the contract does not declare one. The key is scoped by the DECODED bytes, so the same image sent raw and sent as a data URL count as the same request.

Parameters:

  • Idempotency-Key (header) — Client-generated unique key. Strongly recommended: the platform is not documented to de-duplicate repeated uploads.

Request body (UploadAdImageDto): accountId (string, required), adAccountId (string, required), imageBase64 (string, required), filename (string), platform (string)

queryAdInsightsGET /v1/ads/insights

Live, flexible insights query. objectId selects the node (act_<n>, a campaign, an ad set or an ad) and level sets row granularity; caller-chosen fields, breakdowns and filtering are forwarded and the platform's rows come back VERBATIM -- nothing is added, renamed or dropped. Nothing is stored locally: insight numbers are restated by the platform for days after the fact, so a cached copy would be wrong rather than merely stale.

SEMANTIC VALIDATION IS THE PLATFORM'S: an unknown field or an invalid breakdown combination comes back as the platform's own 400, carrying its message. This API validates SHAPE only (types, character set, page size, the date rule) -- enumerating the breakdown list here would mean rejecting a query the platform accepts the day it adds one.

DATES: datePreset and fromDate+toDate are mutually exclusive, the pair is all-or-nothing, and sending NEITHER is legal -- the platform then applies its own documented default rather than one invented here. An explicit pair may span at most 730 days.

For long ranges or agency-scale accounts prefer the async variant (POST /v1/ads/insights/reports).

Parameters:

  • after (query) — Cursor from paging.after of the previous page.
  • limit (query)
  • timeIncrement (query) — Days per row (1-90), monthly, or all_days.
  • toDate (query) — End of range (YYYY-MM-DD); requires fromDate.
  • fromDate (query) — Start of range (YYYY-MM-DD); requires toDate.
  • datePreset (query) — Platform date preset (e.g. last_7d). Mutually exclusive with fromDate/toDate.
  • filtering (query) — JSON array of Meta filter objects: [{"field", "operator", "value"}]. Applied server-side by the platform; value is forwarded unchanged.
  • useUnifiedAttributionSetting (query) — Use the ad sets' own attribution settings.
  • actionReportTime (query) — When actions are counted: impression, conversion or mixed.
  • actionAttributionWindows (query) — Comma-separated attribution windows (e.g. 7d_click,1d_view). Action values come back keyed per window.
  • actionBreakdowns (query) — Comma-separated Graph action breakdowns.
  • breakdowns (query) — Comma-separated Graph breakdowns (e.g. age,gender).
  • fields (query) — Comma-separated Graph insights fields (e.g. spend,impressions). Omitted = the platform's default set.
  • level (query) — Row granularity.
  • platform (query) — Defaults to 'facebook'. Only Meta is served today; anything else is a 501 rather than a silent empty list.
  • objectId (query, required) — Insights node: act_<n>, a campaign id, an ad set id or an ad id.
  • accountId (query, required) — Social account id used to resolve the platform token.

createAdInsightsReportPOST /v1/ads/insights/reports

Submits an ASYNCHRONOUS insights report. Same query surface as GET /v1/ads/insights, in the JSON body; the platform processes the report server-side, which is the right choice for long ranges or large accounts where the sync query is slow or rate-limited.

The answer is 202, not 201: what comes back is not a created RESOURCE but an ACCEPTED job. Poll it with GET /v1/ads/insights/reports/{reportRunId}; the rows are read there and never on this response.

This operation is NOT idempotent on the platform: the same body sent twice starts TWO runs, and the platform bills synchronous and asynchronous insights calls against the SAME budget -- so a retry spends the account's quota twice. An Idempotency-Key header is therefore honoured here even though the contract does not declare one.

The status field the contract shows on this response is NOT published: the platform's submit endpoint returns only the run id, so filling it would mean either a second billed call the caller did not ask for, or stating a status we never observed.

Parameters:

  • Idempotency-Key (header) — Client-generated unique key. Strongly recommended: the platform starts a second run for a repeated body and charges the same budget.

Request body (CreateAdInsightsReportDto): accountId (string, required), objectId (string, required), level (enum: ad, adset, campaign, account), fields (string), breakdowns (string), actionBreakdowns (string), actionAttributionWindows (array of string), actionReportTime (string), useUnifiedAttributionSetting (boolean), filtering (array of object), datePreset (string), fromDate (string), toDate (string), timeIncrement (integer | enum: monthly, all_days), platform (string)

getAdInsightsReportGET /v1/ads/insights/reports/{reportRunId}

Status and results for a run created via POST /v1/ads/insights/reports.

ONE CALL, UP TO TWO PLATFORM CALLS: the run is polled first, and its rows are read ONLY once the status is "Job Completed". Reading rows earlier would return an empty page for a job that is merely unfinished, which reads as a completed-but-empty report.

data IS ABSENT while the run is unfinished -- not an empty array. An empty array means "no rows matched"; a missing data means "the job is not done", and those are different facts.

An UNRECOGNISED status is answered as a normal 200 with the status passed through verbatim, not as an error: the platform documents six values today and may add a seventh, and calling an unknown one a failure would lose a customer a finished report. Poll again.

The platform expires a run id after 30 days; an expired id and an unknown id are indistinguishable on the wire (both surface as "Job Skipped"), which is the platform's gap and not one this API can close.

Parameters:

  • reportRunId (path, required) — The run id returned by POST /v1/ads/insights/reports.
  • after (query) — Cursor from paging.after of the previous page.
  • limit (query)
  • platform (query) — Defaults to 'facebook'. Only Meta is served today.
  • accountId (query, required) — Social account id used to resolve the platform token; it must be the same connection that created the run.

listAdKeywordsGET /v1/ads/keywords

Reads Google Ads live; provider data is not cached or persisted by Zhyper. accountId is required so tenant and credential revision fences can be enforced.

Parameters:

  • pageToken (query)
  • limit (query)
  • search (query)
  • negative (query)
  • matchType (query)
  • status (query)
  • adSetId (query)
  • campaignId (query)
  • adAccountId (query)
  • accountId (query, required)

generateKeywordHistoricalMetricsPOST /v1/ads/keywords/historical-metrics

Request body (GoogleAdsHistoricalMetricsRequestDto): accountId (string, required), customerId (string), countries (array of string), languageConstantId (string), network (enum: GOOGLE_SEARCH, GOOGLE_SEARCH_AND_PARTNERS), includeAdultKeywords (boolean), keywords (array of string, required), includeAverageCpc (boolean)

generateKeywordIdeasPOST /v1/ads/keywords/ideas

Request body (GoogleAdsKeywordIdeasRequestDto): accountId (string, required), customerId (string), countries (array of string), languageConstantId (string), network (enum: GOOGLE_SEARCH, GOOGLE_SEARCH_AND_PARTNERS), includeAdultKeywords (boolean), seedKeywords (array of string), seedUrl (string), pageSize (number), pageToken (string)

listAdLabelsGET /v1/ads/labels

Reads the ad account's labels, live from the platform.

ROWS ARE FORWARDED VERBATIM: keys are the ones the platform sent, nothing is added, renamed, dropped or coerced. Read them against the platform's own field reference and treat every field as optional.

THIS EDGE TAKES NO FIELD SELECTION, and that is a limit rather than an omission: the contract declares none, so any projection sent would be this API guessing. The practical consequence is that the platform decides what a row carries and it may hold nothing but id.

Only served for Meta ad accounts. Another platform answers 501 -- the request is valid, the capability is what is missing.

Parameters:

  • platform (query) — Defaults to 'facebook'. Only Meta is served today.
  • after (query) — Opaque cursor from a previous page.
  • limit (query) — Rows per page.
  • adAccountId (query, required) — Platform ad account id (act_<n>) whose labels are read.
  • accountId (query, required) — Social account id used to resolve the platform token.

listLeadFormsGET /v1/ads/lead-forms

Lists the Lead Gen FORM DEFINITIONS owned by the connected Facebook Page, read live from the platform. NO LEAD DATA IS RETURNED: this endpoint answers "which forms exist", never "who filled them in".

Rows are forwarded VERBATIM -- the contract declares them as an open object, so nothing is added, renamed or dropped and every field should be treated as optional.

pagination.hasMore is TRUE only when the platform announced a next page. It is not inferred from the row count, and the cursor is opaque: send it back as cursor, do not decode or store it.

A 403 with code facebook_lead_form_permission_missing means the connected Facebook account lacks pages_manage_ads. Lead form edges are PAGE edges, not ad-account edges, so they need a Page permission that older connections were never asked for: reconnect the account to grant it. The call is stopped before it reaches the platform, so it costs nothing.

Parameters:

  • cursor (query) — Cursor from pagination.cursor of the previous page.
  • limit (query)
  • platform (query) — Defaults to 'facebook'. Only Meta is served today; anything else is a 501 rather than a silent empty list.
  • adAccountId (query) — LinkedIn only: the ad account id used to resolve the owning organization. LinkedIn lead forms are NOT served today, so sending it answers 501 rather than being silently ignored. Meta forms live on the connected Page, which the account's own credential already names.
  • accountId (query, required) — Social account id used to resolve the platform token.

createLeadFormPOST /v1/ads/lead-forms

Creates a Lead Gen form on the connected Facebook Page and returns its id.

SEND THE FORM CONTENT INSIDE platformSpecificData. The old top-level fields (questions, thankYou*, locale, ...) are DEPRECATED but still accepted while platformSpecificData is ABSENT; sending BOTH shapes is a 400, because there would be no way to tell which one you meant. name and privacyPolicyUrl stay top-level in both shapes.

platformSpecificData is STRICT-PARSED: an unknown field is a 400, never a silently dropped one.

The form is created ACTIVE: the platform takes no status on this edge, so a draft cannot be created in one call.

THIS OPERATION IS NOT IDEMPOTENT: the same body sent twice creates TWO forms on the Page. No Idempotency-Key is accepted, because the contract this API tracks declares none for this resource while declaring one explicitly for other write endpoints -- the silence is a choice, not an omission. Do not auto-retry a create.

The returned name is the name the form was created with -- ECHOED from your request when the platform does not return one. This edge accepts no field selection, so a read-after-write is not available and a second call would double the operation's price.

A 403 with code facebook_lead_form_permission_missing means the connected Facebook account lacks pages_manage_ads. Lead form edges are PAGE edges, not ad-account edges, so they need a Page permission that older connections were never asked for: reconnect the account to grant it. The call is stopped before it reaches the platform, so it costs nothing.

Request body (CreateAdLeadFormDto): accountId (string, required), name (string, required), privacyPolicyUrl (string, required), questions (array of AdLeadFormQuestionDto), privacyPolicyLinkText (string), followUpActionUrl (string), locale (string), thankYouTitle (string), thankYouBody (string), thankYouButtonText (string), thankYouButtonType (string), thankYouWebsiteUrl (string), isOptimizedForQuality (boolean), platformSpecificData (MetaAdLeadFormPlatformDataDto), platform (string)

getLeadFormGET /v1/ads/lead-forms/{formId}

Reads one lead form DEFINITION live from the platform, forwarded VERBATIM -- same open shape as the list, and the same projection behind it. NO LEAD DATA IS RETURNED.

adAccountId IS NOT ACCEPTED on this operation: the form id resolves the Page by itself, and the contract does not declare it here. Sending it is a 400 rather than a silently ignored parameter.

A 403 with code facebook_lead_form_permission_missing means the connected Facebook account lacks pages_manage_ads. Lead form edges are PAGE edges, not ad-account edges, so they need a Page permission that older connections were never asked for: reconnect the account to grant it. The call is stopped before it reaches the platform, so it costs nothing.

Parameters:

  • formId (path, required) — The platform's own lead form id.
  • platform (query) — Defaults to 'facebook'. Only Meta is served today.
  • accountId (query, required) — Social account id used to resolve the platform token.

archiveLeadFormDELETE /v1/ads/lead-forms/{formId}

ARCHIVES the form; it is NOT deleted. The platform does not hard-delete lead forms: the form stays with status ARCHIVED and the leads it already collected stay reachable. That is why the response says archived: true and not deleted -- calling this operation is not the irreversible act the HTTP verb suggests.

Archiving an already archived form reaches the same state, so a retry is safe.

adAccountId IS NOT ACCEPTED here -- the form id resolves the Page.

A 403 with code facebook_lead_form_permission_missing means the connected Facebook account lacks pages_manage_ads. Lead form edges are PAGE edges, not ad-account edges, so they need a Page permission that older connections were never asked for: reconnect the account to grant it. The call is stopped before it reaches the platform, so it costs nothing.

Parameters:

  • formId (path, required) — The platform's own lead form id.
  • platform (query) — Defaults to 'facebook'. Only Meta is served today.
  • accountId (query, required) — Social account id used to resolve the platform token.

listFormLeadsGET /v1/ads/lead-forms/{formId}/leads

Reads /{form-id}/leads live with the connected Page token. The response is allowlisted and bounded; no personal data is cached, logged or persisted.

Parameters:

  • formId (path, required)
  • cursor (query)
  • since (query)
  • limit (query)
  • accountId (query, required)

createTestLeadPOST /v1/ads/lead-forms/{formId}/test-leads

Submits field_data and optional custom_disclaimer_responses to /{form-id}/test_leads. Meta permits one test lead per form at a time. The Idempotency-Key is mandatory and an uncertain provider outcome is fenced for the full replay-retention window rather than retried. The personal-data body is represented in the replay store only by one-way digests; the response stores only the created id.

Parameters:

  • formId (path, required)
  • Idempotency-Key (header, required)

Request body (CreateMetaTestLeadDto): accountId (string, required), fieldData (array of MetaLeadFieldDataDto, required), customDisclaimerResponses (array of MetaTestLeadDisclaimerResponseDto)

listLeadsGET /v1/ads/leads

This implementation is a live Meta Graph proxy and stores no lead rows. Therefore both accountId (to resolve the Page token) and formId (Meta only exposes leads under a form edge) are required. Zernio can make these filters optional because it keeps a webhook-fed cache; this API deliberately does not scan every form or persist submitted personal data.

The response is a bounded allowlisted projection; provider fields not named by this contract are never forwarded. No request, response or cursor is logged, cached or persisted.

Parameters:

  • cursor (query)
  • since (query) — Inclusive Unix-seconds lower bound.
  • limit (query)
  • formId (query, required)
  • accountId (query, required)

listLocalServicesLeadsGET /v1/ads/local-services/leads

Reads Local Services leads live from Google Ads; no lead contact data is persisted.

Parameters:

  • pageToken (query)
  • chargedOnly (query)
  • leadStatus (query)
  • leadType (query)
  • toDate (query)
  • fromDate (query)
  • customerId (query)
  • accountId (query, required)

listLocalServicesLeadConversationsGET /v1/ads/local-services/leads/{leadId}/conversations

Reads phone-call and message entries live from Google Ads; no conversation data is persisted.

Parameters:

  • leadId (path, required)
  • pageToken (query)
  • customerId (query)
  • accountId (query, required)

createMessagingAdPOST /v1/ads/messaging

Builds a whole campaign hierarchy in ONE call, or supplies adSetId to add creatives and ads under an existing Meta ad set while preserving its learning history. adSetId cannot be combined with campaign or adSet. A new campaign is created PAUSED and only flipped to ACTIVE as the LAST step. On the existing-ad-set path every new ad is created PAUSED and only flipped after ALL requested ads exist; the parent campaign/ad set is never paused because doing so could interrupt unrelated delivery.

PARTIAL FAILURE LEAVES WHAT IT BUILT. There is no compensating delete: if a later step fails, the campaign and ad set that already exist stay on the platform, paused, and the error carries them in details.created as {kind, platformId} entries. That is deliberate -- a surviving ad set is a HANDLE, not litter, and deleting it would close the documented way to continue.

The objects materialize in /v1/ads/tree on the next sync discovery pass, so no row is written locally here.

This endpoint accepts MORE THAN ONE ad under a single ad set: one creative each, all sharing the campaign, the ad set and the targeting. The response arrays line up with the request ads array.

The chat surface is chosen with the creative appDestination field (WHATSAPP or MESSENGER). Instagram Direct is NOT offered: the platform publishes no example of the value it expects, and sending an invented one would build a valid-looking ad on an undocumented field.

Parameters:

  • Idempotency-Key (header) — Client-generated unique key that makes retries safe. The same key with the same body replays the original 201 without calling the platform again; the same key with a different body is a 422, and a key still being processed is a 409. A request whose worst-case duration is longer than the replay lease is REFUSED with 422 rather than accepted, because a retry could otherwise start a second chain while the first is still running. Send fewer ads to fit, or send no key at all and accept that a retry duplicates the whole hierarchy.

Request body (CreateMessagingAdDto): accountId (string, required), adAccountId (string, required), campaign (AdChainCampaignDto), adSet (AdChainAdSetDto), adSetId (string), ads (array of AdChainAdDto, required), status (enum: ACTIVE, PAUSED)

generateAdPreviewsPOST /v1/ads/preview

Renders how a creative would look per placement BEFORE any ad exists, via Meta's /generatepreviews. Provide exactly one creative source: existingCreativeId or creativeSpec. Each preview is an HTML <iframe> snippet embeddable directly. Unknown formats values return Meta's 400 verbatim.

Request body (GenerateAdPreviewsDto): accountId (string, required), adAccountId (string, required), formats (array of string), existingCreativeId (string), creativeSpec (object), platform (string)

createRfPredictionPOST /v1/ads/rf-predictions

Creates a reach and frequency prediction -- a QUOTE. NOTHING IS BOUGHT here and no ad entities are created; the money-binding verb is POST /v1/ads/rf-predictions/{predictionId}/reserve.

SEND EXACTLY ONE of budgetAmount / reach. They ask opposite questions: send budgetAmount and the platform predicts the REACH, send reach and it predicts the BUDGET. Both, or neither, is a 400 -- and the two cases carry DIFFERENT codes (ambiguous_prediction_basis and missing_prediction_basis), because they are different mistakes with different fixes.

THE WINDOW MAY SPAN AT MOST EIGHT WEEKS (56 days) and endDate must be after startDate; both are checked before the request leaves this API. The rule that startDate be in the future is NOT checked here -- "future" depends on a clock this API does not share with the platform, so a borderline window is left for the platform to accept or refuse.

MONEY IS FORWARDED UNCONVERTED, IN BOTH DIRECTIONS. budgetAmount goes to the platform exactly as you send it and the platform reads it in the MINOR unit of the ad account currency (cents for USD); the quoted budget, minBudget and maxBudget come back in that same minor unit. This request carries no currency field, so this API cannot convert whole units for you -- and it does not guess, because the divisor is currency dependent (JPY, KRW and CLP have no minor unit).

THIS OPERATION IS NOT IDEMPOTENT: the same body sent twice leaves TWO predictions on the platform. No Idempotency-Key is accepted, because the contract this API tracks declares none for this resource while declaring one explicitly for other write endpoints -- the silence is a choice, not an omission.

Only served for Meta today. An account whose adapter does not implement reach and frequency answers 501 -- the request is valid, the capability is what is missing.

Request body (CreateRfPredictionDto): accountId (string, required), adAccountId (string, required), budgetAmount (number), reach (number), startDate (string, required), endDate (string, required), frequencyCap (number), targeting (object), placements (object)

getRfPredictionGET /v1/ads/rf-predictions/{predictionId}

Reads one prediction live from the platform.

THIS IS THE POLLING ENDPOINT. Predictions are produced asynchronously and reservations are confirmed asynchronously, so prediction.status may still be moving when a create or a reserve returns. Neither of those operations waits for you: waiting would mean unpriced extra calls against your platform quota.

prediction.status IS THE PLATFORM'S OWN NUMBER and this API publishes no names for it -- the vendor documents the field only as an unsigned integer and never says what each value means, so any ready/pending/failed mapping here would be invented.

RESERVING MINTS A NEW PREDICTION ID. The prediction.predictionId you get back from the reserve is NOT the id you reserved -- it is a new object. That RESERVED id is the one that belongs on POST /v1/ads/create with buyingType: "RESERVED", and it is the one the cancelling DELETE expects. Cancelling with the ORIGINAL quote id is refused by the platform.

MONEY FIELDS COME BACK IN THE MINOR UNIT of currency (cents for USD), unconverted. A bad prediction id answers 400, NOT 404: the platform reports it with one error that cannot be told apart from "no permission", and naming one of the two would state a fact this API never observed.

Only served for Meta today. An account whose adapter does not implement reach and frequency answers 501 -- the request is valid, the capability is what is missing.

Parameters:

  • predictionId (path, required) — The platform's own prediction id -- a quote id, or the RESERVED id a reserve minted.
  • adAccountId (query, required) — Meta ad account id (act_<n>). The platform reads the prediction from the node itself, so this value never reaches the wire -- it is still REQUIRED and validated, because silently ignoring it would let a call naming the wrong account succeed and never tell you.
  • accountId (query, required) — Social account id used to resolve the platform token.

cancelRfReservationDELETE /v1/ads/rf-predictions/{predictionId}

Releases a RESERVATION's locked price and inventory. It does NOT delete the prediction, and unreserved quotes need no cleanup at all -- they expire on their own.

THE ID MUST BE THE RESERVED ONE. That is the id a reserve minted, not the quote id you passed to the reserve; cancelling with the original quote id is refused by the platform.

THIS IS SAFE TO REPEAT: cancelling an already cancelled reservation reaches the same state. It is NOT safe to undo -- released inventory may go to other advertisers -- so the answer here is deliberately thin: a 2xx from the platform with no error is treated as success, and no confirmation shape is imposed on top of it. Imposing one that was never measured would answer "it failed" to a cancel that HAPPENED, and send you to undo something that cannot be undone.

THE 200 CARRIES NO BODY. It is the only one of the four reach and frequency operations that answers without one, and that is the contract's own shape: there is nothing left to read.

Only served for Meta today. An account whose adapter does not implement reach and frequency answers 501 -- the request is valid, the capability is what is missing.

Parameters:

  • predictionId (path, required) — The RESERVED prediction id -- the one the reserve returned, not the one you sent to it.
  • adAccountId (query, required) — Meta ad account id (act_<n>) the reservation lives under.
  • accountId (query, required) — Social account id used to resolve the platform token.

reserveRfPredictionPOST /v1/ads/rf-predictions/{predictionId}/reserve

THIS IS THE OPERATION THAT BINDS MONEY. It locks the quoted price and the inventory until the returned expiresAt. Everything before it -- the create, the reads -- buys nothing.

RESERVING MINTS A NEW PREDICTION ID. The prediction.predictionId you get back from the reserve is NOT the id you reserved -- it is a new object. That RESERVED id is the one that belongs on POST /v1/ads/create with buyingType: "RESERVED", and it is the one the cancelling DELETE expects. Cancelling with the ORIGINAL quote id is refused by the platform.

DO NOT AUTO-RETRY THIS CALL. A single prediction can be reserved MORE THAN ONCE: a repeat does not return the reservation you already have, it mints a SECOND one and locks the inventory again. That is measured platform behaviour, not a guess, and it is why this endpoint accepts no Idempotency-Key and is classified as an unsafe write. If a call times out, READ the prediction instead of reserving again.

THE RESERVATION IS CONFIRMED ASYNCHRONOUSLY, so prediction.status may not be final when this returns; this endpoint does not poll for you. Use GET /v1/ads/rf-predictions/{predictionId} with the RESERVED id.

RELEASE AN UNUSED RESERVATION with DELETE /v1/ads/rf-predictions/{predictionId}. Unreserved quotes need no cleanup -- they expire on their own.

Only served for Meta today. An account whose adapter does not implement reach and frequency answers 501 -- the request is valid, the capability is what is missing.

Parameters:

  • predictionId (path, required) — The QUOTE id to reserve. The id that comes back is a different one.

Request body (ReserveRfPredictionDto): accountId (string, required), adAccountId (string, required)

getAdsSearchTermsGET /v1/ads/search-terms

Parameters:

  • pageToken (query)
  • adGroupId (query)
  • campaignId (query)
  • toDate (query)
  • fromDate (query)
  • customerId (query)
  • accountId (query, required)

listAdStudiesGET /v1/ads/studies

Reads the ad account's A/B studies, live from the platform.

ROWS ARE FORWARDED VERBATIM: keys are the ones the platform sent, nothing is added, renamed, dropped or coerced. Read them against the platform's own field reference and treat every field as optional.

fields IS A RAW OVERRIDE, not a convenience: send it and you get exactly the projection you asked for, including nested ones such as cells{id,name,treatment_percentage,control_percentage}. A FLAT cells RETURNS IDS ONLY, so the split percentages need the nested form. Omit fields and the default projection is id,name,type,description,start_time,end_time,updated_time,cells{id,name,treatment_percentage,control_percentage}.

Only served for Meta ad accounts. Another platform answers 501 -- the request is valid, the capability is what is missing.

Parameters:

  • platform (query) — Defaults to 'facebook'. Only Meta is served today.
  • fields (query) — Platform field selection, forwarded VERBATIM. Nested projections such as cells{id,name} are supported and a flat cells returns ids only.
  • after (query) — Opaque cursor from a previous page.
  • limit (query) — Rows per page.
  • adAccountId (query, required) — Platform ad account id (act_<n>) whose studies are read.
  • accountId (query, required) — Social account id used to resolve the platform token.

getAdsTimelineGET /v1/ads/timeline

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.

Date range defaults to the last 90 days. Capped at 730 days. Ranges older than the ingested history return a 202 immediately with the covered part and backfillPending: true while the rest is backfilled in the background; repeat the request shortly until it returns 200 with full data.

Parameters:

  • platform (query) — Restrict to one platform. Values this API does not ingest ads for return an empty series rather than an error.
  • toDate (query) — Inclusive end of the metrics range. Defaults to today. Max 730-day range.
  • fromDate (query) — Inclusive start of the metrics range. Defaults to 90 days ago.
  • adAccountId (query) — Optional platform ad account id. Use when the connection wraps multiple platform ad accounts and the chart should show one only.
  • accountId (query, required) — Social account id whose ads are aggregated.

getAdTreeGET /v1/ads/tree

A two-stage aggregation over the same ad documents: ads group into ad sets, ad sets into campaigns, and metrics roll up from the leaves so a parent's totals are its children's totals by construction. Ads the platform reported without a campaign or ad set fall into synthetic ungrouped:none buckets. Pagination is at the campaign level.

Parameters:

  • limit (query)
  • page (query)
  • toDate (query)
  • fromDate (query)
  • source (query)
  • adAccountId (query)
  • profileId (query)
  • accountId (query)

listValueRuleSetsGET /v1/ads/value-rule-sets

Lists the ad account's value rule sets, read live from the platform. Nothing is stored locally, so valueRuleSetId here is the id every other operation on this resource takes in its path.

A value rule set raises or lowers the bid by a percentage for specific audiences. RULES ARE EVALUATED IN ARRAY ORDER and only the FIRST matching rule adjusts the bid for an audience several rules would match, so the order returned here is behaviour, not presentation.

The platform does not document paging on this edge: paging.after comes back null when it omits cursors, which is also what an exhausted page looks like.

Value rule sets only apply to LOWEST_COST_WITHOUT_CAP and COST_CAP ad sets; the platform rejects the rest server-side and this API does not pre-check it.

Parameters:

  • after (query) — Cursor from paging.after of the previous page.
  • limit (query)
  • platform (query) — Defaults to 'facebook'. Only Meta is served today; anything else is a 501 rather than a silent empty list.
  • adAccountId (query, required) — Platform ad account id (act_<n>) whose value rule sets are listed. It is REQUIRED here and on create, and NOT accepted on read, update or delete -- those resolve from the rule set id alone.
  • accountId (query, required) — Social account id used to resolve the platform token.

createValueRuleSetPOST /v1/ads/value-rule-sets

Creates a value rule set in the ad account and returns its id.

RULES ARE EVALUATED IN ARRAY ORDER: only the FIRST matching rule adjusts the bid for an audience that several rules would match, so the order you send is the behaviour you get. Send rules WITHOUT an id -- on a create there is nothing to keep.

THIS OPERATION IS NOT IDEMPOTENT: the same body sent twice creates TWO rule sets. No Idempotency-Key is accepted, because the contract this API tracks declares none for this resource while declaring one explicitly for other write endpoints -- the silence is a choice, not an omission. Do not auto-retry a create.

The platform enforces its own ceiling on how many rule sets an ad account may hold; this API does not pre-check it.

Request body (CreateAdValueRuleSetDto): accountId (string, required), adAccountId (string, required), name (string, required), rules (array of AdValueRuleDto, required), platform (string)

getValueRuleSetGET /v1/ads/value-rule-sets/{valueRuleSetId}

Reads one value rule set live from the platform.

THIS IS STEP ONE OF ANY EDIT. The update endpoint is a full replace, so the safe way to change one rule is to read the set here, edit what you got, and send the whole thing back -- keeping the id of every rule you want to keep.

adAccountId IS NOT ACCEPTED on this operation: the rule set id resolves the account by itself. Sending it is a 400 rather than a silently ignored parameter.

A bad rule set id answers 400, NOT 404. The platform reports it with one error that cannot be told apart from "no permission" or "account not enabled", and naming one of the three would state a fact this API never observed.

Parameters:

  • valueRuleSetId (path, required) — The platform's own value rule set id.
  • platform (query) — Defaults to 'facebook'. Only Meta is served today.
  • accountId (query, required) — Social account id used to resolve the platform token.

updateValueRuleSetPUT /v1/ads/value-rule-sets/{valueRuleSetId}

THIS IS A FULL REPLACE, NOT A PATCH. The body you send BECOMES the rule set. Keep a rule by echoing its id; create one by sending the object WITHOUT an id; DELETE one by OMITTING it from the array. THERE IS NO WARNING AND NO UNDO. name and rules are both required for exactly this reason: a partial body would silently destroy every rule left out. Read the set with GET /v1/ads/value-rule-sets/{valueRuleSetId} FIRST and edit what you get back.

RULE ORDER IS BEHAVIOUR: only the FIRST matching rule adjusts the bid for an overlapping audience, and the order you send is the order that is stored and returned.

adAccountId IS NOT ACCEPTED here -- the rule set id resolves the account. Sending it is a 400.

Ad sets pointing at this rule set are not touched by an update. A bad rule set id answers 400, not 404, for the reason given on the read operation.

Parameters:

  • valueRuleSetId (path, required) — The platform's own value rule set id.

Request body (UpdateAdValueRuleSetDto): accountId (string, required), name (string, required), rules (array of AdValueRuleDto, required), platform (string)

deleteValueRuleSetDELETE /v1/ads/value-rule-sets/{valueRuleSetId}

Deletes the value rule set on the platform. Nothing local is removed, because nothing local was ever written.

AD SETS POINTING AT IT ARE NOT MODIFIED HERE. Detach them first with valueRulesApplied: false on PUT /v1/ads/ad-sets/{adSetId}; doing it for you would mean silently editing objects you did not name in this request.

A bad rule set id answers 400, not 404, for the reason given on the read operation.

Parameters:

  • valueRuleSetId (path, required) — The platform's own value rule set id.
  • platform (query) — Defaults to 'facebook'. Only Meta is served today.
  • accountId (query, required) — Social account id used to resolve the platform token.

uploadAdVideoPOST /v1/ads/videos

Accepts exactly one source. Public URL downloads are HTTPS-only, DNS and redirect targets are SSRF checked, and both time and bytes are bounded. The call returns only after Meta reports the video ready.

Parameters:

  • Idempotency-Key (header, required) — Required retry key for this at-most-once video upload.

Request body (UploadAdVideoDto): accountId (string, required), adAccountId (string, required), videoUrl (string), videoBase64 (string), filename (string)

listAdVideosGET /v1/ads/videos

Parameters:

  • after (query)
  • limit (query)
  • fields (query)
  • adAccountId (query, required)
  • accountId (query, required)

deleteAdVideoDELETE /v1/ads/videos/{videoId}

Parameters:

  • videoId (path, required)
  • Idempotency-Key (header)
  • adAccountId (query, required)
  • accountId (query, required)

analytics

OperationMethodPathSummary
getFacebookPostReactionsGET/v1/accounts/{accountId}/facebook-post-reactionsGet Facebook post reactions
getLinkedInAggregateAnalyticsGET/v1/accounts/{accountId}/linkedin-aggregate-analyticsGet LinkedIn aggregate stats
getLinkedInMentionsGET/v1/accounts/{accountId}/linkedin-mentionsResolve LinkedIn mention
getLinkedInPostAnalyticsGET/v1/accounts/{accountId}/linkedin-post-analyticsGet LinkedIn post stats
getLinkedInPostReactionsGET/v1/accounts/{accountId}/linkedin-post-reactionsGet LinkedIn post reactions
getAnalyticsGET/v1/analyticsRead metrics for one published post
getBestTimeToPostGET/v1/analytics/best-timeGet best times to post
getContentDecayGET/v1/analytics/content-decayGet content performance decay
getDailyMetricsGET/v1/analytics/daily-metricsGet daily publication metrics
getFacebookPageInsightsGET/v1/analytics/facebook/page-insightsGet Facebook Page insights
getFacebookPostEarningsGET/v1/analytics/facebook/post-earningsGet Facebook post monetization earnings
getGoogleBusinessPerformanceGET/v1/analytics/googlebusiness/performanceGet Google Business performance metrics
getGoogleBusinessSearchKeywordsGET/v1/analytics/googlebusiness/search-keywordsGet Google Business search keywords
getInstagramAccountInsightsGET/v1/analytics/instagram/account-insightsGet Instagram account insights
getInstagramDemographicsGET/v1/analytics/instagram/demographicsGet Instagram audience demographics
getInstagramFollowerHistoryGET/v1/analytics/instagram/follower-historyGet Instagram follower history
getLinkedInOrgAggregateAnalyticsGET/v1/analytics/linkedin/org-aggregate-analyticsGet LinkedIn org analytics
getPostTimelineGET/v1/analytics/post-timelineGet post analytics timeline
getPostingFrequencyGET/v1/analytics/posting-frequencyGet frequency vs engagement
getTikTokAccountInsightsGET/v1/analytics/tiktok/account-insightsGet TikTok account-level insights
getYouTubeChannelInsightsGET/v1/analytics/youtube/channel-insightsGet YouTube channel insights
getYouTubeDailyViewsGET/v1/analytics/youtube/daily-viewsGet YouTube daily views
getYouTubeDemographicsGET/v1/analytics/youtube/demographicsGet YouTube audience demographics
getYouTubeVideoRetentionGET/v1/analytics/youtube/video-retentionGet YouTube video retention

getFacebookPostReactionsGET /v1/accounts/{accountId}/facebook-post-reactions

Returns the provider-verified reaction breakdown for one stored published Facebook Page post.

Parameters:

  • accountId (path, required) — The internal id of the connected Facebook Page account.
  • postId (query, required) — The numeric Facebook post id or canonical Page post id.

getLinkedInAggregateAnalyticsGET /v1/accounts/{accountId}/linkedin-aggregate-analytics

Returns TOTAL or DAILY analytics across posts published through Zernio for a personal LinkedIn account.

Parameters:

  • accountId (path, required)
  • metrics (query)
  • endDate (query)
  • startDate (query)
  • aggregation (query)

getLinkedInMentionsGET /v1/accounts/{accountId}/linkedin-mentions

Resolves a LinkedIn person or organization vanity URL to a safe ready-to-use mention format.

Parameters:

  • accountId (path, required)
  • displayName (query)
  • url (query, required)

getLinkedInPostAnalyticsGET /v1/accounts/{accountId}/linkedin-post-analytics

Returns available analytics for one LinkedIn share or UGC Post without fabricating unsupported counters.

Parameters:

  • accountId (path, required)
  • urn (query, required)

getLinkedInPostReactionsGET /v1/accounts/{accountId}/linkedin-post-reactions

Returns one offset-paginated page of organization-post reactions without inventing unavailable reactor profile fields.

Parameters:

  • accountId (path, required)
  • cursor (query)
  • limit (query)
  • urn (query, required)

getAnalyticsGET /v1/analytics

Reads each published Pinterest, X, Facebook, Instagram or Threads target through its connected account. Only provider-backed metrics are returned; unsupported fields are omitted instead of being filled with invented zeroes.

Parameters:

  • postId (query, required)

getBestTimeToPostGET /v1/analytics/best-time

Groups complete provider-backed engagement snapshots by publication weekday and UTC hour. Returns an empty slots array until honest coverage exists. Without fromDate/toDate the endpoint reads a default 180-day publication window. The requested fromDate/toDate range cannot exceed 365 days. A window matching more than 50000 underlying rows is refused with 400 analytics_window_too_large rather than aggregated from a truncated set; narrow it by raising fromDate or by adding a platform, profileId or accountId filter.

Parameters:

  • source (query)
  • toDate (query)
  • fromDate (query)
  • accountId (query)
  • profileId (query)
  • platform (query)

getContentDecayGET /v1/analytics/content-decay

Calculates observed engagement accumulation from durable cumulative snapshots. Sparse or incomplete histories are omitted rather than converted to zero. Without fromDate/toDate the endpoint reads a default 180-day publication window. The requested fromDate/toDate range cannot exceed 365 days. A window matching more than 50000 underlying rows is refused with 400 analytics_window_too_large rather than aggregated from a truncated set; narrow it by raising fromDate or by adding a platform, profileId or accountId filter.

Parameters:

  • source (query)
  • toDate (query)
  • fromDate (query)
  • accountId (query)
  • profileId (query)
  • platform (query)

getDailyMetricsGET /v1/analytics/daily-metrics

Returns durable publication counts for Zhyper and imported external posts. Provider engagement fields are omitted because Zhyper does not persist historical metric snapshots. Without fromDate/toDate the endpoint reads a default 180-day publication window. The requested fromDate/toDate range cannot exceed 365 days. A window matching more than 50000 underlying rows is refused with 400 analytics_window_too_large rather than aggregated from a truncated set; narrow it by raising fromDate or by adding a platform, profileId or accountId filter.

Parameters:

  • attribution (query)
  • source (query)
  • toDate (query)
  • fromDate (query)
  • accountId (query)
  • profileId (query)
  • platform (query)

getFacebookPageInsightsGET /v1/analytics/facebook/page-insights

Returns current Facebook Page counters, follower deltas, and optional unscaled daily monetization metrics for at most 89 days.

Parameters:

  • metricType (query)
  • until (query)
  • since (query)
  • metrics (query)
  • accountId (query, required)

getFacebookPostEarningsGET /v1/analytics/facebook/post-earnings

Returns unscaled lifetime monetization earnings for one Page-owned Facebook post.

Parameters:

  • metrics (query)
  • postId (query, required)
  • accountId (query, required)

getGoogleBusinessPerformanceGET /v1/analytics/googlebusiness/performance

TEST MODE ONLY TODAY. No Google Business adapter is composed into the live registry, so a Google Business account cannot be connected in live mode and this operation fails closed there rather than returning partial data. Setting a live-mode environment flag does not change this. Use test mode for this vertical.

Parameters:

  • endDate (query)
  • startDate (query)
  • metrics (query)
  • accountId (query, required)

getGoogleBusinessSearchKeywordsGET /v1/analytics/googlebusiness/search-keywords

TEST MODE ONLY TODAY. No Google Business adapter is composed into the live registry, so a Google Business account cannot be connected in live mode and this operation fails closed there rather than returning partial data. Setting a live-mode environment flag does not change this. Use test mode for this vertical.

Parameters:

  • endMonth (query)
  • startMonth (query)
  • accountId (query, required)

getInstagramAccountInsightsGET /v1/analytics/instagram/account-insights

Returns Meta account-level totals, a reach time series, or compatible total-value breakdowns for at most 90 days.

Parameters:

  • breakdown (query)
  • metricType (query)
  • until (query)
  • since (query)
  • metrics (query)
  • accountId (query, required)

getInstagramDemographicsGET /v1/analytics/instagram/demographics

Returns Meta audience demographics, expanding each requested dimension into one provider insights read.

Parameters:

  • timeframe (query)
  • breakdown (query) — Comma-separated unique dimensions. Defaults to all dimensions.
  • metric (query)
  • accountId (query, required)

getInstagramFollowerHistoryGET /v1/analytics/instagram/follower-history

Returns the locally captured daily follower count and derived daily gains/losses for at most 90 days.

Parameters:

  • metricType (query)
  • until (query)
  • since (query)
  • metrics (query)
  • accountId (query, required)

getLinkedInOrgAggregateAnalyticsGET /v1/analytics/linkedin/org-aggregate-analytics

Returns organization share, follower-gain, and page-view analytics for at most 89 days.

Parameters:

  • metricType (query)
  • until (query)
  • since (query)
  • metrics (query)
  • accountId (query, required)

getPostTimelineGET /v1/analytics/post-timeline

Returns the latest durable provider metrics for each UTC day. Internal post IDs, external-post IDs and provider post IDs are resolved within the caller scope.

Parameters:

  • toDate (query)
  • fromDate (query)
  • postId (query, required)

getPostingFrequencyGET /v1/analytics/posting-frequency

Groups fully covered UTC calendar weeks by platform and publication count. Incomplete weeks are omitted instead of lowering their averages with invented zeroes. Without fromDate/toDate the endpoint reads a default 180-day publication window. The requested fromDate/toDate range cannot exceed 365 days. A window matching more than 50000 underlying rows is refused with 400 analytics_window_too_large rather than aggregated from a truncated set; narrow it by raising fromDate or by adding a platform, profileId or accountId filter.

Parameters:

  • source (query)
  • toDate (query)
  • fromDate (query)
  • accountId (query)
  • profileId (query)
  • platform (query)

getTikTokAccountInsightsGET /v1/analytics/tiktok/account-insights

Reads TikTok public account counters. Metrics without retained daily snapshots are explicitly reported as unavailable, never fabricated as zero.

Parameters:

  • metricType (query)
  • until (query)
  • since (query)
  • metrics (query)
  • accountId (query, required)

getYouTubeChannelInsightsGET /v1/analytics/youtube/channel-insights

Returns YouTube Analytics API v2 channel totals or daily values. The provider query is clamped to three days ago because recent data may not be finalized; requested tail days remain present as zeroes.

Parameters:

  • metricType (query)
  • until (query)
  • since (query)
  • metrics (query)
  • accountId (query, required)

getYouTubeDailyViewsGET /v1/analytics/youtube/daily-views

Returns one owned video's YouTube Analytics API v2 daily metrics. The default end date excludes YouTube's three-day processing window; an explicit recent endDate is honored and marked provisional.

Parameters:

  • endDate (query)
  • startDate (query)
  • accountId (query, required)
  • videoId (query, required)

getYouTubeDemographicsGET /v1/analytics/youtube/demographics

Returns requested age/gender viewer percentages and country view counts for a channel or owned video.

Parameters:

  • endDate (query)
  • startDate (query)
  • breakdown (query)
  • videoId (query)
  • accountId (query, required)

getYouTubeVideoRetentionGET /v1/analytics/youtube/video-retention

Returns up to 100 official elapsed-video-time retention segments for one owned video.

Parameters:

  • endDate (query)
  • startDate (query)
  • accountId (query, required)
  • videoId (query, required)

apikeys

OperationMethodPathSummary
createApiKeyPOST/v1/api-keysCreate an API key
listApiKeysGET/v1/api-keysList active API keys
deleteApiKeyDELETE/v1/api-keys/{keyId}Delete key

createApiKeyPOST /v1/api-keys

Request body (CreateApiKeyDto): name (string), mode (enum: live, test), readOnly (boolean), profileIds (array of string), expiresIn (integer), scope (enum: full, profiles), permission (enum: read-write, read)

listApiKeysGET /v1/api-keys

Parameters:

  • cursor (query)
  • limit (query)

deleteApiKeyDELETE /v1/api-keys/{keyId}

Permanently revokes and deletes an API key.

Parameters:

  • keyId (path, required)

audit-log

OperationMethodPathSummary
listAuditLogGET/v1/audit-logList the team audit log

listAuditLogGET /v1/audit-log

Parameters:

  • createdUntil (query) — Exclusive upper bound (created_at < value), RFC 3339 UTC.
  • createdFrom (query) — Inclusive lower bound (created_at >= value), RFC 3339 UTC.
  • action (query)
  • cursor (query)
  • limit (query)

auth

OperationMethodPathSummary
forgotPasswordPOST/v1/auth/forgot-passwordRequest a password reset e-mail
loginPOST/v1/auth/loginStart a panel session
logoutPOST/v1/auth/logoutEnd the current session
getCurrentSessionGET/v1/auth/meRetrieve the signed-in user, team and role
changePasswordPOST/v1/auth/passwordChange the password and revoke every other session
registerPOST/v1/auth/registerCreate a team and its owner account
resetPasswordPOST/v1/auth/reset-passwordConsume a password reset token and set a new password
verifyCredentialGET/v1/auth/verifyVerify credential
verifyEmailPOST/v1/auth/verify-emailConsume an email verification token

forgotPasswordPOST /v1/auth/forgot-password

Always returns the same response and takes the same time whether or not an account exists for the address. If one does, a single-use, time-limited reset link is e-mailed to it.

Request body (ForgotPasswordDto): email (string, required)

loginPOST /v1/auth/login

Request body (LoginDto): email (string, required), password (string, required)

logoutPOST /v1/auth/logout

getCurrentSessionGET /v1/auth/me

changePasswordPOST /v1/auth/password

Request body (ChangePasswordDto): currentPassword (string, required), newPassword (string, required)

registerPOST /v1/auth/register

Request body (RegisterDto): email (string, required), password (string, required), name (string), teamName (string), timezone (string)

resetPasswordPOST /v1/auth/reset-password

Single use: the token is invalidated on success, when a newer one is issued, and when it expires. Every existing session for the account is revoked.

Request body (ResetPasswordDto): token (string, required), newPassword (string, required)

verifyCredentialGET /v1/auth/verify

Checks whether the bearer credential or panel session on this request is valid without reading product data.

verifyEmailPOST /v1/auth/verify-email

Request body (VerifyEmailDto): token (string, required)

billing

OperationMethodPathSummary
getBillingGET/v1/billingPrepaid balance and the insufficient-balance rule
createBillingCheckoutPOST/v1/billing/checkoutStart a Stripe Checkout session to top up the prepaid balance
getXApiPricingGET/v1/billing/x-pricingMachine-readable X (twitter) pricing and the tariff rule

getBillingGET /v1/billing

The PREPAID balance of the calling team. Amounts are INTEGER micro-USD; the scale is DECLARED through microsPerUnit, because for X the unit price is BELOW a cent ($0.015) and a cent based integer could not have represented that item at all. A team that has NEVER TOPPED UP gets a zero balance, NOT a 404: the balance is the sum of the ledger and the sum of an empty ledger is zero; the information "no movement ever happened" is carried by updatedAt: null. The balance CAN also be NEGATIVE - escrow reservation is deliberately absent and concurrent publishes can produce a bounded overdraw. WHAT HAPPENS IF THE BALANCE IS NOT ENOUGH, and THE ASYMMETRY: publishing stops only on METERED platforms (today the single item is X). None of the other publish platforms are metered, so on them publishing is NEVER blocked and nothing is deducted from the balance. Both lists are in the response (insufficientBalance) and together they cover ALL generic publish platforms; Ads/Blog account platforms are not in these publish lists; the error code that stops it is there as well (errorCode). A ZERO BALANCE DOES NOT MEAN "exempt": a team that has never topped up CANNOT publish on a metered platform. Whether the rule is enforced TODAY is written in a separate field (enforcement); we do not declare an unenforced rule as if it were in force. This endpoint DOES NOT RETURN PAYMENT METHOD, CARD, INVOICE, collection history or the Stripe customer id.

createBillingCheckoutPOST /v1/billing/checkout

Starts a top-up of the PREPAID balance and returns the Stripe Checkout address the customer is redirected to. THIS ENDPOINT DOES NOT CHANGE THE BALANCE: the balance increases only after the payment completes, through the Stripe signed webhook. So calling this endpoint twice opens two sessions but the customer earns credit only for the session(s) they actually paid for. PANEL-ONLY: it cannot be called with an API key (403 actor_not_allowed); the billing:manage permission is required; it is open to the owner, admin and billing_admin roles. If Stripe is not configured in this deployment the endpoint returns 404 - so that the declaration and the behavior cannot diverge, the endpoint of a configuration that does not exist does not exist either.

Request body (CreateBillingCheckoutDto): amountUsd (string, required)

getXApiPricingGET /v1/billing/x-pricing

The per-operation tariff of X publishes, machine-readable (PRD "16. Faturalandirma" -> transparency bar). The numbers come from the SAME expression as the value actually deducted on the publish path; that the declared price and the charged price cannot diverge is the sole reason this endpoint exists. Amounts are INTEGER micro-USD (microsPerUnit), because $0.015 cannot be represented exactly in floating point and the sum of thousands of rows drifts. THERE ARE TWO TARIFFS and there is more than a 13-FOLD difference between them; the link detection that determines which one applies is DECLARED under rule. The detector is BROAD and the direction is DELIBERATE: a narrow detector under-bills us and we pay the difference, a broad detector may charge the customer too much but it is VISIBLE - the name of the applied tariff is written both into the metadata field of the billable event and defined here, so the customer CAN DISPUTE it. rule.examples shows the rule with runnable examples and does not hide the accepted false positive class. There is NO metered platform BESIDES X; publishing on other platforms is not billed (see GET /v1/billing).

blogs

OperationMethodPathSummary
listBlogsGET/v1/accounts/{accountId}/blogsList Shopify blogs
createBlogPOST/v1/accounts/{accountId}/blogsCreate a Shopify blog
getBlogGET/v1/accounts/{accountId}/blogs/{blogId}Get a Shopify blog
updateBlogPATCH/v1/accounts/{accountId}/blogs/{blogId}Update a Shopify blog
deleteBlogDELETE/v1/accounts/{accountId}/blogs/{blogId}Permanently delete a Shopify blog and its articles
listBlogArticlesGET/v1/accounts/{accountId}/blogs/{blogId}/articlesList Shopify blog articles
createBlogArticlePOST/v1/accounts/{accountId}/blogs/{blogId}/articlesCreate or natively schedule a Shopify article
getBlogArticleGET/v1/accounts/{accountId}/blogs/{blogId}/articles/{articleId}Get a Shopify blog article
updateBlogArticlePATCH/v1/accounts/{accountId}/blogs/{blogId}/articles/{articleId}Update a Shopify blog article
deleteBlogArticleDELETE/v1/accounts/{accountId}/blogs/{blogId}/articles/{articleId}Permanently delete a Shopify blog article

listBlogsGET /v1/accounts/{accountId}/blogs

Parameters:

  • accountId (path, required)
  • cursor (query)
  • limit (query)

createBlogPOST /v1/accounts/{accountId}/blogs

A required Idempotency-Key retains the completed create outcome (the provider resource ID) for 24 hours. Every retry reads the current Shopify resource, so a later edit or deletion can change the representation or return a typed read error, but it never starts a second create. If the provider may have committed but its response was lost, retries return 409 idempotency_key_outcome_unknown instead of creating a duplicate.

Parameters:

  • accountId (path, required)
  • Idempotency-Key (header, required) — Required client-generated retry key for this exact create request.

Request body (ShopifyBlogCreateDto): title (string, required), handle (string)

getBlogGET /v1/accounts/{accountId}/blogs/{blogId}

Parameters:

  • accountId (path, required)
  • blogId (path, required)

updateBlogPATCH /v1/accounts/{accountId}/blogs/{blogId}

Parameters:

  • accountId (path, required)
  • blogId (path, required)

Request body (ShopifyBlogUpdateDto): title (string), handle (string)

deleteBlogDELETE /v1/accounts/{accountId}/blogs/{blogId}

Parameters:

  • accountId (path, required)
  • blogId (path, required)

listBlogArticlesGET /v1/accounts/{accountId}/blogs/{blogId}/articles

Parameters:

  • accountId (path, required)
  • blogId (path, required)
  • cursor (query)
  • limit (query)

createBlogArticlePOST /v1/accounts/{accountId}/blogs/{blogId}/articles

A required Idempotency-Key retains the completed create outcome (the provider resource ID) for 24 hours. Every retry reads the current Shopify article, so later content changes or deletion can change the representation or return a typed read error, but it never starts a second create. An ambiguous provider outcome is fenced and returns 409 on retry.

Parameters:

  • accountId (path, required)
  • blogId (path, required)
  • Idempotency-Key (header, required) — Required client-generated retry key for this exact create request.

Request body (ShopifyBlogArticleCreateDto): title (string, required), bodyHtml (string), handle (string), tags (array of string), author (string), excerpt (string), image (ShopifyBlogImageDto), seo (ShopifyBlogSeoDto), isPublished (boolean), publishDate (string)

getBlogArticleGET /v1/accounts/{accountId}/blogs/{blogId}/articles/{articleId}

Parameters:

  • accountId (path, required)
  • blogId (path, required)
  • articleId (path, required)

updateBlogArticlePATCH /v1/accounts/{accountId}/blogs/{blogId}/articles/{articleId}

Parameters:

  • accountId (path, required)
  • blogId (path, required)
  • articleId (path, required)

Request body (ShopifyBlogArticleWriteDto): title (string), bodyHtml (string), handle (string), tags (array of string), author (string), excerpt (string), image (ShopifyBlogImageDto), seo (ShopifyBlogSeoDto), isPublished (boolean), publishDate (string)

deleteBlogArticleDELETE /v1/accounts/{accountId}/blogs/{blogId}/articles/{articleId}

Parameters:

  • accountId (path, required)
  • blogId (path, required)
  • articleId (path, required)

broadcasts

OperationMethodPathSummary
listBroadcastsGET/v1/broadcastsList broadcasts
createBroadcastPOST/v1/broadcastsCreate a broadcast
getBroadcastGET/v1/broadcasts/{broadcastId}Retrieve a broadcast
updateBroadcastPATCH/v1/broadcasts/{broadcastId}Update a broadcast
deleteBroadcastDELETE/v1/broadcasts/{broadcastId}Delete a broadcast
cancelBroadcastPOST/v1/broadcasts/{broadcastId}/cancelCancel a broadcast
addBroadcastRecipientsPOST/v1/broadcasts/{broadcastId}/recipientsAdd recipients to a broadcast
listBroadcastRecipientsGET/v1/broadcasts/{broadcastId}/recipientsList broadcast recipients
scheduleBroadcastPOST/v1/broadcasts/{broadcastId}/scheduleSchedule a broadcast over its frozen audience
sendBroadcastPOST/v1/broadcasts/{broadcastId}/sendStart durable broadcast delivery

listBroadcastsGET /v1/broadcasts

Offset pagination with skip/limit. This endpoint does not accept a cursor.

Parameters:

  • skip (query)
  • limit (query)
  • platform (query)
  • status (query)
  • profileId (query) — If omitted, the records of ALL profiles the key can reach are returned.

createBroadcastPOST /v1/broadcasts

Creates a draft. Compile its audience with POST /v1/broadcasts/{broadcastId}/recipients before scheduling or sending.

Request body (CreateBroadcastDto): profileId (string, required), accountId (string, required), platform (string, required), name (string, required), description (string), message (string), template (object), segmentFilters (object)

getBroadcastGET /v1/broadcasts/{broadcastId}

Parameters:

  • broadcastId (path, required)

updateBroadcastPATCH /v1/broadcasts/{broadcastId}

Only provided fields change; sending null clears a nullable field. profileId, accountId, platform and status are immutable. Editing is only allowed while the broadcast is a draft or scheduled: once sending has started the audience is frozen and changing the body would change what part of that audience receives.

Parameters:

  • broadcastId (path, required)

Request body (UpdateBroadcastDto): name (string), description (string), message (string), template (object), segmentFilters (object)

deleteBroadcastDELETE /v1/broadcasts/{broadcastId}

Permanently deletes the broadcast and its recipient rows. There is no soft-delete window. A broadcast that is currently sending cannot be deleted: cancel it first.

Parameters:

  • broadcastId (path, required)

cancelBroadcastPOST /v1/broadcasts/{broadcastId}/cancel

Cancels a draft, scheduled or sending broadcast. Recipients that were already sent stay sent and recipients that were never attempted stay pending - there is no recipient-level cancelled state, and marking them failed would claim an attempt that never happened. Cancelling an already cancelled broadcast is a no-op; a completed or failed broadcast cannot be cancelled.

Parameters:

  • broadcastId (path, required)

addBroadcastRecipientsPOST /v1/broadcasts/{broadcastId}/recipients

Compiles recipients into concrete rows and FREEZES them (PRD 11.4: the audience is frozen at compile time, not at send time). Sources combine: contactIds, raw phones, and useSegment which compiles the broadcast stored segmentFilters. Re-sending the same request is harmless - duplicate addresses are skipped and counted. Only allowed while the broadcast is a draft or scheduled.

Parameters:

  • broadcastId (path, required)

Request body (AddBroadcastRecipientsDto): contactIds (array of string), phones (array of string), useSegment (boolean)

listBroadcastRecipientsGET /v1/broadcasts/{broadcastId}/recipients

Offset pagination with skip/limit, oldest first (this is a delivery queue, not a history). This endpoint does not accept a cursor.

Parameters:

  • broadcastId (path, required)
  • skip (query)
  • limit (query)
  • status (query)

scheduleBroadcastPOST /v1/broadcasts/{broadcastId}/schedule

Records a future send time. Requires a non-empty frozen audience and a message or template. The durable runtime moves it to sending when due and never resolves new recipients at delivery time.

Parameters:

  • broadcastId (path, required)

Request body (ScheduleBroadcastDto): scheduledAt (string, required)

sendBroadcastPOST /v1/broadcasts/{broadcastId}/send

Moves the broadcast into the sending state over its already frozen audience; it never resolves new recipients. The durable runtime delivers pending recipients and writes recipient plus broadcast terminal states. Calling this again on an already sending broadcast is a no-op.

Parameters:

  • broadcastId (path, required)

comment-automations

OperationMethodPathSummary
listCommentAutomationsGET/v1/comment-automationsList comment automations
createCommentAutomationPOST/v1/comment-automationsCreate a comment automation
getCommentAutomationGET/v1/comment-automations/{automationId}Retrieve a comment automation
updateCommentAutomationPATCH/v1/comment-automations/{automationId}Update a comment automation
deleteCommentAutomationDELETE/v1/comment-automations/{automationId}Delete a comment automation
listCommentAutomationLogsGET/v1/comment-automations/{automationId}/logsList durable comment automation execution logs

listCommentAutomationsGET /v1/comment-automations

Offset pagination with skip/limit. This endpoint does not accept a cursor. Active rules are evaluated by the durable comment.received worker.

Parameters:

  • skip (query)
  • limit (query)
  • isActive (query)
  • profileId (query) — If omitted, the records of ALL profiles the key can reach are returned.

createCommentAutomationPOST /v1/comment-automations

Stores an active-by-default rule. The durable comment.received worker matches eligible Instagram/Facebook comments, sends a private reply through the provider adapter and records a terminal log.

Request body (CreateCommentAutomationDto): profileId (string, required), accountId (string, required), name (string, required), dmMessage (string, required), trigger (string), platformPostId (string), postId (string), postTitle (string), keywords (array of string), matchMode (string), excludeKeywords (array of string), typoTolerance (boolean), buttons (array of object), template (object), commentReply (string), dmMessageVariations (array of string), commentReplyVariations (array of string), linkTracking (boolean), clickTag (string), dmDelaySeconds (number), commentReplyDelaySeconds (number), alsoMatchInDms (boolean), audience (object), followGate (object)

getCommentAutomationGET /v1/comment-automations/{automationId}

Parameters:

  • automationId (path, required)

updateCommentAutomationPATCH /v1/comment-automations/{automationId}

Only provided fields change; sending null clears a nullable field. profileId and accountId are immutable. isActive controls whether the durable comment.received worker can match the rule.

Parameters:

  • automationId (path, required)

Request body (UpdateCommentAutomationDto): name (string), dmMessage (string), trigger (string), platformPostId (string), postId (string), postTitle (string), keywords (array of string), matchMode (string), excludeKeywords (array of string), typoTolerance (boolean), buttons (array of object), template (object), commentReply (string), dmMessageVariations (array of string), commentReplyVariations (array of string), linkTracking (boolean), clickTag (string), dmDelaySeconds (number), commentReplyDelaySeconds (number), alsoMatchInDms (boolean), audience (object), followGate (object), isActive (boolean)

deleteCommentAutomationDELETE /v1/comment-automations/{automationId}

Permanently deletes the rule and its execution log rows (the log table cascades). There is no soft-delete window in this API version.

Parameters:

  • automationId (path, required)

listCommentAutomationLogsGET /v1/comment-automations/{automationId}/logs

Lists rule match/private-reply outcomes written by the durable comment.received worker. Offset pagination with skip/limit; no cursor.

Parameters:

  • automationId (path, required)
  • skip (query)
  • limit (query)
  • status (query)

comments

OperationMethodPathSummary
listInboxCommentsGET/v1/inbox/commentsList commented posts
deleteInboxCommentDELETE/v1/inbox/comments/{postId}Delete an authored YouTube comment
replyToInboxPostPOST/v1/inbox/comments/{postId}Reply to a YouTube comment
getInboxPostCommentsGET/v1/inbox/comments/{postId}Get YouTube post comments
editInboxCommentPATCH/v1/inbox/comments/{postId}/{commentId}Edit comment
hideInboxCommentPOST/v1/inbox/comments/{postId}/{commentId}/hideHide a Threads comment
unhideInboxCommentDELETE/v1/inbox/comments/{postId}/{commentId}/hideUnhide a Threads comment
likeInboxCommentPOST/v1/inbox/comments/{postId}/{commentId}/likeLike a comment
unlikeInboxCommentDELETE/v1/inbox/comments/{postId}/{commentId}/likeRemove a comment like
setCommentModerationPOST/v1/inbox/comments/{postId}/{commentId}/moderationSet a YouTube comment moderation status
sendPrivateReplyToCommentPOST/v1/inbox/comments/{postId}/{commentId}/private-replySend private reply

listInboxCommentsGET /v1/inbox/comments

Returns published post targets whose latest durable analytics observation contains a comment count. Unknown counts are omitted rather than represented as zero.

Parameters:

  • accountId (query)
  • cursor (query)
  • limit (query)
  • sortOrder (query)
  • sortBy (query)
  • since (query)
  • minComments (query)
  • platform (query)
  • profileId (query)

deleteInboxCommentDELETE /v1/inbox/comments/{postId}

Makes the selected comment absent when its author is the connected YouTube channel. The post selects and fences the account/video context; YouTube does not expose parent-video identity on this delete operation, so no parent-video correlation beyond that context is claimed. Repeating the identical target is safe.

Parameters:

  • postId (path, required) — Internal post id or provider YouTube video id.
  • commentId (query, required)
  • accountId (query, required)

replyToInboxPostPOST /v1/inbox/comments/{postId}

Creates exactly one reply to a top-level comment associated with the connected YouTube channel. commentId is required for YouTube. The provider companion does not expose the parent video id, so the requested post and account are fenced locally while the provider binds the parent comment to the connected channel.

Parameters:

  • postId (path, required) — Internal post id or provider YouTube video id.

Request body (YoutubeCommentReplyInputDto): accountId (string, required), message (string, required), commentId (string)

getInboxPostCommentsGET /v1/inbox/comments/{postId}

Returns one owned YouTube video comment-thread page. An internal post id must resolve to exactly one matching published YouTube target.

Parameters:

  • postId (path, required) — Internal post id or provider YouTube video id.
  • moderationStatus (query) — Optional YouTube owner moderation-queue filter.
  • cursor (query) — Opaque context-bound cursor returned by this endpoint.
  • limit (query)
  • accountId (query, required)

editInboxCommentPATCH /v1/inbox/comments/{postId}/{commentId}

Edit the body of a comment the connected account posted. Supported on Reddit only. Reddit keeps the same comment id after an edit. Reddit exposes no API to edit a post title, and a link post has no editable body. To edit a published post's body, use POST /v1/posts/{postId}/edit.

Parameters:

  • postId (path, required) — Reddit post id that the comment sits under, with or without its t3_ prefix.
  • commentId (path, required) — Reddit comment id to edit, with or without its t1_ prefix.

Request body (InboxCommentEditRequestDto): accountId (string, required), platform (enum: reddit, required), content (string, required)

hideInboxCommentPOST /v1/inbox/comments/{postId}/{commentId}/hide

Resolves the selected Threads post, proves the reply belongs directly to it, and applies one exact hidden desired state. Retry only the identical request when the provider outcome is ambiguous.

Parameters:

  • postId (path, required) — Internal post id or provider Threads media id.
  • commentId (path, required)

Request body (ThreadsInboxCommentVisibilityRequestDto): accountId (string, required)

unhideInboxCommentDELETE /v1/inbox/comments/{postId}/{commentId}/hide

Resolves the selected Threads post, proves the reply belongs directly to it, and applies one exact visible desired state. Retry only the identical request when the provider outcome is ambiguous.

Parameters:

  • postId (path, required) — Internal post id or provider Threads media id.
  • commentId (path, required)
  • accountId (query, required)

likeInboxCommentPOST /v1/inbox/comments/{postId}/{commentId}/like

Likes one comment with the selected account. Supported for Bluesky and Reddit; every other platform is rejected with 400 rather than silently ignored. On Bluesky a comment is itself an app.bsky.feed.post record, so commentId is its AT URI and cid is required; the like is a separate record and its likeUri is returned because zhyper stores nothing and only that uri can undo the like. On Reddit the like is an upvote on the t1_ comment fullname, which is not a record and therefore has no likeUri.

Parameters:

  • postId (path, required) — Platform post id that owns the comment. Required by the published contract; neither Bluesky nor Reddit needs it to address the like.
  • commentId (path, required) — Bluesky: the canonical AT URI of the comment record (percent-encoded in the path). Reddit: the t1_ comment fullname or its bare id.

Request body (InboxCommentLikeRequestDto): accountId (string, required), cid (string)

unlikeInboxCommentDELETE /v1/inbox/comments/{postId}/{commentId}/like

Removes a like from one comment with the selected account. Supported for Bluesky and Reddit; every other platform is rejected with 400. On Bluesky the like is its own record, so likeUri is required and must be the uri returned by the like. On Reddit the vote is simply cleared, so likeUri does not apply and is rejected.

Parameters:

  • postId (path, required) — Platform post id that owns the comment. Required by the published contract; neither Bluesky nor Reddit needs it to address the like.
  • commentId (path, required) — Bluesky: the canonical AT URI of the comment record (percent-encoded in the path). Reddit: the t1_ comment fullname or its bare id.
  • accountId (query, required)
  • likeUri (query) — Bluesky only, and required there: the AT URI of the like record to delete, exactly as returned by likeInboxComment.

setCommentModerationPOST /v1/inbox/comments/{postId}/{commentId}/moderation

Applies one exact desired moderation state to a YouTube comment. The request is bound to the selected account and owned video context. YouTube may not make rejected comments readable, so no discovery preflight is claimed. If an outcome is ambiguous, retry only the identical commentId, moderationStatus and banAuthor value.

Parameters:

  • postId (path, required) — Internal post id or provider YouTube video id.
  • commentId (path, required)

Request body (YoutubeCommentModerationRequestDto): accountId (string, required), platform (enum: youtube, required), moderationStatus (enum: published, rejected, heldForReview, required), banAuthor (boolean)

sendPrivateReplyToCommentPOST /v1/inbox/comments/{postId}/{commentId}/private-reply

Send a private message to the author of a comment. Supported on Instagram and Facebook only.

Meta allows one reply per comment within a 7-day window. Both are provider-side state and are not pre-checked here; a violation is returned as the provider classified it.

Optionally attach interactive elements: quickReplies (chips above the keyboard) or buttons (inline buttons in the same bubble). They are mutually exclusive. Buttons are recommended for cold reach since chips do not render in the Instagram Message Requests folder. phone buttons are Facebook only.

Parameters:

  • postId (path, required)
  • commentId (path, required)

Request body (InboxPrivateReplyInputDto): accountId (string, required), message (string, required), quickReplies (array of InboxPrivateReplyQuickReplyDto), buttons (array of InboxPrivateReplyButtonDto)

connect

OperationMethodPathSummary
getFacebookPagesGET/v1/accounts/{accountId}/facebook-pageList Facebook pages
updateFacebookPagePUT/v1/accounts/{accountId}/facebook-pageUpdate Facebook page
getGmbLocationsGET/v1/accounts/{accountId}/gmb-locationsList GBP locations
updateGmbLocationPUT/v1/accounts/{accountId}/gmb-locationsUpdate GBP location
assignGoogleBusinessLocationPOST/v1/accounts/{accountId}/gmb-locations/assignAssign GBP location to another profile
updateLinkedInOrganizationPUT/v1/accounts/{accountId}/linkedin-organizationSwitch LinkedIn account type
getLinkedInOrganizationsGET/v1/accounts/{accountId}/linkedin-organizationsList LinkedIn orgs
setRedditPostFlairPOST/v1/accounts/{accountId}/reddit-flairsSet Reddit post flair
getSubredditRulesGET/v1/accounts/{accountId}/reddit-subreddits/{subreddit}/rulesGet subreddit rules
voteRedditThingPOST/v1/accounts/{accountId}/reddit-voteVote on a Reddit post or comment
getConnectUrlGET/v1/connect/{platform}Get a headless OAuth connect URL
handleOAuthCallbackPOST/v1/connect/{platform}Complete a bound headless OAuth callback
connectAdsGET/v1/connect/{platform}/adsStart a separate-token ads OAuth flow
listAllowedRedirectsGET/v1/connect/allowed-redirectsList the OAuth redirect URIs registered for this team
createAllowedRedirectPOST/v1/connect/allowed-redirectsRegister an OAuth redirect URI
deleteAllowedRedirectDELETE/v1/connect/allowed-redirectsRemove a registered OAuth redirect URI
authorizeConnectPOST/v1/connect/authorizeStart an OAuth connect flow
connectBlueskyCredentialsPOST/v1/connect/bluesky/credentialsConnect Bluesky account
completeConnectPOST/v1/connect/completeComplete an OAuth connect flow
connectCredentialsPOST/v1/connect/credentialsConnect an account with direct credentials
connectDiscordChannelPOST/v1/connect/discordComplete a bound Discord OAuth callback
listFacebookPagesGET/v1/connect/facebook/select-pageList Facebook pages
selectFacebookPagePOST/v1/connect/facebook/select-pageSelect Facebook page
listGoogleBusinessLocationsGET/v1/connect/googlebusiness/locationsList stored Google Business locations
selectGoogleBusinessLocationPOST/v1/connect/googlebusiness/select-locationSelect a stored Google Business location
listInstagramPagesGET/v1/connect/instagram/select-accountList Pages with a linked Instagram account
selectInstagramAccountPOST/v1/connect/instagram/select-accountSelect the Page whose Instagram account to connect
listLinkedInOrganizationsGET/v1/connect/linkedin/organizationsList LinkedIn orgs
selectLinkedInOrganizationPOST/v1/connect/linkedin/select-organizationSelect LinkedIn org
getPendingOAuthDataGET/v1/connect/pending-dataGet pending OAuth data
completePendingConnectionPOST/v1/connect/pending/{id}/completeSelect a resource for a pending connection
listPendingConnectionResourcesGET/v1/connect/pending/{id}/resourcesList resources stored for a pending connection
listPinterestBoardsForSelectionGET/v1/connect/pinterest/select-boardList Pinterest boards
selectPinterestBoardPOST/v1/connect/pinterest/select-boardSelect Pinterest board
getShopifyConnectUrlGET/v1/connect/shopifyGet a Shopify OAuth connection URL
connectShopifyWithTokenPOST/v1/connect/shopify/tokenConnect a Shopify store with a custom-app Admin token
connectSlackChannelPOST/v1/connect/slackComplete a bound Slack OAuth callback
listSnapchatProfilesGET/v1/connect/snapchat/select-profileList Snapchat profiles
selectSnapchatProfilePOST/v1/connect/snapchat/select-profileSelect Snapchat profile
initiateTelegramConnectPOST/v1/connect/telegramConnect Telegram directly
configureTikTokAdsBrandIdentityPATCH/v1/connect/tiktok-adsSet TikTok brand identity
connectWhatsAppCredentialsPOST/v1/connect/whatsapp/credentialsConnect WhatsApp via credentials
connectWhatsAppEmbeddedSignupPOST/v1/connect/whatsapp/embedded-signupConnect WhatsApp from Embedded Signup
listWhatsAppPhoneNumbersGET/v1/connect/whatsapp/select-phone-numberList numbers for selection
completeWhatsAppPhoneSelectionPOST/v1/connect/whatsapp/select-phone-numberComplete number selection

getFacebookPagesGET /v1/accounts/{accountId}/facebook-page

Returns every Facebook Page still available to the connected account and the active Page.

Parameters:

  • accountId (path, required)
  • refresh (query) — Bypasses the short Page cache. Explicit refresh is limited to one per 60 seconds.

updateFacebookPagePUT /v1/accounts/{accountId}/facebook-page

Switch which Facebook Page is active for a connected account.

Parameters:

  • accountId (path, required)

Request body (UpdateFacebookPageDto): selectedPageId (string, required)

getGmbLocationsGET /v1/accounts/{accountId}/gmb-locations

Returns the Google Business Profile locations the connected account can access, plus the currently selected location. The list is bounded: the provider enumeration is capped at 2000 locations and hasMore reports whether more were available than limit returned. The search and filter query parameters are NOT implemented and are rejected with 400 rather than silently ignored — the provider enumeration carries no server-side filter expression. Each entry carries id, name and accountId only; address, category, websiteUrl, storeCode and the human-readable accountName are not returned because the provider enumeration reads locations with a name,title mask.

TEST MODE ONLY TODAY. No Google Business adapter is composed into the live registry, so a Google Business account cannot be connected in live mode and this operation fails closed there rather than returning partial data. Setting a live-mode environment flag does not change this. Use test mode for this vertical.

Parameters:

  • accountId (path, required)
  • limit (query) — Max locations to return (default 100, max 500).

updateGmbLocationPUT /v1/accounts/{accountId}/gmb-locations

Switch which GBP location is active for a connected account. googleAccountId (legacy alias accountId) is optional and acts as a constraint: when supplied the selected location must belong to that Google Business account, otherwise the request is rejected with 400. It does not enable a direct-resolve fast path — the provider enumeration always walks every managed account.

TEST MODE ONLY TODAY. No Google Business adapter is composed into the live registry, so a Google Business account cannot be connected in live mode and this operation fails closed there rather than returning partial data. Setting a live-mode environment flag does not change this. Use test mode for this vertical.

Parameters:

  • accountId (path, required)

Request body (UpdateGmbLocationDto): selectedLocationId (string, required), googleAccountId (string), accountId (string)

assignGoogleBusinessLocationPOST /v1/accounts/{accountId}/gmb-locations/assign

Connect a Google Business location onto a DIFFERENT profile by reusing the OAuth grant from an already-connected GBP account — no browser, no re-authorization. Built for agencies whose single Google account has manager access to many client locations and who run one profile per client: connect one location the normal way, then assign the rest onto each client profile with this operation. The path accountId is the SOURCE connected GBP account (the token holder); the body profileId is the TARGET profile. The source account is left untouched. Returns 409 if the target profile already has a Google Business connection — including a disconnected one — in which case switch its location with PUT gmb-locations or remove the existing account first. googleAccountId is optional and acts as a CONSTRAINT: when supplied the selected location must belong to that Google Business account, otherwise the request is rejected with 400. It does NOT enable a direct-resolve fast path — the provider enumeration always walks every managed account, exactly as PUT gmb-locations documents. NO PLAN LIMIT IS ENFORCED HERE and none is enforced on the browser connect flow either, so this operation cannot be used to exceed a ceiling the normal flow would have applied; the shared-application admission gate was deliberately not shipped.

TEST MODE ONLY TODAY. No Google Business adapter is composed into the live registry, so a Google Business account cannot be connected in live mode and this operation fails closed there rather than returning partial data. Setting a live-mode environment flag does not change this. Use test mode for this vertical.

Parameters:

  • accountId (path, required) — A source connected GBP account whose OAuth grant is reused.

Request body (AssignGmbLocationDto): profileId (string, required), selectedLocationId (string, required), googleAccountId (string)

updateLinkedInOrganizationPUT /v1/accounts/{accountId}/linkedin-organization

Switch an existing LinkedIn account between a person and a verified organization.

Parameters:

  • accountId (path, required)

Request body (LinkedInOrganizationUpdateRequestDto): accountType (enum: personal, organization, required), selectedOrganization (LinkedInOrganizationUpdateSelectionDto)

getLinkedInOrganizationsGET /v1/accounts/{accountId}/linkedin-organizations

Returns LinkedIn organizations the connected account can administer and publish for.

Parameters:

  • accountId (path, required)

setRedditPostFlairPOST /v1/accounts/{accountId}/reddit-flairs

Applies a selectable link-flair template to a post owned by the connected Reddit account.

Parameters:

  • accountId (path, required)

Request body (RedditPostFlairInputDto): subreddit (string, required), postId (string, required), flairTemplateId (string, required), text (string)

getSubredditRulesGET /v1/accounts/{accountId}/reddit-subreddits/{subreddit}/rules

Returns the provider-ordered subreddit posting rules and Reddit site-wide rules for one connected Reddit account.

Parameters:

  • accountId (path, required)
  • subreddit (path, required) — Subreddit name without an r/ prefix.

voteRedditThingPOST /v1/accounts/{accountId}/reddit-vote

Proxies one explicit human vote action. Automated or agent-decided voting is prohibited by Reddit.

Parameters:

  • accountId (path, required)

Request body (RedditVoteInputDto): thingId (string, required), direction (enum: 1, 0, -1, required)

getConnectUrlGET /v1/connect/{platform}

Zernio-compatible subset backed by Zhyper OAuth state. Requires headless=true and an exact allowlisted redirect_url; hosted selection redirects are not implemented.

Parameters:

  • platform (path, required)
  • loginMethod (query) — Instagram only. Selects Instagram Login or Facebook Login.
  • headless (query, required)
  • redirect_url (query, required)
  • profileId (query, required)

handleOAuthCallbackPOST /v1/connect/{platform}

Exchanges a code only when the path platform and profileId match the immutable Zhyper OAuth state. Mismatches leave the state available for a correct retry.

Parameters:

  • platform (path, required)

Request body (ConnectCallbackCompatibilityDto): state (string, required), code (string, required), profileId (string, required)

connectAdsGET /v1/connect/{platform}/ads

Starts a dedicated Google Ads or TikTok for Business OAuth connection. Zhyper deliberately requires an exact allowlisted HTTPS redirect_url and does not copy posting-account credentials or create parent links.

Parameters:

  • platform (path, required)
  • redirect_url (query, required) — Exact team-allowlisted HTTPS OAuth return URL.
  • profileId (query, required)

listAllowedRedirectsGET /v1/connect/allowed-redirects

Parameters:

  • sort (query)
  • cursor (query)
  • limit (query)

createAllowedRedirectPOST /v1/connect/allowed-redirects

The URI is stored normalized and matched exactly when a connect flow starts. Scheme and host are lowercased; the path stays case-sensitive.

Request body (CreateAllowedRedirectDto): redirectUri (string, required)

deleteAllowedRedirectDELETE /v1/connect/allowed-redirects

Parameters:

  • redirectUri (query, required)

authorizeConnectPOST /v1/connect/authorize

Request body (ConnectAuthorizeDto): profileId (string, required), platform (string, required), redirectUrl (string, required), instanceUrl (string), accountId (string)

connectBlueskyCredentialsPOST /v1/connect/bluesky/credentials

Connect a Bluesky account using identifier (handle or email) and an app password.

Request body (BlueskyCredentialsRequestDto): identifier (string, required), appPassword (string, required), state (string, required), redirectUri (string)

completeConnectPOST /v1/connect/complete

Request body (ConnectCompleteDto): state (string, required), code (string, required)

connectCredentialsPOST /v1/connect/credentials

Request body (ConnectCredentialsDto): profileId (string, required), platform (string, required), accountId (string), credentials (object, required)

connectDiscordChannelPOST /v1/connect/discord

Dedicated compatibility facade over the real Zhyper bound OAuth callback. It accepts state, code and profileId, fixes the platform to Discord, and returns a tenant-scoped pendingDataToken when channel selection is required. Select the channel through the authenticated pending-connection endpoints; this route does not expose provider tokens or accept an unbound guild/channel pair.

Request body (ConnectCallbackCompatibilityDto): state (string, required), code (string, required), profileId (string, required)

listFacebookPagesGET /v1/connect/facebook/select-page

Returns the Facebook Pages available during a short-lived OAuth flow.

Parameters:

  • tempToken (query, required)
  • profileId (query, required)
  • X-Connect-Token (header, required) — Short-lived connect token from the OAuth redirect.

selectFacebookPagePOST /v1/connect/facebook/select-page

Complete the connection using the selected Facebook Page.

Parameters:

  • X-Connect-Token (header, required) — Short-lived connect token from the OAuth redirect.

Request body (FacebookPageSelectionRequestDto): profileId (string, required), pageId (string, required), tempToken (string, required), userProfile (FacebookPageSelectionUserProfileDto, required), redirect_url (string)

listGoogleBusinessLocationsGET /v1/connect/googlebusiness/locations

Lists the bounded immutable location snapshot captured by Zhyper during OAuth completion. Direct tempToken, provider search, raw filters, and X-Connect-Token authentication are not supported.

TEST MODE ONLY TODAY. No Google Business adapter is composed into the live registry, so a Google Business account cannot be connected in live mode and this operation fails closed there rather than returning partial data. Setting a live-mode environment flag does not change this. Use test mode for this vertical.

Parameters:

  • pendingDataToken (query, required)
  • profileId (query)

selectGoogleBusinessLocationPOST /v1/connect/googlebusiness/select-location

Completes a Zhyper pending Google Business connection. The compatibility subset accepts a pendingDataToken backed by a tenant-scoped pending row and an optional redirect_url returned with the connection params appended; tempToken and X-Connect-Token are not implemented.

TEST MODE ONLY TODAY. No Google Business adapter is composed into the live registry, so a Google Business account cannot be connected in live mode and this operation fails closed there rather than returning partial data. Setting a live-mode environment flag does not change this. Use test mode for this vertical.

Request body (GoogleBusinessSelectLocationCompatibilityDto): profileId (string, required), locationId (string, required), accountId (string), pendingDataToken (string, required), redirect_url (string)

listInstagramPagesGET /v1/connect/instagram/select-account

Lists the immutable Facebook Page selection bound to a short-lived Zhyper pending handle. Provider access tokens are never returned.

Parameters:

  • tempToken (query, required)
  • profileId (query, required)
  • X-Connect-Token (header, required)

selectInstagramAccountPOST /v1/connect/instagram/select-account

Parameters:

  • X-Connect-Token (header, required)

Request body (InstagramFacebookLoginSelectionRequestDto): profileId (string, required), pageId (string, required), tempToken (string, required), redirect_url (string)

listLinkedInOrganizationsGET /v1/connect/linkedin/organizations

Fetch organization details for a bounded pending OAuth flow.

Parameters:

  • orgIds (query, required)
  • tempToken (query, required)
  • X-Connect-Token (header, required)

selectLinkedInOrganizationPOST /v1/connect/linkedin/select-organization

Complete the pending LinkedIn connection as a person or organization.

Parameters:

  • X-Connect-Token (header, required)

Request body (LinkedInOrganizationSelectionRequestDto): profileId (string, required), tempToken (string, required), userProfile (LinkedInOrganizationSelectionUserProfileDto, required), accountType (enum: personal, organization, required), selectedOrganization (LinkedInSelectedOrganizationDto), redirect_url (string)

getPendingOAuthDataGET /v1/connect/pending-data

Atomically reads a one-time, ten-minute LinkedIn organization, Pinterest board, or Snapchat profile selection projection. Provider access and refresh tokens remain sealed and are never returned by this endpoint.

Parameters:

  • token (query, required) — Opaque pendingDataToken from the OAuth completion redirect.

completePendingConnectionPOST /v1/connect/pending/{id}/complete

Parameters:

  • id (path, required)

Request body (ConnectPendingCompleteDto): resourceId (string, required)

listPendingConnectionResourcesGET /v1/connect/pending/{id}/resources

Pages the immutable provider resource snapshot captured when the pending connection was created. This endpoint does not call the provider.

Parameters:

  • id (path, required)
  • cursor (query) — Opaque cursor returned by this pending connection resource list.
  • limit (query)

listPinterestBoardsForSelectionGET /v1/connect/pinterest/select-board

Returns the Pinterest boards available during a short-lived headless OAuth flow.

Parameters:

  • tempToken (query, required) — Temporary Pinterest access token from the OAuth callback.
  • profileId (query, required)
  • X-Connect-Token (header, required) — Short-lived connect token from the OAuth redirect.

selectPinterestBoardPOST /v1/connect/pinterest/select-board

Complete the Pinterest connection flow by saving the selected board.

Parameters:

  • X-Connect-Token (header, required) — Short-lived connect token from the OAuth redirect.

Request body (PinterestBoardSelectionCompleteRequestDto): profileId (string, required), boardId (string, required), boardName (string), tempToken (string, required), userProfile (object), refreshToken (string), expiresIn (number), redirect_url (string)

getShopifyConnectUrlGET /v1/connect/shopify

Starts an OAuth flow for a single-label myshopify.com store with read_content and write_content. Reconnecting the same store refreshes the existing account credential in place.

Parameters:

  • redirect_url (query) — An exact allowlisted post-connect redirect URL or a relative path.
  • shop (query, required) — A bare Shopify store label or its canonical single-label myshopify.com domain.
  • profileId (query, required)

connectShopifyWithTokenPOST /v1/connect/shopify/token

Validates a merchant-owned Admin API token with write_content (which includes content read access) or write_online_store_pages before storing it. Reconnecting the same store replaces the token on the existing account.

Request body (ShopifyTokenConnectDto): profileId (string, required), shop (string, required), accessToken (string, required)

connectSlackChannelPOST /v1/connect/slack

Dedicated compatibility facade over the real Zhyper bound OAuth callback. It accepts state, code and profileId, fixes the platform to Slack, and returns a tenant-scoped pendingDataToken when channel selection is required. Select the channel through the authenticated pending-connection endpoints; this route does not expose provider tokens or silently reuse another account credential.

Request body (ConnectCallbackCompatibilityDto): state (string, required), code (string, required), profileId (string, required)

listSnapchatProfilesGET /v1/connect/snapchat/select-profile

Returns the Snapchat Public Profile available during a short-lived headless OAuth flow.

Parameters:

  • tempToken (query, required)
  • profileId (query, required)
  • X-Connect-Token (header, required)

selectSnapchatProfilePOST /v1/connect/snapchat/select-profile

Completes the Snapchat connection flow with the selected Public Profile.

Parameters:

  • X-Connect-Token (header)

Request body (SnapchatProfileSelectionRequestDto): profileId (string, required), selectedPublicProfile (SnapchatSelectedPublicProfileRequestDto, required), tempToken (string, required), userProfile (object, required), refreshToken (string), expiresIn (number), redirect_url (string)

initiateTelegramConnectPOST /v1/connect/telegram

Connects a Telegram channel or group after verifying that the platform bot is an administrator.

Request body (TelegramConnectDirectRequestDto): chatId (string, required), profileId (string, required)

configureTikTokAdsBrandIdentityPATCH /v1/connect/tiktok-ads

Uploads a public brand image and creates an immutable TikTok Ads CUSTOMIZED_USER identity.

Request body (ConfigureTikTokAdsBrandIdentityDto): accountId (string, required), displayName (string, required), imageUrl (string, required)

connectWhatsAppCredentialsPOST /v1/connect/whatsapp/credentials

Request body (WhatsAppCredentialsConnectRequestDto): profileId (string, required), accessToken (string, required), wabaId (string, required), phoneNumberId (string, required), pin (string)

connectWhatsAppEmbeddedSignupPOST /v1/connect/whatsapp/embedded-signup

Consumes the one-time code returned by Meta Embedded Signup without a redirect URI.

Request body (WhatsAppEmbeddedSignupRequestDto): code (string, required), profileId (string, required), wabaId (string), phoneNumberId (string), isCoexistence (boolean), expectedPhoneNumber (string)

listWhatsAppPhoneNumbersGET /v1/connect/whatsapp/select-phone-number

Parameters:

  • tempToken (query, required)
  • profileId (query, required)
  • X-Connect-Token (header)

completeWhatsAppPhoneSelectionPOST /v1/connect/whatsapp/select-phone-number

Parameters:

  • X-Connect-Token (header)

Request body (WhatsAppPhoneSelectionRequestDto): profileId (string, required), phoneNumberId (string, required), wabaId (string, required), tempToken (string, required), userProfile (object), redirect_url (string)

contacts

OperationMethodPathSummary
listContactsGET/v1/contactsList contacts
createContactPOST/v1/contactsCreate a contact
getContactGET/v1/contacts/{contactId}Retrieve a contact
updateContactPATCH/v1/contacts/{contactId}Update a contact
deleteContactDELETE/v1/contacts/{contactId}Delete a contact
getContactChannelsGET/v1/contacts/{contactId}/channelsList a contact's channels
setContactFieldValuePUT/v1/contacts/{contactId}/fields/{slug}Set a custom field value on a contact
clearContactFieldValueDELETE/v1/contacts/{contactId}/fields/{slug}Clear a custom field value on a contact
bulkCreateContactsPOST/v1/contacts/bulkImport contacts in bulk

listContactsGET /v1/contacts

Parameters:

  • isSubscribed (query)
  • search (query) — Case-insensitive substring search over displayName, email and company.
  • tag (query) — A single tag; if it is supplied together with tags the two are MERGED.
  • tags (query) — Comma separated tag list, OR semantics. Case insensitive.
  • cursor (query)
  • skip (query)
  • limit (query)
  • profileId (query) — If omitted, the contacts of ALL profiles the key can reach are returned.

createContactPOST /v1/contacts

Request body (CreateContactDto): profileId (string, required), displayName (string), email (string), company (string), avatarUrl (string), notes (string), locale (string), timezone (string), tags (array of string), isSubscribed (boolean), isBlocked (boolean), firstSeenAt (string), accountId (string), externalId (string), platform (string), displayIdentifier (string)

getContactGET /v1/contacts/{contactId}

Parameters:

  • contactId (path, required)

updateContactPATCH /v1/contacts/{contactId}

Only provided fields change; sending null clears a nullable field. isBlocked is a tenant-side suppression flag and never calls a provider block API.

Parameters:

  • contactId (path, required)

Request body (UpdateContactDto): displayName (string), email (string), company (string), avatarUrl (string), notes (string), locale (string), timezone (string), tags (array of string), isSubscribed (boolean), isBlocked (boolean)

deleteContactDELETE /v1/contacts/{contactId}

Permanently deletes the contact together with its channels, tag links and custom field values. There is no soft-delete window in this API version.

Parameters:

  • contactId (path, required)

getContactChannelsGET /v1/contacts/{contactId}/channels

Returns the account bindings only. Conversation identity and last-active timestamps belong to the Messages area and are not part of this API version.

Parameters:

  • contactId (path, required)

setContactFieldValuePUT /v1/contacts/{contactId}/fields/{slug}

Parameters:

  • contactId (path, required)
  • slug (path, required)

Request body (SetContactFieldValueDto): value (string | number | boolean, required)

clearContactFieldValueDELETE /v1/contacts/{contactId}/fields/{slug}

Removes the stored value. The field definition is untouched.

Parameters:

  • contactId (path, required)
  • slug (path, required)

bulkCreateContactsPOST /v1/contacts/bulk

Per-row failures are reported in errors[] under a 200. A row whose channel already belongs to another contact is skipped and its tags are merged onto that contact.

Request body (BulkCreateContactsDto): profileId (string, required), contacts (array of BulkContactDto, required)

custom-fields

OperationMethodPathSummary
listCustomFieldsGET/v1/custom-fieldsList custom field definitions
createCustomFieldPOST/v1/custom-fieldsCreate a custom field definition
getCustomFieldGET/v1/custom-fields/{fieldId}Retrieve a custom field definition
updateCustomFieldPATCH/v1/custom-fields/{fieldId}Update a custom field definition
deleteCustomFieldDELETE/v1/custom-fields/{fieldId}Delete a custom field definition

listCustomFieldsGET /v1/custom-fields

Parameters:

  • sort (query)
  • cursor (query)
  • limit (query)
  • profileId (query) — If omitted, the definitions of ALL profiles the key can reach are returned.

createCustomFieldPOST /v1/custom-fields

Request body (CreateCustomFieldDto): profileId (string, required), name (string, required), slug (string), type (enum: text, number, date, boolean, select, required), options (array of string)

getCustomFieldGET /v1/custom-fields/{fieldId}

Parameters:

  • fieldId (path, required)

updateCustomFieldPATCH /v1/custom-fields/{fieldId}

Parameters:

  • fieldId (path, required)

Request body (UpdateCustomFieldDto): name (string), options (array of string)

deleteCustomFieldDELETE /v1/custom-fields/{fieldId}

Deletes the definition. Stored values are not part of this API version; when the value plane ships they are removed with the definition.

Parameters:

  • fieldId (path, required)

discord

OperationMethodPathSummary
getDiscordSettingsGET/v1/accounts/{accountId}/discord-settingsGet Discord account settings
updateDiscordSettingsPATCH/v1/accounts/{accountId}/discord-settingsUpdate Discord account settings
deleteDiscordMessageDELETE/v1/discord/channels/{channelId}/messages/{messageId}Delete a Discord channel message
crosspostDiscordMessagePOST/v1/discord/channels/{channelId}/messages/{messageId}/crosspostCrosspost Discord message
listDiscordPinnedMessagesGET/v1/discord/channels/{channelId}/pinsList pinned messages
pinDiscordMessagePUT/v1/discord/channels/{channelId}/pins/{messageId}Pin a published Discord message
unpinDiscordMessageDELETE/v1/discord/channels/{channelId}/pins/{messageId}Unpin a published Discord message
createDiscordThreadPOST/v1/discord/channels/{channelId}/threadsCreate a Discord public thread
sendDiscordDirectMessagePOST/v1/discord/dmsSend a Discord Direct Message
listDiscordScheduledEventsGET/v1/discord/guilds/{guildId}/eventsList Discord scheduled events
createDiscordScheduledEventPOST/v1/discord/guilds/{guildId}/eventsCreate a Discord scheduled event
getDiscordScheduledEventGET/v1/discord/guilds/{guildId}/events/{eventId}Get a Discord scheduled event
updateDiscordScheduledEventPATCH/v1/discord/guilds/{guildId}/events/{eventId}Update a Discord scheduled event
deleteDiscordScheduledEventDELETE/v1/discord/guilds/{guildId}/events/{eventId}Delete a Discord scheduled event
listDiscordGuildMembersGET/v1/discord/guilds/{guildId}/membersList Discord guild members
getDiscordGuildMemberGET/v1/discord/guilds/{guildId}/members/{userId}Get a Discord guild member
addDiscordMemberRolePUT/v1/discord/guilds/{guildId}/members/{userId}/roles/{roleId}Add a Discord member role
removeDiscordMemberRoleDELETE/v1/discord/guilds/{guildId}/members/{userId}/roles/{roleId}Remove a Discord member role
searchDiscordGuildMembersGET/v1/discord/guilds/{guildId}/members/searchSearch Discord guild members
listDiscordGuildRolesGET/v1/discord/guilds/{guildId}/rolesList Discord guild roles
createDiscordGuildRolePOST/v1/discord/guilds/{guildId}/rolesCreate a Discord guild role
editDiscordGuildRolePATCH/v1/discord/guilds/{guildId}/roles/{roleId}Edit a Discord guild role
deleteDiscordGuildRoleDELETE/v1/discord/guilds/{guildId}/roles/{roleId}Delete a Discord guild role

getDiscordSettingsGET /v1/accounts/{accountId}/discord-settings

Returns the connected guild/channel and the account-level webhook identity defaults.

Parameters:

  • accountId (path, required)

updateDiscordSettingsPATCH /v1/accounts/{accountId}/discord-settings

Updates webhook identity defaults and/or switches to a writable channel in the same guild.

Parameters:

  • accountId (path, required)

Request body (UpdateDiscordSettingsDto): webhookUsername (string), webhookAvatarUrl (string), channelId (string)

deleteDiscordMessageDELETE /v1/discord/channels/{channelId}/messages/{messageId}

Parameters:

  • channelId (path, required)
  • messageId (path, required)
  • accountId (query, required)

crosspostDiscordMessagePOST /v1/discord/channels/{channelId}/messages/{messageId}/crosspost

Parameters:

  • channelId (path, required)
  • messageId (path, required)
  • accountId (query, required)

listDiscordPinnedMessagesGET /v1/discord/channels/{channelId}/pins

Parameters:

  • channelId (path, required)
  • accountId (query, required)

pinDiscordMessagePUT /v1/discord/channels/{channelId}/pins/{messageId}

Pins the uniquely stored published Zhyper Discord target bound to this account, channel and message. Replaying the exact PUT is safe.

Parameters:

  • channelId (path, required)
  • messageId (path, required)
  • accountId (query, required)

unpinDiscordMessageDELETE /v1/discord/channels/{channelId}/pins/{messageId}

Unpins the uniquely stored published Zhyper Discord target bound to this account, channel and message. Replaying the exact DELETE is safe.

Parameters:

  • channelId (path, required)
  • messageId (path, required)
  • accountId (query, required)

createDiscordThreadPOST /v1/discord/channels/{channelId}/threads

Parameters:

  • channelId (path, required)
  • accountId (query, required)

Request body (DiscordThreadCreateRequestDto): name (string, required), messageId (string), autoArchiveDuration (enum: 60, 1440, 4320, 10080)

sendDiscordDirectMessagePOST /v1/discord/dms

Request body (DiscordDirectMessageRequestDto): accountId (string, required), userId (string, required), content (string), embeds (array of object), attachments (array of DiscordDirectMessageAttachmentDto), tts (boolean)

listDiscordScheduledEventsGET /v1/discord/guilds/{guildId}/events

Parameters:

  • guildId (path, required)
  • withUserCount (query)
  • accountId (query, required)

createDiscordScheduledEventPOST /v1/discord/guilds/{guildId}/events

Parameters:

  • guildId (path, required)

Request body (DiscordScheduledEventCreateRequestDto): accountId (string, required), name (string, required), description (string), startsAt (string, required), entity (DiscordScheduledEventExternalEntityDto | DiscordScheduledEventVoiceEntityDto | DiscordScheduledEventStageEntityDto, required), imageDataUri (string)

getDiscordScheduledEventGET /v1/discord/guilds/{guildId}/events/{eventId}

Parameters:

  • guildId (path, required)
  • eventId (path, required)
  • accountId (query, required)

updateDiscordScheduledEventPATCH /v1/discord/guilds/{guildId}/events/{eventId}

Parameters:

  • guildId (path, required)
  • eventId (path, required)

Request body (DiscordScheduledEventUpdateRequestDto): accountId (string, required), name (string), description (string), startsAt (string), endsAt (string), location (string), status (enum: scheduled, active, completed, cancelled), imageDataUri (string)

deleteDiscordScheduledEventDELETE /v1/discord/guilds/{guildId}/events/{eventId}

Parameters:

  • guildId (path, required)
  • eventId (path, required)
  • accountId (query, required)

listDiscordGuildMembersGET /v1/discord/guilds/{guildId}/members

Parameters:

  • guildId (path, required)
  • after (query)
  • limit (query)
  • accountId (query, required)

getDiscordGuildMemberGET /v1/discord/guilds/{guildId}/members/{userId}

Parameters:

  • guildId (path, required)
  • userId (path, required)
  • accountId (query, required)

addDiscordMemberRolePUT /v1/discord/guilds/{guildId}/members/{userId}/roles/{roleId}

Parameters:

  • guildId (path, required)
  • userId (path, required)
  • roleId (path, required)
  • accountId (query, required)

removeDiscordMemberRoleDELETE /v1/discord/guilds/{guildId}/members/{userId}/roles/{roleId}

Parameters:

  • guildId (path, required)
  • userId (path, required)
  • roleId (path, required)
  • accountId (query, required)

searchDiscordGuildMembersGET /v1/discord/guilds/{guildId}/members/search

Parameters:

  • guildId (path, required)
  • limit (query)
  • query (query, required)
  • accountId (query, required)

listDiscordGuildRolesGET /v1/discord/guilds/{guildId}/roles

Lists the roles visible to the connected Discord bot in its bound guild.

Parameters:

  • guildId (path, required)
  • accountId (query, required)

createDiscordGuildRolePOST /v1/discord/guilds/{guildId}/roles

Parameters:

  • guildId (path, required)
  • accountId (query, required)

Request body (DiscordGuildRoleCreateRequestDto): name (string, required), color (integer), hoist (boolean), mentionable (boolean), permissions (string)

editDiscordGuildRolePATCH /v1/discord/guilds/{guildId}/roles/{roleId}

Parameters:

  • guildId (path, required)
  • roleId (path, required)
  • accountId (query, required)

Request body (DiscordGuildRoleEditRequestDto): name (string), color (integer), hoist (boolean), mentionable (boolean), permissions (string)

deleteDiscordGuildRoleDELETE /v1/discord/guilds/{guildId}/roles/{roleId}

Parameters:

  • guildId (path, required)
  • roleId (path, required)
  • accountId (query, required)

gmb-attributes

OperationMethodPathSummary
getGmbAttributeMetadataGET/v1/accounts/{accountId}/gmb-attribute-metadataGet Google Business attribute metadata
getGoogleBusinessAttributesGET/v1/accounts/{accountId}/gmb-attributesGet Google Business attributes
updateGoogleBusinessAttributesPUT/v1/accounts/{accountId}/gmb-attributesUpdate Google Business attributes

getGmbAttributeMetadataGET /v1/accounts/{accountId}/gmb-attribute-metadata

TEST MODE ONLY TODAY. No Google Business adapter is composed into the live registry, so a Google Business account cannot be connected in live mode and this operation fails closed there rather than returning partial data. Setting a live-mode environment flag does not change this. Use test mode for this vertical.

Parameters:

  • accountId (path, required)
  • pageToken (query)
  • pageSize (query)
  • languageCode (query)
  • regionCode (query)
  • categoryName (query)
  • locationId (query)

getGoogleBusinessAttributesGET /v1/accounts/{accountId}/gmb-attributes

TEST MODE ONLY TODAY. No Google Business adapter is composed into the live registry, so a Google Business account cannot be connected in live mode and this operation fails closed there rather than returning partial data. Setting a live-mode environment flag does not change this. Use test mode for this vertical.

Parameters:

  • accountId (path, required)
  • locationId (query)

updateGoogleBusinessAttributesPUT /v1/accounts/{accountId}/gmb-attributes

TEST MODE ONLY TODAY. No Google Business adapter is composed into the live registry, so a Google Business account cannot be connected in live mode and this operation fails closed there rather than returning partial data. Setting a live-mode environment flag does not change this. Use test mode for this vertical.

Parameters:

  • accountId (path, required)
  • locationId (query)

Request body (ZernioGoogleBusinessAttributesUpdateInputDto): attributes (array of ZernioGoogleBusinessAttributeUpdateInputDto, required), attributeMask (string, required)

gmb-media

OperationMethodPathSummary
listGoogleBusinessMediaGET/v1/accounts/{accountId}/gmb-mediaList media
createGoogleBusinessMediaPOST/v1/accounts/{accountId}/gmb-mediaUpload photo
deleteGoogleBusinessMediaDELETE/v1/accounts/{accountId}/gmb-mediaDelete photo

listGoogleBusinessMediaGET /v1/accounts/{accountId}/gmb-media

Lists media items for a Google Business Profile location.

TEST MODE ONLY TODAY. No Google Business adapter is composed into the live registry, so a Google Business account cannot be connected in live mode and this operation fails closed there rather than returning partial data. Setting a live-mode environment flag does not change this. Use test mode for this vertical.

Parameters:

  • accountId (path, required)
  • pageToken (query)
  • pageSize (query)
  • locationId (query)

createGoogleBusinessMediaPOST /v1/accounts/{accountId}/gmb-media

Creates a location media item from a publicly accessible URL.

TEST MODE ONLY TODAY. No Google Business adapter is composed into the live registry, so a Google Business account cannot be connected in live mode and this operation fails closed there rather than returning partial data. Setting a live-mode environment flag does not change this. Use test mode for this vertical.

Parameters:

  • accountId (path, required)
  • locationId (query)

Request body (ZernioGoogleBusinessMediaCreateInputDto): sourceUrl (string, required), mediaFormat (enum: PHOTO, VIDEO), description (string), category (enum: CATEGORY_UNSPECIFIED, COVER, PROFILE, LOGO, EXTERIOR, INTERIOR, PRODUCT, FOOD_AND_DRINK, MENU, COMMON_AREA, ROOMS, TEAMS, AT_WORK, ADDITIONAL)

deleteGoogleBusinessMediaDELETE /v1/accounts/{accountId}/gmb-media

Deletes a media item from a Google Business Profile location.

TEST MODE ONLY TODAY. No Google Business adapter is composed into the live registry, so a Google Business account cannot be connected in live mode and this operation fails closed there rather than returning partial data. Setting a live-mode environment flag does not change this. Use test mode for this vertical.

Parameters:

  • accountId (path, required)
  • mediaId (query, required)
  • locationId (query)

gmb-place-actions

OperationMethodPathSummary
listGoogleBusinessPlaceActionsGET/v1/accounts/{accountId}/gmb-place-actionsList Google Business place-action links
createGoogleBusinessPlaceActionPOST/v1/accounts/{accountId}/gmb-place-actionsCreate a Google Business place-action link
updateGoogleBusinessPlaceActionPATCH/v1/accounts/{accountId}/gmb-place-actionsUpdate a Google Business place-action link
deleteGoogleBusinessPlaceActionDELETE/v1/accounts/{accountId}/gmb-place-actionsDelete a Google Business place-action link

listGoogleBusinessPlaceActionsGET /v1/accounts/{accountId}/gmb-place-actions

TEST MODE ONLY TODAY. No Google Business adapter is composed into the live registry, so a Google Business account cannot be connected in live mode and this operation fails closed there rather than returning partial data. Setting a live-mode environment flag does not change this. Use test mode for this vertical.

Parameters:

  • accountId (path, required)
  • pageToken (query)
  • pageSize (query)
  • locationId (query)

createGoogleBusinessPlaceActionPOST /v1/accounts/{accountId}/gmb-place-actions

TEST MODE ONLY TODAY. No Google Business adapter is composed into the live registry, so a Google Business account cannot be connected in live mode and this operation fails closed there rather than returning partial data. Setting a live-mode environment flag does not change this. Use test mode for this vertical.

Parameters:

  • accountId (path, required)
  • locationId (query)

Request body (ZernioGoogleBusinessPlaceActionCreateInputDto): uri (string, required), placeActionType (enum: APPOINTMENT, ONLINE_APPOINTMENT, DINING_RESERVATION, FOOD_ORDERING, FOOD_DELIVERY, FOOD_TAKEOUT, SHOP_ONLINE, required)

updateGoogleBusinessPlaceActionPATCH /v1/accounts/{accountId}/gmb-place-actions

TEST MODE ONLY TODAY. No Google Business adapter is composed into the live registry, so a Google Business account cannot be connected in live mode and this operation fails closed there rather than returning partial data. Setting a live-mode environment flag does not change this. Use test mode for this vertical.

Parameters:

  • accountId (path, required)
  • locationId (query)

Request body (ZernioGoogleBusinessPlaceActionUpdateInputDto): name (string, required), uri (string), placeActionType (enum: APPOINTMENT, ONLINE_APPOINTMENT, DINING_RESERVATION, FOOD_ORDERING, FOOD_DELIVERY, FOOD_TAKEOUT, SHOP_ONLINE)

deleteGoogleBusinessPlaceActionDELETE /v1/accounts/{accountId}/gmb-place-actions

TEST MODE ONLY TODAY. No Google Business adapter is composed into the live registry, so a Google Business account cannot be connected in live mode and this operation fails closed there rather than returning partial data. Setting a live-mode environment flag does not change this. Use test mode for this vertical.

Parameters:

  • accountId (path, required)
  • name (query, required)
  • locationId (query)

gmb-reviews

OperationMethodPathSummary
getGoogleBusinessReviewsGET/v1/accounts/{accountId}/gmb-reviewsGet Google Business reviews
getGoogleBusinessReviewGET/v1/accounts/{accountId}/gmb-reviews/{reviewId}Get a Google Business review
replyToGoogleBusinessReviewPOST/v1/accounts/{accountId}/gmb-reviews/{reviewId}/replyPost or update a Google Business review reply
deleteGoogleBusinessReviewReplyDELETE/v1/accounts/{accountId}/gmb-reviews/{reviewId}/replyDelete a Google Business review reply
batchGetGoogleBusinessReviewsPOST/v1/accounts/{accountId}/gmb-reviews/batchBatch get Google Business reviews

getGoogleBusinessReviewsGET /v1/accounts/{accountId}/gmb-reviews

TEST MODE ONLY TODAY. No Google Business adapter is composed into the live registry, so a Google Business account cannot be connected in live mode and this operation fails closed there rather than returning partial data. Setting a live-mode environment flag does not change this. Use test mode for this vertical.

Parameters:

  • accountId (path, required)
  • pageToken (query)
  • pageSize (query)
  • locationId (query)

getGoogleBusinessReviewGET /v1/accounts/{accountId}/gmb-reviews/{reviewId}

TEST MODE ONLY TODAY. No Google Business adapter is composed into the live registry, so a Google Business account cannot be connected in live mode and this operation fails closed there rather than returning partial data. Setting a live-mode environment flag does not change this. Use test mode for this vertical.

Parameters:

  • accountId (path, required)
  • reviewId (path, required)
  • locationId (query)

replyToGoogleBusinessReviewPOST /v1/accounts/{accountId}/gmb-reviews/{reviewId}/reply

TEST MODE ONLY TODAY. No Google Business adapter is composed into the live registry, so a Google Business account cannot be connected in live mode and this operation fails closed there rather than returning partial data. Setting a live-mode environment flag does not change this. Use test mode for this vertical.

Parameters:

  • accountId (path, required)
  • reviewId (path, required)

Request body (ZernioGoogleBusinessReviewReplyInputDto): comment (string, required)

deleteGoogleBusinessReviewReplyDELETE /v1/accounts/{accountId}/gmb-reviews/{reviewId}/reply

TEST MODE ONLY TODAY. No Google Business adapter is composed into the live registry, so a Google Business account cannot be connected in live mode and this operation fails closed there rather than returning partial data. Setting a live-mode environment flag does not change this. Use test mode for this vertical.

Parameters:

  • accountId (path, required)
  • reviewId (path, required)

batchGetGoogleBusinessReviewsPOST /v1/accounts/{accountId}/gmb-reviews/batch

TEST MODE ONLY TODAY. No Google Business adapter is composed into the live registry, so a Google Business account cannot be connected in live mode and this operation fails closed there rather than returning partial data. Setting a live-mode environment flag does not change this. Use test mode for this vertical.

Parameters:

  • accountId (path, required)

Request body (ZernioGoogleBusinessReviewBatchInputDto): locationNames (array of string, required), pageSize (number), pageToken (string), orderBy (enum: updateTime desc, rating, rating desc)

gmb-services

OperationMethodPathSummary
getGoogleBusinessServicesGET/v1/accounts/{accountId}/gmb-servicesGet Google Business services
updateGoogleBusinessServicesPUT/v1/accounts/{accountId}/gmb-servicesReplace Google Business services

getGoogleBusinessServicesGET /v1/accounts/{accountId}/gmb-services

TEST MODE ONLY TODAY. No Google Business adapter is composed into the live registry, so a Google Business account cannot be connected in live mode and this operation fails closed there rather than returning partial data. Setting a live-mode environment flag does not change this. Use test mode for this vertical.

Parameters:

  • accountId (path, required)
  • locationId (query)

updateGoogleBusinessServicesPUT /v1/accounts/{accountId}/gmb-services

TEST MODE ONLY TODAY. No Google Business adapter is composed into the live registry, so a Google Business account cannot be connected in live mode and this operation fails closed there rather than returning partial data. Setting a live-mode environment flag does not change this. Use test mode for this vertical.

Parameters:

  • accountId (path, required)
  • locationId (query)

Request body (ZernioGoogleBusinessServicesUpdateInputDto): serviceItems (array of GoogleBusinessLocationServiceItemDto, required)

gmb-verifications

OperationMethodPathSummary
getGoogleBusinessVerificationsGET/v1/accounts/{accountId}/gmb-verificationsGet Google Business verification state and history
startGoogleBusinessVerificationPOST/v1/accounts/{accountId}/gmb-verificationsStart a Google Business verification
completeGoogleBusinessVerificationPOST/v1/accounts/{accountId}/gmb-verifications/{verificationId}/completeComplete a Google Business verification
fetchGoogleBusinessVerificationOptionsPOST/v1/accounts/{accountId}/gmb-verifications/optionsFetch eligible Google Business verification options

getGoogleBusinessVerificationsGET /v1/accounts/{accountId}/gmb-verifications

TEST MODE ONLY TODAY. No Google Business adapter is composed into the live registry, so a Google Business account cannot be connected in live mode and this operation fails closed there rather than returning partial data. Setting a live-mode environment flag does not change this. Use test mode for this vertical.

Parameters:

  • accountId (path, required)
  • locationId (query)

startGoogleBusinessVerificationPOST /v1/accounts/{accountId}/gmb-verifications

TEST MODE ONLY TODAY. No Google Business adapter is composed into the live registry, so a Google Business account cannot be connected in live mode and this operation fails closed there rather than returning partial data. Setting a live-mode environment flag does not change this. Use test mode for this vertical.

Parameters:

  • accountId (path, required)
  • locationId (query)

Request body (GoogleBusinessVerificationStartInputDto): method (enum: ADDRESS, EMAIL, PHONE_CALL, SMS, AUTO, VETTED_PARTNER, required), languageCode (string), phoneNumber (string), emailAddress (string), mailerContact (GoogleBusinessVerificationMailerContactDto), context (GoogleBusinessServiceBusinessContextDto)

completeGoogleBusinessVerificationPOST /v1/accounts/{accountId}/gmb-verifications/{verificationId}/complete

TEST MODE ONLY TODAY. No Google Business adapter is composed into the live registry, so a Google Business account cannot be connected in live mode and this operation fails closed there rather than returning partial data. Setting a live-mode environment flag does not change this. Use test mode for this vertical.

Parameters:

  • accountId (path, required)
  • verificationId (path, required)
  • locationId (query)

Request body (GoogleBusinessVerificationCompleteInputDto): pin (string, required)

fetchGoogleBusinessVerificationOptionsPOST /v1/accounts/{accountId}/gmb-verifications/options

TEST MODE ONLY TODAY. No Google Business adapter is composed into the live registry, so a Google Business account cannot be connected in live mode and this operation fails closed there rather than returning partial data. Setting a live-mode environment flag does not change this. Use test mode for this vertical.

Parameters:

  • accountId (path, required)
  • locationId (query)

Request body (GoogleBusinessVerificationOptionsInputDto): languageCode (string, required), context (GoogleBusinessServiceBusinessContextDto)

health

OperationMethodPathSummary
getHealthGET/v1/healthService health probe

getHealthGET /v1/health

inbox

OperationMethodPathSummary
createInboxConversationPOST/v1/inbox/conversationsCreate conversation
listInboxConversationsGET/v1/inbox/conversationsList conversations
getInboxConversationGET/v1/inbox/conversations/{conversationId}Get conversation
updateInboxConversationPUT/v1/inbox/conversations/{conversationId}Update conversation status
getInboxConversationMessagesGET/v1/inbox/conversations/{conversationId}/messagesList messages
sendInboxMessagePOST/v1/inbox/conversations/{conversationId}/messagesSend a text-only message to an existing inbox conversation
editInboxMessagePATCH/v1/inbox/conversations/{conversationId}/messages/{messageId}Edit message
deleteInboxMessageDELETE/v1/inbox/conversations/{conversationId}/messages/{messageId}Delete message
getMessageAttachmentGET/v1/inbox/conversations/{conversationId}/messages/{messageId}/attachments/{index}Resolve message attachment
addMessageReactionPOST/v1/inbox/conversations/{conversationId}/messages/{messageId}/reactionsAdd reaction
removeMessageReactionDELETE/v1/inbox/conversations/{conversationId}/messages/{messageId}/reactionsRemove reaction
markConversationReadPOST/v1/inbox/conversations/{conversationId}/readMark a conversation as read
sendTypingIndicatorPOST/v1/inbox/conversations/{conversationId}/typingSend typing indicator
likePostPOST/v1/inbox/posts/{postId}/likeLike a post
unlikePostDELETE/v1/inbox/posts/{postId}/likeUnlike a post

createInboxConversationPOST /v1/inbox/conversations

Opens a Slack direct message with a workspace member and sends the first message. Pass the member id as participantId; list candidates with GET /v1/accounts/{accountId}/slack-members. The member must belong to the connected workspace. Only Slack is supported: an account on any other platform returns 400. The direct-message open converges, but Slack offers no idempotency key for the send, so an ambiguous result must not be retried blindly.

Request body (InboxConversationCreateInputDto): accountId (string, required), participantId (string, required), message (string, required)

listInboxConversationsGET /v1/inbox/conversations

Reads conversation threads live from every eligible connected account in one request. Omit accountId to fan out across all eligible accounts; supply it to read a single account. An account is eligible only when its platform adapter implements conversation listing, so a platform without that capability is skipped rather than reported as a failure. A per-account failure does not fail the request: it is counted in meta.accountsFailed and named in meta.failedAccounts, and the remaining accounts are still returned.

Parameters:

  • cursor (query) — Opaque cursor returned by this endpoint. It encodes one resume position per account and is only valid for the query that produced it.
  • limit (query)
  • sortOrder (query) — Order by last message time. Conversations with no known last message always sort last, in both directions.
  • status (query) — Conversation status filter. Only active is supported today; archived returns 400 because no connected platform exposes archived conversations here.
  • platform (query) — Restrict the fan-out to accounts on one platform.
  • profileId (query) — Restrict the fan-out to accounts on one profile.
  • accountId (query) — Restrict the fan-out to one account. Omit to read every eligible account.

getInboxConversationGET /v1/inbox/conversations/{conversationId}

Reads Slack through its connected adapter and Facebook/Instagram/Telegram/WhatsApp from the signed, durable inbox projection.

Parameters:

  • conversationId (path, required)
  • accountId (query, required)

updateInboxConversationPUT /v1/inbox/conversations/{conversationId}

Slack applies the connected provider operation. Facebook/Instagram/Telegram/WhatsApp persist active/archived state only in the durable local inbox projection.

Parameters:

  • conversationId (path, required)

Request body (UpdateInboxConversationRequestDto): accountId (string, required), status (enum: active, archived, required)

getInboxConversationMessagesGET /v1/inbox/conversations/{conversationId}/messages

Reads without changing read state. Slack provider cursors and Facebook/Instagram/Telegram/WhatsApp durable cursors are returned only inside an authenticated, context-bound opaque envelope. sortOrderApplied reports the exact delivered order.

Parameters:

  • conversationId (path, required)
  • sortOrder (query)
  • cursor (query)
  • limit (query)
  • accountId (query, required)

sendInboxMessagePOST /v1/inbox/conversations/{conversationId}/messages

Sends exactly one text message to an existing Slack, Telegram, or WhatsApp conversation. Telegram and WhatsApp require a canonically retained active conversation; this endpoint never invents a participant. Credential-scoped Idempotency-Key values replay completed responses for 24 hours. These providers expose no provider-side idempotency key, so an ambiguous outcome keeps its claim and must not be retried with a new key.

Parameters:

  • conversationId (path, required) — Existing provider conversation id retained for this account.
  • Idempotency-Key (header) — Credential-scoped key retained for 24 hours. Reusing it with the same request replays the original response.

Request body (SlackInboxMessageInputDto): accountId (string, required), message (string, required), replyTo (string)

editInboxMessagePATCH /v1/inbox/conversations/{conversationId}/messages/{messageId}

Edits the text and/or inline keyboard of a message the bot already sent. Only supported for Telegram; other platforms return 400. The edit writes an absolute desired state, so replaying the identical request is safe.

Parameters:

  • conversationId (path, required) — Platform conversation id. For Telegram this is the connected chat id.
  • messageId (path, required) — The Telegram message id to edit.

Request body (TelegramInboxMessageEditRequestDto): accountId (string, required), text (string), replyMarkup (TelegramInboxReplyMarkupDto)

deleteInboxMessageDELETE /v1/inbox/conversations/{conversationId}/messages/{messageId}

Deletes a message from a conversation. Telegram deletes the bot's own messages, and other participants' messages when the bot is an administrator. Platforms without a delete capability return 400.

Parameters:

  • conversationId (path, required) — Platform conversation id. For Telegram this is the connected chat id.
  • messageId (path, required) — The platform message id to delete.
  • accountId (query, required)

getMessageAttachmentGET /v1/inbox/conversations/{conversationId}/messages/{messageId}/attachments/{index}

Returns only an attachment URL actually retained or resolved by the connected adapter. A missing retained attachment is 404 and is never synthesized.

Parameters:

  • conversationId (path, required)
  • messageId (path, required)
  • index (path, required)
  • format (query)
  • accountId (query, required)

addMessageReactionPOST /v1/inbox/conversations/{conversationId}/messages/{messageId}/reactions

Adds one emoji reaction to a message. Telegram accepts a provider-defined subset of Unicode emoji and rejects the rest; platforms without a reaction capability return 400.

Parameters:

  • conversationId (path, required) — Platform conversation id. For Telegram this is the connected chat id.
  • messageId (path, required) — The platform message id to react to.

Request body (TelegramInboxReactionRequestDto): accountId (string, required), emoji (string, required)

removeMessageReactionDELETE /v1/inbox/conversations/{conversationId}/messages/{messageId}/reactions

Removes the reaction previously sent on a message. The emoji is not repeated: Telegram clears the reaction by writing an empty reaction set, which makes replaying the identical request safe. Platforms without a reaction capability return 400.

Parameters:

  • conversationId (path, required) — Platform conversation id. For Telegram this is the connected chat id.
  • messageId (path, required) — The platform message id.
  • accountId (query, required)

markConversationReadPOST /v1/inbox/conversations/{conversationId}/read

Slack applies the connected provider operation. Facebook/Instagram/Telegram/WhatsApp update only the durable local read watermark; the response does not claim an upstream provider read receipt.

Parameters:

  • conversationId (path, required)

Request body (MarkInboxConversationReadRequestDto): accountId (string, required)

sendTypingIndicatorPOST /v1/inbox/conversations/{conversationId}/typing

Shows a typing indicator in a conversation. Telegram shows it for about five seconds. Typing indicators are best-effort: platforms without the capability return 200 with success false, and a failed platform call also returns 200 with success false.

Parameters:

  • conversationId (path, required) — Platform conversation id. For Telegram this is the connected chat id.

Request body (TelegramInboxTypingRequestDto): accountId (string, required)

likePostPOST /v1/inbox/posts/{postId}/like

Likes a post with the connected account. Supported for YouTube (videos.rate) and Bluesky (an app.bsky.feed.like record). Reddit returns 400 and points at POST /v1/accounts/{accountId}/reddit-vote, which covers upvote, downvote and clear on both posts and comments; every other platform returns 400 as well. Bluesky requires cid and returns likeUri, which unlikePost then requires: nothing about the like is persisted here.

Parameters:

  • postId (path, required) — Zhyper post ID or the platform native post ID. A Bluesky native id is the canonical at:// feed-post URI and must be percent-encoded here.

Request body (InboxPostLikeRequestDto): accountId (string, required), cid (string)

unlikePostDELETE /v1/inbox/posts/{postId}/like

Clears the like on a post. YouTube writes rating=none, which is set-to-value and therefore safe to replay. Bluesky deletes the like record named by likeUri, so likeUri is required there and must be the value likePost returned; nothing is persisted here. Reddit returns 400 and points at POST /v1/accounts/{accountId}/reddit-vote; every other platform returns 400.

Parameters:

  • postId (path, required) — Zhyper post ID or the platform native post ID. A Bluesky native id is the canonical at:// feed-post URI and must be percent-encoded here.
  • likeUri (query) — Bluesky only, and required there: the likeUri returned by likePost. Rejected with 400 for a youtube account.
  • accountId (query, required) — Zhyper account id whose like is being cleared.

inbox-analytics

OperationMethodPathSummary
listInboxConversationAnalyticsGET/v1/analytics/inbox/conversationsList conversation analytics
getInboxConversationAnalyticsGET/v1/analytics/inbox/conversations/{conversationId}Get conversation analytics
getInboxHeatmapGET/v1/analytics/inbox/heatmapGet day by hour inbox heatmap
getInboxResponseTimeGET/v1/analytics/inbox/response-timeGet inbox response-time stats
getInboxSourceBreakdownGET/v1/analytics/inbox/source-breakdownGet inbox source breakdown
getInboxTopAccountsGET/v1/analytics/inbox/top-accountsGet top accounts by inbox volume
getInboxVolumeGET/v1/analytics/inbox/volumeGet inbox messaging volume

listInboxConversationAnalyticsGET /v1/analytics/inbox/conversations

Results are projections of retained durable message events; phone-number management, SMS, calls, and voice are outside this surface. Terminal outbox events have a finite retention window, so an older accepted range can be sparse or empty. Read and failed counts come only from retained provider lifecycle events. Pagination and sorting apply to conversations with at least one retained event in the range.

Parameters:

  • order (query)
  • sortBy (query)
  • page (query)
  • limit (query)
  • fromDate (query, required)
  • toDate (query)
  • profileId (query)
  • platform (query)
  • accountId (query)
  • source (query)

getInboxConversationAnalyticsGET /v1/analytics/inbox/conversations/{conversationId}

Results are projections of retained durable message events; phone-number management, SMS, calls, and voice are outside this surface. Terminal outbox events have a finite retention window, so an older accepted range can be sparse or empty. Read and failed counts come only from retained provider lifecycle events. The path accepts an internal durable conversation id or a platform conversation id.

Parameters:

  • conversationId (path, required)
  • toDate (query)
  • fromDate (query, required)

getInboxHeatmapGET /v1/analytics/inbox/heatmap

Results are projections of retained durable message events; phone-number management, SMS, calls, and voice are outside this surface. Terminal outbox events have a finite retention window, so an older accepted range can be sparse or empty. Read and failed counts come only from retained provider lifecycle events. Buckets are sparse and use UTC; dow is 1 Monday through 7 Sunday.

Parameters:

  • action (query)
  • fromDate (query, required)
  • toDate (query)
  • profileId (query)
  • platform (query)
  • accountId (query)
  • source (query)

getInboxResponseTimeGET /v1/analytics/inbox/response-time

Results are projections of retained durable message events; phone-number management, SMS, calls, and voice are outside this surface. Terminal outbox events have a finite retention window, so an older accepted range can be sparse or empty. Read and failed counts come only from retained provider lifecycle events. A sample exists only when the same retained conversation has a received event followed by a sent event.

Parameters:

  • fromDate (query, required)
  • toDate (query)
  • profileId (query)
  • platform (query)
  • accountId (query)

getInboxSourceBreakdownGET /v1/analytics/inbox/source-breakdown

Results are projections of retained durable message events; phone-number management, SMS, calls, and voice are outside this surface. Terminal outbox events have a finite retention window, so an older accepted range can be sparse or empty. Read and failed counts come only from retained provider lifecycle events. Only proven platform and api lineage rows are returned.

Parameters:

  • fromDate (query, required)
  • toDate (query)
  • profileId (query)
  • platform (query)
  • accountId (query)

getInboxTopAccountsGET /v1/analytics/inbox/top-accounts

Results are projections of retained durable message events; phone-number management, SMS, calls, and voice are outside this surface. Terminal outbox events have a finite retention window, so an older accepted range can be sparse or empty. Read and failed counts come only from retained provider lifecycle events. Account labels come from the current account row or the retained event projection.

Parameters:

  • limit (query)
  • source (query)
  • platform (query)
  • profileId (query)
  • toDate (query)
  • fromDate (query, required)

getInboxVolumeGET /v1/analytics/inbox/volume

Results are projections of retained durable message events; phone-number management, SMS, calls, and voice are outside this surface. Terminal outbox events have a finite retention window, so an older accepted range can be sparse or empty. Read and failed counts come only from retained provider lifecycle events. Max date range is 365 days.

Parameters:

  • fromDate (query, required)
  • toDate (query)
  • profileId (query)
  • platform (query)
  • accountId (query)
  • source (query)

incoming

OperationMethodPathSummary
receiveTelegramIncomingPOST/v1/incoming/telegram/{bindingId}Receive a signed Telegram incoming event

receiveTelegramIncomingPOST /v1/incoming/telegram/{bindingId}

Parameters:

  • bindingId (path, required)

instagram

OperationMethodPathSummary
searchInstagramAudioGET/v1/accounts/{accountId}/instagram/audioSearch Instagram audio
getInstagramAudioGET/v1/accounts/{accountId}/instagram/audio/{audioId}Get Instagram audio metadata
listInstagramStoriesGET/v1/accounts/{accountId}/instagram/storiesList active Instagram stories
getInstagramStoryInsightsGET/v1/accounts/{accountId}/instagram/stories/{storyId}/insightsGet Instagram story insights

searchInstagramAudioGET /v1/accounts/{accountId}/instagram/audio

Searches Meta Instagram Audio for an account connected through Facebook Login. Classic Instagram Login receives instagram_audio_requires_facebook_login.

Parameters:

  • accountId (path, required)
  • q (query)
  • audioType (query, required)

getInstagramAudioGET /v1/accounts/{accountId}/instagram/audio/{audioId}

Parameters:

  • accountId (path, required)
  • audioId (path, required)

listInstagramStoriesGET /v1/accounts/{accountId}/instagram/stories

Returns the connected Instagram professional account stories that remain active in Meta’s 24-hour window. Live videos and reshared stories are excluded by Meta.

Parameters:

  • accountId (path, required)

getInstagramStoryInsightsGET /v1/accounts/{accountId}/instagram/stories/{storyId}/insights

Returns live Meta metrics for an active story, a persisted webhook final state when available, or explicit unavailable zeroes.

Parameters:

  • accountId (path, required) — The internal id of the connected Instagram account.
  • storyId (path, required) — The numeric Instagram media id of the story.

invites

OperationMethodPathSummary
createInviteTokenPOST/v1/invite/tokensCreate invite token

createInviteTokenPOST /v1/invite/tokens

Generate a secure, single-use invite link scoped to the current tenant and execution mode. Invites expire after 7 days. Restricted API keys are rejected because invite management is admin-plane.

Request body (CreateInviteTokenDto): scope (enum: all, profiles, required), profileIds (array of string), role (enum: admin, member, billing_admin, viewer), readOnly (boolean)

logs

OperationMethodPathSummary
listLogsGET/v1/logsList provider publish attempts

listLogsGET /v1/logs

Publish attempts, NOT a ledger of the API requests you made. The only writer is the publish worker, which appends one row per post target whose provider call produced exactly one observation. Requests you send to this API are not recorded here, and neither is connection, webhook or messaging activity. CONSTANT FIELDS: method, path and status are written as "POST", "/v1/posts" and 202 on every row - they name the call that created the post, not the attempt being logged, so filtering or grouping by them separates nothing. The fields that actually vary per row are id, requestId (the idempotency key of the post being published, which is what you correlate on), platform, upstreamStatus, upstreamError, durationMs, attempts and createdAt. FILTERS: platform, request_id, from, to and days narrow the result; they compose as an intersection. THESE FIVE ARE THE ONLY FILTERS, and any other query parameter is REJECTED with 400 unknown_parameter rather than ignored - so a filter you send is either applied or named back to you in the error, never silently dropped. Pagination parameters (skip, page, offset) are refused with their own code, because this endpoint paginates with limit and cursor. RETENTION: rows are kept for 30 days and anything older is deleted by partition retention, so this endpoint is a rolling window, not a full archive. Export what you need to keep. "days" therefore has no default and no value above 30: omitting it returns every row still retained, and a longer window is refused with 400 invalid_days rather than accepted and answered from data that no longer exists. Retention drops whole monthly partitions, so a row slightly older than 30 days may still be present; 30 days is the guarantee, not the maximum age you will ever observe.

Parameters:

  • days (query) — Trailing window in days (createdAt >= now() - days). No default: omitting it searches every row still retained. The maximum is 30 because rows are deleted after 30 days; a larger value is refused with 400 invalid_days instead of returning a window the data cannot fill. Combines with "from" as an intersection, so the effective lower bound is the later of the two.
  • to (query) — Exclusive upper bound (createdAt < value), RFC 3339 UTC.
  • from (query) — Inclusive lower bound (createdAt >= value), RFC 3339 UTC.
  • request_id (query) — Correlation id: returns every publish attempt spawned by one create-post request. This is the idempotency key of the post, the value carried in the requestId response field.
  • platform (query) — Filter by platform. "all" applies no filter. Rows whose platform is unknown (null) are excluded whenever a concrete platform is given.
  • cursor (query)
  • limit (query)

media

OperationMethodPathSummary
deleteMediaDELETE/v1/media/{id}Delete a media object
completeMediaPOST/v1/media/{id}/completeComplete a presigned media upload
getMediaPresignedUrlPOST/v1/media/presignCreate a presigned media upload
uploadMediaPOST/v1/media/uploadUpload a media file
uploadMediaDirectPOST/v1/media/upload-directUpload media file

deleteMediaDELETE /v1/media/{id}

Parameters:

  • id (path, required)

completeMediaPOST /v1/media/{id}/complete

Parameters:

  • id (path, required)

getMediaPresignedUrlPOST /v1/media/presign

Creates a tenant-owned media record and a bounded presigned PUT URL. Complete the upload with POST /v1/media/{id}/complete; this operation does not publish an anonymous temporary public-media lifecycle.

Request body (PresignMediaDto): mime (enum: image/jpeg, image/png, image/webp, image/gif, video/mp4, video/quicktime, video/webm, application/pdf, application/zip, application/octet-stream, required), sizeBytes (number, required), checksum (string)

uploadMediaPOST /v1/media/upload

uploadMediaDirectPOST /v1/media/upload-direct

Uploads one bounded file and returns a bearer-free signed URL. The durable media-retention worker automatically deletes the object after seven days.

mentions

OperationMethodPathSummary
listInboxMentionsGET/v1/inbox/mentionsList mentions
replyToMentionPOST/v1/inbox/mentions/replyReply to a mention

listInboxMentionsGET /v1/inbox/mentions

Returns mentions of your connected organization accounts, delivered via platform webhooks. Currently supports LinkedIn organization mentions.

Mentions are served from local storage, not from a provider fan-out, so this endpoint does not consult the platform at request time.

Parameters:

  • cursor (query)
  • limit (query)
  • sortOrder (query)
  • profileId (query)
  • accountId (query)

replyToMentionPOST /v1/inbox/mentions/reply

Reply to a mention of the connected account. Supported on Instagram only.

Two shapes, selected by whether commentId is present. Comment mention: pass both mediaId and commentId, and the reply is posted under that comment. Caption mention: pass mediaId only, and the reply is posted as a comment on the media.

Story mentions are not supported by Instagram. Source mediaId and commentId from Instagram's comments webhook.

Request body (InboxMentionReplyInputDto): accountId (string, required), mediaId (string, required), commentId (string), message (string, required)

messages

OperationMethodPathSummary
searchInboxConversationsGET/v1/inbox/conversations/searchSearch conversations

searchInboxConversationsGET /v1/inbox/conversations/search

Searches durable Facebook, Instagram, Telegram, and WhatsApp inbox projections. Message text uses case/accent-insensitive exact tokens; identity uses a case/accent-insensitive name or WhatsApp phone substring. Direction disables identity matching. Slack and SMS are rejected because this service has no durable searchable message projection for them.

Parameters:

  • cursor (query)
  • limit (query)
  • accountId (query)
  • platform (query) — Target-compatible filter values. SMS and Slack return a typed 501 until a durable local message projection exists.
  • profileId (query)
  • direction (query)
  • query (query, required)

phone-numbers

OperationMethodPathSummary
listPhoneNumbersGET/v1/phone-numbersList phone numbers
getPhoneNumberGET/v1/phone-numbers/{id}Get phone number
releasePhoneNumberDELETE/v1/phone-numbers/{id}Release phone number
getPhoneNumberRemediationGET/v1/phone-numbers/{id}/remediateGet declined requirements
remediatePhoneNumberPOST/v1/phone-numbers/{id}/remediateResubmit a declined number
checkPhoneNumberAvailabilityGET/v1/phone-numbers/availabilityCheck country availability
searchAvailablePhoneNumbersGET/v1/phone-numbers/availableSearch available numbers
listPhoneNumberCountriesGET/v1/phone-numbers/countriesList offerable number countries
submitPhoneNumberKycPOST/v1/phone-numbers/kycSubmit KYC
createPhoneNumberKycLinkPOST/v1/phone-numbers/kyc/shareCreate a hosted KYC link
uploadPhoneNumberKycDocumentPOST/v1/phone-numbers/kyc/upload-documentUpload a KYC document
validatePhoneNumberKycAddressPOST/v1/phone-numbers/kyc/validate-addressPre-validate KYC address
purchasePhoneNumberPOST/v1/phone-numbers/purchasePurchase phone number

listPhoneNumbersGET /v1/phone-numbers

Canonical provider-backed phone-number surface. The durable WhatsApp provisioning/KYC subset is available; live mode fails closed unless a carrier provider is configured.

Parameters:

  • profileId (query)
  • status (query)

getPhoneNumberGET /v1/phone-numbers/{id}

Canonical provider-backed phone-number surface. The durable WhatsApp provisioning/KYC subset is available; live mode fails closed unless a carrier provider is configured.

Parameters:

  • id (path, required)

releasePhoneNumberDELETE /v1/phone-numbers/{id}

Canonical provider-backed phone-number surface. The durable WhatsApp provisioning/KYC subset is available; live mode fails closed unless a carrier provider is configured.

Parameters:

  • id (path, required)

getPhoneNumberRemediationGET /v1/phone-numbers/{id}/remediate

Canonical provider-backed phone-number surface. The durable WhatsApp provisioning/KYC subset is available; live mode fails closed unless a carrier provider is configured.

Parameters:

  • id (path, required)

remediatePhoneNumberPOST /v1/phone-numbers/{id}/remediate

Canonical provider-backed phone-number surface. The durable WhatsApp provisioning/KYC subset is available; live mode fails closed unless a carrier provider is configured.

Parameters:

  • id (path, required)

Request body (RemediateWhatsAppNumberDto): values (object), documents (array of WhatsAppKycInlineDocumentDto | WhatsAppKycUploadedDocumentDto), address (WhatsAppKycAddressDto)

checkPhoneNumberAvailabilityGET /v1/phone-numbers/availability

Canonical provider-backed phone-number surface. The durable WhatsApp provisioning/KYC subset is available; live mode fails closed unless a carrier provider is configured.

Parameters:

  • sms (query)
  • numberType (query)
  • country (query, required)

searchAvailablePhoneNumbersGET /v1/phone-numbers/available

Canonical provider-backed phone-number surface. The durable WhatsApp provisioning/KYC subset is available; live mode fails closed unless a carrier provider is configured.

Parameters:

  • limit (query)
  • contains (query)
  • locality (query)
  • prefix (query)
  • type (query)
  • country (query)

listPhoneNumberCountriesGET /v1/phone-numbers/countries

Canonical provider-backed phone-number surface. The durable WhatsApp provisioning/KYC subset is available; live mode fails closed unless a carrier provider is configured.

submitPhoneNumberKycPOST /v1/phone-numbers/kyc

Canonical provider-backed phone-number surface. The durable WhatsApp provisioning/KYC subset is available; live mode fails closed unless a carrier provider is configured.

Request body (SubmitWhatsAppNumberKycDto): profileId (string, required), country (string, required), submissionId (string), quantity (number), reuse (boolean), reuseOptionId (string), reuseFrom (string), areaCode (string), endUserFirstName (string), endUserLastName (string), entityType (enum: business, individual), values (object), documents (array of WhatsAppKycInlineDocumentDto | WhatsAppKycUploadedDocumentDto), address (WhatsAppKycAddressDto)

createPhoneNumberKycLinkPOST /v1/phone-numbers/kyc/share

Canonical provider-backed phone-number surface. The durable WhatsApp provisioning/KYC subset is available; live mode fails closed unless a carrier provider is configured.

Request body (CreateWhatsAppNumberKycLinkDto): profileId (string, required), country (string, required), areaCode (string), branding (WhatsAppKycBrandingDto), redirect_url (string)

uploadPhoneNumberKycDocumentPOST /v1/phone-numbers/kyc/upload-document

Canonical provider-backed phone-number surface. The durable WhatsApp provisioning/KYC subset is available; live mode fails closed unless a carrier provider is configured.

Parameters:

  • X-Filename (header, required)

validatePhoneNumberKycAddressPOST /v1/phone-numbers/kyc/validate-address

Canonical provider-backed phone-number surface. The durable WhatsApp provisioning/KYC subset is available; live mode fails closed unless a carrier provider is configured.

Request body (ValidateWhatsAppNumberKycAddressDto): country (string, required), street_address (string, required), locality (string, required), administrative_area (string), postal_code (string, required)

purchasePhoneNumberPOST /v1/phone-numbers/purchase

Canonical provider-backed phone-number surface. The durable WhatsApp provisioning/KYC subset is available; live mode fails closed unless a carrier provider is configured.

Request body (PurchaseWhatsAppPhoneNumberDto): profileId (string, required), country (string), purchaseIntentId (string), allowMultiple (boolean)

platforms

OperationMethodPathSummary
listPlatformsGET/v1/platformsList platform capabilities

listPlatformsGET /v1/platforms

Parameters:

  • accountId (query) — Optional owned active account whose provider-backed capability is requested.

posts

OperationMethodPathSummary
listPostsGET/v1/postsList posts
createPostPOST/v1/postsCreate a post
cancelPendingTargetsPOST/v1/posts/{id}/cancel-pendingCancel pending targets of a post
recyclePostPOST/v1/posts/{id}/recycleRepublish a published post as a new post
updatePostPUT/v1/posts/{postId}Update a post
patchPostLegacyPATCH/v1/posts/{postId}Update a post (legacy PATCH)
deletePostDELETE/v1/posts/{postId}Delete a draft post
getPostGET/v1/posts/{postId}Retrieve a post
editPostPOST/v1/posts/{postId}/editEdit a published platform post
retryPostPOST/v1/posts/{postId}/retryRetry failed targets of a post
cancelHeldTargetPOST/v1/posts/{postId}/targets/{targetId}/held-decisions/{decisionId}/cancelCancel a held post target
resumeHeldTargetPOST/v1/posts/{postId}/targets/{targetId}/held-decisions/{decisionId}/resumeResume a held post target
unpublishPostPOST/v1/posts/{postId}/unpublishRemove a published post from its platform
updatePostMetadataPOST/v1/posts/{postId}/update-metadataUpdate published YouTube video metadata
createPostsBulkPOST/v1/posts/bulkCreate many posts from a CSV file
bulkUploadPostsPOST/v1/posts/bulk-uploadBulk upload from CSV
syncExternalPostsPOST/v1/posts/sync-externalDeclare posts published outside zhyper

listPostsGET /v1/posts

Parameters:

  • platform (query)
  • accountId (query)
  • profileId (query)
  • status (query)
  • sort (query)
  • sortBy (query) — Zernio-compatible aliases for the supported created/scheduled sort orders. Mutually exclusive with sort.
  • search (query) — Case-insensitive literal substring search over post content.
  • source (query) — zernio reads posts authored through this API. external reads the provider history reported through POST /v1/posts/sync-external, which lives in its own table and never appears in a zernio read. External rows carry no lifecycle status and no scheduled_for, so status and scheduled sorts are rejected for them.
  • page (query) — One-based compatibility page. Mutually exclusive with cursor; cursor remains the preferred stable pagination mode.
  • cursor (query)
  • limit (query) — Page size. The legacy cursor default remains 25; the bounded maximum is Zernio-compatible.

createPostPOST /v1/posts

Parameters:

  • Idempotency-Key (header) — Five-minute replay key scoped by team and execution mode. Takes precedence over x-request-id, so an SDK retry that reuses the key replays the original response instead of creating a second post.
  • x-request-id (header) — Five-minute replay key scoped by team and execution mode. Used only when Idempotency-Key is absent.

Request body (CreatePostDto): title (string), content (string), tags (array of string), hashtags (array of string), mentions (array of string), crosspostingEnabled (boolean), metadata (object), platforms (array of CreatePostPlatformDto), tiktokSettings (object), facebookSettings (object), mediaIds (array of string), mediaItems (array of PostMediaItemDto), scheduledFor (string), publishNow (boolean), isDraft (boolean), recycling (RecyclingConfigDto), timezone (string), exactTiming (boolean), queuedFromProfile (string), queueId (string), skipDuplicateCheck (boolean)

cancelPendingTargetsPOST /v1/posts/{id}/cancel-pending

Parameters:

  • id (path, required)

recyclePostPOST /v1/posts/{id}/recycle

Creates an INDEPENDENT new post from a published one: content, media and targets are copied, the schedule comes from the request. The source post is not modified and the two are not linked afterwards — the link lives only in the post.recycled event. This endpoint itself is a one-shot manual action; automatic weekly/monthly copies are configured separately through the recycling field on POST/PUT /v1/posts.

Parameters:

  • id (path, required)
  • x-request-id (header) — Five-minute replay key scoped by team and execution mode.

Request body (RecyclePostDto): platforms (array of CreatePostPlatformDto), publishNow (boolean), scheduledFor (string), queuedFromProfile (string), queueId (string), timezone (string), exactTiming (boolean), skipDuplicateCheck (boolean)

updatePostPUT /v1/posts/{postId}

Parameters:

  • postId (path, required)

Request body (PatchPostDto): title (string), content (string), tags (array of string), hashtags (array of string), mentions (array of string), visibility (enum: public, private, unlisted), crosspostingEnabled (boolean), metadata (object), platforms (array of CreatePostPlatformDto), tiktokSettings (object), facebookSettings (object), mediaIds (array of string), mediaItems (array of PostMediaItemDto), scheduledFor (string), publishNow (boolean), isDraft (boolean), recycling (RecyclingConfigDto), timezone (string), exactTiming (boolean), queuedFromProfile (string), queueId (string), skipDuplicateCheck (boolean)

patchPostLegacyPATCH /v1/posts/{postId}

Deprecated transport alias for PUT /v1/posts/{postId}. Existing raw PATCH clients remain supported.

Parameters:

  • postId (path, required)

Request body (PatchPostDto): title (string), content (string), tags (array of string), hashtags (array of string), mentions (array of string), visibility (enum: public, private, unlisted), crosspostingEnabled (boolean), metadata (object), platforms (array of CreatePostPlatformDto), tiktokSettings (object), facebookSettings (object), mediaIds (array of string), mediaItems (array of PostMediaItemDto), scheduledFor (string), publishNow (boolean), isDraft (boolean), recycling (RecyclingConfigDto), timezone (string), exactTiming (boolean), queuedFromProfile (string), queueId (string), skipDuplicateCheck (boolean)

deletePostDELETE /v1/posts/{postId}

Parameters:

  • postId (path, required)

getPostGET /v1/posts/{postId}

Parameters:

  • postId (path, required)

editPostPOST /v1/posts/{postId}/edit

Parameters:

  • postId (path, required)

Request body (EditPostDto): platform (enum: twitter, discord, facebook, reddit, mastodon, google_business, required), content (string, required)

retryPostPOST /v1/posts/{postId}/retry

Parameters:

  • postId (path, required)

Request body (RetryPostDto): targetIds (array of string)

cancelHeldTargetPOST /v1/posts/{postId}/targets/{targetId}/held-decisions/{decisionId}/cancel

Parameters:

  • postId (path, required)
  • targetId (path, required)
  • decisionId (path, required)

resumeHeldTargetPOST /v1/posts/{postId}/targets/{targetId}/held-decisions/{decisionId}/resume

Parameters:

  • postId (path, required)
  • targetId (path, required)
  • decisionId (path, required)

unpublishPostPOST /v1/posts/{postId}/unpublish

Parameters:

  • postId (path, required)

Request body (UnpublishPostDto): platform (enum: threads, facebook, twitter, linkedin, youtube, pinterest, reddit, bluesky, googlebusiness, telegram, required)

updatePostMetadataPOST /v1/posts/{postId}/update-metadata

Updates caller-selected fields without re-uploading the video. Use a stored Zhyper post id, or "_" with accountId and videoId for a video created elsewhere. playlistId may be sent alone or with metadata. Combined requests acquire both provider quotas and revalidate the source before dispatch; exact desired-state metadata runs first, followed by one at-most-once playlist membership operation.

Parameters:

  • postId (path, required)

Request body (YoutubeVideoMetadataUpdateDto): platform (enum: youtube, required), videoId (string), accountId (string), title (string), description (string), tags (array of string), categoryId (string), privacyStatus (enum: private, public, unlisted), thumbnailUrl (string), madeForKids (boolean), containsSyntheticMedia (boolean), playlistId (string)

createPostsBulkPOST /v1/posts/bulk

Each CSV data row is exactly one POST /v1/posts body; column names match the JSON field names. Always answers 207 when the file was parsed — read the per-row status. Whole-request failures (bad file, too many rows) answer 4xx.

Parameters:

  • x-request-id (header) — Five-minute replay key. Each row derives its own key as "<requestId>:<row>", so replaying the same file replays each row instead of duplicating it.
  • dryRun (query) — Validate every row against the real code path and persist nothing. Rows are validated INDEPENDENTLY: two rows that would collide with each other (same account, same content) both pass here but the second one gets 409 for real.

bulkUploadPostsPOST /v1/posts/bulk-upload

Zernio-compatible response projection over the same tenant-scoped create-post validation and idempotency path as POST /v1/posts. Every CSV row is isolated. All-success and all-failure batches return 200; mixed batches return 207.

Parameters:

  • x-request-id (header) — Five-minute replay key. A deterministic per-row key is derived as "<requestId>:<row>".
  • dryRun (query) — Run every row through the real create path, including tenant, account, media, queue, adapter, replay, and dedup checks, then roll back every write.

syncExternalPostsPOST /v1/posts/sync-external

PUSH direction: the customer declares posts they published outside zhyper and zhyper stores a read-only mirror. zhyper makes NO provider request here. Idempotency is STATE-based, not request-based: the natural key is (account, platformPostId) and an exact re-submission returns every item as "unchanged" and emits no webhooks — the five-minute x-request-id replay fence used by POST /v1/posts does NOT apply. post.external.deleted carries source: 'client_assertion' because the deletion is asserted by you, not observed by zhyper.

Request body (SyncExternalPostsBodyDto): posts (array of SyncExternalPostItemDto, required)

profiles

OperationMethodPathSummary
listProfilesGET/v1/profilesList profiles
createProfilePOST/v1/profilesCreate a profile
getProfileGET/v1/profiles/{profileId}Retrieve a profile
updateProfilePUT/v1/profiles/{profileId}Update a profile
patchProfilePATCH/v1/profiles/{profileId}Update a profile (legacy PATCH)
deleteProfileDELETE/v1/profiles/{profileId}Delete a profile

listProfilesGET /v1/profiles

Parameters:

  • sort (query)
  • cursor (query)
  • includeOverLimit (query)
  • name (query)
  • skip (query)
  • limit (query)

createProfilePOST /v1/profiles

Parameters:

  • Idempotency-Key (header) — Credential-scoped key retained for 24 hours. Reusing it with the same body replays the original response.

Request body (CreateProfileDto): name (string, required), timezone (string), description (string), color (string)

getProfileGET /v1/profiles/{profileId}

Parameters:

  • profileId (path, required)

updateProfilePUT /v1/profiles/{profileId}

Parameters:

  • profileId (path, required)

Request body (UpdateProfileDto): name (string), timezone (string), description (string), color (string), isDefault (boolean)

patchProfilePATCH /v1/profiles/{profileId}

Parameters:

  • profileId (path, required)

Request body (UpdateProfileDto): name (string), timezone (string), description (string), color (string), isDefault (boolean)

deleteProfileDELETE /v1/profiles/{profileId}

Parameters:

  • profileId (path, required)

queue

OperationMethodPathSummary
getNextQueueSlotGET/v1/queue/next-slotGet next available slot
previewQueueGET/v1/queue/previewPreview upcoming slots
listQueueSlotsGET/v1/queue/slotsList schedules
createQueueSlotPOST/v1/queue/slotsCreate schedule
updateQueueSlotPUT/v1/queue/slotsUpdate schedule
deleteQueueSlotDELETE/v1/queue/slotsDelete schedule

getNextQueueSlotGET /v1/queue/next-slot

Parameters:

  • queueId (query)
  • profileId (query, required)

previewQueueGET /v1/queue/preview

Parameters:

  • count (query)
  • queueId (query)
  • profileId (query, required)

listQueueSlotsGET /v1/queue/slots

Parameters:

  • all (query)
  • queueId (query)
  • profileId (query, required)

createQueueSlotPOST /v1/queue/slots

Request body (CreateQueueScheduleRequestDto): profileId (string, required), name (string, required), timezone (string, required), slots (array of QueueScheduleSlotDto, required), active (boolean)

updateQueueSlotPUT /v1/queue/slots

Request body (UpdateQueueScheduleRequestDto): profileId (string, required), queueId (string), name (string), timezone (string, required), slots (array of QueueScheduleSlotDto, required), active (boolean), setAsDefault (boolean), reshuffleExisting (boolean)

deleteQueueSlotDELETE /v1/queue/slots

Parameters:

  • queueId (query)
  • profileId (query, required)

queues

OperationMethodPathSummary
listQueuesGET/v1/queuesList queues
createQueuePOST/v1/queuesCreate a queue
getQueueGET/v1/queues/{id}Retrieve a queue
updateQueuePATCH/v1/queues/{id}Update a queue
deleteQueueDELETE/v1/queues/{id}Delete a queue

listQueuesGET /v1/queues

Parameters:

  • profileId (query)
  • sort (query)
  • cursor (query)
  • limit (query)

createQueuePOST /v1/queues

Request body (CreateQueueDto): profileId (string, required), name (string, required), slots (array of QueueSlotDto, required), timezone (string)

getQueueGET /v1/queues/{id}

Parameters:

  • id (path, required)

updateQueuePATCH /v1/queues/{id}

Parameters:

  • id (path, required)

Request body (UpdateQueueDto): name (string), slots (array of QueueSlotDto), timezone (string)

deleteQueueDELETE /v1/queues/{id}

Parameters:

  • id (path, required)

reddit

OperationMethodPathSummary
getRedditFeedGET/v1/reddit/feedRead a connected Reddit account feed
searchRedditGET/v1/reddit/searchSearch Reddit with a connected account

getRedditFeedGET /v1/reddit/feed

Parameters:

  • t (query)
  • after (query)
  • limit (query)
  • sort (query)
  • subreddit (query)
  • accountId (query, required)

searchRedditGET /v1/reddit/search

Parameters:

  • after (query)
  • limit (query)
  • sort (query)
  • restrict_sr (query)
  • subreddit (query)
  • q (query, required)
  • accountId (query, required)

reviews

OperationMethodPathSummary
listInboxReviewsGET/v1/inbox/reviewsList reviews
replyToInboxReviewPOST/v1/inbox/reviews/{reviewId}/replyReply to a Google Business review
deleteInboxReviewReplyDELETE/v1/inbox/reviews/{reviewId}/replyDelete a Google Business review reply

listInboxReviewsGET /v1/inbox/reviews

Reads reviews live from every eligible connected account in one request. Omit accountId to fan out across all eligible accounts; supply it to read a single account. Only Google Business has a shipped review pipeline today, so an account on any other platform is skipped rather than reported as a failure. A per-account failure does not fail the request: it is counted in meta.accountsFailed and named in meta.failedAccounts, and the remaining accounts are still returned.

TEST MODE ONLY TODAY. No Google Business adapter is composed into the live registry, so no Google Business account can be connected in live mode and none is eligible for this fan-out there. In live mode this operation reads zero accounts and returns 200 with an empty data array and meta.accountsQueried 0 -- it does not fail. Setting a live-mode environment flag does not change this. Use test mode for this vertical.

Parameters:

  • cursor (query) — Opaque cursor returned by this endpoint. It encodes one resume position per account and is only valid for the query that produced it.
  • limit (query)
  • sortOrder (query) — Sort direction. sortBy=date supports desc only and returns 400 for asc; sortBy=rating supports both.
  • sortBy (query) — date orders by the time Google last updated the review, which is the only date order Google exposes; the published created field is the creation time and the two differ only for an edited review. date is available in descending order only.
  • hasReply (query) — Keep only reviews that already have a reply, or only those that do not.
  • maxRating (query) — Drop reviews rated above this many stars.
  • minRating (query) — Drop reviews rated below this many stars.
  • platform (query) — Restrict the fan-out to accounts on one platform. facebook is accepted but returns no reviews: it has no review pipeline here.
  • profileId (query) — Restrict the fan-out to accounts on one profile.
  • accountId (query) — Restrict the fan-out to one account. Omit to read every eligible account.

replyToInboxReviewPOST /v1/inbox/reviews/{reviewId}/reply

Provider-backed Google Business subset. reviewId must be the URL-encoded full review resource name returned by Google Business.

TEST MODE ONLY TODAY. No Google Business adapter is composed into the live registry, so a Google Business account cannot be connected in live mode and this operation fails closed there rather than returning partial data. Setting a live-mode environment flag does not change this. Use test mode for this vertical.

Parameters:

  • reviewId (path, required)

Request body (InboxReviewReplyInputDto): accountId (string, required), message (string, required)

deleteInboxReviewReplyDELETE /v1/inbox/reviews/{reviewId}/reply

Provider-backed Google Business subset. reviewId must be the URL-encoded full review resource name returned by Google Business.

TEST MODE ONLY TODAY. No Google Business adapter is composed into the live registry, so a Google Business account cannot be connected in live mode and this operation fails closed there rather than returning partial data. Setting a live-mode environment flag does not change this. Use test mode for this vertical.

Parameters:

  • reviewId (path, required)

Request body (InboxReviewReplyDeleteInputDto): accountId (string, required)

sequences

OperationMethodPathSummary
listSequencesGET/v1/sequencesList sequences
createSequencePOST/v1/sequencesCreate a sequence
getSequenceGET/v1/sequences/{sequenceId}Retrieve a sequence with its steps
updateSequencePATCH/v1/sequences/{sequenceId}Update a sequence
deleteSequenceDELETE/v1/sequences/{sequenceId}Delete a sequence
activateSequencePOST/v1/sequences/{sequenceId}/activateActivate a sequence
enrollContactsPOST/v1/sequences/{sequenceId}/enrollEnroll contacts into a sequence
unenrollContactDELETE/v1/sequences/{sequenceId}/enroll/{contactId}Unenroll a contact from a sequence
listSequenceEnrollmentsGET/v1/sequences/{sequenceId}/enrollmentsList a sequence's enrollments
pauseSequencePOST/v1/sequences/{sequenceId}/pausePause a sequence

listSequencesGET /v1/sequences

Parameters:

  • skip (query)
  • limit (query)
  • status (query)
  • profileId (query) — If omitted, the sequences of ALL profiles the key can reach are returned.

createSequencePOST /v1/sequences

The sequence is always created as a draft. Contacts can only be enrolled after activateSequence, which requires at least one step and content on every step.

Request body (CreateSequenceDto): profileId (string, required), accountId (string, required), platform (string, required), name (string, required), description (string), steps (array of SequenceStepInputDto), exitOnReply (boolean), exitOnUnsubscribe (boolean)

getSequenceGET /v1/sequences/{sequenceId}

Parameters:

  • sequenceId (path, required)

updateSequencePATCH /v1/sequences/{sequenceId}

Only provided fields change; sending null clears a nullable field. Sending steps replaces the whole list and is only allowed while the sequence is a draft.

Parameters:

  • sequenceId (path, required)

Request body (UpdateSequenceDto): name (string), description (string), steps (array of SequenceStepInputDto), exitOnReply (boolean), exitOnUnsubscribe (boolean)

deleteSequenceDELETE /v1/sequences/{sequenceId}

Permanently deletes the sequence together with its steps and every enrollment, including active ones. There is no soft-delete window in this API version.

Parameters:

  • sequenceId (path, required)

activateSequencePOST /v1/sequences/{sequenceId}/activate

Requires at least one step and content (message or template) on every step. Idempotent: activating an already active sequence returns it unchanged.

Parameters:

  • sequenceId (path, required)

enrollContactsPOST /v1/sequences/{sequenceId}/enroll

Per-contact outcomes are reported in enrolled[], skipped[] and errors[] under a 200. A contact that already has a live enrollment is skipped, never reset: resetting would resend step 1 to someone in the middle of the drip. The sequence must be active.

Parameters:

  • sequenceId (path, required)

Request body (EnrollContactsDto): contactIds (array of string, required), channelIds (array of string)

unenrollContactDELETE /v1/sequences/{sequenceId}/enroll/{contactId}

Ends the live enrollment (status becomes exited with exitReason manual). The row is kept so the progress history survives and the contact can be enrolled again later.

Parameters:

  • sequenceId (path, required)
  • contactId (path, required)

listSequenceEnrollmentsGET /v1/sequences/{sequenceId}/enrollments

Parameters:

  • sequenceId (path, required)
  • skip (query)
  • limit (query)
  • status (query)

pauseSequencePOST /v1/sequences/{sequenceId}/pause

New enrollments are refused while paused. Existing enrollments keep their status and their due date; nothing advances them in this API version anyway.

Parameters:

  • sequenceId (path, required)

sms

OperationMethodPathSummary
listSmsBrandsGET/v1/sms/brandsList 10DLC brands
createSmsBrandPOST/v1/sms/brandsRegister a 10DLC brand
getSmsBrandGET/v1/sms/brands/{id}Get one 10DLC brand
listSmsCampaignsGET/v1/sms/campaignsList 10DLC campaigns of one brand
createSmsCampaignPOST/v1/sms/campaignsRegister a 10DLC campaign
getSmsCampaignGET/v1/sms/campaigns/{id}Get one 10DLC campaign
sendSmsPOST/v1/sms/messagesSend an SMS message
listSmsMessagesGET/v1/sms/messagesList SMS messages
getSmsMessageGET/v1/sms/messages/{id}Get one SMS message
listSmsSenderIdsGET/v1/sms/sender-idsList alphanumeric sender IDs
createSmsSenderIdPOST/v1/sms/sender-idsRegister an alphanumeric sender ID
getSmsSenderIdGET/v1/sms/sender-ids/{id}Get one alphanumeric sender ID
deleteSmsSenderIdDELETE/v1/sms/sender-ids/{id}Delete an alphanumeric sender ID

listSmsBrandsGET /v1/sms/brands

Reads and writes the CARRIER registry directly. There is no local copy of these records in zhyper, on purpose: two copies of a registration would drift and nothing would notice. Consequences: results reflect the carrier at call time, pagination is the carrier page model rather than this API's cursor model, and a create is not idempotent - repeating it registers again. When the SMS provider is not configured the endpoint answers 503 sms_provider_unavailable rather than an empty list, so "not configured" is never mistaken for "nothing registered".

Parameters:

  • country (query)
  • entityType (query)
  • displayName (query)
  • recordsPerPage (query)
  • page (query)

createSmsBrandPOST /v1/sms/brands

Reads and writes the CARRIER registry directly. There is no local copy of these records in zhyper, on purpose: two copies of a registration would drift and nothing would notice. Consequences: results reflect the carrier at call time, pagination is the carrier page model rather than this API's cursor model, and a create is not idempotent - repeating it registers again. When the SMS provider is not configured the endpoint answers 503 sms_provider_unavailable rather than an empty list, so "not configured" is never mistaken for "nothing registered". Set "mock": true to have the registry score the brand without charging vetting fees - the carrier offers this so a typo in an EIN is not discovered by paying for it.

Request body (CreateSmsBrandDto): entityType (enum: PRIVATE_PROFIT, PUBLIC_PROFIT, NON_PROFIT, GOVERNMENT, SOLE_PROPRIETOR, required), displayName (string, required), country (string, required), email (string, required), vertical (enum: AGRICULTURE, COMMUNICATION, CONSTRUCTION, EDUCATION, ENERGY, ENTERTAINMENT, FINANCIAL, GAMBLING, GOVERNMENT, HEALTHCARE, HOSPITALITY, HUMAN_RESOURCES, INSURANCE, LEGAL, MANUFACTURING, NGO, POLITICAL, POSTAL, PROFESSIONAL, REAL_ESTATE, RETAIL, TECHNOLOGY, TRANSPORTATION, required), companyName (string), ein (string), phone (string), street (string), city (string), state (string), postalCode (string), website (string), isReseller (boolean), mock (boolean)

getSmsBrandGET /v1/sms/brands/{id}

Reads and writes the CARRIER registry directly. There is no local copy of these records in zhyper, on purpose: two copies of a registration would drift and nothing would notice. Consequences: results reflect the carrier at call time, pagination is the carrier page model rather than this API's cursor model, and a create is not idempotent - repeating it registers again. When the SMS provider is not configured the endpoint answers 503 sms_provider_unavailable rather than an empty list, so "not configured" is never mistaken for "nothing registered".

Parameters:

  • id (path, required)

listSmsCampaignsGET /v1/sms/campaigns

Reads and writes the CARRIER registry directly. There is no local copy of these records in zhyper, on purpose: two copies of a registration would drift and nothing would notice. Consequences: results reflect the carrier at call time, pagination is the carrier page model rather than this API's cursor model, and a create is not idempotent - repeating it registers again. When the SMS provider is not configured the endpoint answers 503 sms_provider_unavailable rather than an empty list, so "not configured" is never mistaken for "nothing registered". "brandId" is REQUIRED: the carrier publishes no account-wide campaign listing, so a call without it would be a guaranteed provider rejection rather than a broader result.

Parameters:

  • recordsPerPage (query)
  • page (query)
  • brandId (query, required)

createSmsCampaignPOST /v1/sms/campaigns

Reads and writes the CARRIER registry directly. There is no local copy of these records in zhyper, on purpose: two copies of a registration would drift and nothing would notice. Consequences: results reflect the carrier at call time, pagination is the carrier page model rather than this API's cursor model, and a create is not idempotent - repeating it registers again. When the SMS provider is not configured the endpoint answers 503 sms_provider_unavailable rather than an empty list, so "not configured" is never mistaken for "nothing registered".

Request body (CreateSmsCampaignDto): brandId (string, required), usecase (string, required), description (string, required), subUsecases (array of string), messageFlow (string), helpMessage (string), sample1 (string), sample2 (string), optinKeywords (string), optoutKeywords (string), helpKeywords (string), embeddedLink (boolean), embeddedPhone (boolean), ageGated (boolean), directLending (boolean), numberPool (boolean), subscriberOptin (boolean), subscriberOptout (boolean), subscriberHelp (boolean), termsAndConditions (boolean), privacyPolicyLink (string), termsAndConditionsLink (string), mock (boolean)

getSmsCampaignGET /v1/sms/campaigns/{id}

Reads and writes the CARRIER registry directly. There is no local copy of these records in zhyper, on purpose: two copies of a registration would drift and nothing would notice. Consequences: results reflect the carrier at call time, pagination is the carrier page model rather than this API's cursor model, and a create is not idempotent - repeating it registers again. When the SMS provider is not configured the endpoint answers 503 sms_provider_unavailable rather than an empty list, so "not configured" is never mistaken for "nothing registered".

Parameters:

  • id (path, required)

sendSmsPOST /v1/sms/messages

Sends one outbound SMS and records it. The order is fixed and is not an implementation detail: the local row is committed BEFORE the carrier is called, so a crash can never leave a message that reached a handset but was never recorded. The usage event is written AFTERWARDS, inside the same transaction that flips the row to "sent", so a send the carrier refused is never billed. Consequence you can observe: a message may briefly read "queued", and a message that stays "queued" is one whose outcome was never recorded - no carrier answer was written to it and it was not billed. That does NOT prove it never left: a crash after the carrier accepted it leaves the same row. The bias is deliberate and favours under-billing, because usage_events is append-only and an overcharge could never be corrected. SEGMENTS is the billed quantity and is measured here, not by the carrier - a body that falls back to UCS-2 (one emoji is enough) carries 70 characters per segment instead of 160. FROM must be E.164; alphanumeric sender IDs are registered through /v1/sms/sender-ids and are not accepted on this field, because the billing record constrains the sending address to E.164.

Parameters:

  • Idempotency-Key (header) — Optional client key. Repeating a request with the same key returns the original message instead of sending a second one. Scoped to your team and mode.

Request body (SendSmsMessageDto): profileId (string, required), from (string, required), to (string, required), body (string, required)

listSmsMessagesGET /v1/sms/messages

Reads the LOCAL record, not the carrier. The rows here are the billing anchor for this team and mode, newest first, and they outlive whatever retention the carrier applies. Test-mode and live-mode history are separate: a message sent in test mode never appears in live history.

Parameters:

  • profileId (query)
  • status (query)
  • cursor (query)
  • limit (query)

getSmsMessageGET /v1/sms/messages/{id}

Reads the LOCAL record. A message whose profile is outside this credential's scope answers 404, not 403: a 403 would confirm that the message exists.

Parameters:

  • id (path, required)

listSmsSenderIdsGET /v1/sms/sender-ids

Reads and writes the CARRIER registry directly. There is no local copy of these records in zhyper, on purpose: two copies of a registration would drift and nothing would notice. Consequences: results reflect the carrier at call time, pagination is the carrier page model rather than this API's cursor model, and a create is not idempotent - repeating it registers again. When the SMS provider is not configured the endpoint answers 503 sms_provider_unavailable rather than an empty list, so "not configured" is never mistaken for "nothing registered".

Parameters:

  • pageSize (query)
  • pageNumber (query)
  • messagingProfileId (query)

createSmsSenderIdPOST /v1/sms/sender-ids

Reads and writes the CARRIER registry directly. There is no local copy of these records in zhyper, on purpose: two copies of a registration would drift and nothing would notice. Consequences: results reflect the carrier at call time, pagination is the carrier page model rather than this API's cursor model, and a create is not idempotent - repeating it registers again. When the SMS provider is not configured the endpoint answers 503 sms_provider_unavailable rather than an empty list, so "not configured" is never mistaken for "nothing registered". The carrier rule is three to eleven ASCII letters, digits and spaces, never digits alone. "usLongCodeFallback" is an E.164 number used where alphanumeric senders are refused outright, which is the case for US and Canadian carriers.

Request body (CreateSmsSenderIdDto): senderId (string, required), messagingProfileId (string, required), usLongCodeFallback (string)

getSmsSenderIdGET /v1/sms/sender-ids/{id}

Reads and writes the CARRIER registry directly. There is no local copy of these records in zhyper, on purpose: two copies of a registration would drift and nothing would notice. Consequences: results reflect the carrier at call time, pagination is the carrier page model rather than this API's cursor model, and a create is not idempotent - repeating it registers again. When the SMS provider is not configured the endpoint answers 503 sms_provider_unavailable rather than an empty list, so "not configured" is never mistaken for "nothing registered".

Parameters:

  • id (path, required)

deleteSmsSenderIdDELETE /v1/sms/sender-ids/{id}

Reads and writes the CARRIER registry directly. There is no local copy of these records in zhyper, on purpose: two copies of a registration would drift and nothing would notice. Consequences: results reflect the carrier at call time, pagination is the carrier page model rather than this API's cursor model, and a create is not idempotent - repeating it registers again. When the SMS provider is not configured the endpoint answers 503 sms_provider_unavailable rather than an empty list, so "not configured" is never mistaken for "nothing registered". The removed record is returned rather than an empty body, so the caller can log which messaging profile lost a sender without a read-before-write.

Parameters:

  • id (path, required)

status

OperationMethodPathSummary
getStatusGET/v1/statusPublic platform status page

getStatusGET /v1/status

team

OperationMethodPathSummary
createInvitationPOST/v1/team/invitationsInvite someone to the team
listInvitationsGET/v1/team/invitationsList open invitations
revokeInvitationDELETE/v1/team/invitations/{id}Revoke an open invitation
acceptInvitationPOST/v1/team/invitations/acceptAccept a team invitation
listTeamMembersGET/v1/team/membersList team members and their roles
updateTeamMemberRolePATCH/v1/team/members/{userId}Change a member's role
removeTeamMemberDELETE/v1/team/members/{userId}Remove a member from the team

createInvitationPOST /v1/team/invitations

Request body (CreateInvitationDto): email (string, required), role (enum: owner, admin, billing_admin, member, read_only, required)

listInvitationsGET /v1/team/invitations

Parameters:

  • sort (query)
  • cursor (query)
  • limit (query)

revokeInvitationDELETE /v1/team/invitations/{id}

Parameters:

  • id (path, required)

acceptInvitationPOST /v1/team/invitations/accept

Request body (AcceptInvitationDto): token (string, required), email (string, required), password (string), name (string)

listTeamMembersGET /v1/team/members

Parameters:

  • sort (query)
  • cursor (query)
  • limit (query)

updateTeamMemberRolePATCH /v1/team/members/{userId}

Parameters:

  • userId (path, required)

Request body (UpdateMemberRoleDto): role (enum: owner, admin, billing_admin, member, read_only, required)

removeTeamMemberDELETE /v1/team/members/{userId}

Parameters:

  • userId (path, required)

twitter-engagement

OperationMethodPathSummary
bookmarkPostPOST/v1/twitter/bookmarkBookmark a post
removeBookmarkDELETE/v1/twitter/bookmarkRemove a bookmark
followUserPOST/v1/twitter/followFollow a user
unfollowUserDELETE/v1/twitter/followUnfollow a user
retweetPostPOST/v1/twitter/retweetRetweet a post
undoRetweetDELETE/v1/twitter/retweetUndo a retweet
searchTweetsGET/v1/twitter/searchSearch recent tweets
getTweetGET/v1/twitter/tweetLook up a tweet

bookmarkPostPOST /v1/twitter/bookmark

Bookmarks an X Post by ID using the selected connected account.

Request body (TwitterBookmarkInputDto): accountId (string, required), tweetId (string, required)

removeBookmarkDELETE /v1/twitter/bookmark

Removes the selected connected account bookmark from an X Post.

Parameters:

  • tweetId (query, required)
  • accountId (query, required)

followUserPOST /v1/twitter/follow

Follows an X user, or creates a pending request for a protected account.

Request body (TwitterFollowInputDto): accountId (string, required), targetUserId (string, required)

unfollowUserDELETE /v1/twitter/follow

Unfollows an X user with the selected connected account.

Parameters:

  • targetUserId (query, required)
  • accountId (query, required)

retweetPostPOST /v1/twitter/retweet

Retweets an X Post by ID using the selected connected account.

Request body (TwitterRetweetInputDto): accountId (string, required), tweetId (string, required)

undoRetweetDELETE /v1/twitter/retweet

Removes the selected connected account retweet from an X Post.

Parameters:

  • tweetId (query, required)
  • accountId (query, required)

searchTweetsGET /v1/twitter/search

Searches public X Posts from the last seven days using the connected account.

Parameters:

  • sortOrder (query)
  • cursor (query)
  • endTime (query)
  • startTime (query)
  • untilId (query)
  • sinceId (query)
  • limit (query)
  • query (query, required)
  • accountId (query, required)

getTweetGET /v1/twitter/tweet

Resolves one numeric X Post id or canonical Post URL.

Parameters:

  • id (query, required)
  • accountId (query, required)

usage

OperationMethodPathSummary
getUsageGET/v1/usageQuota usage snapshot for the current UTC day
getUsageStatsGET/v1/usage-statsGet quota usage snapshot

getUsageGET /v1/usage

Returned from the accounting ledger of your own consumption, and this ledger is a LOWER BOUND: it can undercount, it NEVER overcounts. The three cases that can drop a row of the ledger are listed ONE BY ONE in the unitsUsed description; start there if you are reconciling. The counters are PER PROFILE: if the same platform account is linked to more than one profile in the same team, each profile sees ONLY ITS OWN consumption; the spend of a profile outside the scope of the key DOES NOT ENTER the response. On shared pool models a "remaining" number IS NOT RETURNED: because the pool is shared across all tenants, a per-team remainder computation would promise a capacity that does not exist, and the real remainder of the pool would leak cross-tenant traffic. This endpoint enforces no quota; it only makes the accounting visible. The response carries EVERY account in scope and is not silently truncated: if the scope ceiling is exceeded the request returns 400 with account_scope_too_large, and the way out is a more narrowly scoped API key.

getUsageStatsGET /v1/usage-stats

Deprecated path alias of a bare GET /v1/usage call. It returns the same durable snapshot from the tenant, mode, account, and profile-scoped quota ledger. Zhyper has no cached external subscription snapshot, so reconcile does not change the read; this compatibility endpoint does not invent plan or payment fields that are not present in local durable data.

Parameters:

  • reconcile (query) — Accepted for target compatibility. The local quota ledger is read directly on every request and therefore has no cached plan state to reconcile.

users

OperationMethodPathSummary
listUsersGET/v1/usersList users
getUserGET/v1/users/{userId}Get user

listUsersGET /v1/users

Returns all users in the workspace including roles and profile access. Also returns the currentUserId of the caller.

getUserGET /v1/users/{userId}

Returns a single user's details by ID, including name, email, and role.

Parameters:

  • userId (path, required)

validate

OperationMethodPathSummary
validateMediaPOST/v1/tools/validate/mediaValidate media URL
validatePostPOST/v1/tools/validate/postValidate a post against platform rules
validatePostLengthPOST/v1/tools/validate/post-lengthValidate weighted post length across platforms
validateSubredditGET/v1/tools/validate/subredditCheck subreddit existence
validatePostLegacyPOST/v1/validate/postValidate a post against platform rules (legacy route)

validateMediaPOST /v1/tools/validate/media

Checks a public media URL with an SSRF-safe HEAD request and a tiny bounded range GET fallback. Redirect targets are revalidated; platform limits come from the selected adapter registry.

Request body (ValidateMediaRequestDto): url (string, required)

validatePostPOST /v1/tools/validate/post

Canonical callers use the required platforms array. The former single-platform body remains accepted during migration and is documented on the deprecated /v1/validate/post alias.

Request body (ValidatePostDto): content (string), platforms (array of ValidatePostPlatformDto, required), mediaItems (array of ValidatePostMediaItemDto)

validatePostLengthPOST /v1/tools/validate/post-length

X uses the official twitter-text weighted algorithm, including t.co URL length and Unicode weights. Other counts use JavaScript string length; limits come from the selected adapter registry capabilities.

Request body (ValidatePostLengthRequestDto): text (string, required)

validateSubredditGET /v1/tools/validate/subreddit

Uses an authenticated Reddit account when accountId is provided and the public Reddit JSON fallback otherwise. Private, banned and nonexistent subreddits return exists=false.

Parameters:

  • accountId (query) — Optional connected Reddit social-account id.
  • name (query, required) — Subreddit name with or without an r/ prefix.

validatePostLegacyPOST /v1/validate/post

Deprecated transport alias for POST /v1/tools/validate/post.

webhooks

OperationMethodPathSummary
createWebhookEndpointPOST/v1/webhooksCreate a webhook endpoint
listWebhookEndpointsGET/v1/webhooksList webhook endpoints
getWebhookEndpointGET/v1/webhooks/{id}Retrieve a webhook endpoint
deleteWebhookEndpointDELETE/v1/webhooks/{id}Delete a webhook endpoint
listWebhookDeliveriesGET/v1/webhooks/{id}/logsList webhook delivery attempts
sendWebhookTestPOST/v1/webhooks/{id}/testSend a test webhook delivery
getWebhookLogsGET/v1/webhooks/logsList webhook delivery logs
getWebhookSettingsGET/v1/webhooks/settingsList webhook settings
createWebhookSettingsPOST/v1/webhooks/settingsCreate webhook settings
updateWebhookSettingsPUT/v1/webhooks/settingsUpdate webhook settings
deleteWebhookSettingsDELETE/v1/webhooks/settingsDelete webhook settings
testWebhookPOST/v1/webhooks/testQueue a test webhook delivery

createWebhookEndpointPOST /v1/webhooks

Request body (CreateWebhookDto): url (string, required), events (array of enum: post.scheduled, post.published, post.failed, post.partial, post.cancelled, post.recycled, post.platform.published, post.platform.failed, post.platform.deleted, post.tiktok.url_resolved, post.platform.action_required, post.platform.action_resolved, post.external.created, post.external.updated, post.external.deleted, account.connected, account.health_changed, account.disconnected, account.ads.initial_sync_completed, message.received, message.sent, message.edited, message.deleted, message.delivered, message.read, message.failed, conversation.started, reaction.received, referral.received, comment.received, review.new, review.updated, whatsapp.template.status_updated, whatsapp.template.category_updated, whatsapp.account.name_status_updated, whatsapp.automatic_event, lead.received, ad.status_changed, required)

listWebhookEndpointsGET /v1/webhooks

Parameters:

  • sort (query)
  • cursor (query)
  • limit (query)

getWebhookEndpointGET /v1/webhooks/{id}

Parameters:

  • id (path, required)

deleteWebhookEndpointDELETE /v1/webhooks/{id}

Parameters:

  • id (path, required)

listWebhookDeliveriesGET /v1/webhooks/{id}/logs

Parameters:

  • id (path, required)
  • sort (query)
  • cursor (query)
  • limit (query)

sendWebhookTestPOST /v1/webhooks/{id}/test

Parameters:

  • id (path, required)

getWebhookLogsGET /v1/webhooks/logs

Parameters:

  • eventId (query) — Filter by stable webhook event ID
  • webhookId (query) — Filter by webhook configuration ID
  • status (query) — Filter by delivery outcome
  • skip (query) — Number of logs to skip
  • limit (query) — Maximum number of logs to return

getWebhookSettingsGET /v1/webhooks/settings

createWebhookSettingsPOST /v1/webhooks/settings

Request body (CreateWebhookSettingsDto): name (string, required), url (string, required), events (array of enum: post.scheduled, post.published, post.failed, post.partial, post.cancelled, post.recycled, post.platform.published, post.platform.failed, post.platform.deleted, post.tiktok.url_resolved, post.platform.action_required, post.platform.action_resolved, post.external.created, post.external.updated, post.external.deleted, account.connected, account.health_changed, account.disconnected, account.ads.initial_sync_completed, message.received, message.sent, message.edited, message.deleted, message.delivered, message.read, message.failed, conversation.started, reaction.received, referral.received, comment.received, review.new, review.updated, whatsapp.template.status_updated, whatsapp.template.category_updated, whatsapp.account.name_status_updated, whatsapp.automatic_event, lead.received, ad.status_changed, required), secret (string), isActive (boolean), customHeaders (object), disabledResourceGroups (array of enum: publishing, engagement, messages, contacts, analytics, ads, telephony, accounts, billing, webhooks)

updateWebhookSettingsPUT /v1/webhooks/settings

Request body (UpdateWebhookSettingsDto): _id (string, required), name (string), url (string), events (array of enum: post.scheduled, post.published, post.failed, post.partial, post.cancelled, post.recycled, post.platform.published, post.platform.failed, post.platform.deleted, post.tiktok.url_resolved, post.platform.action_required, post.platform.action_resolved, post.external.created, post.external.updated, post.external.deleted, account.connected, account.health_changed, account.disconnected, account.ads.initial_sync_completed, message.received, message.sent, message.edited, message.deleted, message.delivered, message.read, message.failed, conversation.started, reaction.received, referral.received, comment.received, review.new, review.updated, whatsapp.template.status_updated, whatsapp.template.category_updated, whatsapp.account.name_status_updated, whatsapp.automatic_event, lead.received, ad.status_changed), secret (string), isActive (boolean), customHeaders (object), disabledResourceGroups (array of enum: publishing, engagement, messages, contacts, analytics, ads, telephony, accounts, billing, webhooks)

deleteWebhookSettingsDELETE /v1/webhooks/settings

Parameters:

  • id (query, required)

testWebhookPOST /v1/webhooks/test

Zhyper delivers asynchronously; a successful response means queued, not delivered.

Request body (CanonicalWebhookTestDto): webhookId (string, required)

whatsapp

OperationMethodPathSummary
registerWhatsAppNumberPOST/v1/accounts/{accountId}/whatsapp/registerRegister a connected WhatsApp number on the Cloud API
listWhatsAppAccountEventsGET/v1/whatsapp/account-eventsList Meta-originated WhatsApp account events
getWhatsAppBlockedUsersGET/v1/whatsapp/block-usersList blocked users
blockWhatsAppUsersPOST/v1/whatsapp/block-usersBlock users
unblockWhatsAppUsersDELETE/v1/whatsapp/block-usersUnblock users
getWhatsAppBlockStatusGET/v1/whatsapp/block-users/statusCheck if a user is blocked
getWhatsAppBusinessProfileGET/v1/whatsapp/business-profileGet a WhatsApp business profile
updateWhatsAppBusinessProfilePOST/v1/whatsapp/business-profileUpdate a WhatsApp business profile
getWhatsAppDisplayNameGET/v1/whatsapp/business-profile/display-nameGet a WhatsApp display name
updateWhatsAppDisplayNamePOST/v1/whatsapp/business-profile/display-nameSubmit a WhatsApp display-name change
uploadWhatsAppProfilePhotoPOST/v1/whatsapp/business-profile/photoUpload a WhatsApp business profile photo
getWhatsappBusinessUsernameGET/v1/whatsapp/business-profile/usernameGet a WhatsApp business username
setWhatsappBusinessUsernamePOST/v1/whatsapp/business-profile/usernameSet a WhatsApp business username
deleteWhatsappBusinessUsernameDELETE/v1/whatsapp/business-profile/usernameDelete a WhatsApp business username
getWhatsappBusinessUsernameSuggestionsGET/v1/whatsapp/business-profile/username/suggestionsGet WhatsApp username suggestions
listWhatsAppConversionsGET/v1/whatsapp/conversionsList conversion events
sendWhatsAppConversionPOST/v1/whatsapp/conversionsSend WhatsApp conversion event
getWhatsAppDatasetGET/v1/whatsapp/datasetGet CTWA conversions dataset
createWhatsAppDatasetPOST/v1/whatsapp/datasetProvision CTWA dataset
getWhatsAppMediaGET/v1/whatsapp/media/{mediaId}Download WhatsApp media
getWhatsAppLibraryTemplateGET/v1/whatsapp/template-libraryLook up a library template
createWhatsAppTemplatePOST/v1/whatsapp/templatesCreate a WhatsApp message template
getWhatsAppTemplatesGET/v1/whatsapp/templatesList WhatsApp message templates
getWhatsAppTemplateGET/v1/whatsapp/templates/{templateName}Get one exact WhatsApp template language variant
updateWhatsAppTemplatePATCH/v1/whatsapp/templates/{templateName}Update one exact WhatsApp template language variant
deleteWhatsAppTemplateDELETE/v1/whatsapp/templates/{templateName}Delete one WhatsApp template language variant
getWhatsAppTemplateByIdGET/v1/whatsapp/templates/id/{templateId}Get one WhatsApp template variant by its Meta id
updateWhatsAppTemplateByIdPATCH/v1/whatsapp/templates/id/{templateId}Update one WhatsApp template variant by its Meta id
deleteWhatsAppTemplateByIdDELETE/v1/whatsapp/templates/id/{templateId}Delete one WhatsApp template variant by its Meta id
listWhatsAppGroupChatsGET/v1/whatsapp/wa-groupsList active groups
createWhatsAppGroupChatPOST/v1/whatsapp/wa-groupsCreate group
getWhatsAppGroupChatGET/v1/whatsapp/wa-groups/{groupId}Get group info
updateWhatsAppGroupChatPOST/v1/whatsapp/wa-groups/{groupId}Update group settings
deleteWhatsAppGroupChatDELETE/v1/whatsapp/wa-groups/{groupId}Delete group
createWhatsAppGroupInviteLinkPOST/v1/whatsapp/wa-groups/{groupId}/invite-linkCreate invite link
listWhatsAppGroupJoinRequestsGET/v1/whatsapp/wa-groups/{groupId}/join-requestsList join requests
approveWhatsAppGroupJoinRequestsPOST/v1/whatsapp/wa-groups/{groupId}/join-requestsApprove join requests
rejectWhatsAppGroupJoinRequestsDELETE/v1/whatsapp/wa-groups/{groupId}/join-requestsReject join requests
addWhatsAppGroupParticipantsPOST/v1/whatsapp/wa-groups/{groupId}/participantsAdd participants
removeWhatsAppGroupParticipantsDELETE/v1/whatsapp/wa-groups/{groupId}/participantsRemove participants

registerWhatsAppNumberPOST /v1/accounts/{accountId}/whatsapp/register

Parameters:

  • accountId (path, required)

Request body (RegisterWhatsAppNumberRequestDto): pin (string)

listWhatsAppAccountEventsGET /v1/whatsapp/account-events

Parameters:

  • limit (query)
  • accountId (query, required)

getWhatsAppBlockedUsersGET /v1/whatsapp/block-users

Parameters:

  • after (query)
  • limit (query)
  • accountId (query, required)

blockWhatsAppUsersPOST /v1/whatsapp/block-users

Request body (WhatsAppBlockUsersRequestDto): accountId (string, required), users (array of string, required)

unblockWhatsAppUsersDELETE /v1/whatsapp/block-users

Request body (WhatsAppBlockUsersRequestDto): accountId (string, required), users (array of string, required)

getWhatsAppBlockStatusGET /v1/whatsapp/block-users/status

Parameters:

  • user (query, required)
  • accountId (query, required)

getWhatsAppBusinessProfileGET /v1/whatsapp/business-profile

Parameters:

  • accountId (query, required)

updateWhatsAppBusinessProfilePOST /v1/whatsapp/business-profile

Request body (UpdateWhatsAppBusinessProfileDto): accountId (string, required), about (string), address (string), description (string), email (string), websites (array of string), vertical (string), profilePictureHandle (string)

getWhatsAppDisplayNameGET /v1/whatsapp/business-profile/display-name

Parameters:

  • accountId (query, required)

updateWhatsAppDisplayNamePOST /v1/whatsapp/business-profile/display-name

Request body (UpdateWhatsAppDisplayNameDto): accountId (string, required), displayName (string, required)

uploadWhatsAppProfilePhotoPOST /v1/whatsapp/business-profile/photo

Accepts one JPEG/PNG file up to 5 MiB or fetches one direct public HTTPS URL without redirects.

Request body (WhatsAppProfilePhotoUrlDto): accountId (string, required), url (string, required)

getWhatsappBusinessUsernameGET /v1/whatsapp/business-profile/username

Parameters:

  • accountId (query, required)

setWhatsappBusinessUsernamePOST /v1/whatsapp/business-profile/username

Request body (SetWhatsAppUsernameDto): accountId (string, required), username (string, required), transferAction (enum: none, force_transfer)

deleteWhatsappBusinessUsernameDELETE /v1/whatsapp/business-profile/username

Request body (DeleteWhatsAppUsernameDto): accountId (string, required)

getWhatsappBusinessUsernameSuggestionsGET /v1/whatsapp/business-profile/username/suggestions

Parameters:

  • accountId (query, required)

listWhatsAppConversionsGET /v1/whatsapp/conversions

Parameters:

  • limit (query)
  • accountId (query, required)

sendWhatsAppConversionPOST /v1/whatsapp/conversions

Request body (SendWhatsAppConversionRequestDto): accountId (string, required), eventName (enum: LeadSubmitted, Purchase, AddToCart, InitiateCheckout, ViewContent, required), eventId (string, required), eventTime (number), conversationId (string), phoneE164 (string), value (number), currency (string), contentIds (array of string), email (string), externalId (string), testCode (string)

getWhatsAppDatasetGET /v1/whatsapp/dataset

Parameters:

  • accountId (query, required)

createWhatsAppDatasetPOST /v1/whatsapp/dataset

Request body (WhatsAppDatasetRequestDto): accountId (string, required)

getWhatsAppMediaGET /v1/whatsapp/media/{mediaId}

Parameters:

  • mediaId (path, required)
  • accountId (query, required)

getWhatsAppLibraryTemplateGET /v1/whatsapp/template-library

Parameters:

  • language (query) — Preferred language variant. Falls back to the first available variant.
  • name (query, required)
  • accountId (query, required)

createWhatsAppTemplatePOST /v1/whatsapp/templates

Creates one bounded custom template or imports one pre-approved UTILITY template from Meta Template Library on the credential WABA. The request is dispatched once and is never retried automatically; an ambiguous result must be reconciled by exact name and language. Custom mode and library mode are mutually exclusive. Supported text and authentication copy-code components accept canonical Meta uppercase values and Zernio lowercase aliases. Media headers, carousel/LTO, custom URL or phone buttons, named parameters, and one-tap OTP remain deliberately unsupported.

Request body (WhatsAppTemplateCreateRequestDto): accountId (string, required), name (string, required), category (enum: AUTHENTICATION, MARKETING, UTILITY, required), language (enum: af, sq, ar, ar_EG, ar_AE, ar_LB, ar_MA, ar_QA, az, be_BY, bn, bn_IN, bg, ca, zh_CN, zh_HK, zh_TW, hr, cs, da, prs_AF, nl, nl_BE, en, en_GB, en_US, en_AE, en_AU, en_CA, en_GH, en_IE, en_IN, en_JM, en_MY, en_NZ, en_QA, en_SG, en_UG, en_ZA, et, fil, fi, fr, fr_BE, fr_CA, fr_CH, fr_CI, fr_MA, ka, de, de_AT, de_CH, el, gu, ha, he, hi, hu, id, ga, it, ja, kn, kk, rw_RW, ko, ky_KG, lo, lv, lt, mk, ms, ml, mr, nb, ps_AF, fa, pl, pt_BR, pt_PT, pa, ro, ru, sr, si_LK, sk, sl, es, es_AR, es_CL, es_CO, es_CR, es_DO, es_EC, es_HN, es_MX, es_PA, es_PE, es_ES, es_UY, sw, sv, ta, te, th, tr, uk, ur, uz, vi, zu, required), components (array of WhatsAppTemplateCreateTextHeaderDto | WhatsAppTemplateCreateTextBodyDto | WhatsAppTemplateCreateTextFooterDto | WhatsAppTemplateCreateTextButtonsDto | WhatsAppTemplateCreateAuthenticationBodyDto | WhatsAppTemplateCreateAuthenticationFooterDto | WhatsAppTemplateCreateAuthenticationButtonsDto), library_template_name (string), library_template_body_inputs (WhatsAppTemplateLibraryBodyInputsDto), library_template_button_inputs (array of WhatsAppTemplateLibraryUrlButtonInputDto | WhatsAppTemplateLibraryPhoneButtonInputDto | WhatsAppTemplateLibraryQuickReplyButtonInputDto)

getWhatsAppTemplatesGET /v1/whatsapp/templates

Pages provider-owned template lifecycle state for the connected WhatsApp Business Account.

Parameters:

  • cursor (query) — Opaque cursor returned by the immediately preceding page.
  • limit (query)
  • accountId (query, required)

getWhatsAppTemplateGET /v1/whatsapp/templates/{templateName}

Requires templateId or language as a safety extension because a Meta template name can identify multiple language variants.

Parameters:

  • templateName (path, required)
  • language (query) — Exact language variant. Required when templateId is omitted.
  • templateId (query) — Language-specific Meta template id. Required when language is omitted.
  • accountId (query, required)

updateWhatsAppTemplatePATCH /v1/whatsapp/templates/{templateName}

Requires the exact template id, language and immutable category before replacing the bounded component set. Supported text and authentication copy-code components accept canonical Meta uppercase values and Zernio lowercase aliases. The provider POST is never replayed blindly; ambiguous outcomes are reconciled by exact id.

Parameters:

  • templateName (path, required)

Request body (WhatsAppTemplateUpdateRequestDto): accountId (string, required), templateId (string, required), language (enum: af, sq, ar, ar_EG, ar_AE, ar_LB, ar_MA, ar_QA, az, be_BY, bn, bn_IN, bg, ca, zh_CN, zh_HK, zh_TW, hr, cs, da, prs_AF, nl, nl_BE, en, en_GB, en_US, en_AE, en_AU, en_CA, en_GH, en_IE, en_IN, en_JM, en_MY, en_NZ, en_QA, en_SG, en_UG, en_ZA, et, fil, fi, fr, fr_BE, fr_CA, fr_CH, fr_CI, fr_MA, ka, de, de_AT, de_CH, el, gu, ha, he, hi, hu, id, ga, it, ja, kn, kk, rw_RW, ko, ky_KG, lo, lv, lt, mk, ms, ml, mr, nb, ps_AF, fa, pl, pt_BR, pt_PT, pa, ro, ru, sr, si_LK, sk, sl, es, es_AR, es_CL, es_CO, es_CR, es_DO, es_EC, es_HN, es_MX, es_PA, es_PE, es_ES, es_UY, sw, sv, ta, te, th, tr, uk, ur, uz, vi, zu, required), category (enum: AUTHENTICATION, MARKETING, UTILITY, required), components (array of WhatsAppTemplateUpdateTextHeaderDto | WhatsAppTemplateUpdateTextBodyDto | WhatsAppTemplateUpdateTextFooterDto | WhatsAppTemplateUpdateTextButtonsDto | WhatsAppTemplateUpdateAuthenticationBodyDto | WhatsAppTemplateUpdateAuthenticationFooterDto | WhatsAppTemplateUpdateAuthenticationButtonsDto, required)

deleteWhatsAppTemplateDELETE /v1/whatsapp/templates/{templateName}

Requires the language-specific Meta templateId as a deliberate safety extension. Name-only deletion is unsupported because Meta would delete every language variant sharing that name. The operation is dispatched once and is never retried automatically.

Parameters:

  • templateName (path, required)
  • templateId (query, required) — Language-specific Meta template id; Meta accepts no language parameter on deletion.
  • accountId (query, required)

getWhatsAppTemplateByIdGET /v1/whatsapp/templates/id/{templateId}

Addresses exactly one language variant by the Meta id that every variant carries on its own, which is also the id the template status webhook reports. The id is first resolved to its identity and the response is then read WABA-bound, so an id outside the account WhatsApp Business Account is reported as not found.

Parameters:

  • templateId (path, required)
  • accountId (query, required)

updateWhatsAppTemplateByIdPATCH /v1/whatsapp/templates/id/{templateId}

Replaces the bounded component set of exactly one language variant. Name, language and category are not accepted: Meta treats all three as immutable, and they are resolved from the id rather than restated by the caller. The provider POST is never replayed blindly; ambiguous outcomes are reconciled by exact id.

Parameters:

  • templateId (path, required)

Request body (WhatsAppTemplateUpdateByIdRequestDto): accountId (string, required), components (array of WhatsAppTemplateUpdateTextHeaderDto | WhatsAppTemplateUpdateTextBodyDto | WhatsAppTemplateUpdateTextFooterDto | WhatsAppTemplateUpdateTextButtonsDto | WhatsAppTemplateUpdateAuthenticationBodyDto | WhatsAppTemplateUpdateAuthenticationFooterDto | WhatsAppTemplateUpdateAuthenticationButtonsDto, required)

deleteWhatsAppTemplateByIdDELETE /v1/whatsapp/templates/id/{templateId}

Deletes exactly one language variant; other languages sharing the name are untouched. Family-wide deletion is unreachable from this operation by construction, because a Meta id addresses a single variant. The operation is dispatched once and is never retried automatically.

Parameters:

  • templateId (path, required)
  • accountId (query, required)

listWhatsAppGroupChatsGET /v1/whatsapp/wa-groups

Parameters:

  • after (query)
  • limit (query)
  • accountId (query, required)

createWhatsAppGroupChatPOST /v1/whatsapp/wa-groups

Request body (CreateWhatsAppGroupDto): accountId (string, required), subject (string, required), description (string), joinApprovalMode (enum: approval_required, auto_approve)

getWhatsAppGroupChatGET /v1/whatsapp/wa-groups/{groupId}

Parameters:

  • groupId (path, required)
  • accountId (query, required)

updateWhatsAppGroupChatPOST /v1/whatsapp/wa-groups/{groupId}

Parameters:

  • groupId (path, required)
  • accountId (query, required)

Request body (UpdateWhatsAppGroupDto): subject (string), description (string), joinApprovalMode (enum: approval_required, auto_approve)

deleteWhatsAppGroupChatDELETE /v1/whatsapp/wa-groups/{groupId}

Parameters:

  • groupId (path, required)
  • accountId (query, required)

createWhatsAppGroupInviteLinkPOST /v1/whatsapp/wa-groups/{groupId}/invite-link

Parameters:

  • groupId (path, required)
  • accountId (query, required)

listWhatsAppGroupJoinRequestsGET /v1/whatsapp/wa-groups/{groupId}/join-requests

Parameters:

  • groupId (path, required)
  • accountId (query, required)

approveWhatsAppGroupJoinRequestsPOST /v1/whatsapp/wa-groups/{groupId}/join-requests

Parameters:

  • groupId (path, required)
  • accountId (query, required)

Request body (WhatsAppGroupParticipantsDto): phoneNumbers (array of string, required)

rejectWhatsAppGroupJoinRequestsDELETE /v1/whatsapp/wa-groups/{groupId}/join-requests

Parameters:

  • groupId (path, required)
  • accountId (query, required)

Request body (WhatsAppGroupParticipantsDto): phoneNumbers (array of string, required)

addWhatsAppGroupParticipantsPOST /v1/whatsapp/wa-groups/{groupId}/participants

Parameters:

  • groupId (path, required)
  • accountId (query, required)

Request body (WhatsAppGroupParticipantsDto): phoneNumbers (array of string, required)

removeWhatsAppGroupParticipantsDELETE /v1/whatsapp/wa-groups/{groupId}/participants

Parameters:

  • groupId (path, required)
  • accountId (query, required)

Request body (WhatsAppGroupParticipantsDto): phoneNumbers (array of string, required)

whatsapp-calling

OperationMethodPathSummary
startWhatsAppCallerIdVerificationPOST/v1/phone-numbers/{id}/whatsapp/caller-id-verificationStart caller-ID verification for a customer-brought number
verifyWhatsAppCallerIdPOST/v1/phone-numbers/{id}/whatsapp/caller-id-verification/verifyConfirm the caller-ID verification code
getWhatsAppCallingGET/v1/phone-numbers/{id}/whatsapp/callingGet calling config for a number
enableWhatsAppCallingPOST/v1/phone-numbers/{id}/whatsapp/callingEnable calling on a number
updateWhatsAppCallingPATCH/v1/phone-numbers/{id}/whatsapp/callingUpdate calling config
disableWhatsAppCallingDELETE/v1/phone-numbers/{id}/whatsapp/callingDisable calling on a number
getWhatsAppCallPermissionsGET/v1/whatsapp/call-permissionsCheck call permission
getWhatsAppCallingConfigGET/v1/whatsapp/callingGet calling config for an account
initiateWhatsAppCallPOST/v1/whatsapp/callsInitiate outbound call
listWhatsAppCallsGET/v1/whatsapp/callsList call history for an account
getWhatsAppCallGET/v1/whatsapp/calls/{id}Get a single call
getWhatsAppCallRecordingGET/v1/whatsapp/calls/{id}/recordingGet a call recording
getWhatsAppCallEstimateGET/v1/whatsapp/calls/estimateEstimate per-minute cost
enableWhatsAppCallingLegacyPOST/v1/whatsapp/phone-numbers/{id}/callingEnable calling on a number
updateWhatsAppCallingLegacyPATCH/v1/whatsapp/phone-numbers/{id}/callingUpdate calling config
disableWhatsAppCallingLegacyDELETE/v1/whatsapp/phone-numbers/{id}/callingDisable calling on a number

startWhatsAppCallerIdVerificationPOST /v1/phone-numbers/{id}/whatsapp/caller-id-verification

Parameters:

  • id (path, required)

Request body (WhatsAppCallerIdVerificationStartRequestDto): method (enum: sms, call)

verifyWhatsAppCallerIdPOST /v1/phone-numbers/{id}/whatsapp/caller-id-verification/verify

Parameters:

  • id (path, required)

Request body (WhatsAppCallerIdVerificationConfirmRequestDto): code (string, required)

getWhatsAppCallingGET /v1/phone-numbers/{id}/whatsapp/calling

Parameters:

  • id (path, required)

enableWhatsAppCallingPOST /v1/phone-numbers/{id}/whatsapp/calling

Parameters:

  • id (path, required)

Request body (WhatsAppCallingEnableRequestDto): accountId (string, required), forwardTo (string, required), sipAuthUsername (string), sipAuthPassword (string), recordingEnabled (boolean), callIconCountries (array of string), maxCallDurationSeconds (number), forwardCallerId (enum: business, caller)

updateWhatsAppCallingPATCH /v1/phone-numbers/{id}/whatsapp/calling

Parameters:

  • id (path, required)

Request body (WhatsAppCallingUpdateRequestDto): accountId (string, required), forwardTo (string), sipAuthUsername (string), sipAuthPassword (string), recordingEnabled (boolean), callIconCountries (array of string), maxCallDurationSeconds (number), forwardCallerId (enum: business, caller)

disableWhatsAppCallingDELETE /v1/phone-numbers/{id}/whatsapp/calling

Parameters:

  • id (path, required)
  • accountId (query, required)

getWhatsAppCallPermissionsGET /v1/whatsapp/call-permissions

Parameters:

  • to (query, required)
  • accountId (query, required)

getWhatsAppCallingConfigGET /v1/whatsapp/calling

Parameters:

  • accountId (query, required)

initiateWhatsAppCallPOST /v1/whatsapp/calls

Parameters:

  • Idempotency-Key (header)

Request body (WhatsAppCallInitiateRequestDto): accountId (string, required), to (string, required), action (enum: send_call_permission_request), bodyText (string), forwardTo (string), recordOverride (boolean), biz_opaque_callback_data (string)

listWhatsAppCallsGET /v1/whatsapp/calls

Parameters:

  • limit (query)
  • before (query)
  • until (query)
  • since (query)
  • direction (query)
  • status (query)
  • accountId (query, required)

getWhatsAppCallGET /v1/whatsapp/calls/{id}

Parameters:

  • id (path, required)
  • accountId (query, required)

getWhatsAppCallRecordingGET /v1/whatsapp/calls/{id}/recording

Parameters:

  • id (path, required)
  • as (query)
  • accountId (query, required)

getWhatsAppCallEstimateGET /v1/whatsapp/calls/estimate

Parameters:

  • recording (query)
  • minutes (query)
  • to (query, required)
  • accountId (query, required)

enableWhatsAppCallingLegacyPOST /v1/whatsapp/phone-numbers/{id}/calling

Parameters:

  • id (path, required)

Request body (WhatsAppCallingEnableRequestDto): accountId (string, required), forwardTo (string, required), sipAuthUsername (string), sipAuthPassword (string), recordingEnabled (boolean), callIconCountries (array of string), maxCallDurationSeconds (number), forwardCallerId (enum: business, caller)

updateWhatsAppCallingLegacyPATCH /v1/whatsapp/phone-numbers/{id}/calling

Parameters:

  • id (path, required)

Request body (WhatsAppCallingUpdateRequestDto): accountId (string, required), forwardTo (string), sipAuthUsername (string), sipAuthPassword (string), recordingEnabled (boolean), callIconCountries (array of string), maxCallDurationSeconds (number), forwardCallerId (enum: business, caller)

disableWhatsAppCallingLegacyDELETE /v1/whatsapp/phone-numbers/{id}/calling

Parameters:

  • id (path, required)
  • accountId (query, required)

whatsapp-flows

OperationMethodPathSummary
listWhatsAppFlowResponsesGET/v1/whatsapp/flow-responsesList flow responses
listWhatsAppFlowsGET/v1/whatsapp/flowsList flows
createWhatsAppFlowPOST/v1/whatsapp/flowsCreate flow
getWhatsAppFlowGET/v1/whatsapp/flows/{flowId}Get flow
updateWhatsAppFlowPATCH/v1/whatsapp/flows/{flowId}Update flow
deleteWhatsAppFlowDELETE/v1/whatsapp/flows/{flowId}Delete flow
deprecateWhatsAppFlowPOST/v1/whatsapp/flows/{flowId}/deprecateDeprecate flow
getWhatsAppFlowJsonGET/v1/whatsapp/flows/{flowId}/jsonGet flow JSON asset
uploadWhatsAppFlowJsonPUT/v1/whatsapp/flows/{flowId}/jsonUpload flow JSON
getWhatsAppFlowPreviewGET/v1/whatsapp/flows/{flowId}/previewGet flow preview URL
publishWhatsAppFlowPOST/v1/whatsapp/flows/{flowId}/publishPublish flow
listWhatsAppFlowVersionsGET/v1/whatsapp/flows/{flowId}/versionsList flow versions
sendWhatsAppFlowMessagePOST/v1/whatsapp/flows/sendSend flow message

listWhatsAppFlowResponsesGET /v1/whatsapp/flow-responses

Parameters:

  • limit (query)
  • flowId (query)
  • accountId (query, required)

listWhatsAppFlowsGET /v1/whatsapp/flows

Parameters:

  • accountId (query, required)

createWhatsAppFlowPOST /v1/whatsapp/flows

Request body (CreateWhatsAppFlowDto): accountId (string, required), name (string, required), categories (array of enum: SIGN_UP, SIGN_IN, APPOINTMENT_BOOKING, LEAD_GENERATION, CONTACT_US, CUSTOMER_SUPPORT, SURVEY, OTHER, required), cloneFlowId (string), asVersion (boolean), endpointUri (string)

getWhatsAppFlowGET /v1/whatsapp/flows/{flowId}

Parameters:

  • flowId (path, required)
  • fields (query)
  • accountId (query, required)

updateWhatsAppFlowPATCH /v1/whatsapp/flows/{flowId}

Parameters:

  • flowId (path, required)

Request body (UpdateWhatsAppFlowDto): accountId (string, required), name (string), categories (array of enum: SIGN_UP, SIGN_IN, APPOINTMENT_BOOKING, LEAD_GENERATION, CONTACT_US, CUSTOMER_SUPPORT, SURVEY, OTHER), endpointUri (string)

deleteWhatsAppFlowDELETE /v1/whatsapp/flows/{flowId}

Parameters:

  • flowId (path, required)
  • accountId (query, required)

deprecateWhatsAppFlowPOST /v1/whatsapp/flows/{flowId}/deprecate

Parameters:

  • flowId (path, required)

Request body (WhatsAppFlowAccountDto): accountId (string, required)

getWhatsAppFlowJsonGET /v1/whatsapp/flows/{flowId}/json

Parameters:

  • flowId (path, required)
  • accountId (query, required)

uploadWhatsAppFlowJsonPUT /v1/whatsapp/flows/{flowId}/json

Parameters:

  • flowId (path, required)

Request body (UploadWhatsAppFlowJsonDto): accountId (string, required), flow_json (object | string, required)

getWhatsAppFlowPreviewGET /v1/whatsapp/flows/{flowId}/preview

Parameters:

  • flowId (path, required)
  • invalidate (query)
  • accountId (query, required)

publishWhatsAppFlowPOST /v1/whatsapp/flows/{flowId}/publish

Parameters:

  • flowId (path, required)

Request body (WhatsAppFlowAccountDto): accountId (string, required)

listWhatsAppFlowVersionsGET /v1/whatsapp/flows/{flowId}/versions

Parameters:

  • flowId (path, required)
  • accountId (query, required)

sendWhatsAppFlowMessagePOST /v1/whatsapp/flows/send

Request body (SendWhatsAppFlowMessageDto): accountId (string, required), to (string, required), flow_id (string, required), flow_cta (string, required), flow_action (enum: navigate, data_exchange), flow_token (string), flow_action_payload (WhatsAppFlowActionPayloadDto), body (string, required), header (WhatsAppFlowHeaderDto), footer (string), draft (boolean)

whatsapp-phone-numbers

OperationMethodPathSummary
getWhatsAppNumberInfoGET/v1/whatsapp/number-infoGet live WhatsApp phone and WABA information
getWhatsAppPhoneNumbersGET/v1/whatsapp/phone-numbersList phone numbers
moveWhatsAppNumberToProfilePATCH/v1/whatsapp/phone-numbers/{id}/profileMove a number to another profile
getWhatsAppNumberRemediationGET/v1/whatsapp/phone-numbers/{id}/remediateGet declined requirements
remediateWhatsAppNumberPOST/v1/whatsapp/phone-numbers/{id}/remediateResubmit a declined number
getWhatsAppPhoneNumberGET/v1/whatsapp/phone-numbers/{phoneNumberId}Get phone number
releaseWhatsAppPhoneNumberDELETE/v1/whatsapp/phone-numbers/{phoneNumberId}Release phone number
checkWhatsAppNumberAvailabilityGET/v1/whatsapp/phone-numbers/availabilityCheck country availability
searchAvailableWhatsAppNumbersGET/v1/whatsapp/phone-numbers/availableSearch available numbers
listWhatsAppNumberCountriesGET/v1/whatsapp/phone-numbers/countriesList offerable number countries
getWhatsAppNumberKycFormGET/v1/whatsapp/phone-numbers/kycGet KYC form spec
submitWhatsAppNumberKycPOST/v1/whatsapp/phone-numbers/kycSubmit KYC
createWhatsAppNumberKycLinkPOST/v1/whatsapp/phone-numbers/kyc/shareCreate a hosted KYC link
uploadWhatsAppNumberKycDocumentPOST/v1/whatsapp/phone-numbers/kyc/upload-documentUpload a KYC document
validateWhatsAppNumberKycAddressPOST/v1/whatsapp/phone-numbers/kyc/validate-addressPre-validate KYC address
purchaseWhatsAppPhoneNumberPOST/v1/whatsapp/phone-numbers/purchasePurchase phone number

getWhatsAppNumberInfoGET /v1/whatsapp/number-info

Parameters:

  • accountId (query, required)

getWhatsAppPhoneNumbersGET /v1/whatsapp/phone-numbers

Carrier provisioning/KYC is a non-Meta provider boundary. Live mode fails closed unless an explicit carrier provider is configured.

Parameters:

  • profileId (query)
  • status (query)

moveWhatsAppNumberToProfilePATCH /v1/whatsapp/phone-numbers/{id}/profile

Carrier provisioning/KYC is a non-Meta provider boundary. Live mode fails closed unless an explicit carrier provider is configured.

Parameters:

  • id (path, required)

Request body (MoveWhatsAppNumberToProfileDto): profileId (string, required)

getWhatsAppNumberRemediationGET /v1/whatsapp/phone-numbers/{id}/remediate

Carrier provisioning/KYC is a non-Meta provider boundary. Live mode fails closed unless an explicit carrier provider is configured.

Parameters:

  • id (path, required)

remediateWhatsAppNumberPOST /v1/whatsapp/phone-numbers/{id}/remediate

Carrier provisioning/KYC is a non-Meta provider boundary. Live mode fails closed unless an explicit carrier provider is configured.

Parameters:

  • id (path, required)

Request body (RemediateWhatsAppNumberDto): values (object), documents (array of WhatsAppKycInlineDocumentDto | WhatsAppKycUploadedDocumentDto), address (WhatsAppKycAddressDto)

getWhatsAppPhoneNumberGET /v1/whatsapp/phone-numbers/{phoneNumberId}

Carrier provisioning/KYC is a non-Meta provider boundary. Live mode fails closed unless an explicit carrier provider is configured.

Parameters:

  • phoneNumberId (path, required)

releaseWhatsAppPhoneNumberDELETE /v1/whatsapp/phone-numbers/{phoneNumberId}

Carrier provisioning/KYC is a non-Meta provider boundary. Live mode fails closed unless an explicit carrier provider is configured.

Parameters:

  • phoneNumberId (path, required)

checkWhatsAppNumberAvailabilityGET /v1/whatsapp/phone-numbers/availability

Carrier provisioning/KYC is a non-Meta provider boundary. Live mode fails closed unless an explicit carrier provider is configured.

Parameters:

  • sms (query)
  • numberType (query)
  • country (query, required)

searchAvailableWhatsAppNumbersGET /v1/whatsapp/phone-numbers/available

Carrier provisioning/KYC is a non-Meta provider boundary. Live mode fails closed unless an explicit carrier provider is configured.

Parameters:

  • limit (query)
  • contains (query)
  • locality (query)
  • prefix (query)
  • type (query)
  • country (query)

listWhatsAppNumberCountriesGET /v1/whatsapp/phone-numbers/countries

Carrier provisioning/KYC is a non-Meta provider boundary. Live mode fails closed unless an explicit carrier provider is configured.

getWhatsAppNumberKycFormGET /v1/whatsapp/phone-numbers/kyc

Carrier provisioning/KYC is a non-Meta provider boundary. Live mode fails closed unless an explicit carrier provider is configured.

Parameters:

  • profileId (query, required)
  • country (query, required)

submitWhatsAppNumberKycPOST /v1/whatsapp/phone-numbers/kyc

Carrier provisioning/KYC is a non-Meta provider boundary. Live mode fails closed unless an explicit carrier provider is configured.

Request body (SubmitWhatsAppNumberKycDto): profileId (string, required), country (string, required), submissionId (string), quantity (number), reuse (boolean), reuseOptionId (string), reuseFrom (string), areaCode (string), endUserFirstName (string), endUserLastName (string), entityType (enum: business, individual), values (object), documents (array of WhatsAppKycInlineDocumentDto | WhatsAppKycUploadedDocumentDto), address (WhatsAppKycAddressDto)

createWhatsAppNumberKycLinkPOST /v1/whatsapp/phone-numbers/kyc/share

Carrier provisioning/KYC is a non-Meta provider boundary. Live mode fails closed unless an explicit carrier provider is configured.

Request body (CreateWhatsAppNumberKycLinkDto): profileId (string, required), country (string, required), areaCode (string), branding (WhatsAppKycBrandingDto), redirect_url (string)

uploadWhatsAppNumberKycDocumentPOST /v1/whatsapp/phone-numbers/kyc/upload-document

Carrier provisioning/KYC is a non-Meta provider boundary. Live mode fails closed unless an explicit carrier provider is configured.

Parameters:

  • X-Filename (header, required)

validateWhatsAppNumberKycAddressPOST /v1/whatsapp/phone-numbers/kyc/validate-address

Carrier provisioning/KYC is a non-Meta provider boundary. Live mode fails closed unless an explicit carrier provider is configured.

Request body (ValidateWhatsAppNumberKycAddressDto): country (string, required), street_address (string, required), locality (string, required), administrative_area (string), postal_code (string, required)

purchaseWhatsAppPhoneNumberPOST /v1/whatsapp/phone-numbers/purchase

Carrier provisioning/KYC is a non-Meta provider boundary. Live mode fails closed unless an explicit carrier provider is configured.

Request body (PurchaseWhatsAppPhoneNumberDto): profileId (string, required), country (string), purchaseIntentId (string), allowMultiple (boolean)

whatsapp-sandbox

OperationMethodPathSummary
listWhatsAppSandboxSessionsGET/v1/whatsapp/sandbox/sessionsList your sandbox sessions
createWhatsAppSandboxSessionPOST/v1/whatsapp/sandbox/sessionsStart a sandbox activation
deleteWhatsAppSandboxSessionDELETE/v1/whatsapp/sandbox/sessions/{sessionId}Revoke a sandbox session

listWhatsAppSandboxSessionsGET /v1/whatsapp/sandbox/sessions

createWhatsAppSandboxSessionPOST /v1/whatsapp/sandbox/sessions

Request body (CreateWhatsAppSandboxSessionRequestDto): phone (string, required)

deleteWhatsAppSandboxSessionDELETE /v1/whatsapp/sandbox/sessions/{sessionId}

Parameters:

  • sessionId (path, required)

workflows

OperationMethodPathSummary
listWorkflowsGET/v1/workflowsList workflows
createWorkflowPOST/v1/workflowsCreate a workflow
getWorkflowGET/v1/workflows/{workflowId}Retrieve a workflow with its graph
updateWorkflowPATCH/v1/workflows/{workflowId}Update a workflow
deleteWorkflowDELETE/v1/workflows/{workflowId}Delete a workflow
activateWorkflowPOST/v1/workflows/{workflowId}/activateActivate a workflow
duplicateWorkflowPOST/v1/workflows/{workflowId}/duplicateDuplicate a workflow
listWorkflowExecutionsGET/v1/workflows/{workflowId}/executionsList a workflow's durable executions
triggerWorkflowPOST/v1/workflows/{workflowId}/executionsCreate and enqueue a durable workflow execution
listWorkflowExecutionEventsGET/v1/workflows/{workflowId}/executions/{executionId}/eventsList an execution's ordered runtime events
pauseWorkflowPOST/v1/workflows/{workflowId}/pausePause a workflow
listWorkflowVersionsGET/v1/workflows/{workflowId}/versionsList a workflow's versions
getWorkflowVersionGET/v1/workflows/{workflowId}/versions/{version}Retrieve one version of a workflow
restoreWorkflowVersionPOST/v1/workflows/{workflowId}/versions/{version}/restoreRestore a workflow version

listWorkflowsGET /v1/workflows

Parameters:

  • skip (query)
  • limit (query)
  • status (query)
  • profileId (query) — If omitted, the workflows of ALL profiles the key can reach are returned.

createWorkflowPOST /v1/workflows

The workflow is always created as a draft with no published version. It can only be triggered after activateWorkflow, which requires at least one node, an entryNodeId and a structurally valid graph.

Request body (CreateWorkflowDto): profileId (string, required), accountId (string, required), name (string, required), platform (string), description (string), nodes (array of WorkflowNodeDto), edges (array of WorkflowEdgeDto), entryNodeId (string)

getWorkflowGET /v1/workflows/{workflowId}

Returns the WORKING graph — the one being edited. While the workflow is active this is byte-identical to the graph of currentVersion; the API refuses graph edits on an active workflow precisely to keep that true.

Parameters:

  • workflowId (path, required)

updateWorkflowPATCH /v1/workflows/{workflowId}

Only provided fields change; sending null clears a nullable field. Graph fields (nodes, edges, entryNodeId) are only accepted while the workflow is a draft or paused — pause it first. Updating never creates a version.

Parameters:

  • workflowId (path, required)

Request body (UpdateWorkflowDto): name (string), description (string), nodes (array of WorkflowNodeDto), edges (array of WorkflowEdgeDto), entryNodeId (string), accountId (string)

deleteWorkflowDELETE /v1/workflows/{workflowId}

Permanently deletes the workflow together with every version, execution and execution event, including executions that have not finished. There is no soft-delete window in this API version.

Parameters:

  • workflowId (path, required)

activateWorkflowPOST /v1/workflows/{workflowId}/activate

Publishes the working graph as a new version and makes it live. Requires at least one node, an entryNodeId and a structurally valid graph. Idempotent: activating a workflow whose graph is already the live version returns it unchanged and does NOT create a second version.

Parameters:

  • workflowId (path, required)

duplicateWorkflowPOST /v1/workflows/{workflowId}/duplicate

Copies the working graph into a new draft workflow named '<name> (copy)'. Version history and executions are NOT copied: they record what the original published and what actually ran, and attributing them to a workflow that never did either would be false. Takes no request body; rename with updateWorkflow.

Parameters:

  • workflowId (path, required)

listWorkflowExecutionsGET /v1/workflows/{workflowId}/executions

Every execution carries the version it was pinned to when triggered. The durable worker advances waiting executions through running to a terminal status without switching their pinned graph.

Parameters:

  • workflowId (path, required)
  • skip (query)
  • limit (query)
  • status (query)

triggerWorkflowPOST /v1/workflows/{workflowId}/executions

Creates a waiting execution pinned to the workflow's current version and entry node. The durable worker claims and advances it; message/send_message nodes send their text through the linked account to conversationId (or to as fallback) behind an at-most-once delivery fence. Later edits or restores never move an existing execution to another version. The workflow must be active.

Parameters:

  • workflowId (path, required)

Request body (TriggerWorkflowDto): to (string), conversationId (string), text (string)

listWorkflowExecutionEventsGET /v1/workflows/{workflowId}/executions/{executionId}/events

Events are returned oldest first. triggerWorkflow writes execution.created; the durable worker appends node.started, node.completed and a terminal execution event with a monotonically increasing sequence.

Parameters:

  • workflowId (path, required)
  • executionId (path, required)
  • skip (query)
  • limit (query)

pauseWorkflowPOST /v1/workflows/{workflowId}/pause

New triggers are refused while paused, and the graph becomes editable again. Executions that already exist are left exactly as they are; nothing advances them in this API version anyway.

Parameters:

  • workflowId (path, required)

listWorkflowVersionsGET /v1/workflows/{workflowId}/versions

Newest version first. A version is created when a workflow is activated with a graph that differs from the live one, and when a version is restored. Updating a workflow does NOT create a version. Graphs are omitted here; fetch one with getWorkflowVersion.

Parameters:

  • workflowId (path, required)
  • skip (query)
  • limit (query)

getWorkflowVersionGET /v1/workflows/{workflowId}/versions/{version}

Returns the frozen graph of that version — the exact graph every execution pinned to it will run. A version is never modified after it is written.

Parameters:

  • workflowId (path, required)
  • version (path, required)

restoreWorkflowVersionPOST /v1/workflows/{workflowId}/versions/{version}/restore

Copies that version's graph back onto the working graph and records it as a NEW version whose restoredFromVersion names the source; version numbers never move backwards. EXECUTIONS ALREADY CREATED ARE NOT AFFECTED — each one keeps running the version it was pinned to. The status is unchanged: restoring a paused workflow leaves it paused. Idempotent on graph identity, like activateWorkflow.

Parameters:

  • workflowId (path, required)
  • version (path, required)

On this page