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.
Total: 613
account-groups
| Operation | Method | Path | Summary |
|---|---|---|---|
listAccountGroups | GET | /v1/account-groups | List account groups |
createAccountGroup | POST | /v1/account-groups | Create an account group |
updateAccountGroup | PUT | /v1/account-groups/{groupId} | Update an account group |
patchAccountGroup | PATCH | /v1/account-groups/{groupId} | Update an account group (legacy PATCH) |
deleteAccountGroup | DELETE | /v1/account-groups/{groupId} | Delete an account group |
getAccountGroup | GET | /v1/account-groups/{id} | Retrieve an account group |
listAccountGroups — GET /v1/account-groups
Parameters:
sort(query)cursor(query)limit(query)
createAccountGroup — POST /v1/account-groups
Request body (CreateAccountGroupDto): name (string, required), accountIds (array of string, required), profileId (string)
updateAccountGroup — PUT /v1/account-groups/{groupId}
Parameters:
groupId(path, required)
Request body (UpdateAccountGroupDto): name (string), accountIds (array of string)
patchAccountGroup — PATCH /v1/account-groups/{groupId}
Parameters:
groupId(path, required)
Request body (UpdateAccountGroupDto): name (string), accountIds (array of string)
deleteAccountGroup — DELETE /v1/account-groups/{groupId}
Parameters:
groupId(path, required)
getAccountGroup — GET /v1/account-groups/{id}
Parameters:
id(path, required)
account-settings
| Operation | Method | Path | Summary |
|---|---|---|---|
getInstagramIceBreakers | GET | /v1/accounts/{accountId}/instagram-ice-breakers | Get IG ice breakers |
setInstagramIceBreakers | PUT | /v1/accounts/{accountId}/instagram-ice-breakers | Set IG ice breakers |
deleteInstagramIceBreakers | DELETE | /v1/accounts/{accountId}/instagram-ice-breakers | Delete IG ice breakers |
getMessengerMenu | GET | /v1/accounts/{accountId}/messenger-menu | Get Messenger menu |
setMessengerMenu | PUT | /v1/accounts/{accountId}/messenger-menu | Set Messenger menu |
deleteMessengerMenu | DELETE | /v1/accounts/{accountId}/messenger-menu | Delete Messenger menu |
getInstagramIceBreakers — GET /v1/accounts/{accountId}/instagram-ice-breakers
Get the ice breaker configuration for an Instagram account.
Parameters:
accountId(path, required)
setInstagramIceBreakers — PUT /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)
deleteInstagramIceBreakers — DELETE /v1/accounts/{accountId}/instagram-ice-breakers
Removes the ice breaker questions from an Instagram account's Messenger experience.
Parameters:
accountId(path, required)
getMessengerMenu — GET /v1/accounts/{accountId}/messenger-menu
Get the Messenger persistent menu for a Facebook Page account.
Parameters:
accountId(path, required)
setMessengerMenu — PUT /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)
deleteMessengerMenu — DELETE /v1/accounts/{accountId}/messenger-menu
Removes the persistent menu from a Facebook Page's Messenger experience.
Parameters:
accountId(path, required)
accounts
| Operation | Method | Path | Summary |
|---|---|---|---|
listAccounts | GET | /v1/accounts | List connected accounts |
updateAccount | PUT | /v1/accounts/{accountId} | Update an account identity or X capabilities |
moveAccountToProfile | PATCH | /v1/accounts/{accountId} | Move an account to another profile |
deleteAccount | DELETE | /v1/accounts/{accountId} | Disconnect an account |
getBlueskySettings | GET | /v1/accounts/{accountId}/bluesky-settings | Get Bluesky account settings |
updateBlueskySettings | PATCH | /v1/accounts/{accountId}/bluesky-settings | Update Bluesky account settings |
getDiscordChannels | GET | /v1/accounts/{accountId}/discord-channels | List writable Discord guild channels |
getInstagramFollowStatus | GET | /v1/accounts/{accountId}/follow-status/{userId} | Check whether an Instagram user follows the account |
getGoogleBusinessFoodMenus | GET | /v1/accounts/{accountId}/gmb-food-menus | Get Google Business food menus |
updateGoogleBusinessFoodMenus | PUT | /v1/accounts/{accountId}/gmb-food-menus | Update Google Business food menus |
getGoogleBusinessLocationDetails | GET | /v1/accounts/{accountId}/gmb-location-details | Get Google Business location details |
updateGoogleBusinessLocationDetails | PUT | /v1/accounts/{accountId}/gmb-location-details | Update Google Business location details |
getGoogleBusinessAccountPerformanceLegacy | GET | /v1/accounts/{accountId}/google-business-performance | Read Google Business daily performance metrics |
listGoogleBusinessPosts | GET | /v1/accounts/{accountId}/google-business-posts | List Google Business local posts |
getGoogleBusinessPost | GET | /v1/accounts/{accountId}/google-business-posts/{postId} | Get a Google Business local post |
listGoogleBusinessReviews | GET | /v1/accounts/{accountId}/google-business-reviews | List Google Business reviews |
getGoogleBusinessReviewNative | GET | /v1/accounts/{accountId}/google-business-reviews/{reviewId} | Get a Google Business review |
upsertGoogleBusinessReviewReplyLegacy | PUT | /v1/accounts/{accountId}/google-business-reviews/{reviewId}/reply | Create or replace a Google Business review reply |
deleteGoogleBusinessReviewReplyLegacy | DELETE | /v1/accounts/{accountId}/google-business-reviews/{reviewId}/reply | Delete a Google Business review reply |
listGoogleBusinessSearchKeywords | GET | /v1/accounts/{accountId}/google-business-search-keywords | List Google Business monthly search keywords |
getAccountHealth | GET | /v1/accounts/{accountId}/health | Retrieve the stored health record for an account |
getInstagramPublishingLimit | GET | /v1/accounts/{accountId}/instagram/publishing-limit | Get the current Instagram publishing limit |
listPinterestBoardSections | GET | /v1/accounts/{accountId}/pinterest-board-sections | List sections of the selected Pinterest board |
getPinterestBoards | GET | /v1/accounts/{accountId}/pinterest-boards | List Pinterest boards |
updatePinterestBoards | PUT | /v1/accounts/{accountId}/pinterest-boards | Set the default Pinterest board |
createPinterestBoard | POST | /v1/accounts/{accountId}/pinterest-boards | Create a Pinterest board |
getRedditFlairs | GET | /v1/accounts/{accountId}/reddit-flairs | List selectable Reddit link flairs |
getRedditSubreddits | GET | /v1/accounts/{accountId}/reddit-subreddits | Discover Reddit subreddits |
updateRedditSubreddits | PUT | /v1/accounts/{accountId}/reddit-subreddits | Set the Reddit default subreddit |
listSlackMembers | GET | /v1/accounts/{accountId}/slack-members | List Slack workspace members |
getSlackSettings | GET | /v1/accounts/{accountId}/slack-settings | Get Slack account settings |
updateSlackSettings | PATCH | /v1/accounts/{accountId}/slack-settings | Update Slack account settings |
getTelegramCommands | GET | /v1/accounts/{accountId}/telegram-commands | Get Telegram bot commands |
setTelegramCommands | PUT | /v1/accounts/{accountId}/telegram-commands | Set Telegram bot commands |
deleteTelegramCommands | DELETE | /v1/accounts/{accountId}/telegram-commands | Delete Telegram bot commands |
listThreadsPostConversation | GET | /v1/accounts/{accountId}/threads-posts/{postId}/conversation | List a flattened Threads reply conversation |
listThreadsPendingReplies | GET | /v1/accounts/{accountId}/threads-posts/{postId}/pending-replies | List pending or ignored replies awaiting Threads approval |
listThreadsPostReplies | GET | /v1/accounts/{accountId}/threads-posts/{postId}/replies | List immediate replies to a Threads post or reply |
listThreadsAuthoredReplies | GET | /v1/accounts/{accountId}/threads-replies | List replies authored by the connected Threads profile |
decideThreadsPendingReply | POST | /v1/accounts/{accountId}/threads-replies/{replyId}/approval | Approve or ignore a pending Threads reply |
hideThreadsReply | PUT | /v1/accounts/{accountId}/threads-replies/{replyId}/hidden | Hide a Threads reply |
unhideThreadsReply | DELETE | /v1/accounts/{accountId}/threads-replies/{replyId}/hidden | Unhide a Threads reply |
getTikTokCreatorInfo | GET | /v1/accounts/{accountId}/tiktok/creator-info | Get current TikTok creator posting constraints |
getYoutubeCaptions | GET | /v1/accounts/{accountId}/youtube-captions | Get a YouTube video transcript |
getYoutubePlaylists | GET | /v1/accounts/{accountId}/youtube-playlists | List playlists owned by a connected YouTube channel |
updateYoutubeDefaultPlaylist | PUT | /v1/accounts/{accountId}/youtube-playlists | Set the default YouTube playlist preference |
getAccount | GET | /v1/accounts/{id} | Retrieve an account |
disconnectAccount | POST | /v1/accounts/{id}/actions/disconnect | Disconnect an account |
getFollowerStats | GET | /v1/accounts/follower-stats | Get stored follower statistics |
getAllAccountsHealth | GET | /v1/accounts/health | Summarise stored health for every account in scope |
listAccounts — GET /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)
updateAccount — PUT /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)
moveAccountToProfile — PATCH /v1/accounts/{accountId}
Parameters:
accountId(path, required)
Request body (MoveAccountToProfileDto): profileId (string, required)
deleteAccount — DELETE /v1/accounts/{accountId}
Parameters:
accountId(path, required)
getBlueskySettings — GET /v1/accounts/{accountId}/bluesky-settings
Parameters:
accountId(path, required)
updateBlueskySettings — PATCH /v1/accounts/{accountId}/bluesky-settings
Parameters:
accountId(path, required)
Request body (UpdateBlueskySettingsDto): defaultLangs (array of string, required)
getDiscordChannels — GET /v1/accounts/{accountId}/discord-channels
Lists writable text, announcement and forum channels in the connected Discord guild.
Parameters:
accountId(path, required)
getInstagramFollowStatus — GET /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.
getGoogleBusinessFoodMenus — GET /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.
updateGoogleBusinessFoodMenus — PUT /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)
getGoogleBusinessLocationDetails — GET /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.
updateGoogleBusinessLocationDetails — PUT /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)
getGoogleBusinessAccountPerformanceLegacy — GET /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)
listGoogleBusinessPosts — GET /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)
getGoogleBusinessPost — GET /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)
listGoogleBusinessReviews — GET /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)
getGoogleBusinessReviewNative — GET /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)
upsertGoogleBusinessReviewReplyLegacy — PUT /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)
deleteGoogleBusinessReviewReplyLegacy — DELETE /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)
listGoogleBusinessSearchKeywords — GET /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)
getAccountHealth — GET /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)
getInstagramPublishingLimit — GET /v1/accounts/{accountId}/instagram/publishing-limit
Reads the provider-owned rolling publishing quota for one active Instagram account.
Parameters:
accountId(path, required)
listPinterestBoardSections — GET /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)
getPinterestBoards — GET /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)
updatePinterestBoards — PUT /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)
createPinterestBoard — POST /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)
getRedditFlairs — GET /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)
getRedditSubreddits — GET /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.
updateRedditSubreddits — PUT /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)
listSlackMembers — GET /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.
getSlackSettings — GET /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)
updateSlackSettings — PATCH /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)
getTelegramCommands — GET /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)
setTelegramCommands — PUT /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)
deleteTelegramCommands — DELETE /v1/accounts/{accountId}/telegram-commands
Clears the bot-global default command list. Replaying the request is safe.
Parameters:
accountId(path, required)
listThreadsPostConversation — GET /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)
listThreadsPendingReplies — GET /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)
listThreadsPostReplies — GET /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)
listThreadsAuthoredReplies — GET /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)
decideThreadsPendingReply — POST /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)
hideThreadsReply — PUT /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)
unhideThreadsReply — DELETE /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)
getTikTokCreatorInfo — GET /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.
getYoutubeCaptions — GET /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)
getYoutubePlaylists — GET /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)
updateYoutubeDefaultPlaylist — PUT /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)
getAccount — GET /v1/accounts/{id}
Parameters:
id(path, required)
disconnectAccount — POST /v1/accounts/{id}/actions/disconnect
Parameters:
id(path, required)
getFollowerStats — GET /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.
getAllAccountsHealth — GET /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
| Operation | Method | Path | Summary |
|---|---|---|---|
searchAdInterests | GET | /v1/ads/interests | Search Meta interests (deprecated alias) |
getLinkedInBidPricing | POST | /v1/ads/targeting/bid-pricing | Suggested LinkedIn bid and budget bounds |
estimateAdReach | POST | /v1/ads/targeting/reach-estimate | Estimate how many people a targeting spec would reach |
searchAdTargeting | GET | /v1/ads/targeting/search | Search the platform for targeting entries |
getLinkedInSupplyForecast | POST | /v1/ads/targeting/supply-forecast | Forecast LinkedIn ad supply |
searchAdInterests — GET /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)
getLinkedInBidPricing — POST /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)
estimateAdReach — POST /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)
searchAdTargeting — GET /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, inact_<id>form. REQUIRED for every dimension exceptgeo, because the platform serves interest, behavior and income search under an ad account. IGNORED forgeo: that edge is not account scoped, so the value is dropped rather than rejected. Get the id fromGET /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 WHENdimension=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 documentscityas 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 tointerestfor backward compatibility with the deprecated /v1/ads/interests alias. Anything other thangeorequiresadAccountId.q(query, required) — Search query. For geo, the locality name only (no region or country suffix) -- sendParis, notParis, 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.
getLinkedInSupplyForecast — POST /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
| Operation | Method | Path | Summary |
|---|---|---|---|
listConversionDestinations | GET | /v1/accounts/{accountId}/conversion-destinations | List conversion destinations |
createConversionDestination | POST | /v1/accounts/{accountId}/conversion-destinations | Create a conversion destination |
getConversionDestination | GET | /v1/accounts/{accountId}/conversion-destinations/{destinationId} | Get a conversion destination |
updateConversionDestination | PATCH | /v1/accounts/{accountId}/conversion-destinations/{destinationId} | Update a conversion destination |
deleteConversionDestination | DELETE | /v1/accounts/{accountId}/conversion-destinations/{destinationId} | Delete a conversion destination |
listConversionAssociations | GET | /v1/accounts/{accountId}/conversion-destinations/{destinationId}/associations | List associated campaigns |
addConversionAssociations | POST | /v1/accounts/{accountId}/conversion-destinations/{destinationId}/associations | Associate campaigns |
removeConversionAssociations | DELETE | /v1/accounts/{accountId}/conversion-destinations/{destinationId}/associations | Remove associated campaigns |
getConversionMetrics | GET | /v1/accounts/{accountId}/conversion-destinations/{destinationId}/metrics | Get attribution metrics |
listCustomConversions | GET | /v1/accounts/{accountId}/custom-conversions | List the custom conversions on an ad account |
createCustomConversion | POST | /v1/accounts/{accountId}/custom-conversions | Create a custom conversion, or reuse the matching one |
listTrackingTags | GET | /v1/accounts/{accountId}/tracking-tags | List the tracking tags on an ad account |
createTrackingTag | POST | /v1/accounts/{accountId}/tracking-tags | Create a tracking tag |
getTrackingTag | GET | /v1/accounts/{accountId}/tracking-tags/{tagId} | Read one tracking tag |
updateTrackingTag | PATCH | /v1/accounts/{accountId}/tracking-tags/{tagId} | Update a tracking tag |
listTrackingTagSharedAccounts | GET | /v1/accounts/{accountId}/tracking-tags/{tagId}/shared-accounts | List the ad accounts a tracking tag is shared with |
addTrackingTagSharedAccount | POST | /v1/accounts/{accountId}/tracking-tags/{tagId}/shared-accounts | Share a tracking tag with an ad account |
removeTrackingTagSharedAccount | DELETE | /v1/accounts/{accountId}/tracking-tags/{tagId}/shared-accounts | Stop sharing a tracking tag with an ad account |
getTrackingTagStats | GET | /v1/accounts/{accountId}/tracking-tags/{tagId}/stats | Read tracking tag activity statistics |
listAds | GET | /v1/ads | List ad documents with metrics over a date range |
updateAd | PUT | /v1/ads/{adId} | Update a single ad |
deleteAd | DELETE | /v1/ads/{adId} | Delete a single ad -- permanently |
getAd | GET | /v1/ads/{adId} | Get one ad by any identifier dialect it is indexed under |
getAdAnalytics | GET | /v1/ads/{adId}/analytics | One ad's performance: totals plus a day-by-day series |
getAdComments | GET | /v1/ads/{adId}/comments | Read comments on the post behind an ad |
duplicateAd | POST | /v1/ads/{adId}/duplicate | Duplicate a single ad |
getAdMedia | GET | /v1/ads/{adId}/media | Resolve every media asset used by an ad |
getAdPreviews | GET | /v1/ads/{adId}/preview | Render an existing ad as it appears in a placement |
updateAdStatus | PUT | /v1/ads/{adId}/status | Pause or resume a single ad |
getAdTrackingTags | GET | /v1/ads/{adId}/tracking-tags | Read an ad's click-URL tracking parameters |
updateAdTrackingTags | PATCH | /v1/ads/{adId}/tracking-tags | Set an ad's click-URL tracking parameters |
listAdAccounts | GET | /v1/ads/accounts | List the ad accounts reachable from a connected account |
updateAdAccount | PATCH | /v1/ads/accounts | Set the default DSA beneficiary and payor on an ad account |
getAdAccountFinance | GET | /v1/ads/accounts/finance | Read the balance, spend and funding source of an ad account |
getAdsActivityLog | GET | /v1/ads/activity | List the change log of an ad account |
updateAdSet | PUT | /v1/ads/ad-sets/{adSetId} | Update an ad set |
deleteAdSet | DELETE | /v1/ads/ad-sets/{adSetId} | Delete an ad set |
getAdSetDetails | GET | /v1/ads/ad-sets/{adSetId} | Live ad-set details incl. learning phase |
duplicateAdSet | POST | /v1/ads/ad-sets/{adSetId}/duplicate | Duplicate an ad set |
updateAdSetStatus | PUT | /v1/ads/ad-sets/{adSetId}/status | Pause or resume a single ad set |
listAdAudiences | GET | /v1/ads/audiences | List custom audiences in an ad account |
createAdAudience | POST | /v1/ads/audiences | Create a custom audience |
getAdAudience | GET | /v1/ads/audiences/{audienceId} | Read a custom audience |
updateAdAudience | PUT | /v1/ads/audiences/{audienceId} | Rename or re-describe a custom audience |
deleteAdAudience | DELETE | /v1/ads/audiences/{audienceId} | Delete a custom audience |
replaceAdAudienceCompanies | POST | /v1/ads/audiences/{audienceId}/companies | Replace the companies in a LinkedIn audience |
addUsersToAdAudience | POST | /v1/ads/audiences/{audienceId}/users | Add members to a customer-list audience |
removeAdAudienceUsers | DELETE | /v1/ads/audiences/{audienceId}/users | Remove members from a customer-list audience |
boostPost | POST | /v1/ads/boost | Promote an existing Page post with a new campaign |
listAdsBusinessCenters | GET | /v1/ads/business-centers | List TikTok Business Centers |
listMetaBusinesses | GET | /v1/ads/businesses | List the Meta Business Manager portfolios the connected user belongs to |
createCallAd | POST | /v1/ads/call | Create an ad whose button places a phone call |
listAdCampaigns | GET | /v1/ads/campaigns | List ad campaigns as aggregations over ad documents |
createAdCampaign | POST | /v1/ads/campaigns | Create a standalone campaign (ODAX shell, no budget) |
deleteAdCampaign | DELETE | /v1/ads/campaigns/{campaignId} | Delete a campaign |
updateAdCampaign | PUT | /v1/ads/campaigns/{campaignId} | Rename a campaign |
getCampaignAnalytics | GET | /v1/ads/campaigns/{campaignId}/analytics | Campaign totals plus a day-by-day timeline |
attachCampaignAssets | POST | /v1/ads/campaigns/{campaignId}/assets | Attach assets to a Google Search campaign |
duplicateAdCampaign | POST | /v1/ads/campaigns/{campaignId}/duplicate | Duplicate a campaign |
updateAdCampaignStatus | PUT | /v1/ads/campaigns/{campaignId}/status | Pause or resume a campaign |
bulkUpdateAdCampaignStatus | POST | /v1/ads/campaigns/bulk-status | Pause or resume many campaigns |
listAdCatalogs | GET | /v1/ads/catalogs | List the product catalogs an ad account can advertise from |
listAdCatalogProductSets | GET | /v1/ads/catalogs/{catalogId}/product-sets | List the product sets in a product catalog |
sendConversions | POST | /v1/ads/conversions | Send conversion events to Meta |
adjustConversions | POST | /v1/ads/conversions/adjustments | Adjust Google Ads conversions |
getConversionsQuality | GET | /v1/ads/conversions/quality | Read how well your conversion events match real people |
createStandaloneAd | POST | /v1/ads/create | Create a campaign, ad set and ad in one call |
listAdCreatives | GET | /v1/ads/creatives | The ad account's creative library |
createAdCreative | POST | /v1/ads/creatives | Create a standalone creative |
getAdCreative | GET | /v1/ads/creatives/{creativeId} | One creative's details |
updateAdCreative | PUT | /v1/ads/creatives/{creativeId} | Rename a creative |
deleteAdCreative | DELETE | /v1/ads/creatives/{creativeId} | Delete a creative from the library |
createCtwaAd | POST | /v1/ads/ctwa | Create click-to-WhatsApp ads |
getDsaDefaults | GET | /v1/ads/dsa-defaults | Read the DSA defaults set on an ad account |
getDsaRecommendations | GET | /v1/ads/dsa-recommendations | Suggest legal entity names for an ad account |
listHighDemandPeriods | GET | /v1/ads/high-demand-periods | List the scheduled high demand periods on a campaign or ad set |
createHighDemandPeriod | POST | /v1/ads/high-demand-periods | Schedule a high demand period on a campaign or ad set |
listAdImages | GET | /v1/ads/images | Ad image library |
uploadAdImage | POST | /v1/ads/images | Upload an ad image from base64 |
queryAdInsights | GET | /v1/ads/insights | Flexible live insights query |
createAdInsightsReport | POST | /v1/ads/insights/reports | Submit an async insights report run |
getAdInsightsReport | GET | /v1/ads/insights/reports/{reportRunId} | Poll an async insights report run |
listAdKeywords | GET | /v1/ads/keywords | List Google Search keywords live |
generateKeywordHistoricalMetrics | POST | /v1/ads/keywords/historical-metrics | Generate Google keyword historical metrics |
generateKeywordIdeas | POST | /v1/ads/keywords/ideas | Generate Google Keyword Planner ideas |
listAdLabels | GET | /v1/ads/labels | List the ad labels in an ad account |
listLeadForms | GET | /v1/ads/lead-forms | List lead forms |
createLeadForm | POST | /v1/ads/lead-forms | Create a lead form |
getLeadForm | GET | /v1/ads/lead-forms/{formId} | Get a lead form |
archiveLeadForm | DELETE | /v1/ads/lead-forms/{formId} | Archive a lead form |
listFormLeads | GET | /v1/ads/lead-forms/{formId}/leads | List submitted leads for one Meta form |
createTestLead | POST | /v1/ads/lead-forms/{formId}/test-leads | Create one Meta test lead |
listLeads | GET | /v1/ads/leads | List submitted Meta leads |
listLocalServicesLeads | GET | /v1/ads/local-services/leads | List Google Local Services Ads leads |
listLocalServicesLeadConversations | GET | /v1/ads/local-services/leads/{leadId}/conversations | List conversations for a Google Local Services lead |
createMessagingAd | POST | /v1/ads/messaging | Create ads that open a chat thread |
generateAdPreviews | POST | /v1/ads/preview | Render a creative as it would look, before any ad exists |
createRfPrediction | POST | /v1/ads/rf-predictions | Create a Reach & Frequency prediction |
getRfPrediction | GET | /v1/ads/rf-predictions/{predictionId} | Read a Reach & Frequency prediction |
cancelRfReservation | DELETE | /v1/ads/rf-predictions/{predictionId} | Cancel a Reach & Frequency reservation |
reserveRfPrediction | POST | /v1/ads/rf-predictions/{predictionId}/reserve | Reserve a Reach & Frequency prediction |
getAdsSearchTerms | GET | /v1/ads/search-terms | Read Google Ads search terms live |
listAdStudies | GET | /v1/ads/studies | List the A/B studies in an ad account |
getAdsTimeline | GET | /v1/ads/timeline | Get daily account metrics |
getAdTree | GET | /v1/ads/tree | Campaign > ad set > ad hierarchy with rolled-up metrics |
listValueRuleSets | GET | /v1/ads/value-rule-sets | List value rule sets in an ad account |
createValueRuleSet | POST | /v1/ads/value-rule-sets | Create a value rule set |
getValueRuleSet | GET | /v1/ads/value-rule-sets/{valueRuleSetId} | Read a value rule set |
updateValueRuleSet | PUT | /v1/ads/value-rule-sets/{valueRuleSetId} | Replace a value rule set |
deleteValueRuleSet | DELETE | /v1/ads/value-rule-sets/{valueRuleSetId} | Delete a value rule set |
uploadAdVideo | POST | /v1/ads/videos | Upload a video into a Meta ad account library |
listAdVideos | GET | /v1/ads/videos | List Meta ad videos |
deleteAdVideo | DELETE | /v1/ads/videos/{videoId} | Delete a video through the account-scoped Meta edge |
listConversionDestinations — GET /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.
createConversionDestination — POST /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)
getConversionDestination — GET /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 fullurn:li:sponsoredAccount:{id}URN.
updateConversionDestination — PATCH /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)
deleteConversionDestination — DELETE /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)
listConversionAssociations — GET /v1/accounts/{accountId}/conversion-destinations/{destinationId}/associations
Parameters:
accountId(path, required)destinationId(path, required)adAccountId(query, required)
addConversionAssociations — POST /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)
removeConversionAssociations — DELETE /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)
getConversionMetrics — GET /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)
listCustomConversions — GET /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 frompaging.afterof 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) — Theact_<n>ad account whose custom conversions are listed.
createCustomConversion — POST /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)
listTrackingTags — GET /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 frompaging.afterof 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) — Theact_<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_accountsorfacebook_tracking_tag_directory_page_limit_exceeded) instead of returning a truncated success.
createTrackingTag — POST /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)
getTrackingTag — GET /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.
updateTrackingTag — PATCH /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)
listTrackingTagSharedAccounts — GET /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.
addTrackingTagSharedAccount — POST /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)
removeTrackingTagSharedAccount — DELETE /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) — Theact_<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)
getTrackingTagStats — GET /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 thanstartTime.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.
listAds — GET /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)
updateAd — PUT /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 replacingcreative; optional for the idempotent fields.
Request body (UpdateAdDto): status (enum: active, paused), budget (UpdateAdBudgetDto), targeting (UpdateAdTargetingDto), creative (UpdateAdReplacementCreativeDto), name (string), platform (string)
deleteAd — DELETE /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.
getAd — GET /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)
getAdAnalytics — GET /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)
getAdComments — GET /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) — Thepagination.cursorfrom a previous response.limit(query)placement(query) — Which side to read. Omit to get the Instagram side when it exists.
duplicateAd — POST /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)
getAdMedia — GET /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.
getAdPreviews — GET /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).
updateAdStatus — PUT /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)
getAdTrackingTags — GET /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.
updateAdTrackingTags — PATCH /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)
listAdAccounts — GET /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.
updateAdAccount — PATCH /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)
getAdAccountFinance — GET /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.
getAdsActivityLog — GET /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.
updateAdSet — PUT /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)
deleteAdSet — DELETE /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.
getAdSetDetails — GET /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.
duplicateAdSet — POST /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)
updateAdSetStatus — PUT /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)
listAdAudiences — GET /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 frompaging.afterof 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 thanlimit.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.
createAdAudience — POST /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)
getAdAudience — GET /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.
updateAdAudience — PUT /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)
deleteAdAudience — DELETE /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)
replaceAdAudienceCompanies — POST /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)
addUsersToAdAudience — POST /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)
removeAdAudienceUsers — DELETE /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)
boostPost — POST /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)
listAdsBusinessCenters — GET /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)
listMetaBusinesses — GET /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'spaging.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.
createCallAd — POST /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)
listAdCampaigns — GET /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) —allreturns both locally created ads and those discovered from the platform's ad manager;zernioreturns 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.
createAdCampaign — POST /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)
deleteAdCampaign — DELETE /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)
updateAdCampaign — PUT /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)
getCampaignAnalytics — GET /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)
attachCampaignAssets — POST /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)
duplicateAdCampaign — POST /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)
updateAdCampaignStatus — PUT /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)
bulkUpdateAdCampaignStatus — POST /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)
listAdCatalogs — GET /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.
listAdCatalogProductSets — GET /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. Noact_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.
sendConversions — POST /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)
adjustConversions — POST /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)
getConversionsQuality — GET /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 fromaccountId: one account can see several datasets, and choosing one for you would report scores for a dataset you did not ask about. Anact_<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.
createStandaloneAd — POST /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)
listAdCreatives — GET /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.
createAdCreative — POST /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)
getAdCreative — GET /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.
updateAdCreative — PUT /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)
deleteAdCreative — DELETE /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.
createCtwaAd — POST /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)
getDsaDefaults — GET /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.
getDsaRecommendations — GET /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.
listHighDemandPeriods — GET /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 frompaging.afterof 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.
createHighDemandPeriod — POST /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)
listAdImages — GET /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 frompaging.afterof 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.
uploadAdImage — POST /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)
queryAdInsights — GET /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 frompaging.afterof 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;valueis 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.
createAdInsightsReport — POST /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)
getAdInsightsReport — GET /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 frompaging.afterof 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.
listAdKeywords — GET /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)
generateKeywordHistoricalMetrics — POST /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)
generateKeywordIdeas — POST /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)
listAdLabels — GET /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.
listLeadForms — GET /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 frompagination.cursorof 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.
createLeadForm — POST /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)
getLeadForm — GET /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.
archiveLeadForm — DELETE /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.
listFormLeads — GET /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)
createTestLead — POST /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)
listLeads — GET /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)
listLocalServicesLeads — GET /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)
listLocalServicesLeadConversations — GET /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)
createMessagingAd — POST /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)
generateAdPreviews — POST /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)
createRfPrediction — POST /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)
getRfPrediction — GET /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.
cancelRfReservation — DELETE /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.
reserveRfPrediction — POST /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)
getAdsSearchTerms — GET /v1/ads/search-terms
Parameters:
pageToken(query)adGroupId(query)campaignId(query)toDate(query)fromDate(query)customerId(query)accountId(query, required)
listAdStudies — GET /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 ascells{id,name}are supported and a flatcellsreturns 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.
getAdsTimeline — GET /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.
getAdTree — GET /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)
listValueRuleSets — GET /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 frompaging.afterof 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.
createValueRuleSet — POST /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)
getValueRuleSet — GET /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.
updateValueRuleSet — PUT /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)
deleteValueRuleSet — DELETE /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.
uploadAdVideo — POST /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)
listAdVideos — GET /v1/ads/videos
Parameters:
after(query)limit(query)fields(query)adAccountId(query, required)accountId(query, required)
deleteAdVideo — DELETE /v1/ads/videos/{videoId}
Parameters:
videoId(path, required)Idempotency-Key(header)adAccountId(query, required)accountId(query, required)
analytics
| Operation | Method | Path | Summary |
|---|---|---|---|
getFacebookPostReactions | GET | /v1/accounts/{accountId}/facebook-post-reactions | Get Facebook post reactions |
getLinkedInAggregateAnalytics | GET | /v1/accounts/{accountId}/linkedin-aggregate-analytics | Get LinkedIn aggregate stats |
getLinkedInMentions | GET | /v1/accounts/{accountId}/linkedin-mentions | Resolve LinkedIn mention |
getLinkedInPostAnalytics | GET | /v1/accounts/{accountId}/linkedin-post-analytics | Get LinkedIn post stats |
getLinkedInPostReactions | GET | /v1/accounts/{accountId}/linkedin-post-reactions | Get LinkedIn post reactions |
getAnalytics | GET | /v1/analytics | Read metrics for one published post |
getBestTimeToPost | GET | /v1/analytics/best-time | Get best times to post |
getContentDecay | GET | /v1/analytics/content-decay | Get content performance decay |
getDailyMetrics | GET | /v1/analytics/daily-metrics | Get daily publication metrics |
getFacebookPageInsights | GET | /v1/analytics/facebook/page-insights | Get Facebook Page insights |
getFacebookPostEarnings | GET | /v1/analytics/facebook/post-earnings | Get Facebook post monetization earnings |
getGoogleBusinessPerformance | GET | /v1/analytics/googlebusiness/performance | Get Google Business performance metrics |
getGoogleBusinessSearchKeywords | GET | /v1/analytics/googlebusiness/search-keywords | Get Google Business search keywords |
getInstagramAccountInsights | GET | /v1/analytics/instagram/account-insights | Get Instagram account insights |
getInstagramDemographics | GET | /v1/analytics/instagram/demographics | Get Instagram audience demographics |
getInstagramFollowerHistory | GET | /v1/analytics/instagram/follower-history | Get Instagram follower history |
getLinkedInOrgAggregateAnalytics | GET | /v1/analytics/linkedin/org-aggregate-analytics | Get LinkedIn org analytics |
getPostTimeline | GET | /v1/analytics/post-timeline | Get post analytics timeline |
getPostingFrequency | GET | /v1/analytics/posting-frequency | Get frequency vs engagement |
getTikTokAccountInsights | GET | /v1/analytics/tiktok/account-insights | Get TikTok account-level insights |
getYouTubeChannelInsights | GET | /v1/analytics/youtube/channel-insights | Get YouTube channel insights |
getYouTubeDailyViews | GET | /v1/analytics/youtube/daily-views | Get YouTube daily views |
getYouTubeDemographics | GET | /v1/analytics/youtube/demographics | Get YouTube audience demographics |
getYouTubeVideoRetention | GET | /v1/analytics/youtube/video-retention | Get YouTube video retention |
getFacebookPostReactions — GET /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.
getLinkedInAggregateAnalytics — GET /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)
getLinkedInMentions — GET /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)
getLinkedInPostAnalytics — GET /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)
getLinkedInPostReactions — GET /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)
getAnalytics — GET /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)
getBestTimeToPost — GET /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)
getContentDecay — GET /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)
getDailyMetrics — GET /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)
getFacebookPageInsights — GET /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)
getFacebookPostEarnings — GET /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)
getGoogleBusinessPerformance — GET /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)
getGoogleBusinessSearchKeywords — GET /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)
getInstagramAccountInsights — GET /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)
getInstagramDemographics — GET /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)
getInstagramFollowerHistory — GET /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)
getLinkedInOrgAggregateAnalytics — GET /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)
getPostTimeline — GET /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)
getPostingFrequency — GET /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)
getTikTokAccountInsights — GET /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)
getYouTubeChannelInsights — GET /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)
getYouTubeDailyViews — GET /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)
getYouTubeDemographics — GET /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)
getYouTubeVideoRetention — GET /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
| Operation | Method | Path | Summary |
|---|---|---|---|
createApiKey | POST | /v1/api-keys | Create an API key |
listApiKeys | GET | /v1/api-keys | List active API keys |
deleteApiKey | DELETE | /v1/api-keys/{keyId} | Delete key |
createApiKey — POST /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)
listApiKeys — GET /v1/api-keys
Parameters:
cursor(query)limit(query)
deleteApiKey — DELETE /v1/api-keys/{keyId}
Permanently revokes and deletes an API key.
Parameters:
keyId(path, required)
audit-log
| Operation | Method | Path | Summary |
|---|---|---|---|
listAuditLog | GET | /v1/audit-log | List the team audit log |
listAuditLog — GET /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
| Operation | Method | Path | Summary |
|---|---|---|---|
forgotPassword | POST | /v1/auth/forgot-password | Request a password reset e-mail |
login | POST | /v1/auth/login | Start a panel session |
logout | POST | /v1/auth/logout | End the current session |
getCurrentSession | GET | /v1/auth/me | Retrieve the signed-in user, team and role |
changePassword | POST | /v1/auth/password | Change the password and revoke every other session |
register | POST | /v1/auth/register | Create a team and its owner account |
resetPassword | POST | /v1/auth/reset-password | Consume a password reset token and set a new password |
verifyCredential | GET | /v1/auth/verify | Verify credential |
verifyEmail | POST | /v1/auth/verify-email | Consume an email verification token |
forgotPassword — POST /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)
login — POST /v1/auth/login
Request body (LoginDto): email (string, required), password (string, required)
logout — POST /v1/auth/logout
getCurrentSession — GET /v1/auth/me
changePassword — POST /v1/auth/password
Request body (ChangePasswordDto): currentPassword (string, required), newPassword (string, required)
register — POST /v1/auth/register
Request body (RegisterDto): email (string, required), password (string, required), name (string), teamName (string), timezone (string)
resetPassword — POST /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)
verifyCredential — GET /v1/auth/verify
Checks whether the bearer credential or panel session on this request is valid without reading product data.
verifyEmail — POST /v1/auth/verify-email
Request body (VerifyEmailDto): token (string, required)
billing
| Operation | Method | Path | Summary |
|---|---|---|---|
getBilling | GET | /v1/billing | Prepaid balance and the insufficient-balance rule |
createBillingCheckout | POST | /v1/billing/checkout | Start a Stripe Checkout session to top up the prepaid balance |
getXApiPricing | GET | /v1/billing/x-pricing | Machine-readable X (twitter) pricing and the tariff rule |
getBilling — GET /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.
createBillingCheckout — POST /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)
getXApiPricing — GET /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
| Operation | Method | Path | Summary |
|---|---|---|---|
listBlogs | GET | /v1/accounts/{accountId}/blogs | List Shopify blogs |
createBlog | POST | /v1/accounts/{accountId}/blogs | Create a Shopify blog |
getBlog | GET | /v1/accounts/{accountId}/blogs/{blogId} | Get a Shopify blog |
updateBlog | PATCH | /v1/accounts/{accountId}/blogs/{blogId} | Update a Shopify blog |
deleteBlog | DELETE | /v1/accounts/{accountId}/blogs/{blogId} | Permanently delete a Shopify blog and its articles |
listBlogArticles | GET | /v1/accounts/{accountId}/blogs/{blogId}/articles | List Shopify blog articles |
createBlogArticle | POST | /v1/accounts/{accountId}/blogs/{blogId}/articles | Create or natively schedule a Shopify article |
getBlogArticle | GET | /v1/accounts/{accountId}/blogs/{blogId}/articles/{articleId} | Get a Shopify blog article |
updateBlogArticle | PATCH | /v1/accounts/{accountId}/blogs/{blogId}/articles/{articleId} | Update a Shopify blog article |
deleteBlogArticle | DELETE | /v1/accounts/{accountId}/blogs/{blogId}/articles/{articleId} | Permanently delete a Shopify blog article |
listBlogs — GET /v1/accounts/{accountId}/blogs
Parameters:
accountId(path, required)cursor(query)limit(query)
createBlog — POST /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)
getBlog — GET /v1/accounts/{accountId}/blogs/{blogId}
Parameters:
accountId(path, required)blogId(path, required)
updateBlog — PATCH /v1/accounts/{accountId}/blogs/{blogId}
Parameters:
accountId(path, required)blogId(path, required)
Request body (ShopifyBlogUpdateDto): title (string), handle (string)
deleteBlog — DELETE /v1/accounts/{accountId}/blogs/{blogId}
Parameters:
accountId(path, required)blogId(path, required)
listBlogArticles — GET /v1/accounts/{accountId}/blogs/{blogId}/articles
Parameters:
accountId(path, required)blogId(path, required)cursor(query)limit(query)
createBlogArticle — POST /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)
getBlogArticle — GET /v1/accounts/{accountId}/blogs/{blogId}/articles/{articleId}
Parameters:
accountId(path, required)blogId(path, required)articleId(path, required)
updateBlogArticle — PATCH /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)
deleteBlogArticle — DELETE /v1/accounts/{accountId}/blogs/{blogId}/articles/{articleId}
Parameters:
accountId(path, required)blogId(path, required)articleId(path, required)
broadcasts
| Operation | Method | Path | Summary |
|---|---|---|---|
listBroadcasts | GET | /v1/broadcasts | List broadcasts |
createBroadcast | POST | /v1/broadcasts | Create a broadcast |
getBroadcast | GET | /v1/broadcasts/{broadcastId} | Retrieve a broadcast |
updateBroadcast | PATCH | /v1/broadcasts/{broadcastId} | Update a broadcast |
deleteBroadcast | DELETE | /v1/broadcasts/{broadcastId} | Delete a broadcast |
cancelBroadcast | POST | /v1/broadcasts/{broadcastId}/cancel | Cancel a broadcast |
addBroadcastRecipients | POST | /v1/broadcasts/{broadcastId}/recipients | Add recipients to a broadcast |
listBroadcastRecipients | GET | /v1/broadcasts/{broadcastId}/recipients | List broadcast recipients |
scheduleBroadcast | POST | /v1/broadcasts/{broadcastId}/schedule | Schedule a broadcast over its frozen audience |
sendBroadcast | POST | /v1/broadcasts/{broadcastId}/send | Start durable broadcast delivery |
listBroadcasts — GET /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.
createBroadcast — POST /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)
getBroadcast — GET /v1/broadcasts/{broadcastId}
Parameters:
broadcastId(path, required)
updateBroadcast — PATCH /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)
deleteBroadcast — DELETE /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)
cancelBroadcast — POST /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)
addBroadcastRecipients — POST /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)
listBroadcastRecipients — GET /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)
scheduleBroadcast — POST /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)
sendBroadcast — POST /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
| Operation | Method | Path | Summary |
|---|---|---|---|
listCommentAutomations | GET | /v1/comment-automations | List comment automations |
createCommentAutomation | POST | /v1/comment-automations | Create a comment automation |
getCommentAutomation | GET | /v1/comment-automations/{automationId} | Retrieve a comment automation |
updateCommentAutomation | PATCH | /v1/comment-automations/{automationId} | Update a comment automation |
deleteCommentAutomation | DELETE | /v1/comment-automations/{automationId} | Delete a comment automation |
listCommentAutomationLogs | GET | /v1/comment-automations/{automationId}/logs | List durable comment automation execution logs |
listCommentAutomations — GET /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.
createCommentAutomation — POST /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)
getCommentAutomation — GET /v1/comment-automations/{automationId}
Parameters:
automationId(path, required)
updateCommentAutomation — PATCH /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)
deleteCommentAutomation — DELETE /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)
listCommentAutomationLogs — GET /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
| Operation | Method | Path | Summary |
|---|---|---|---|
listInboxComments | GET | /v1/inbox/comments | List commented posts |
deleteInboxComment | DELETE | /v1/inbox/comments/{postId} | Delete an authored YouTube comment |
replyToInboxPost | POST | /v1/inbox/comments/{postId} | Reply to a YouTube comment |
getInboxPostComments | GET | /v1/inbox/comments/{postId} | Get YouTube post comments |
editInboxComment | PATCH | /v1/inbox/comments/{postId}/{commentId} | Edit comment |
hideInboxComment | POST | /v1/inbox/comments/{postId}/{commentId}/hide | Hide a Threads comment |
unhideInboxComment | DELETE | /v1/inbox/comments/{postId}/{commentId}/hide | Unhide a Threads comment |
likeInboxComment | POST | /v1/inbox/comments/{postId}/{commentId}/like | Like a comment |
unlikeInboxComment | DELETE | /v1/inbox/comments/{postId}/{commentId}/like | Remove a comment like |
setCommentModeration | POST | /v1/inbox/comments/{postId}/{commentId}/moderation | Set a YouTube comment moderation status |
sendPrivateReplyToComment | POST | /v1/inbox/comments/{postId}/{commentId}/private-reply | Send private reply |
listInboxComments — GET /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)
deleteInboxComment — DELETE /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)
replyToInboxPost — POST /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)
getInboxPostComments — GET /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)
editInboxComment — PATCH /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)
hideInboxComment — POST /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)
unhideInboxComment — DELETE /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)
likeInboxComment — POST /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)
unlikeInboxComment — DELETE /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.
setCommentModeration — POST /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)
sendPrivateReplyToComment — POST /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
| Operation | Method | Path | Summary |
|---|---|---|---|
getFacebookPages | GET | /v1/accounts/{accountId}/facebook-page | List Facebook pages |
updateFacebookPage | PUT | /v1/accounts/{accountId}/facebook-page | Update Facebook page |
getGmbLocations | GET | /v1/accounts/{accountId}/gmb-locations | List GBP locations |
updateGmbLocation | PUT | /v1/accounts/{accountId}/gmb-locations | Update GBP location |
assignGoogleBusinessLocation | POST | /v1/accounts/{accountId}/gmb-locations/assign | Assign GBP location to another profile |
updateLinkedInOrganization | PUT | /v1/accounts/{accountId}/linkedin-organization | Switch LinkedIn account type |
getLinkedInOrganizations | GET | /v1/accounts/{accountId}/linkedin-organizations | List LinkedIn orgs |
setRedditPostFlair | POST | /v1/accounts/{accountId}/reddit-flairs | Set Reddit post flair |
getSubredditRules | GET | /v1/accounts/{accountId}/reddit-subreddits/{subreddit}/rules | Get subreddit rules |
voteRedditThing | POST | /v1/accounts/{accountId}/reddit-vote | Vote on a Reddit post or comment |
getConnectUrl | GET | /v1/connect/{platform} | Get a headless OAuth connect URL |
handleOAuthCallback | POST | /v1/connect/{platform} | Complete a bound headless OAuth callback |
connectAds | GET | /v1/connect/{platform}/ads | Start a separate-token ads OAuth flow |
listAllowedRedirects | GET | /v1/connect/allowed-redirects | List the OAuth redirect URIs registered for this team |
createAllowedRedirect | POST | /v1/connect/allowed-redirects | Register an OAuth redirect URI |
deleteAllowedRedirect | DELETE | /v1/connect/allowed-redirects | Remove a registered OAuth redirect URI |
authorizeConnect | POST | /v1/connect/authorize | Start an OAuth connect flow |
connectBlueskyCredentials | POST | /v1/connect/bluesky/credentials | Connect Bluesky account |
completeConnect | POST | /v1/connect/complete | Complete an OAuth connect flow |
connectCredentials | POST | /v1/connect/credentials | Connect an account with direct credentials |
connectDiscordChannel | POST | /v1/connect/discord | Complete a bound Discord OAuth callback |
listFacebookPages | GET | /v1/connect/facebook/select-page | List Facebook pages |
selectFacebookPage | POST | /v1/connect/facebook/select-page | Select Facebook page |
listGoogleBusinessLocations | GET | /v1/connect/googlebusiness/locations | List stored Google Business locations |
selectGoogleBusinessLocation | POST | /v1/connect/googlebusiness/select-location | Select a stored Google Business location |
listInstagramPages | GET | /v1/connect/instagram/select-account | List Pages with a linked Instagram account |
selectInstagramAccount | POST | /v1/connect/instagram/select-account | Select the Page whose Instagram account to connect |
listLinkedInOrganizations | GET | /v1/connect/linkedin/organizations | List LinkedIn orgs |
selectLinkedInOrganization | POST | /v1/connect/linkedin/select-organization | Select LinkedIn org |
getPendingOAuthData | GET | /v1/connect/pending-data | Get pending OAuth data |
completePendingConnection | POST | /v1/connect/pending/{id}/complete | Select a resource for a pending connection |
listPendingConnectionResources | GET | /v1/connect/pending/{id}/resources | List resources stored for a pending connection |
listPinterestBoardsForSelection | GET | /v1/connect/pinterest/select-board | List Pinterest boards |
selectPinterestBoard | POST | /v1/connect/pinterest/select-board | Select Pinterest board |
getShopifyConnectUrl | GET | /v1/connect/shopify | Get a Shopify OAuth connection URL |
connectShopifyWithToken | POST | /v1/connect/shopify/token | Connect a Shopify store with a custom-app Admin token |
connectSlackChannel | POST | /v1/connect/slack | Complete a bound Slack OAuth callback |
listSnapchatProfiles | GET | /v1/connect/snapchat/select-profile | List Snapchat profiles |
selectSnapchatProfile | POST | /v1/connect/snapchat/select-profile | Select Snapchat profile |
initiateTelegramConnect | POST | /v1/connect/telegram | Connect Telegram directly |
configureTikTokAdsBrandIdentity | PATCH | /v1/connect/tiktok-ads | Set TikTok brand identity |
connectWhatsAppCredentials | POST | /v1/connect/whatsapp/credentials | Connect WhatsApp via credentials |
connectWhatsAppEmbeddedSignup | POST | /v1/connect/whatsapp/embedded-signup | Connect WhatsApp from Embedded Signup |
listWhatsAppPhoneNumbers | GET | /v1/connect/whatsapp/select-phone-number | List numbers for selection |
completeWhatsAppPhoneSelection | POST | /v1/connect/whatsapp/select-phone-number | Complete number selection |
getFacebookPages — GET /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.
updateFacebookPage — PUT /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)
getGmbLocations — GET /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).
updateGmbLocation — PUT /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)
assignGoogleBusinessLocation — POST /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)
updateLinkedInOrganization — PUT /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)
getLinkedInOrganizations — GET /v1/accounts/{accountId}/linkedin-organizations
Returns LinkedIn organizations the connected account can administer and publish for.
Parameters:
accountId(path, required)
setRedditPostFlair — POST /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)
getSubredditRules — GET /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.
voteRedditThing — POST /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)
getConnectUrl — GET /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)
handleOAuthCallback — POST /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)
connectAds — GET /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)
listAllowedRedirects — GET /v1/connect/allowed-redirects
Parameters:
sort(query)cursor(query)limit(query)
createAllowedRedirect — POST /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)
deleteAllowedRedirect — DELETE /v1/connect/allowed-redirects
Parameters:
redirectUri(query, required)
authorizeConnect — POST /v1/connect/authorize
Request body (ConnectAuthorizeDto): profileId (string, required), platform (string, required), redirectUrl (string, required), instanceUrl (string), accountId (string)
connectBlueskyCredentials — POST /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)
completeConnect — POST /v1/connect/complete
Request body (ConnectCompleteDto): state (string, required), code (string, required)
connectCredentials — POST /v1/connect/credentials
Request body (ConnectCredentialsDto): profileId (string, required), platform (string, required), accountId (string), credentials (object, required)
connectDiscordChannel — POST /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)
listFacebookPages — GET /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.
selectFacebookPage — POST /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)
listGoogleBusinessLocations — GET /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)
selectGoogleBusinessLocation — POST /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)
listInstagramPages — GET /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)
selectInstagramAccount — POST /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)
listLinkedInOrganizations — GET /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)
selectLinkedInOrganization — POST /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)
getPendingOAuthData — GET /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.
completePendingConnection — POST /v1/connect/pending/{id}/complete
Parameters:
id(path, required)
Request body (ConnectPendingCompleteDto): resourceId (string, required)
listPendingConnectionResources — GET /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)
listPinterestBoardsForSelection — GET /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.
selectPinterestBoard — POST /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)
getShopifyConnectUrl — GET /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)
connectShopifyWithToken — POST /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)
connectSlackChannel — POST /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)
listSnapchatProfiles — GET /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)
selectSnapchatProfile — POST /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)
initiateTelegramConnect — POST /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)
configureTikTokAdsBrandIdentity — PATCH /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)
connectWhatsAppCredentials — POST /v1/connect/whatsapp/credentials
Request body (WhatsAppCredentialsConnectRequestDto): profileId (string, required), accessToken (string, required), wabaId (string, required), phoneNumberId (string, required), pin (string)
connectWhatsAppEmbeddedSignup — POST /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)
listWhatsAppPhoneNumbers — GET /v1/connect/whatsapp/select-phone-number
Parameters:
tempToken(query, required)profileId(query, required)X-Connect-Token(header)
completeWhatsAppPhoneSelection — POST /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
| Operation | Method | Path | Summary |
|---|---|---|---|
listContacts | GET | /v1/contacts | List contacts |
createContact | POST | /v1/contacts | Create a contact |
getContact | GET | /v1/contacts/{contactId} | Retrieve a contact |
updateContact | PATCH | /v1/contacts/{contactId} | Update a contact |
deleteContact | DELETE | /v1/contacts/{contactId} | Delete a contact |
getContactChannels | GET | /v1/contacts/{contactId}/channels | List a contact's channels |
setContactFieldValue | PUT | /v1/contacts/{contactId}/fields/{slug} | Set a custom field value on a contact |
clearContactFieldValue | DELETE | /v1/contacts/{contactId}/fields/{slug} | Clear a custom field value on a contact |
bulkCreateContacts | POST | /v1/contacts/bulk | Import contacts in bulk |
listContacts — GET /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 withtagsthe 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.
createContact — POST /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)
getContact — GET /v1/contacts/{contactId}
Parameters:
contactId(path, required)
updateContact — PATCH /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)
deleteContact — DELETE /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)
getContactChannels — GET /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)
setContactFieldValue — PUT /v1/contacts/{contactId}/fields/{slug}
Parameters:
contactId(path, required)slug(path, required)
Request body (SetContactFieldValueDto): value (string | number | boolean, required)
clearContactFieldValue — DELETE /v1/contacts/{contactId}/fields/{slug}
Removes the stored value. The field definition is untouched.
Parameters:
contactId(path, required)slug(path, required)
bulkCreateContacts — POST /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
| Operation | Method | Path | Summary |
|---|---|---|---|
listCustomFields | GET | /v1/custom-fields | List custom field definitions |
createCustomField | POST | /v1/custom-fields | Create a custom field definition |
getCustomField | GET | /v1/custom-fields/{fieldId} | Retrieve a custom field definition |
updateCustomField | PATCH | /v1/custom-fields/{fieldId} | Update a custom field definition |
deleteCustomField | DELETE | /v1/custom-fields/{fieldId} | Delete a custom field definition |
listCustomFields — GET /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.
createCustomField — POST /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)
getCustomField — GET /v1/custom-fields/{fieldId}
Parameters:
fieldId(path, required)
updateCustomField — PATCH /v1/custom-fields/{fieldId}
Parameters:
fieldId(path, required)
Request body (UpdateCustomFieldDto): name (string), options (array of string)
deleteCustomField — DELETE /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
| Operation | Method | Path | Summary |
|---|---|---|---|
getDiscordSettings | GET | /v1/accounts/{accountId}/discord-settings | Get Discord account settings |
updateDiscordSettings | PATCH | /v1/accounts/{accountId}/discord-settings | Update Discord account settings |
deleteDiscordMessage | DELETE | /v1/discord/channels/{channelId}/messages/{messageId} | Delete a Discord channel message |
crosspostDiscordMessage | POST | /v1/discord/channels/{channelId}/messages/{messageId}/crosspost | Crosspost Discord message |
listDiscordPinnedMessages | GET | /v1/discord/channels/{channelId}/pins | List pinned messages |
pinDiscordMessage | PUT | /v1/discord/channels/{channelId}/pins/{messageId} | Pin a published Discord message |
unpinDiscordMessage | DELETE | /v1/discord/channels/{channelId}/pins/{messageId} | Unpin a published Discord message |
createDiscordThread | POST | /v1/discord/channels/{channelId}/threads | Create a Discord public thread |
sendDiscordDirectMessage | POST | /v1/discord/dms | Send a Discord Direct Message |
listDiscordScheduledEvents | GET | /v1/discord/guilds/{guildId}/events | List Discord scheduled events |
createDiscordScheduledEvent | POST | /v1/discord/guilds/{guildId}/events | Create a Discord scheduled event |
getDiscordScheduledEvent | GET | /v1/discord/guilds/{guildId}/events/{eventId} | Get a Discord scheduled event |
updateDiscordScheduledEvent | PATCH | /v1/discord/guilds/{guildId}/events/{eventId} | Update a Discord scheduled event |
deleteDiscordScheduledEvent | DELETE | /v1/discord/guilds/{guildId}/events/{eventId} | Delete a Discord scheduled event |
listDiscordGuildMembers | GET | /v1/discord/guilds/{guildId}/members | List Discord guild members |
getDiscordGuildMember | GET | /v1/discord/guilds/{guildId}/members/{userId} | Get a Discord guild member |
addDiscordMemberRole | PUT | /v1/discord/guilds/{guildId}/members/{userId}/roles/{roleId} | Add a Discord member role |
removeDiscordMemberRole | DELETE | /v1/discord/guilds/{guildId}/members/{userId}/roles/{roleId} | Remove a Discord member role |
searchDiscordGuildMembers | GET | /v1/discord/guilds/{guildId}/members/search | Search Discord guild members |
listDiscordGuildRoles | GET | /v1/discord/guilds/{guildId}/roles | List Discord guild roles |
createDiscordGuildRole | POST | /v1/discord/guilds/{guildId}/roles | Create a Discord guild role |
editDiscordGuildRole | PATCH | /v1/discord/guilds/{guildId}/roles/{roleId} | Edit a Discord guild role |
deleteDiscordGuildRole | DELETE | /v1/discord/guilds/{guildId}/roles/{roleId} | Delete a Discord guild role |
getDiscordSettings — GET /v1/accounts/{accountId}/discord-settings
Returns the connected guild/channel and the account-level webhook identity defaults.
Parameters:
accountId(path, required)
updateDiscordSettings — PATCH /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)
deleteDiscordMessage — DELETE /v1/discord/channels/{channelId}/messages/{messageId}
Parameters:
channelId(path, required)messageId(path, required)accountId(query, required)
crosspostDiscordMessage — POST /v1/discord/channels/{channelId}/messages/{messageId}/crosspost
Parameters:
channelId(path, required)messageId(path, required)accountId(query, required)
listDiscordPinnedMessages — GET /v1/discord/channels/{channelId}/pins
Parameters:
channelId(path, required)accountId(query, required)
pinDiscordMessage — PUT /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)
unpinDiscordMessage — DELETE /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)
createDiscordThread — POST /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)
sendDiscordDirectMessage — POST /v1/discord/dms
Request body (DiscordDirectMessageRequestDto): accountId (string, required), userId (string, required), content (string), embeds (array of object), attachments (array of DiscordDirectMessageAttachmentDto), tts (boolean)
listDiscordScheduledEvents — GET /v1/discord/guilds/{guildId}/events
Parameters:
guildId(path, required)withUserCount(query)accountId(query, required)
createDiscordScheduledEvent — POST /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)
getDiscordScheduledEvent — GET /v1/discord/guilds/{guildId}/events/{eventId}
Parameters:
guildId(path, required)eventId(path, required)accountId(query, required)
updateDiscordScheduledEvent — PATCH /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)
deleteDiscordScheduledEvent — DELETE /v1/discord/guilds/{guildId}/events/{eventId}
Parameters:
guildId(path, required)eventId(path, required)accountId(query, required)
listDiscordGuildMembers — GET /v1/discord/guilds/{guildId}/members
Parameters:
guildId(path, required)after(query)limit(query)accountId(query, required)
getDiscordGuildMember — GET /v1/discord/guilds/{guildId}/members/{userId}
Parameters:
guildId(path, required)userId(path, required)accountId(query, required)
addDiscordMemberRole — PUT /v1/discord/guilds/{guildId}/members/{userId}/roles/{roleId}
Parameters:
guildId(path, required)userId(path, required)roleId(path, required)accountId(query, required)
removeDiscordMemberRole — DELETE /v1/discord/guilds/{guildId}/members/{userId}/roles/{roleId}
Parameters:
guildId(path, required)userId(path, required)roleId(path, required)accountId(query, required)
searchDiscordGuildMembers — GET /v1/discord/guilds/{guildId}/members/search
Parameters:
guildId(path, required)limit(query)query(query, required)accountId(query, required)
listDiscordGuildRoles — GET /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)
createDiscordGuildRole — POST /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)
editDiscordGuildRole — PATCH /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)
deleteDiscordGuildRole — DELETE /v1/discord/guilds/{guildId}/roles/{roleId}
Parameters:
guildId(path, required)roleId(path, required)accountId(query, required)
gmb-attributes
| Operation | Method | Path | Summary |
|---|---|---|---|
getGmbAttributeMetadata | GET | /v1/accounts/{accountId}/gmb-attribute-metadata | Get Google Business attribute metadata |
getGoogleBusinessAttributes | GET | /v1/accounts/{accountId}/gmb-attributes | Get Google Business attributes |
updateGoogleBusinessAttributes | PUT | /v1/accounts/{accountId}/gmb-attributes | Update Google Business attributes |
getGmbAttributeMetadata — GET /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)
getGoogleBusinessAttributes — GET /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)
updateGoogleBusinessAttributes — PUT /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
| Operation | Method | Path | Summary |
|---|---|---|---|
listGoogleBusinessMedia | GET | /v1/accounts/{accountId}/gmb-media | List media |
createGoogleBusinessMedia | POST | /v1/accounts/{accountId}/gmb-media | Upload photo |
deleteGoogleBusinessMedia | DELETE | /v1/accounts/{accountId}/gmb-media | Delete photo |
listGoogleBusinessMedia — GET /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)
createGoogleBusinessMedia — POST /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)
deleteGoogleBusinessMedia — DELETE /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
| Operation | Method | Path | Summary |
|---|---|---|---|
listGoogleBusinessPlaceActions | GET | /v1/accounts/{accountId}/gmb-place-actions | List Google Business place-action links |
createGoogleBusinessPlaceAction | POST | /v1/accounts/{accountId}/gmb-place-actions | Create a Google Business place-action link |
updateGoogleBusinessPlaceAction | PATCH | /v1/accounts/{accountId}/gmb-place-actions | Update a Google Business place-action link |
deleteGoogleBusinessPlaceAction | DELETE | /v1/accounts/{accountId}/gmb-place-actions | Delete a Google Business place-action link |
listGoogleBusinessPlaceActions — GET /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)
createGoogleBusinessPlaceAction — POST /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)
updateGoogleBusinessPlaceAction — PATCH /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)
deleteGoogleBusinessPlaceAction — DELETE /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
| Operation | Method | Path | Summary |
|---|---|---|---|
getGoogleBusinessReviews | GET | /v1/accounts/{accountId}/gmb-reviews | Get Google Business reviews |
getGoogleBusinessReview | GET | /v1/accounts/{accountId}/gmb-reviews/{reviewId} | Get a Google Business review |
replyToGoogleBusinessReview | POST | /v1/accounts/{accountId}/gmb-reviews/{reviewId}/reply | Post or update a Google Business review reply |
deleteGoogleBusinessReviewReply | DELETE | /v1/accounts/{accountId}/gmb-reviews/{reviewId}/reply | Delete a Google Business review reply |
batchGetGoogleBusinessReviews | POST | /v1/accounts/{accountId}/gmb-reviews/batch | Batch get Google Business reviews |
getGoogleBusinessReviews — GET /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)
getGoogleBusinessReview — GET /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)
replyToGoogleBusinessReview — POST /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)
deleteGoogleBusinessReviewReply — DELETE /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)
batchGetGoogleBusinessReviews — POST /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
| Operation | Method | Path | Summary |
|---|---|---|---|
getGoogleBusinessServices | GET | /v1/accounts/{accountId}/gmb-services | Get Google Business services |
updateGoogleBusinessServices | PUT | /v1/accounts/{accountId}/gmb-services | Replace Google Business services |
getGoogleBusinessServices — GET /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)
updateGoogleBusinessServices — PUT /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
| Operation | Method | Path | Summary |
|---|---|---|---|
getGoogleBusinessVerifications | GET | /v1/accounts/{accountId}/gmb-verifications | Get Google Business verification state and history |
startGoogleBusinessVerification | POST | /v1/accounts/{accountId}/gmb-verifications | Start a Google Business verification |
completeGoogleBusinessVerification | POST | /v1/accounts/{accountId}/gmb-verifications/{verificationId}/complete | Complete a Google Business verification |
fetchGoogleBusinessVerificationOptions | POST | /v1/accounts/{accountId}/gmb-verifications/options | Fetch eligible Google Business verification options |
getGoogleBusinessVerifications — GET /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)
startGoogleBusinessVerification — POST /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)
completeGoogleBusinessVerification — POST /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)
fetchGoogleBusinessVerificationOptions — POST /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
| Operation | Method | Path | Summary |
|---|---|---|---|
getHealth | GET | /v1/health | Service health probe |
getHealth — GET /v1/health
inbox
| Operation | Method | Path | Summary |
|---|---|---|---|
createInboxConversation | POST | /v1/inbox/conversations | Create conversation |
listInboxConversations | GET | /v1/inbox/conversations | List conversations |
getInboxConversation | GET | /v1/inbox/conversations/{conversationId} | Get conversation |
updateInboxConversation | PUT | /v1/inbox/conversations/{conversationId} | Update conversation status |
getInboxConversationMessages | GET | /v1/inbox/conversations/{conversationId}/messages | List messages |
sendInboxMessage | POST | /v1/inbox/conversations/{conversationId}/messages | Send a text-only message to an existing inbox conversation |
editInboxMessage | PATCH | /v1/inbox/conversations/{conversationId}/messages/{messageId} | Edit message |
deleteInboxMessage | DELETE | /v1/inbox/conversations/{conversationId}/messages/{messageId} | Delete message |
getMessageAttachment | GET | /v1/inbox/conversations/{conversationId}/messages/{messageId}/attachments/{index} | Resolve message attachment |
addMessageReaction | POST | /v1/inbox/conversations/{conversationId}/messages/{messageId}/reactions | Add reaction |
removeMessageReaction | DELETE | /v1/inbox/conversations/{conversationId}/messages/{messageId}/reactions | Remove reaction |
markConversationRead | POST | /v1/inbox/conversations/{conversationId}/read | Mark a conversation as read |
sendTypingIndicator | POST | /v1/inbox/conversations/{conversationId}/typing | Send typing indicator |
likePost | POST | /v1/inbox/posts/{postId}/like | Like a post |
unlikePost | DELETE | /v1/inbox/posts/{postId}/like | Unlike a post |
createInboxConversation — POST /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)
listInboxConversations — GET /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.
getInboxConversation — GET /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)
updateInboxConversation — PUT /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)
getInboxConversationMessages — GET /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)
sendInboxMessage — POST /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)
editInboxMessage — PATCH /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)
deleteInboxMessage — DELETE /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)
getMessageAttachment — GET /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)
addMessageReaction — POST /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)
removeMessageReaction — DELETE /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)
markConversationRead — POST /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)
sendTypingIndicator — POST /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)
likePost — POST /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)
unlikePost — DELETE /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
| Operation | Method | Path | Summary |
|---|---|---|---|
listInboxConversationAnalytics | GET | /v1/analytics/inbox/conversations | List conversation analytics |
getInboxConversationAnalytics | GET | /v1/analytics/inbox/conversations/{conversationId} | Get conversation analytics |
getInboxHeatmap | GET | /v1/analytics/inbox/heatmap | Get day by hour inbox heatmap |
getInboxResponseTime | GET | /v1/analytics/inbox/response-time | Get inbox response-time stats |
getInboxSourceBreakdown | GET | /v1/analytics/inbox/source-breakdown | Get inbox source breakdown |
getInboxTopAccounts | GET | /v1/analytics/inbox/top-accounts | Get top accounts by inbox volume |
getInboxVolume | GET | /v1/analytics/inbox/volume | Get inbox messaging volume |
listInboxConversationAnalytics — GET /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)
getInboxConversationAnalytics — GET /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)
getInboxHeatmap — GET /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)
getInboxResponseTime — GET /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)
getInboxSourceBreakdown — GET /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)
getInboxTopAccounts — GET /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)
getInboxVolume — GET /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
| Operation | Method | Path | Summary |
|---|---|---|---|
receiveTelegramIncoming | POST | /v1/incoming/telegram/{bindingId} | Receive a signed Telegram incoming event |
receiveTelegramIncoming — POST /v1/incoming/telegram/{bindingId}
Parameters:
bindingId(path, required)
| Operation | Method | Path | Summary |
|---|---|---|---|
searchInstagramAudio | GET | /v1/accounts/{accountId}/instagram/audio | Search Instagram audio |
getInstagramAudio | GET | /v1/accounts/{accountId}/instagram/audio/{audioId} | Get Instagram audio metadata |
listInstagramStories | GET | /v1/accounts/{accountId}/instagram/stories | List active Instagram stories |
getInstagramStoryInsights | GET | /v1/accounts/{accountId}/instagram/stories/{storyId}/insights | Get Instagram story insights |
searchInstagramAudio — GET /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)
getInstagramAudio — GET /v1/accounts/{accountId}/instagram/audio/{audioId}
Parameters:
accountId(path, required)audioId(path, required)
listInstagramStories — GET /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)
getInstagramStoryInsights — GET /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
| Operation | Method | Path | Summary |
|---|---|---|---|
createInviteToken | POST | /v1/invite/tokens | Create invite token |
createInviteToken — POST /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
| Operation | Method | Path | Summary |
|---|---|---|---|
listLogs | GET | /v1/logs | List provider publish attempts |
listLogs — GET /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
| Operation | Method | Path | Summary |
|---|---|---|---|
deleteMedia | DELETE | /v1/media/{id} | Delete a media object |
completeMedia | POST | /v1/media/{id}/complete | Complete a presigned media upload |
getMediaPresignedUrl | POST | /v1/media/presign | Create a presigned media upload |
uploadMedia | POST | /v1/media/upload | Upload a media file |
uploadMediaDirect | POST | /v1/media/upload-direct | Upload media file |
deleteMedia — DELETE /v1/media/{id}
Parameters:
id(path, required)
completeMedia — POST /v1/media/{id}/complete
Parameters:
id(path, required)
getMediaPresignedUrl — POST /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)
uploadMedia — POST /v1/media/upload
uploadMediaDirect — POST /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
| Operation | Method | Path | Summary |
|---|---|---|---|
listInboxMentions | GET | /v1/inbox/mentions | List mentions |
replyToMention | POST | /v1/inbox/mentions/reply | Reply to a mention |
listInboxMentions — GET /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)
replyToMention — POST /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
| Operation | Method | Path | Summary |
|---|---|---|---|
searchInboxConversations | GET | /v1/inbox/conversations/search | Search conversations |
searchInboxConversations — GET /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
| Operation | Method | Path | Summary |
|---|---|---|---|
listPhoneNumbers | GET | /v1/phone-numbers | List phone numbers |
getPhoneNumber | GET | /v1/phone-numbers/{id} | Get phone number |
releasePhoneNumber | DELETE | /v1/phone-numbers/{id} | Release phone number |
getPhoneNumberRemediation | GET | /v1/phone-numbers/{id}/remediate | Get declined requirements |
remediatePhoneNumber | POST | /v1/phone-numbers/{id}/remediate | Resubmit a declined number |
checkPhoneNumberAvailability | GET | /v1/phone-numbers/availability | Check country availability |
searchAvailablePhoneNumbers | GET | /v1/phone-numbers/available | Search available numbers |
listPhoneNumberCountries | GET | /v1/phone-numbers/countries | List offerable number countries |
submitPhoneNumberKyc | POST | /v1/phone-numbers/kyc | Submit KYC |
createPhoneNumberKycLink | POST | /v1/phone-numbers/kyc/share | Create a hosted KYC link |
uploadPhoneNumberKycDocument | POST | /v1/phone-numbers/kyc/upload-document | Upload a KYC document |
validatePhoneNumberKycAddress | POST | /v1/phone-numbers/kyc/validate-address | Pre-validate KYC address |
purchasePhoneNumber | POST | /v1/phone-numbers/purchase | Purchase phone number |
listPhoneNumbers — GET /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)
getPhoneNumber — GET /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)
releasePhoneNumber — DELETE /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)
getPhoneNumberRemediation — GET /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)
remediatePhoneNumber — POST /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)
checkPhoneNumberAvailability — GET /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)
searchAvailablePhoneNumbers — GET /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)
listPhoneNumberCountries — GET /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.
submitPhoneNumberKyc — POST /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)
createPhoneNumberKycLink — POST /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)
uploadPhoneNumberKycDocument — POST /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)
validatePhoneNumberKycAddress — POST /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)
purchasePhoneNumber — POST /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
| Operation | Method | Path | Summary |
|---|---|---|---|
listPlatforms | GET | /v1/platforms | List platform capabilities |
listPlatforms — GET /v1/platforms
Parameters:
accountId(query) — Optional owned active account whose provider-backed capability is requested.
posts
| Operation | Method | Path | Summary |
|---|---|---|---|
listPosts | GET | /v1/posts | List posts |
createPost | POST | /v1/posts | Create a post |
cancelPendingTargets | POST | /v1/posts/{id}/cancel-pending | Cancel pending targets of a post |
recyclePost | POST | /v1/posts/{id}/recycle | Republish a published post as a new post |
updatePost | PUT | /v1/posts/{postId} | Update a post |
patchPostLegacy | PATCH | /v1/posts/{postId} | Update a post (legacy PATCH) |
deletePost | DELETE | /v1/posts/{postId} | Delete a draft post |
getPost | GET | /v1/posts/{postId} | Retrieve a post |
editPost | POST | /v1/posts/{postId}/edit | Edit a published platform post |
retryPost | POST | /v1/posts/{postId}/retry | Retry failed targets of a post |
cancelHeldTarget | POST | /v1/posts/{postId}/targets/{targetId}/held-decisions/{decisionId}/cancel | Cancel a held post target |
resumeHeldTarget | POST | /v1/posts/{postId}/targets/{targetId}/held-decisions/{decisionId}/resume | Resume a held post target |
unpublishPost | POST | /v1/posts/{postId}/unpublish | Remove a published post from its platform |
updatePostMetadata | POST | /v1/posts/{postId}/update-metadata | Update published YouTube video metadata |
createPostsBulk | POST | /v1/posts/bulk | Create many posts from a CSV file |
bulkUploadPosts | POST | /v1/posts/bulk-upload | Bulk upload from CSV |
syncExternalPosts | POST | /v1/posts/sync-external | Declare posts published outside zhyper |
listPosts — GET /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.
createPost — POST /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)
cancelPendingTargets — POST /v1/posts/{id}/cancel-pending
Parameters:
id(path, required)
recyclePost — POST /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)
updatePost — PUT /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)
patchPostLegacy — PATCH /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)
deletePost — DELETE /v1/posts/{postId}
Parameters:
postId(path, required)
getPost — GET /v1/posts/{postId}
Parameters:
postId(path, required)
editPost — POST /v1/posts/{postId}/edit
Parameters:
postId(path, required)
Request body (EditPostDto): platform (enum: twitter, discord, facebook, reddit, mastodon, google_business, required), content (string, required)
retryPost — POST /v1/posts/{postId}/retry
Parameters:
postId(path, required)
Request body (RetryPostDto): targetIds (array of string)
cancelHeldTarget — POST /v1/posts/{postId}/targets/{targetId}/held-decisions/{decisionId}/cancel
Parameters:
postId(path, required)targetId(path, required)decisionId(path, required)
resumeHeldTarget — POST /v1/posts/{postId}/targets/{targetId}/held-decisions/{decisionId}/resume
Parameters:
postId(path, required)targetId(path, required)decisionId(path, required)
unpublishPost — POST /v1/posts/{postId}/unpublish
Parameters:
postId(path, required)
Request body (UnpublishPostDto): platform (enum: threads, facebook, twitter, linkedin, youtube, pinterest, reddit, bluesky, googlebusiness, telegram, required)
updatePostMetadata — POST /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)
createPostsBulk — POST /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.
bulkUploadPosts — POST /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.
syncExternalPosts — POST /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
| Operation | Method | Path | Summary |
|---|---|---|---|
listProfiles | GET | /v1/profiles | List profiles |
createProfile | POST | /v1/profiles | Create a profile |
getProfile | GET | /v1/profiles/{profileId} | Retrieve a profile |
updateProfile | PUT | /v1/profiles/{profileId} | Update a profile |
patchProfile | PATCH | /v1/profiles/{profileId} | Update a profile (legacy PATCH) |
deleteProfile | DELETE | /v1/profiles/{profileId} | Delete a profile |
listProfiles — GET /v1/profiles
Parameters:
sort(query)cursor(query)includeOverLimit(query)name(query)skip(query)limit(query)
createProfile — POST /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)
getProfile — GET /v1/profiles/{profileId}
Parameters:
profileId(path, required)
updateProfile — PUT /v1/profiles/{profileId}
Parameters:
profileId(path, required)
Request body (UpdateProfileDto): name (string), timezone (string), description (string), color (string), isDefault (boolean)
patchProfile — PATCH /v1/profiles/{profileId}
Parameters:
profileId(path, required)
Request body (UpdateProfileDto): name (string), timezone (string), description (string), color (string), isDefault (boolean)
deleteProfile — DELETE /v1/profiles/{profileId}
Parameters:
profileId(path, required)
queue
| Operation | Method | Path | Summary |
|---|---|---|---|
getNextQueueSlot | GET | /v1/queue/next-slot | Get next available slot |
previewQueue | GET | /v1/queue/preview | Preview upcoming slots |
listQueueSlots | GET | /v1/queue/slots | List schedules |
createQueueSlot | POST | /v1/queue/slots | Create schedule |
updateQueueSlot | PUT | /v1/queue/slots | Update schedule |
deleteQueueSlot | DELETE | /v1/queue/slots | Delete schedule |
getNextQueueSlot — GET /v1/queue/next-slot
Parameters:
queueId(query)profileId(query, required)
previewQueue — GET /v1/queue/preview
Parameters:
count(query)queueId(query)profileId(query, required)
listQueueSlots — GET /v1/queue/slots
Parameters:
all(query)queueId(query)profileId(query, required)
createQueueSlot — POST /v1/queue/slots
Request body (CreateQueueScheduleRequestDto): profileId (string, required), name (string, required), timezone (string, required), slots (array of QueueScheduleSlotDto, required), active (boolean)
updateQueueSlot — PUT /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)
deleteQueueSlot — DELETE /v1/queue/slots
Parameters:
queueId(query)profileId(query, required)
queues
| Operation | Method | Path | Summary |
|---|---|---|---|
listQueues | GET | /v1/queues | List queues |
createQueue | POST | /v1/queues | Create a queue |
getQueue | GET | /v1/queues/{id} | Retrieve a queue |
updateQueue | PATCH | /v1/queues/{id} | Update a queue |
deleteQueue | DELETE | /v1/queues/{id} | Delete a queue |
listQueues — GET /v1/queues
Parameters:
profileId(query)sort(query)cursor(query)limit(query)
createQueue — POST /v1/queues
Request body (CreateQueueDto): profileId (string, required), name (string, required), slots (array of QueueSlotDto, required), timezone (string)
getQueue — GET /v1/queues/{id}
Parameters:
id(path, required)
updateQueue — PATCH /v1/queues/{id}
Parameters:
id(path, required)
Request body (UpdateQueueDto): name (string), slots (array of QueueSlotDto), timezone (string)
deleteQueue — DELETE /v1/queues/{id}
Parameters:
id(path, required)
| Operation | Method | Path | Summary |
|---|---|---|---|
getRedditFeed | GET | /v1/reddit/feed | Read a connected Reddit account feed |
searchReddit | GET | /v1/reddit/search | Search Reddit with a connected account |
getRedditFeed — GET /v1/reddit/feed
Parameters:
t(query)after(query)limit(query)sort(query)subreddit(query)accountId(query, required)
searchReddit — GET /v1/reddit/search
Parameters:
after(query)limit(query)sort(query)restrict_sr(query)subreddit(query)q(query, required)accountId(query, required)
reviews
| Operation | Method | Path | Summary |
|---|---|---|---|
listInboxReviews | GET | /v1/inbox/reviews | List reviews |
replyToInboxReview | POST | /v1/inbox/reviews/{reviewId}/reply | Reply to a Google Business review |
deleteInboxReviewReply | DELETE | /v1/inbox/reviews/{reviewId}/reply | Delete a Google Business review reply |
listInboxReviews — GET /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.
replyToInboxReview — POST /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)
deleteInboxReviewReply — DELETE /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
| Operation | Method | Path | Summary |
|---|---|---|---|
listSequences | GET | /v1/sequences | List sequences |
createSequence | POST | /v1/sequences | Create a sequence |
getSequence | GET | /v1/sequences/{sequenceId} | Retrieve a sequence with its steps |
updateSequence | PATCH | /v1/sequences/{sequenceId} | Update a sequence |
deleteSequence | DELETE | /v1/sequences/{sequenceId} | Delete a sequence |
activateSequence | POST | /v1/sequences/{sequenceId}/activate | Activate a sequence |
enrollContacts | POST | /v1/sequences/{sequenceId}/enroll | Enroll contacts into a sequence |
unenrollContact | DELETE | /v1/sequences/{sequenceId}/enroll/{contactId} | Unenroll a contact from a sequence |
listSequenceEnrollments | GET | /v1/sequences/{sequenceId}/enrollments | List a sequence's enrollments |
pauseSequence | POST | /v1/sequences/{sequenceId}/pause | Pause a sequence |
listSequences — GET /v1/sequences
Parameters:
skip(query)limit(query)status(query)profileId(query) — If omitted, the sequences of ALL profiles the key can reach are returned.
createSequence — POST /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)
getSequence — GET /v1/sequences/{sequenceId}
Parameters:
sequenceId(path, required)
updateSequence — PATCH /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)
deleteSequence — DELETE /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)
activateSequence — POST /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)
enrollContacts — POST /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)
unenrollContact — DELETE /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)
listSequenceEnrollments — GET /v1/sequences/{sequenceId}/enrollments
Parameters:
sequenceId(path, required)skip(query)limit(query)status(query)
pauseSequence — POST /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
| Operation | Method | Path | Summary |
|---|---|---|---|
listSmsBrands | GET | /v1/sms/brands | List 10DLC brands |
createSmsBrand | POST | /v1/sms/brands | Register a 10DLC brand |
getSmsBrand | GET | /v1/sms/brands/{id} | Get one 10DLC brand |
listSmsCampaigns | GET | /v1/sms/campaigns | List 10DLC campaigns of one brand |
createSmsCampaign | POST | /v1/sms/campaigns | Register a 10DLC campaign |
getSmsCampaign | GET | /v1/sms/campaigns/{id} | Get one 10DLC campaign |
sendSms | POST | /v1/sms/messages | Send an SMS message |
listSmsMessages | GET | /v1/sms/messages | List SMS messages |
getSmsMessage | GET | /v1/sms/messages/{id} | Get one SMS message |
listSmsSenderIds | GET | /v1/sms/sender-ids | List alphanumeric sender IDs |
createSmsSenderId | POST | /v1/sms/sender-ids | Register an alphanumeric sender ID |
getSmsSenderId | GET | /v1/sms/sender-ids/{id} | Get one alphanumeric sender ID |
deleteSmsSenderId | DELETE | /v1/sms/sender-ids/{id} | Delete an alphanumeric sender ID |
listSmsBrands — GET /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)
createSmsBrand — POST /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)
getSmsBrand — GET /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)
listSmsCampaigns — GET /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)
createSmsCampaign — POST /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)
getSmsCampaign — GET /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)
sendSms — POST /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)
listSmsMessages — GET /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)
getSmsMessage — GET /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)
listSmsSenderIds — GET /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)
createSmsSenderId — POST /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)
getSmsSenderId — GET /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)
deleteSmsSenderId — DELETE /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
| Operation | Method | Path | Summary |
|---|---|---|---|
getStatus | GET | /v1/status | Public platform status page |
getStatus — GET /v1/status
team
| Operation | Method | Path | Summary |
|---|---|---|---|
createInvitation | POST | /v1/team/invitations | Invite someone to the team |
listInvitations | GET | /v1/team/invitations | List open invitations |
revokeInvitation | DELETE | /v1/team/invitations/{id} | Revoke an open invitation |
acceptInvitation | POST | /v1/team/invitations/accept | Accept a team invitation |
listTeamMembers | GET | /v1/team/members | List team members and their roles |
updateTeamMemberRole | PATCH | /v1/team/members/{userId} | Change a member's role |
removeTeamMember | DELETE | /v1/team/members/{userId} | Remove a member from the team |
createInvitation — POST /v1/team/invitations
Request body (CreateInvitationDto): email (string, required), role (enum: owner, admin, billing_admin, member, read_only, required)
listInvitations — GET /v1/team/invitations
Parameters:
sort(query)cursor(query)limit(query)
revokeInvitation — DELETE /v1/team/invitations/{id}
Parameters:
id(path, required)
acceptInvitation — POST /v1/team/invitations/accept
Request body (AcceptInvitationDto): token (string, required), email (string, required), password (string), name (string)
listTeamMembers — GET /v1/team/members
Parameters:
sort(query)cursor(query)limit(query)
updateTeamMemberRole — PATCH /v1/team/members/{userId}
Parameters:
userId(path, required)
Request body (UpdateMemberRoleDto): role (enum: owner, admin, billing_admin, member, read_only, required)
removeTeamMember — DELETE /v1/team/members/{userId}
Parameters:
userId(path, required)
twitter-engagement
| Operation | Method | Path | Summary |
|---|---|---|---|
bookmarkPost | POST | /v1/twitter/bookmark | Bookmark a post |
removeBookmark | DELETE | /v1/twitter/bookmark | Remove a bookmark |
followUser | POST | /v1/twitter/follow | Follow a user |
unfollowUser | DELETE | /v1/twitter/follow | Unfollow a user |
retweetPost | POST | /v1/twitter/retweet | Retweet a post |
undoRetweet | DELETE | /v1/twitter/retweet | Undo a retweet |
searchTweets | GET | /v1/twitter/search | Search recent tweets |
getTweet | GET | /v1/twitter/tweet | Look up a tweet |
bookmarkPost — POST /v1/twitter/bookmark
Bookmarks an X Post by ID using the selected connected account.
Request body (TwitterBookmarkInputDto): accountId (string, required), tweetId (string, required)
removeBookmark — DELETE /v1/twitter/bookmark
Removes the selected connected account bookmark from an X Post.
Parameters:
tweetId(query, required)accountId(query, required)
followUser — POST /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)
unfollowUser — DELETE /v1/twitter/follow
Unfollows an X user with the selected connected account.
Parameters:
targetUserId(query, required)accountId(query, required)
retweetPost — POST /v1/twitter/retweet
Retweets an X Post by ID using the selected connected account.
Request body (TwitterRetweetInputDto): accountId (string, required), tweetId (string, required)
undoRetweet — DELETE /v1/twitter/retweet
Removes the selected connected account retweet from an X Post.
Parameters:
tweetId(query, required)accountId(query, required)
searchTweets — GET /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)
getTweet — GET /v1/twitter/tweet
Resolves one numeric X Post id or canonical Post URL.
Parameters:
id(query, required)accountId(query, required)
usage
| Operation | Method | Path | Summary |
|---|---|---|---|
getUsage | GET | /v1/usage | Quota usage snapshot for the current UTC day |
getUsageStats | GET | /v1/usage-stats | Get quota usage snapshot |
getUsage — GET /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.
getUsageStats — GET /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
| Operation | Method | Path | Summary |
|---|---|---|---|
listUsers | GET | /v1/users | List users |
getUser | GET | /v1/users/{userId} | Get user |
listUsers — GET /v1/users
Returns all users in the workspace including roles and profile access. Also returns the currentUserId of the caller.
getUser — GET /v1/users/{userId}
Returns a single user's details by ID, including name, email, and role.
Parameters:
userId(path, required)
validate
| Operation | Method | Path | Summary |
|---|---|---|---|
validateMedia | POST | /v1/tools/validate/media | Validate media URL |
validatePost | POST | /v1/tools/validate/post | Validate a post against platform rules |
validatePostLength | POST | /v1/tools/validate/post-length | Validate weighted post length across platforms |
validateSubreddit | GET | /v1/tools/validate/subreddit | Check subreddit existence |
validatePostLegacy | POST | /v1/validate/post | Validate a post against platform rules (legacy route) |
validateMedia — POST /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)
validatePost — POST /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)
validatePostLength — POST /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)
validateSubreddit — GET /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.
validatePostLegacy — POST /v1/validate/post
Deprecated transport alias for POST /v1/tools/validate/post.
webhooks
| Operation | Method | Path | Summary |
|---|---|---|---|
createWebhookEndpoint | POST | /v1/webhooks | Create a webhook endpoint |
listWebhookEndpoints | GET | /v1/webhooks | List webhook endpoints |
getWebhookEndpoint | GET | /v1/webhooks/{id} | Retrieve a webhook endpoint |
deleteWebhookEndpoint | DELETE | /v1/webhooks/{id} | Delete a webhook endpoint |
listWebhookDeliveries | GET | /v1/webhooks/{id}/logs | List webhook delivery attempts |
sendWebhookTest | POST | /v1/webhooks/{id}/test | Send a test webhook delivery |
getWebhookLogs | GET | /v1/webhooks/logs | List webhook delivery logs |
getWebhookSettings | GET | /v1/webhooks/settings | List webhook settings |
createWebhookSettings | POST | /v1/webhooks/settings | Create webhook settings |
updateWebhookSettings | PUT | /v1/webhooks/settings | Update webhook settings |
deleteWebhookSettings | DELETE | /v1/webhooks/settings | Delete webhook settings |
testWebhook | POST | /v1/webhooks/test | Queue a test webhook delivery |
createWebhookEndpoint — POST /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)
listWebhookEndpoints — GET /v1/webhooks
Parameters:
sort(query)cursor(query)limit(query)
getWebhookEndpoint — GET /v1/webhooks/{id}
Parameters:
id(path, required)
deleteWebhookEndpoint — DELETE /v1/webhooks/{id}
Parameters:
id(path, required)
listWebhookDeliveries — GET /v1/webhooks/{id}/logs
Parameters:
id(path, required)sort(query)cursor(query)limit(query)
sendWebhookTest — POST /v1/webhooks/{id}/test
Parameters:
id(path, required)
getWebhookLogs — GET /v1/webhooks/logs
Parameters:
eventId(query) — Filter by stable webhook event IDwebhookId(query) — Filter by webhook configuration IDstatus(query) — Filter by delivery outcomeskip(query) — Number of logs to skiplimit(query) — Maximum number of logs to return
getWebhookSettings — GET /v1/webhooks/settings
createWebhookSettings — POST /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)
updateWebhookSettings — PUT /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)
deleteWebhookSettings — DELETE /v1/webhooks/settings
Parameters:
id(query, required)
testWebhook — POST /v1/webhooks/test
Zhyper delivers asynchronously; a successful response means queued, not delivered.
Request body (CanonicalWebhookTestDto): webhookId (string, required)
| Operation | Method | Path | Summary |
|---|---|---|---|
registerWhatsAppNumber | POST | /v1/accounts/{accountId}/whatsapp/register | Register a connected WhatsApp number on the Cloud API |
listWhatsAppAccountEvents | GET | /v1/whatsapp/account-events | List Meta-originated WhatsApp account events |
getWhatsAppBlockedUsers | GET | /v1/whatsapp/block-users | List blocked users |
blockWhatsAppUsers | POST | /v1/whatsapp/block-users | Block users |
unblockWhatsAppUsers | DELETE | /v1/whatsapp/block-users | Unblock users |
getWhatsAppBlockStatus | GET | /v1/whatsapp/block-users/status | Check if a user is blocked |
getWhatsAppBusinessProfile | GET | /v1/whatsapp/business-profile | Get a WhatsApp business profile |
updateWhatsAppBusinessProfile | POST | /v1/whatsapp/business-profile | Update a WhatsApp business profile |
getWhatsAppDisplayName | GET | /v1/whatsapp/business-profile/display-name | Get a WhatsApp display name |
updateWhatsAppDisplayName | POST | /v1/whatsapp/business-profile/display-name | Submit a WhatsApp display-name change |
uploadWhatsAppProfilePhoto | POST | /v1/whatsapp/business-profile/photo | Upload a WhatsApp business profile photo |
getWhatsappBusinessUsername | GET | /v1/whatsapp/business-profile/username | Get a WhatsApp business username |
setWhatsappBusinessUsername | POST | /v1/whatsapp/business-profile/username | Set a WhatsApp business username |
deleteWhatsappBusinessUsername | DELETE | /v1/whatsapp/business-profile/username | Delete a WhatsApp business username |
getWhatsappBusinessUsernameSuggestions | GET | /v1/whatsapp/business-profile/username/suggestions | Get WhatsApp username suggestions |
listWhatsAppConversions | GET | /v1/whatsapp/conversions | List conversion events |
sendWhatsAppConversion | POST | /v1/whatsapp/conversions | Send WhatsApp conversion event |
getWhatsAppDataset | GET | /v1/whatsapp/dataset | Get CTWA conversions dataset |
createWhatsAppDataset | POST | /v1/whatsapp/dataset | Provision CTWA dataset |
getWhatsAppMedia | GET | /v1/whatsapp/media/{mediaId} | Download WhatsApp media |
getWhatsAppLibraryTemplate | GET | /v1/whatsapp/template-library | Look up a library template |
createWhatsAppTemplate | POST | /v1/whatsapp/templates | Create a WhatsApp message template |
getWhatsAppTemplates | GET | /v1/whatsapp/templates | List WhatsApp message templates |
getWhatsAppTemplate | GET | /v1/whatsapp/templates/{templateName} | Get one exact WhatsApp template language variant |
updateWhatsAppTemplate | PATCH | /v1/whatsapp/templates/{templateName} | Update one exact WhatsApp template language variant |
deleteWhatsAppTemplate | DELETE | /v1/whatsapp/templates/{templateName} | Delete one WhatsApp template language variant |
getWhatsAppTemplateById | GET | /v1/whatsapp/templates/id/{templateId} | Get one WhatsApp template variant by its Meta id |
updateWhatsAppTemplateById | PATCH | /v1/whatsapp/templates/id/{templateId} | Update one WhatsApp template variant by its Meta id |
deleteWhatsAppTemplateById | DELETE | /v1/whatsapp/templates/id/{templateId} | Delete one WhatsApp template variant by its Meta id |
listWhatsAppGroupChats | GET | /v1/whatsapp/wa-groups | List active groups |
createWhatsAppGroupChat | POST | /v1/whatsapp/wa-groups | Create group |
getWhatsAppGroupChat | GET | /v1/whatsapp/wa-groups/{groupId} | Get group info |
updateWhatsAppGroupChat | POST | /v1/whatsapp/wa-groups/{groupId} | Update group settings |
deleteWhatsAppGroupChat | DELETE | /v1/whatsapp/wa-groups/{groupId} | Delete group |
createWhatsAppGroupInviteLink | POST | /v1/whatsapp/wa-groups/{groupId}/invite-link | Create invite link |
listWhatsAppGroupJoinRequests | GET | /v1/whatsapp/wa-groups/{groupId}/join-requests | List join requests |
approveWhatsAppGroupJoinRequests | POST | /v1/whatsapp/wa-groups/{groupId}/join-requests | Approve join requests |
rejectWhatsAppGroupJoinRequests | DELETE | /v1/whatsapp/wa-groups/{groupId}/join-requests | Reject join requests |
addWhatsAppGroupParticipants | POST | /v1/whatsapp/wa-groups/{groupId}/participants | Add participants |
removeWhatsAppGroupParticipants | DELETE | /v1/whatsapp/wa-groups/{groupId}/participants | Remove participants |
registerWhatsAppNumber — POST /v1/accounts/{accountId}/whatsapp/register
Parameters:
accountId(path, required)
Request body (RegisterWhatsAppNumberRequestDto): pin (string)
listWhatsAppAccountEvents — GET /v1/whatsapp/account-events
Parameters:
limit(query)accountId(query, required)
getWhatsAppBlockedUsers — GET /v1/whatsapp/block-users
Parameters:
after(query)limit(query)accountId(query, required)
blockWhatsAppUsers — POST /v1/whatsapp/block-users
Request body (WhatsAppBlockUsersRequestDto): accountId (string, required), users (array of string, required)
unblockWhatsAppUsers — DELETE /v1/whatsapp/block-users
Request body (WhatsAppBlockUsersRequestDto): accountId (string, required), users (array of string, required)
getWhatsAppBlockStatus — GET /v1/whatsapp/block-users/status
Parameters:
user(query, required)accountId(query, required)
getWhatsAppBusinessProfile — GET /v1/whatsapp/business-profile
Parameters:
accountId(query, required)
updateWhatsAppBusinessProfile — POST /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)
getWhatsAppDisplayName — GET /v1/whatsapp/business-profile/display-name
Parameters:
accountId(query, required)
updateWhatsAppDisplayName — POST /v1/whatsapp/business-profile/display-name
Request body (UpdateWhatsAppDisplayNameDto): accountId (string, required), displayName (string, required)
uploadWhatsAppProfilePhoto — POST /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)
getWhatsappBusinessUsername — GET /v1/whatsapp/business-profile/username
Parameters:
accountId(query, required)
setWhatsappBusinessUsername — POST /v1/whatsapp/business-profile/username
Request body (SetWhatsAppUsernameDto): accountId (string, required), username (string, required), transferAction (enum: none, force_transfer)
deleteWhatsappBusinessUsername — DELETE /v1/whatsapp/business-profile/username
Request body (DeleteWhatsAppUsernameDto): accountId (string, required)
getWhatsappBusinessUsernameSuggestions — GET /v1/whatsapp/business-profile/username/suggestions
Parameters:
accountId(query, required)
listWhatsAppConversions — GET /v1/whatsapp/conversions
Parameters:
limit(query)accountId(query, required)
sendWhatsAppConversion — POST /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)
getWhatsAppDataset — GET /v1/whatsapp/dataset
Parameters:
accountId(query, required)
createWhatsAppDataset — POST /v1/whatsapp/dataset
Request body (WhatsAppDatasetRequestDto): accountId (string, required)
getWhatsAppMedia — GET /v1/whatsapp/media/{mediaId}
Parameters:
mediaId(path, required)accountId(query, required)
getWhatsAppLibraryTemplate — GET /v1/whatsapp/template-library
Parameters:
language(query) — Preferred language variant. Falls back to the first available variant.name(query, required)accountId(query, required)
createWhatsAppTemplate — POST /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)
getWhatsAppTemplates — GET /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)
getWhatsAppTemplate — GET /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)
updateWhatsAppTemplate — PATCH /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)
deleteWhatsAppTemplate — DELETE /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)
getWhatsAppTemplateById — GET /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)
updateWhatsAppTemplateById — PATCH /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)
deleteWhatsAppTemplateById — DELETE /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)
listWhatsAppGroupChats — GET /v1/whatsapp/wa-groups
Parameters:
after(query)limit(query)accountId(query, required)
createWhatsAppGroupChat — POST /v1/whatsapp/wa-groups
Request body (CreateWhatsAppGroupDto): accountId (string, required), subject (string, required), description (string), joinApprovalMode (enum: approval_required, auto_approve)
getWhatsAppGroupChat — GET /v1/whatsapp/wa-groups/{groupId}
Parameters:
groupId(path, required)accountId(query, required)
updateWhatsAppGroupChat — POST /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)
deleteWhatsAppGroupChat — DELETE /v1/whatsapp/wa-groups/{groupId}
Parameters:
groupId(path, required)accountId(query, required)
createWhatsAppGroupInviteLink — POST /v1/whatsapp/wa-groups/{groupId}/invite-link
Parameters:
groupId(path, required)accountId(query, required)
listWhatsAppGroupJoinRequests — GET /v1/whatsapp/wa-groups/{groupId}/join-requests
Parameters:
groupId(path, required)accountId(query, required)
approveWhatsAppGroupJoinRequests — POST /v1/whatsapp/wa-groups/{groupId}/join-requests
Parameters:
groupId(path, required)accountId(query, required)
Request body (WhatsAppGroupParticipantsDto): phoneNumbers (array of string, required)
rejectWhatsAppGroupJoinRequests — DELETE /v1/whatsapp/wa-groups/{groupId}/join-requests
Parameters:
groupId(path, required)accountId(query, required)
Request body (WhatsAppGroupParticipantsDto): phoneNumbers (array of string, required)
addWhatsAppGroupParticipants — POST /v1/whatsapp/wa-groups/{groupId}/participants
Parameters:
groupId(path, required)accountId(query, required)
Request body (WhatsAppGroupParticipantsDto): phoneNumbers (array of string, required)
removeWhatsAppGroupParticipants — DELETE /v1/whatsapp/wa-groups/{groupId}/participants
Parameters:
groupId(path, required)accountId(query, required)
Request body (WhatsAppGroupParticipantsDto): phoneNumbers (array of string, required)
whatsapp-calling
| Operation | Method | Path | Summary |
|---|---|---|---|
startWhatsAppCallerIdVerification | POST | /v1/phone-numbers/{id}/whatsapp/caller-id-verification | Start caller-ID verification for a customer-brought number |
verifyWhatsAppCallerId | POST | /v1/phone-numbers/{id}/whatsapp/caller-id-verification/verify | Confirm the caller-ID verification code |
getWhatsAppCalling | GET | /v1/phone-numbers/{id}/whatsapp/calling | Get calling config for a number |
enableWhatsAppCalling | POST | /v1/phone-numbers/{id}/whatsapp/calling | Enable calling on a number |
updateWhatsAppCalling | PATCH | /v1/phone-numbers/{id}/whatsapp/calling | Update calling config |
disableWhatsAppCalling | DELETE | /v1/phone-numbers/{id}/whatsapp/calling | Disable calling on a number |
getWhatsAppCallPermissions | GET | /v1/whatsapp/call-permissions | Check call permission |
getWhatsAppCallingConfig | GET | /v1/whatsapp/calling | Get calling config for an account |
initiateWhatsAppCall | POST | /v1/whatsapp/calls | Initiate outbound call |
listWhatsAppCalls | GET | /v1/whatsapp/calls | List call history for an account |
getWhatsAppCall | GET | /v1/whatsapp/calls/{id} | Get a single call |
getWhatsAppCallRecording | GET | /v1/whatsapp/calls/{id}/recording | Get a call recording |
getWhatsAppCallEstimate | GET | /v1/whatsapp/calls/estimate | Estimate per-minute cost |
enableWhatsAppCallingLegacy | POST | /v1/whatsapp/phone-numbers/{id}/calling | Enable calling on a number |
updateWhatsAppCallingLegacy | PATCH | /v1/whatsapp/phone-numbers/{id}/calling | Update calling config |
disableWhatsAppCallingLegacy | DELETE | /v1/whatsapp/phone-numbers/{id}/calling | Disable calling on a number |
startWhatsAppCallerIdVerification — POST /v1/phone-numbers/{id}/whatsapp/caller-id-verification
Parameters:
id(path, required)
Request body (WhatsAppCallerIdVerificationStartRequestDto): method (enum: sms, call)
verifyWhatsAppCallerId — POST /v1/phone-numbers/{id}/whatsapp/caller-id-verification/verify
Parameters:
id(path, required)
Request body (WhatsAppCallerIdVerificationConfirmRequestDto): code (string, required)
getWhatsAppCalling — GET /v1/phone-numbers/{id}/whatsapp/calling
Parameters:
id(path, required)
enableWhatsAppCalling — POST /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)
updateWhatsAppCalling — PATCH /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)
disableWhatsAppCalling — DELETE /v1/phone-numbers/{id}/whatsapp/calling
Parameters:
id(path, required)accountId(query, required)
getWhatsAppCallPermissions — GET /v1/whatsapp/call-permissions
Parameters:
to(query, required)accountId(query, required)
getWhatsAppCallingConfig — GET /v1/whatsapp/calling
Parameters:
accountId(query, required)
initiateWhatsAppCall — POST /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)
listWhatsAppCalls — GET /v1/whatsapp/calls
Parameters:
limit(query)before(query)until(query)since(query)direction(query)status(query)accountId(query, required)
getWhatsAppCall — GET /v1/whatsapp/calls/{id}
Parameters:
id(path, required)accountId(query, required)
getWhatsAppCallRecording — GET /v1/whatsapp/calls/{id}/recording
Parameters:
id(path, required)as(query)accountId(query, required)
getWhatsAppCallEstimate — GET /v1/whatsapp/calls/estimate
Parameters:
recording(query)minutes(query)to(query, required)accountId(query, required)
enableWhatsAppCallingLegacy — POST /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)
updateWhatsAppCallingLegacy — PATCH /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)
disableWhatsAppCallingLegacy — DELETE /v1/whatsapp/phone-numbers/{id}/calling
Parameters:
id(path, required)accountId(query, required)
whatsapp-flows
| Operation | Method | Path | Summary |
|---|---|---|---|
listWhatsAppFlowResponses | GET | /v1/whatsapp/flow-responses | List flow responses |
listWhatsAppFlows | GET | /v1/whatsapp/flows | List flows |
createWhatsAppFlow | POST | /v1/whatsapp/flows | Create flow |
getWhatsAppFlow | GET | /v1/whatsapp/flows/{flowId} | Get flow |
updateWhatsAppFlow | PATCH | /v1/whatsapp/flows/{flowId} | Update flow |
deleteWhatsAppFlow | DELETE | /v1/whatsapp/flows/{flowId} | Delete flow |
deprecateWhatsAppFlow | POST | /v1/whatsapp/flows/{flowId}/deprecate | Deprecate flow |
getWhatsAppFlowJson | GET | /v1/whatsapp/flows/{flowId}/json | Get flow JSON asset |
uploadWhatsAppFlowJson | PUT | /v1/whatsapp/flows/{flowId}/json | Upload flow JSON |
getWhatsAppFlowPreview | GET | /v1/whatsapp/flows/{flowId}/preview | Get flow preview URL |
publishWhatsAppFlow | POST | /v1/whatsapp/flows/{flowId}/publish | Publish flow |
listWhatsAppFlowVersions | GET | /v1/whatsapp/flows/{flowId}/versions | List flow versions |
sendWhatsAppFlowMessage | POST | /v1/whatsapp/flows/send | Send flow message |
listWhatsAppFlowResponses — GET /v1/whatsapp/flow-responses
Parameters:
limit(query)flowId(query)accountId(query, required)
listWhatsAppFlows — GET /v1/whatsapp/flows
Parameters:
accountId(query, required)
createWhatsAppFlow — POST /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)
getWhatsAppFlow — GET /v1/whatsapp/flows/{flowId}
Parameters:
flowId(path, required)fields(query)accountId(query, required)
updateWhatsAppFlow — PATCH /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)
deleteWhatsAppFlow — DELETE /v1/whatsapp/flows/{flowId}
Parameters:
flowId(path, required)accountId(query, required)
deprecateWhatsAppFlow — POST /v1/whatsapp/flows/{flowId}/deprecate
Parameters:
flowId(path, required)
Request body (WhatsAppFlowAccountDto): accountId (string, required)
getWhatsAppFlowJson — GET /v1/whatsapp/flows/{flowId}/json
Parameters:
flowId(path, required)accountId(query, required)
uploadWhatsAppFlowJson — PUT /v1/whatsapp/flows/{flowId}/json
Parameters:
flowId(path, required)
Request body (UploadWhatsAppFlowJsonDto): accountId (string, required), flow_json (object | string, required)
getWhatsAppFlowPreview — GET /v1/whatsapp/flows/{flowId}/preview
Parameters:
flowId(path, required)invalidate(query)accountId(query, required)
publishWhatsAppFlow — POST /v1/whatsapp/flows/{flowId}/publish
Parameters:
flowId(path, required)
Request body (WhatsAppFlowAccountDto): accountId (string, required)
listWhatsAppFlowVersions — GET /v1/whatsapp/flows/{flowId}/versions
Parameters:
flowId(path, required)accountId(query, required)
sendWhatsAppFlowMessage — POST /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
| Operation | Method | Path | Summary |
|---|---|---|---|
getWhatsAppNumberInfo | GET | /v1/whatsapp/number-info | Get live WhatsApp phone and WABA information |
getWhatsAppPhoneNumbers | GET | /v1/whatsapp/phone-numbers | List phone numbers |
moveWhatsAppNumberToProfile | PATCH | /v1/whatsapp/phone-numbers/{id}/profile | Move a number to another profile |
getWhatsAppNumberRemediation | GET | /v1/whatsapp/phone-numbers/{id}/remediate | Get declined requirements |
remediateWhatsAppNumber | POST | /v1/whatsapp/phone-numbers/{id}/remediate | Resubmit a declined number |
getWhatsAppPhoneNumber | GET | /v1/whatsapp/phone-numbers/{phoneNumberId} | Get phone number |
releaseWhatsAppPhoneNumber | DELETE | /v1/whatsapp/phone-numbers/{phoneNumberId} | Release phone number |
checkWhatsAppNumberAvailability | GET | /v1/whatsapp/phone-numbers/availability | Check country availability |
searchAvailableWhatsAppNumbers | GET | /v1/whatsapp/phone-numbers/available | Search available numbers |
listWhatsAppNumberCountries | GET | /v1/whatsapp/phone-numbers/countries | List offerable number countries |
getWhatsAppNumberKycForm | GET | /v1/whatsapp/phone-numbers/kyc | Get KYC form spec |
submitWhatsAppNumberKyc | POST | /v1/whatsapp/phone-numbers/kyc | Submit KYC |
createWhatsAppNumberKycLink | POST | /v1/whatsapp/phone-numbers/kyc/share | Create a hosted KYC link |
uploadWhatsAppNumberKycDocument | POST | /v1/whatsapp/phone-numbers/kyc/upload-document | Upload a KYC document |
validateWhatsAppNumberKycAddress | POST | /v1/whatsapp/phone-numbers/kyc/validate-address | Pre-validate KYC address |
purchaseWhatsAppPhoneNumber | POST | /v1/whatsapp/phone-numbers/purchase | Purchase phone number |
getWhatsAppNumberInfo — GET /v1/whatsapp/number-info
Parameters:
accountId(query, required)
getWhatsAppPhoneNumbers — GET /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)
moveWhatsAppNumberToProfile — PATCH /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)
getWhatsAppNumberRemediation — GET /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)
remediateWhatsAppNumber — POST /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)
getWhatsAppPhoneNumber — GET /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)
releaseWhatsAppPhoneNumber — DELETE /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)
checkWhatsAppNumberAvailability — GET /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)
searchAvailableWhatsAppNumbers — GET /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)
listWhatsAppNumberCountries — GET /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.
getWhatsAppNumberKycForm — GET /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)
submitWhatsAppNumberKyc — POST /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)
createWhatsAppNumberKycLink — POST /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)
uploadWhatsAppNumberKycDocument — POST /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)
validateWhatsAppNumberKycAddress — POST /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)
purchaseWhatsAppPhoneNumber — POST /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
| Operation | Method | Path | Summary |
|---|---|---|---|
listWhatsAppSandboxSessions | GET | /v1/whatsapp/sandbox/sessions | List your sandbox sessions |
createWhatsAppSandboxSession | POST | /v1/whatsapp/sandbox/sessions | Start a sandbox activation |
deleteWhatsAppSandboxSession | DELETE | /v1/whatsapp/sandbox/sessions/{sessionId} | Revoke a sandbox session |
listWhatsAppSandboxSessions — GET /v1/whatsapp/sandbox/sessions
createWhatsAppSandboxSession — POST /v1/whatsapp/sandbox/sessions
Request body (CreateWhatsAppSandboxSessionRequestDto): phone (string, required)
deleteWhatsAppSandboxSession — DELETE /v1/whatsapp/sandbox/sessions/{sessionId}
Parameters:
sessionId(path, required)
workflows
| Operation | Method | Path | Summary |
|---|---|---|---|
listWorkflows | GET | /v1/workflows | List workflows |
createWorkflow | POST | /v1/workflows | Create a workflow |
getWorkflow | GET | /v1/workflows/{workflowId} | Retrieve a workflow with its graph |
updateWorkflow | PATCH | /v1/workflows/{workflowId} | Update a workflow |
deleteWorkflow | DELETE | /v1/workflows/{workflowId} | Delete a workflow |
activateWorkflow | POST | /v1/workflows/{workflowId}/activate | Activate a workflow |
duplicateWorkflow | POST | /v1/workflows/{workflowId}/duplicate | Duplicate a workflow |
listWorkflowExecutions | GET | /v1/workflows/{workflowId}/executions | List a workflow's durable executions |
triggerWorkflow | POST | /v1/workflows/{workflowId}/executions | Create and enqueue a durable workflow execution |
listWorkflowExecutionEvents | GET | /v1/workflows/{workflowId}/executions/{executionId}/events | List an execution's ordered runtime events |
pauseWorkflow | POST | /v1/workflows/{workflowId}/pause | Pause a workflow |
listWorkflowVersions | GET | /v1/workflows/{workflowId}/versions | List a workflow's versions |
getWorkflowVersion | GET | /v1/workflows/{workflowId}/versions/{version} | Retrieve one version of a workflow |
restoreWorkflowVersion | POST | /v1/workflows/{workflowId}/versions/{version}/restore | Restore a workflow version |
listWorkflows — GET /v1/workflows
Parameters:
skip(query)limit(query)status(query)profileId(query) — If omitted, the workflows of ALL profiles the key can reach are returned.
createWorkflow — POST /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)
getWorkflow — GET /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)
updateWorkflow — PATCH /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)
deleteWorkflow — DELETE /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)
activateWorkflow — POST /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)
duplicateWorkflow — POST /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)
listWorkflowExecutions — GET /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)
triggerWorkflow — POST /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)
listWorkflowExecutionEvents — GET /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)
pauseWorkflow — POST /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)
listWorkflowVersions — GET /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)
getWorkflowVersion — GET /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)
restoreWorkflowVersion — POST /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)