// This file is auto-generated by @hey-api/openapi-ts import { createClient, createConfig, type OptionsLegacyParser, formDataBodySerializer } from '@hey-api/client-fetch'; import type { ValidatePostLengthData, ValidatePostLengthError, ValidatePostLengthResponse, ValidatePostData, ValidatePostError, ValidatePostResponse, ValidateMediaData, ValidateMediaError, ValidateMediaResponse, ValidateSubredditData, ValidateSubredditError, ValidateSubredditResponse, GetAnalyticsData, GetAnalyticsError, GetAnalyticsResponse, GetYouTubeChannelInsightsData, GetYouTubeChannelInsightsError, GetYouTubeChannelInsightsResponse, GetLinkedInOrgAggregateAnalyticsData, GetLinkedInOrgAggregateAnalyticsError, GetLinkedInOrgAggregateAnalyticsResponse, GetTikTokAccountInsightsData, GetTikTokAccountInsightsError, GetTikTokAccountInsightsResponse, GetYouTubeDailyViewsData, GetYouTubeDailyViewsError, GetYouTubeDailyViewsResponse, GetYouTubeVideoRetentionData, GetYouTubeVideoRetentionError, GetYouTubeVideoRetentionResponse, GetFacebookPageInsightsData, GetFacebookPageInsightsError, GetFacebookPageInsightsResponse, GetFacebookPostEarningsData, GetFacebookPostEarningsError, GetFacebookPostEarningsResponse, GetInstagramAccountInsightsData, GetInstagramAccountInsightsError, GetInstagramAccountInsightsResponse, GetInstagramFollowerHistoryData, GetInstagramFollowerHistoryError, GetInstagramFollowerHistoryResponse, GetInstagramDemographicsData, GetInstagramDemographicsError, GetInstagramDemographicsResponse, GetYouTubeDemographicsData, GetYouTubeDemographicsError, GetYouTubeDemographicsResponse, GetDailyMetricsData, GetDailyMetricsError, GetDailyMetricsResponse, GetBestTimeToPostData, GetBestTimeToPostError, GetBestTimeToPostResponse, GetContentDecayData, GetContentDecayError, GetContentDecayResponse, GetPostingFrequencyData, GetPostingFrequencyError, GetPostingFrequencyResponse, GetPostTimelineData, GetPostTimelineError, GetPostTimelineResponse, GetGoogleBusinessPerformanceData, GetGoogleBusinessPerformanceError, GetGoogleBusinessPerformanceResponse, GetGoogleBusinessSearchKeywordsData, GetGoogleBusinessSearchKeywordsError, GetGoogleBusinessSearchKeywordsResponse, GetInboxVolumeData, GetInboxVolumeError, GetInboxVolumeResponse, GetInboxHeatmapData, GetInboxHeatmapError, GetInboxHeatmapResponse, GetInboxSourceBreakdownData, GetInboxSourceBreakdownError, GetInboxSourceBreakdownResponse, GetInboxResponseTimeData, GetInboxResponseTimeError, GetInboxResponseTimeResponse, GetInboxTopAccountsData, GetInboxTopAccountsError, GetInboxTopAccountsResponse, ListInboxConversationAnalyticsData, ListInboxConversationAnalyticsError, ListInboxConversationAnalyticsResponse, GetInboxConversationAnalyticsData, GetInboxConversationAnalyticsError, GetInboxConversationAnalyticsResponse, ListAccountGroupsError, ListAccountGroupsResponse, CreateAccountGroupData, CreateAccountGroupError, CreateAccountGroupResponse, UpdateAccountGroupData, UpdateAccountGroupError, UpdateAccountGroupResponse, DeleteAccountGroupData, DeleteAccountGroupError, DeleteAccountGroupResponse, GetMediaPresignedUrlData, GetMediaPresignedUrlError, GetMediaPresignedUrlResponse, SearchRedditData, SearchRedditError, SearchRedditResponse, GetRedditFeedData, GetRedditFeedError, GetRedditFeedResponse, GetBillingError, GetBillingResponse, GetXApiPricingError, GetXApiPricingResponse, GetUsageData, GetUsageError, GetUsageResponse, GetUsageStatsData, GetUsageStatsError, GetUsageStatsResponse, GetCallsUsageData, GetCallsUsageError, GetCallsUsageResponse, GetSmsUsageData, GetSmsUsageError, GetSmsUsageResponse, ListPostsData, ListPostsError, ListPostsResponse, CreatePostData, CreatePostError, CreatePostResponse, SyncExternalPostsData, SyncExternalPostsError, SyncExternalPostsResponse, GetPostData, GetPostError, GetPostResponse, UpdatePostData, UpdatePostError, UpdatePostResponse, DeletePostData, DeletePostError, DeletePostResponse, BulkUploadPostsData, BulkUploadPostsError, BulkUploadPostsResponse, RetryPostData, RetryPostError, RetryPostResponse, UnpublishPostData, UnpublishPostError, UnpublishPostResponse, EditPostData, EditPostError, EditPostResponse, UpdatePostMetadataData, UpdatePostMetadataError, UpdatePostMetadataResponse, ListUsersError, ListUsersResponse, GetUserData, GetUserError, GetUserResponse, ListProfilesData, ListProfilesError, ListProfilesResponse, CreateProfileData, CreateProfileError, CreateProfileResponse, GetProfileData, GetProfileError, GetProfileResponse, UpdateProfileData, UpdateProfileError, UpdateProfileResponse, DeleteProfileData, DeleteProfileError, DeleteProfileResponse, ListAccountsData, ListAccountsError, ListAccountsResponse, GetFollowerStatsData, GetFollowerStatsError, GetFollowerStatsResponse, UpdateAccountData, UpdateAccountError, UpdateAccountResponse, MoveAccountToProfileData, MoveAccountToProfileError, MoveAccountToProfileResponse, DeleteAccountData, DeleteAccountError, DeleteAccountResponse, GetAllAccountsHealthData, GetAllAccountsHealthError, GetAllAccountsHealthResponse, RegisterWhatsAppNumberData, RegisterWhatsAppNumberError, RegisterWhatsAppNumberResponse, GetAccountHealthData, GetAccountHealthError, GetAccountHealthResponse, GetInstagramFollowStatusData, GetInstagramFollowStatusError, GetInstagramFollowStatusResponse, GetTikTokCreatorInfoData, GetTikTokCreatorInfoError, GetTikTokCreatorInfoResponse, VerifyCredentialError, VerifyCredentialResponse, ListApiKeysError, ListApiKeysResponse, CreateApiKeyData, CreateApiKeyError, CreateApiKeyResponse, DeleteApiKeyData, DeleteApiKeyError, DeleteApiKeyResponse, ListConnectedAppsError, ListConnectedAppsResponse, RevokeConnectedAppData, RevokeConnectedAppError, RevokeConnectedAppResponse, CreateInviteTokenData, CreateInviteTokenError, CreateInviteTokenResponse, GetConnectUrlData, GetConnectUrlError, GetConnectUrlResponse, HandleOAuthCallbackData, HandleOAuthCallbackError, HandleOAuthCallbackResponse, ConnectAdsData, ConnectAdsError, ConnectAdsResponse, GetShopifyConnectUrlData, GetShopifyConnectUrlError, GetShopifyConnectUrlResponse, ConnectShopifyWithTokenData, ConnectShopifyWithTokenError, ConnectShopifyWithTokenResponse, ConfigureTikTokAdsBrandIdentityData, ConfigureTikTokAdsBrandIdentityError, ConfigureTikTokAdsBrandIdentityResponse, ListFacebookPagesData, ListFacebookPagesError, ListFacebookPagesResponse, SelectFacebookPageData, SelectFacebookPageError, SelectFacebookPageResponse, ListInstagramPagesData, ListInstagramPagesError, ListInstagramPagesResponse, SelectInstagramAccountData, SelectInstagramAccountError, SelectInstagramAccountResponse, ListGoogleBusinessLocationsData, ListGoogleBusinessLocationsError, ListGoogleBusinessLocationsResponse, SelectGoogleBusinessLocationData, SelectGoogleBusinessLocationError, SelectGoogleBusinessLocationResponse, GetGoogleBusinessReviewsData, GetGoogleBusinessReviewsError, GetGoogleBusinessReviewsResponse, GetGoogleBusinessVerificationsData, GetGoogleBusinessVerificationsError, GetGoogleBusinessVerificationsResponse, StartGoogleBusinessVerificationData, StartGoogleBusinessVerificationError, StartGoogleBusinessVerificationResponse, FetchGoogleBusinessVerificationOptionsData, FetchGoogleBusinessVerificationOptionsError, FetchGoogleBusinessVerificationOptionsResponse, CompleteGoogleBusinessVerificationData, CompleteGoogleBusinessVerificationError, CompleteGoogleBusinessVerificationResponse, GetGoogleBusinessFoodMenusData, GetGoogleBusinessFoodMenusError, GetGoogleBusinessFoodMenusResponse, UpdateGoogleBusinessFoodMenusData, UpdateGoogleBusinessFoodMenusError, UpdateGoogleBusinessFoodMenusResponse, GetGoogleBusinessLocationDetailsData, GetGoogleBusinessLocationDetailsError, GetGoogleBusinessLocationDetailsResponse, UpdateGoogleBusinessLocationDetailsData, UpdateGoogleBusinessLocationDetailsError, UpdateGoogleBusinessLocationDetailsResponse, ListGoogleBusinessMediaData, ListGoogleBusinessMediaError, ListGoogleBusinessMediaResponse, CreateGoogleBusinessMediaData, CreateGoogleBusinessMediaError, CreateGoogleBusinessMediaResponse, DeleteGoogleBusinessMediaData, DeleteGoogleBusinessMediaError, DeleteGoogleBusinessMediaResponse, GetGmbAttributeMetadataData, GetGmbAttributeMetadataError, GetGmbAttributeMetadataResponse, GetGoogleBusinessAttributesData, GetGoogleBusinessAttributesError, GetGoogleBusinessAttributesResponse, UpdateGoogleBusinessAttributesData, UpdateGoogleBusinessAttributesError, UpdateGoogleBusinessAttributesResponse, ListGoogleBusinessPlaceActionsData, ListGoogleBusinessPlaceActionsError, ListGoogleBusinessPlaceActionsResponse, CreateGoogleBusinessPlaceActionData, CreateGoogleBusinessPlaceActionError, CreateGoogleBusinessPlaceActionResponse, DeleteGoogleBusinessPlaceActionData, DeleteGoogleBusinessPlaceActionError, DeleteGoogleBusinessPlaceActionResponse, UpdateGoogleBusinessPlaceActionData, UpdateGoogleBusinessPlaceActionError, UpdateGoogleBusinessPlaceActionResponse, GetGoogleBusinessServicesData, GetGoogleBusinessServicesError, GetGoogleBusinessServicesResponse, UpdateGoogleBusinessServicesData, UpdateGoogleBusinessServicesError, UpdateGoogleBusinessServicesResponse, BatchGetGoogleBusinessReviewsData, BatchGetGoogleBusinessReviewsError, BatchGetGoogleBusinessReviewsResponse, ReplyToGoogleBusinessReviewData, ReplyToGoogleBusinessReviewError, ReplyToGoogleBusinessReviewResponse, DeleteGoogleBusinessReviewReplyData, DeleteGoogleBusinessReviewReplyError, DeleteGoogleBusinessReviewReplyResponse, GetPendingOAuthDataData, GetPendingOAuthDataError, GetPendingOAuthDataResponse, ListLinkedInOrganizationsData, ListLinkedInOrganizationsError, ListLinkedInOrganizationsResponse, SelectLinkedInOrganizationData, SelectLinkedInOrganizationError, SelectLinkedInOrganizationResponse, ListPinterestBoardsForSelectionData, ListPinterestBoardsForSelectionError, ListPinterestBoardsForSelectionResponse, SelectPinterestBoardData, SelectPinterestBoardError, SelectPinterestBoardResponse, ListSnapchatProfilesData, ListSnapchatProfilesError, ListSnapchatProfilesResponse, SelectSnapchatProfileData, SelectSnapchatProfileError, SelectSnapchatProfileResponse, ConnectBlueskyCredentialsData, ConnectBlueskyCredentialsError, ConnectBlueskyCredentialsResponse, ConnectOpenAiAdsCredentialsData, ConnectOpenAiAdsCredentialsError, ConnectOpenAiAdsCredentialsResponse, ConnectWhatsAppCredentialsData, ConnectWhatsAppCredentialsError, ConnectWhatsAppCredentialsResponse, ListWhatsAppPhoneNumbersData, ListWhatsAppPhoneNumbersError, ListWhatsAppPhoneNumbersResponse, CompleteWhatsAppPhoneSelectionData, CompleteWhatsAppPhoneSelectionError, CompleteWhatsAppPhoneSelectionResponse, GetTelegramConnectStatusData, GetTelegramConnectStatusError, GetTelegramConnectStatusResponse, InitiateTelegramConnectData, InitiateTelegramConnectError, InitiateTelegramConnectResponse, CompleteTelegramConnectData, CompleteTelegramConnectError, CompleteTelegramConnectResponse, GetFacebookPagesData, GetFacebookPagesError, GetFacebookPagesResponse, UpdateFacebookPageData, UpdateFacebookPageError, UpdateFacebookPageResponse, GetLinkedInOrganizationsData, GetLinkedInOrganizationsError, GetLinkedInOrganizationsResponse, GetLinkedInAggregateAnalyticsData, GetLinkedInAggregateAnalyticsError, GetLinkedInAggregateAnalyticsResponse, GetLinkedInPostAnalyticsData, GetLinkedInPostAnalyticsError, GetLinkedInPostAnalyticsResponse, GetLinkedInPostReactionsData, GetLinkedInPostReactionsError, GetLinkedInPostReactionsResponse, UpdateLinkedInOrganizationData, UpdateLinkedInOrganizationError, UpdateLinkedInOrganizationResponse, GetLinkedInMentionsData, GetLinkedInMentionsError, GetLinkedInMentionsResponse, ListInstagramStoriesData, ListInstagramStoriesError, ListInstagramStoriesResponse, GetInstagramPublishingLimitData, GetInstagramPublishingLimitError, GetInstagramPublishingLimitResponse, SearchInstagramAudioData, SearchInstagramAudioError, SearchInstagramAudioResponse, GetInstagramAudioData, GetInstagramAudioError, GetInstagramAudioResponse, GetInstagramStoryInsightsData, GetInstagramStoryInsightsError, GetInstagramStoryInsightsResponse, GetPinterestBoardsData, GetPinterestBoardsError, GetPinterestBoardsResponse, UpdatePinterestBoardsData, UpdatePinterestBoardsError, UpdatePinterestBoardsResponse, CreatePinterestBoardData, CreatePinterestBoardError, CreatePinterestBoardResponse, GetYoutubePlaylistsData, GetYoutubePlaylistsError, GetYoutubePlaylistsResponse, UpdateYoutubeDefaultPlaylistData, UpdateYoutubeDefaultPlaylistError, UpdateYoutubeDefaultPlaylistResponse, GetGmbLocationsData, GetGmbLocationsError, GetGmbLocationsResponse, UpdateGmbLocationData, UpdateGmbLocationError, UpdateGmbLocationResponse, AssignGoogleBusinessLocationData, AssignGoogleBusinessLocationError, AssignGoogleBusinessLocationResponse, GetFacebookPostReactionsData, GetFacebookPostReactionsError, GetFacebookPostReactionsResponse, GetRedditSubredditsData, GetRedditSubredditsError, GetRedditSubredditsResponse, UpdateRedditSubredditsData, UpdateRedditSubredditsError, UpdateRedditSubredditsResponse, GetSubredditRulesData, GetSubredditRulesError, GetSubredditRulesResponse, VoteRedditThingData, VoteRedditThingError, VoteRedditThingResponse, GetRedditFlairsData, GetRedditFlairsError, GetRedditFlairsResponse, SetRedditPostFlairData, SetRedditPostFlairError, SetRedditPostFlairResponse, GetSlackSettingsData, GetSlackSettingsError, GetSlackSettingsResponse, UpdateSlackSettingsData, UpdateSlackSettingsError, UpdateSlackSettingsResponse, GetBlueskySettingsData, GetBlueskySettingsError, GetBlueskySettingsResponse, UpdateBlueskySettingsData, UpdateBlueskySettingsError, UpdateBlueskySettingsResponse, GetDiscordSettingsData, GetDiscordSettingsError, GetDiscordSettingsResponse, UpdateDiscordSettingsData, UpdateDiscordSettingsError, UpdateDiscordSettingsResponse, GetDiscordChannelsData, GetDiscordChannelsError, GetDiscordChannelsResponse, ListSlackMembersData, ListSlackMembersError, ListSlackMembersResponse, SendDiscordDirectMessageData, SendDiscordDirectMessageError, SendDiscordDirectMessageResponse, ListDiscordGuildRolesData, ListDiscordGuildRolesError, ListDiscordGuildRolesResponse, CreateDiscordGuildRoleData, CreateDiscordGuildRoleError, CreateDiscordGuildRoleResponse, EditDiscordGuildRoleData, EditDiscordGuildRoleError, EditDiscordGuildRoleResponse, DeleteDiscordGuildRoleData, DeleteDiscordGuildRoleError, DeleteDiscordGuildRoleResponse, ListDiscordGuildMembersData, ListDiscordGuildMembersError, ListDiscordGuildMembersResponse, SearchDiscordGuildMembersData, SearchDiscordGuildMembersError, SearchDiscordGuildMembersResponse, GetDiscordGuildMemberData, GetDiscordGuildMemberError, GetDiscordGuildMemberResponse, AddDiscordMemberRoleData, AddDiscordMemberRoleError, AddDiscordMemberRoleResponse, RemoveDiscordMemberRoleData, RemoveDiscordMemberRoleError, RemoveDiscordMemberRoleResponse, DeleteDiscordMessageData, DeleteDiscordMessageError, DeleteDiscordMessageResponse, CrosspostDiscordMessageData, CrosspostDiscordMessageError, CrosspostDiscordMessageResponse, CreateDiscordThreadData, CreateDiscordThreadError, CreateDiscordThreadResponse, ListDiscordPinnedMessagesData, ListDiscordPinnedMessagesError, ListDiscordPinnedMessagesResponse, PinDiscordMessageData, PinDiscordMessageError, PinDiscordMessageResponse, UnpinDiscordMessageData, UnpinDiscordMessageError, UnpinDiscordMessageResponse, ListDiscordScheduledEventsData, ListDiscordScheduledEventsError, ListDiscordScheduledEventsResponse, CreateDiscordScheduledEventData, CreateDiscordScheduledEventError, CreateDiscordScheduledEventResponse, GetDiscordScheduledEventData, GetDiscordScheduledEventError, GetDiscordScheduledEventResponse, UpdateDiscordScheduledEventData, UpdateDiscordScheduledEventError, UpdateDiscordScheduledEventResponse, DeleteDiscordScheduledEventData, DeleteDiscordScheduledEventError, DeleteDiscordScheduledEventResponse, ListQueueSlotsData, ListQueueSlotsError, ListQueueSlotsResponse, CreateQueueSlotData, CreateQueueSlotError, CreateQueueSlotResponse, UpdateQueueSlotData, UpdateQueueSlotError, UpdateQueueSlotResponse, DeleteQueueSlotData, DeleteQueueSlotError, DeleteQueueSlotResponse, PreviewQueueData, PreviewQueueError, PreviewQueueResponse, GetNextQueueSlotData, GetNextQueueSlotError, GetNextQueueSlotResponse, GetWebhookSettingsError, GetWebhookSettingsResponse, CreateWebhookSettingsData, CreateWebhookSettingsError, CreateWebhookSettingsResponse, UpdateWebhookSettingsData, UpdateWebhookSettingsError, UpdateWebhookSettingsResponse, DeleteWebhookSettingsData, DeleteWebhookSettingsError, DeleteWebhookSettingsResponse, GetWebhookLogsData, GetWebhookLogsError, GetWebhookLogsResponse, TestWebhookData, TestWebhookError, TestWebhookResponse, ListLogsData, ListLogsError, ListLogsResponse, ListInboxConversationsData, ListInboxConversationsError, ListInboxConversationsResponse, CreateInboxConversationData, CreateInboxConversationError, CreateInboxConversationResponse, SearchInboxConversationsData, SearchInboxConversationsError, SearchInboxConversationsResponse, GetInboxConversationData, GetInboxConversationError, GetInboxConversationResponse, UpdateInboxConversationData, UpdateInboxConversationError, UpdateInboxConversationResponse, GetInboxConversationMessagesData, GetInboxConversationMessagesError, GetInboxConversationMessagesResponse, SendInboxMessageData, SendInboxMessageError, SendInboxMessageResponse, GetWhatsAppMediaData, GetWhatsAppMediaError, GetWhatsAppMediaResponse, EditInboxMessageData, EditInboxMessageError, EditInboxMessageResponse, DeleteInboxMessageData, DeleteInboxMessageError, DeleteInboxMessageResponse, SendTypingIndicatorData, SendTypingIndicatorError, SendTypingIndicatorResponse, MarkConversationReadData, MarkConversationReadError, MarkConversationReadResponse, AddMessageReactionData, AddMessageReactionError, AddMessageReactionResponse, RemoveMessageReactionData, RemoveMessageReactionError, RemoveMessageReactionResponse, UploadMediaDirectData, UploadMediaDirectError, UploadMediaDirectResponse, GetMessengerMenuData, GetMessengerMenuError, GetMessengerMenuResponse, SetMessengerMenuData, SetMessengerMenuError, SetMessengerMenuResponse, DeleteMessengerMenuData, DeleteMessengerMenuError, DeleteMessengerMenuResponse, GetInstagramIceBreakersData, GetInstagramIceBreakersError, GetInstagramIceBreakersResponse, SetInstagramIceBreakersData, SetInstagramIceBreakersError, SetInstagramIceBreakersResponse, DeleteInstagramIceBreakersData, DeleteInstagramIceBreakersError, DeleteInstagramIceBreakersResponse, GetTelegramCommandsData, GetTelegramCommandsError, GetTelegramCommandsResponse, SetTelegramCommandsData, SetTelegramCommandsError, SetTelegramCommandsResponse, DeleteTelegramCommandsData, DeleteTelegramCommandsError, DeleteTelegramCommandsResponse, GetMessageAttachmentData, GetMessageAttachmentError, GetMessageAttachmentResponse, ListInboxCommentsData, ListInboxCommentsError, ListInboxCommentsResponse, GetInboxPostCommentsData, GetInboxPostCommentsError, GetInboxPostCommentsResponse, ReplyToInboxPostData, ReplyToInboxPostError, ReplyToInboxPostResponse, DeleteInboxCommentData, DeleteInboxCommentError, DeleteInboxCommentResponse, EditInboxCommentData, EditInboxCommentError, EditInboxCommentResponse, SetCommentModerationData, SetCommentModerationError, SetCommentModerationResponse, HideInboxCommentData, HideInboxCommentError, HideInboxCommentResponse, UnhideInboxCommentData, UnhideInboxCommentError, UnhideInboxCommentResponse, LikeInboxCommentData, LikeInboxCommentError, LikeInboxCommentResponse, UnlikeInboxCommentData, UnlikeInboxCommentError, UnlikeInboxCommentResponse, LikePostData, LikePostError, LikePostResponse, UnlikePostData, UnlikePostError, UnlikePostResponse, SendPrivateReplyToCommentData, SendPrivateReplyToCommentError, SendPrivateReplyToCommentResponse, RetweetPostData, RetweetPostError, RetweetPostResponse, UndoRetweetData, UndoRetweetError, UndoRetweetResponse, BookmarkPostData, BookmarkPostError, BookmarkPostResponse, RemoveBookmarkData, RemoveBookmarkError, RemoveBookmarkResponse, FollowUserData, FollowUserError, FollowUserResponse, UnfollowUserData, UnfollowUserError, UnfollowUserResponse, SearchTweetsData, SearchTweetsError, SearchTweetsResponse, GetTweetData, GetTweetError, GetTweetResponse, ListInboxMentionsData, ListInboxMentionsError, ListInboxMentionsResponse, ReplyToMentionData, ReplyToMentionError, ReplyToMentionResponse, ListInboxReviewsData, ListInboxReviewsError, ListInboxReviewsResponse, ReplyToInboxReviewData, ReplyToInboxReviewError, ReplyToInboxReviewResponse, DeleteInboxReviewReplyData, DeleteInboxReviewReplyError, DeleteInboxReviewReplyResponse, GetWhatsAppTemplatesData, GetWhatsAppTemplatesError, GetWhatsAppTemplatesResponse, CreateWhatsAppTemplateData, CreateWhatsAppTemplateError, CreateWhatsAppTemplateResponse, GetWhatsAppTemplateData, GetWhatsAppTemplateError, GetWhatsAppTemplateResponse, UpdateWhatsAppTemplateData, UpdateWhatsAppTemplateError, UpdateWhatsAppTemplateResponse, DeleteWhatsAppTemplateData, DeleteWhatsAppTemplateError, DeleteWhatsAppTemplateResponse, GetWhatsAppCallingConfigData, GetWhatsAppCallingConfigError, GetWhatsAppCallingConfigResponse, EnableWhatsAppCallingLegacyData, EnableWhatsAppCallingLegacyError, EnableWhatsAppCallingLegacyResponse, UpdateWhatsAppCallingLegacyData, UpdateWhatsAppCallingLegacyError, UpdateWhatsAppCallingLegacyResponse, DisableWhatsAppCallingLegacyData, DisableWhatsAppCallingLegacyError, DisableWhatsAppCallingLegacyResponse, GetWhatsAppCallPermissionsData, GetWhatsAppCallPermissionsError, GetWhatsAppCallPermissionsResponse, InitiateWhatsAppCallData, InitiateWhatsAppCallError, InitiateWhatsAppCallResponse, ListWhatsAppCallsData, ListWhatsAppCallsError, ListWhatsAppCallsResponse, GetWhatsAppCallData, GetWhatsAppCallError, GetWhatsAppCallResponse, GetWhatsAppCallRecordingData, GetWhatsAppCallRecordingError, GetWhatsAppCallRecordingResponse, GetWhatsAppCallEstimateData, GetWhatsAppCallEstimateError, GetWhatsAppCallEstimateResponse, ListCallsData, ListCallsError, ListCallsResponse, GetCallData, GetCallError, GetCallResponse, GetCallRecordingData, GetCallRecordingError, GetCallRecordingResponse, CreateVoiceCallData, CreateVoiceCallError, CreateVoiceCallResponse, ListVoiceCallsData, ListVoiceCallsError, ListVoiceCallsResponse, GetVoiceCallData, GetVoiceCallError, GetVoiceCallResponse, EndVoiceCallData, EndVoiceCallError, EndVoiceCallResponse, GetVoiceCallRecordingData, GetVoiceCallRecordingError, GetVoiceCallRecordingResponse, TransferVoiceCallData, TransferVoiceCallError, TransferVoiceCallResponse, GetVoiceCallEstimateData, GetVoiceCallEstimateError, GetVoiceCallEstimateResponse, CreateVoiceWebSessionError, CreateVoiceWebSessionResponse, DialVoiceWebCallData, DialVoiceWebCallError, DialVoiceWebCallResponse, SendSmsData, SendSmsError, SendSmsResponse, LookupSmsNumberData, LookupSmsNumberError, LookupSmsNumberResponse, ListSmsOptOutsData, ListSmsOptOutsError, ListSmsOptOutsResponse, CreateSmsSenderIdData, CreateSmsSenderIdError, CreateSmsSenderIdResponse, ListSmsSenderIdsError, ListSmsSenderIdsResponse, RequestSmsSenderIdLimitIncreaseData, RequestSmsSenderIdLimitIncreaseError, RequestSmsSenderIdLimitIncreaseResponse, DeleteSmsSenderIdData, DeleteSmsSenderIdError, DeleteSmsSenderIdResponse, StartSmsRegistrationData, StartSmsRegistrationError, StartSmsRegistrationResponse, ListSmsRegistrationsData, ListSmsRegistrationsError, ListSmsRegistrationsResponse, PreflightSmsRegistrationData, PreflightSmsRegistrationError, PreflightSmsRegistrationResponse, DeactivateSmsRegistrationData, DeactivateSmsRegistrationError, DeactivateSmsRegistrationResponse, GetSmsRegistrationData, GetSmsRegistrationError, GetSmsRegistrationResponse, VerifySmsRegistrationOtpData, VerifySmsRegistrationOtpError, VerifySmsRegistrationOtpResponse, ResendSmsRegistrationOtpData, ResendSmsRegistrationOtpError, ResendSmsRegistrationOtpResponse, AppealSmsRegistrationData, AppealSmsRegistrationError, AppealSmsRegistrationResponse, RespondToSmsRegistrationReviewData, RespondToSmsRegistrationReviewError, RespondToSmsRegistrationReviewResponse, UploadSmsOptInProofFileData, UploadSmsOptInProofFileError, UploadSmsOptInProofFileResponse, UploadSmsOptInProofData, UploadSmsOptInProofError, UploadSmsOptInProofResponse, ShareSmsRegistrationData, ShareSmsRegistrationError, ShareSmsRegistrationResponse, GetWhatsAppLibraryTemplateData, GetWhatsAppLibraryTemplateError, GetWhatsAppLibraryTemplateResponse, GetWhatsAppBusinessProfileData, GetWhatsAppBusinessProfileError, GetWhatsAppBusinessProfileResponse, UpdateWhatsAppBusinessProfileData, UpdateWhatsAppBusinessProfileError, UpdateWhatsAppBusinessProfileResponse, UploadWhatsAppProfilePhotoData, UploadWhatsAppProfilePhotoError, UploadWhatsAppProfilePhotoResponse, GetWhatsAppDisplayNameData, GetWhatsAppDisplayNameError, GetWhatsAppDisplayNameResponse, UpdateWhatsAppDisplayNameData, UpdateWhatsAppDisplayNameError, UpdateWhatsAppDisplayNameResponse, GetWhatsappBusinessUsernameData, GetWhatsappBusinessUsernameError, GetWhatsappBusinessUsernameResponse, SetWhatsappBusinessUsernameData, SetWhatsappBusinessUsernameError, SetWhatsappBusinessUsernameResponse, DeleteWhatsappBusinessUsernameData, DeleteWhatsappBusinessUsernameError, DeleteWhatsappBusinessUsernameResponse, GetWhatsappBusinessUsernameSuggestionsData, GetWhatsappBusinessUsernameSuggestionsError, GetWhatsappBusinessUsernameSuggestionsResponse, GetWhatsAppNumberInfoData, GetWhatsAppNumberInfoError, GetWhatsAppNumberInfoResponse, GetWhatsAppBlockStatusData, GetWhatsAppBlockStatusError, GetWhatsAppBlockStatusResponse, GetWhatsAppBlockedUsersData, GetWhatsAppBlockedUsersError, GetWhatsAppBlockedUsersResponse, BlockWhatsAppUsersData, BlockWhatsAppUsersError, BlockWhatsAppUsersResponse, UnblockWhatsAppUsersData, UnblockWhatsAppUsersError, UnblockWhatsAppUsersResponse, ListWhatsAppAccountEventsData, ListWhatsAppAccountEventsError, ListWhatsAppAccountEventsResponse, GetWhatsAppDatasetData, GetWhatsAppDatasetError, GetWhatsAppDatasetResponse, CreateWhatsAppDatasetData, CreateWhatsAppDatasetError, CreateWhatsAppDatasetResponse, ListPhoneNumbersData, ListPhoneNumbersError, ListPhoneNumbersResponse, GetPhoneNumberData, GetPhoneNumberError, GetPhoneNumberResponse, ReleasePhoneNumberData, ReleasePhoneNumberError, ReleasePhoneNumberResponse, PurchasePhoneNumberData, PurchasePhoneNumberError, PurchasePhoneNumberResponse, ListPhoneNumberCountriesError, ListPhoneNumberCountriesResponse, SearchAvailablePhoneNumbersData, SearchAvailablePhoneNumbersError, SearchAvailablePhoneNumbersResponse, CheckPhoneNumberAvailabilityData, CheckPhoneNumberAvailabilityError, CheckPhoneNumberAvailabilityResponse, GetWhatsAppPhoneNumbersData, GetWhatsAppPhoneNumbersError, GetWhatsAppPhoneNumbersResponse, PurchaseWhatsAppPhoneNumberData, PurchaseWhatsAppPhoneNumberError, PurchaseWhatsAppPhoneNumberResponse, ListWhatsAppNumberCountriesError, ListWhatsAppNumberCountriesResponse, SearchAvailableWhatsAppNumbersData, SearchAvailableWhatsAppNumbersError, SearchAvailableWhatsAppNumbersResponse, CheckWhatsAppNumberAvailabilityData, CheckWhatsAppNumberAvailabilityError, CheckWhatsAppNumberAvailabilityResponse, GetPhoneNumberKycFormData, GetPhoneNumberKycFormError, GetPhoneNumberKycFormResponse, SubmitPhoneNumberKycData, SubmitPhoneNumberKycError, SubmitPhoneNumberKycResponse, ViewPhoneNumberKycDocumentData, ViewPhoneNumberKycDocumentError, ViewPhoneNumberKycDocumentResponse, UploadPhoneNumberKycDocumentData, UploadPhoneNumberKycDocumentError, UploadPhoneNumberKycDocumentResponse, ValidatePhoneNumberKycAddressData, ValidatePhoneNumberKycAddressError, ValidatePhoneNumberKycAddressResponse, CreatePhoneNumberKycLinkData, CreatePhoneNumberKycLinkError, CreatePhoneNumberKycLinkResponse, CreatePhoneNumberPortInData, CreatePhoneNumberPortInError, CreatePhoneNumberPortInResponse, ListPhoneNumberPortInsError, ListPhoneNumberPortInsResponse, CheckPhoneNumberPortabilityData, CheckPhoneNumberPortabilityError, CheckPhoneNumberPortabilityResponse, UploadPhoneNumberPortInDocumentData, UploadPhoneNumberPortInDocumentError, UploadPhoneNumberPortInDocumentResponse, GetPhoneNumberPortInRequirementsData, GetPhoneNumberPortInRequirementsError, GetPhoneNumberPortInRequirementsResponse, GetPhoneNumberPortInOrderRequirementsData, GetPhoneNumberPortInOrderRequirementsError, GetPhoneNumberPortInOrderRequirementsResponse, CancelPhoneNumberPortInData, CancelPhoneNumberPortInError, CancelPhoneNumberPortInResponse, ReviewPhoneNumberKycPacketData, ReviewPhoneNumberKycPacketError, ReviewPhoneNumberKycPacketResponse, GetPhoneNumberRemediationData, GetPhoneNumberRemediationError, GetPhoneNumberRemediationResponse, RemediatePhoneNumberData, RemediatePhoneNumberError, RemediatePhoneNumberResponse, ReplyToPhoneNumberReviewerData, ReplyToPhoneNumberReviewerError, ReplyToPhoneNumberReviewerResponse, RespondToPhoneNumberReviewerData, RespondToPhoneNumberReviewerError, RespondToPhoneNumberReviewerResponse, GetWhatsAppNumberKycFormData, GetWhatsAppNumberKycFormError, GetWhatsAppNumberKycFormResponse, SubmitWhatsAppNumberKycData, SubmitWhatsAppNumberKycError, SubmitWhatsAppNumberKycResponse, UploadWhatsAppNumberKycDocumentData, UploadWhatsAppNumberKycDocumentError, UploadWhatsAppNumberKycDocumentResponse, ValidateWhatsAppNumberKycAddressData, ValidateWhatsAppNumberKycAddressError, ValidateWhatsAppNumberKycAddressResponse, CreateWhatsAppNumberKycLinkData, CreateWhatsAppNumberKycLinkError, CreateWhatsAppNumberKycLinkResponse, MoveWhatsAppNumberToProfileData, MoveWhatsAppNumberToProfileError, MoveWhatsAppNumberToProfileResponse, GetWhatsAppNumberRemediationData, GetWhatsAppNumberRemediationError, GetWhatsAppNumberRemediationResponse, RemediateWhatsAppNumberData, RemediateWhatsAppNumberError, RemediateWhatsAppNumberResponse, EnableVoiceOnNumberData, EnableVoiceOnNumberError, EnableVoiceOnNumberResponse, DisableVoiceOnNumberData, DisableVoiceOnNumberError, DisableVoiceOnNumberResponse, EnableSmsOnNumberData, EnableSmsOnNumberError, EnableSmsOnNumberResponse, DisableSmsOnNumberData, DisableSmsOnNumberError, DisableSmsOnNumberResponse, ReuseSmsRegistrationForNumberData, ReuseSmsRegistrationForNumberError, ReuseSmsRegistrationForNumberResponse, GetWhatsAppCallingData, GetWhatsAppCallingError, GetWhatsAppCallingResponse, EnableWhatsAppCallingData, EnableWhatsAppCallingError, EnableWhatsAppCallingResponse, UpdateWhatsAppCallingData, UpdateWhatsAppCallingError, UpdateWhatsAppCallingResponse, DisableWhatsAppCallingData, DisableWhatsAppCallingError, DisableWhatsAppCallingResponse, StartWhatsAppCallerIdVerificationData, StartWhatsAppCallerIdVerificationError, StartWhatsAppCallerIdVerificationResponse, VerifyWhatsAppCallerIdData, VerifyWhatsAppCallerIdError, VerifyWhatsAppCallerIdResponse, GetWhatsAppPhoneNumberData, GetWhatsAppPhoneNumberError, GetWhatsAppPhoneNumberResponse, ReleaseWhatsAppPhoneNumberData, ReleaseWhatsAppPhoneNumberError, ReleaseWhatsAppPhoneNumberResponse, ListWhatsAppSandboxSessionsError, ListWhatsAppSandboxSessionsResponse, CreateWhatsAppSandboxSessionData, CreateWhatsAppSandboxSessionError, CreateWhatsAppSandboxSessionResponse, DeleteWhatsAppSandboxSessionData, DeleteWhatsAppSandboxSessionError, DeleteWhatsAppSandboxSessionResponse, ListWhatsAppGroupChatsData, ListWhatsAppGroupChatsError, ListWhatsAppGroupChatsResponse, CreateWhatsAppGroupChatData, CreateWhatsAppGroupChatError, CreateWhatsAppGroupChatResponse, GetWhatsAppGroupChatData, GetWhatsAppGroupChatError, GetWhatsAppGroupChatResponse, UpdateWhatsAppGroupChatData, UpdateWhatsAppGroupChatError, UpdateWhatsAppGroupChatResponse, DeleteWhatsAppGroupChatData, DeleteWhatsAppGroupChatError, DeleteWhatsAppGroupChatResponse, AddWhatsAppGroupParticipantsData, AddWhatsAppGroupParticipantsError, AddWhatsAppGroupParticipantsResponse, RemoveWhatsAppGroupParticipantsData, RemoveWhatsAppGroupParticipantsError, RemoveWhatsAppGroupParticipantsResponse, CreateWhatsAppGroupInviteLinkData, CreateWhatsAppGroupInviteLinkError, CreateWhatsAppGroupInviteLinkResponse, ListWhatsAppGroupJoinRequestsData, ListWhatsAppGroupJoinRequestsError, ListWhatsAppGroupJoinRequestsResponse, ApproveWhatsAppGroupJoinRequestsData, ApproveWhatsAppGroupJoinRequestsError, ApproveWhatsAppGroupJoinRequestsResponse, RejectWhatsAppGroupJoinRequestsData, RejectWhatsAppGroupJoinRequestsError, RejectWhatsAppGroupJoinRequestsResponse, ListWhatsAppFlowsData, ListWhatsAppFlowsError, ListWhatsAppFlowsResponse, CreateWhatsAppFlowData, CreateWhatsAppFlowError, CreateWhatsAppFlowResponse, GetWhatsAppFlowData, GetWhatsAppFlowError, GetWhatsAppFlowResponse, UpdateWhatsAppFlowData, UpdateWhatsAppFlowError, UpdateWhatsAppFlowResponse, DeleteWhatsAppFlowData, DeleteWhatsAppFlowError, DeleteWhatsAppFlowResponse, GetWhatsAppFlowJsonData, GetWhatsAppFlowJsonError, GetWhatsAppFlowJsonResponse, UploadWhatsAppFlowJsonData, UploadWhatsAppFlowJsonError, UploadWhatsAppFlowJsonResponse, GetWhatsAppFlowPreviewData, GetWhatsAppFlowPreviewError, GetWhatsAppFlowPreviewResponse, ListWhatsAppFlowVersionsData, ListWhatsAppFlowVersionsError, ListWhatsAppFlowVersionsResponse, PublishWhatsAppFlowData, PublishWhatsAppFlowError, PublishWhatsAppFlowResponse, DeprecateWhatsAppFlowData, DeprecateWhatsAppFlowError, DeprecateWhatsAppFlowResponse, SendWhatsAppFlowMessageData, SendWhatsAppFlowMessageError, SendWhatsAppFlowMessageResponse, ListWhatsAppFlowResponsesData, ListWhatsAppFlowResponsesError, ListWhatsAppFlowResponsesResponse, ListContactsData, ListContactsError, ListContactsResponse, CreateContactData, CreateContactError, CreateContactResponse, GetContactData, GetContactError, GetContactResponse, UpdateContactData, UpdateContactError, UpdateContactResponse, DeleteContactData, DeleteContactError, DeleteContactResponse, GetContactChannelsData, GetContactChannelsError, GetContactChannelsResponse, BulkCreateContactsData, BulkCreateContactsError, BulkCreateContactsResponse, SetContactFieldValueData, SetContactFieldValueError, SetContactFieldValueResponse, ClearContactFieldValueData, ClearContactFieldValueError, ClearContactFieldValueResponse, ListCustomFieldsData, ListCustomFieldsError, ListCustomFieldsResponse, CreateCustomFieldData, CreateCustomFieldError, CreateCustomFieldResponse, UpdateCustomFieldData, UpdateCustomFieldError, UpdateCustomFieldResponse, DeleteCustomFieldData, DeleteCustomFieldError, DeleteCustomFieldResponse, ListBroadcastsData, ListBroadcastsError, ListBroadcastsResponse, CreateBroadcastData, CreateBroadcastError, CreateBroadcastResponse, GetBroadcastData, GetBroadcastError, GetBroadcastResponse, UpdateBroadcastData, UpdateBroadcastError, UpdateBroadcastResponse, DeleteBroadcastData, DeleteBroadcastError, DeleteBroadcastResponse, SendBroadcastData, SendBroadcastError, SendBroadcastResponse, ScheduleBroadcastData, ScheduleBroadcastError, ScheduleBroadcastResponse, CancelBroadcastData, CancelBroadcastError, CancelBroadcastResponse, ListBroadcastRecipientsData, ListBroadcastRecipientsError, ListBroadcastRecipientsResponse, AddBroadcastRecipientsData, AddBroadcastRecipientsError, AddBroadcastRecipientsResponse, ListWorkflowsData, ListWorkflowsError, ListWorkflowsResponse, CreateWorkflowData, CreateWorkflowError, CreateWorkflowResponse, GetWorkflowData, GetWorkflowError, GetWorkflowResponse, UpdateWorkflowData, UpdateWorkflowError, UpdateWorkflowResponse, DeleteWorkflowData, DeleteWorkflowError, DeleteWorkflowResponse, ActivateWorkflowData, ActivateWorkflowError, ActivateWorkflowResponse, PauseWorkflowData, PauseWorkflowError, PauseWorkflowResponse, ListWorkflowExecutionsData, ListWorkflowExecutionsError, ListWorkflowExecutionsResponse, TriggerWorkflowData, TriggerWorkflowError, TriggerWorkflowResponse, ListWorkflowExecutionEventsData, ListWorkflowExecutionEventsError, ListWorkflowExecutionEventsResponse, DuplicateWorkflowData, DuplicateWorkflowError, DuplicateWorkflowResponse, ListWorkflowVersionsData, ListWorkflowVersionsError, ListWorkflowVersionsResponse, GetWorkflowVersionData, GetWorkflowVersionError, GetWorkflowVersionResponse, RestoreWorkflowVersionData, RestoreWorkflowVersionError, RestoreWorkflowVersionResponse, ListSequencesData, ListSequencesError, ListSequencesResponse, CreateSequenceData, CreateSequenceError, CreateSequenceResponse, GetSequenceData, GetSequenceError, GetSequenceResponse, UpdateSequenceData, UpdateSequenceError, UpdateSequenceResponse, DeleteSequenceData, DeleteSequenceError, DeleteSequenceResponse, ActivateSequenceData, ActivateSequenceError, ActivateSequenceResponse, PauseSequenceData, PauseSequenceError, PauseSequenceResponse, EnrollContactsData, EnrollContactsError, EnrollContactsResponse, UnenrollContactData, UnenrollContactError, UnenrollContactResponse, ListSequenceEnrollmentsData, ListSequenceEnrollmentsError, ListSequenceEnrollmentsResponse, ListCommentAutomationsData, ListCommentAutomationsError, ListCommentAutomationsResponse, CreateCommentAutomationData, CreateCommentAutomationError, CreateCommentAutomationResponse, GetCommentAutomationData, GetCommentAutomationError, GetCommentAutomationResponse, UpdateCommentAutomationData, UpdateCommentAutomationError, UpdateCommentAutomationResponse, DeleteCommentAutomationData, DeleteCommentAutomationError, DeleteCommentAutomationResponse, ListCommentAutomationLogsData, ListCommentAutomationLogsError, ListCommentAutomationLogsResponse, ListAdsData, ListAdsError, ListAdsResponse, GetAdsSearchTermsData, GetAdsSearchTermsError, GetAdsSearchTermsResponse, ListLocalServicesLeadsData, ListLocalServicesLeadsError, ListLocalServicesLeadsResponse, ListLocalServicesLeadConversationsData, ListLocalServicesLeadConversationsError, ListLocalServicesLeadConversationsResponse, ListAdKeywordsData, ListAdKeywordsError, ListAdKeywordsResponse, ListAdCampaignsData, ListAdCampaignsError, ListAdCampaignsResponse, CreateAdCampaignData, CreateAdCampaignError, CreateAdCampaignResponse, UpdateAdCampaignStatusData, UpdateAdCampaignStatusError, UpdateAdCampaignStatusResponse, UpdateAdCampaignData, UpdateAdCampaignError, UpdateAdCampaignResponse, DeleteAdCampaignData, DeleteAdCampaignError, DeleteAdCampaignResponse, BulkUpdateAdCampaignStatusData, BulkUpdateAdCampaignStatusError, BulkUpdateAdCampaignStatusResponse, DuplicateAdCampaignData, DuplicateAdCampaignError, DuplicateAdCampaignResponse, DuplicateAdSetData, DuplicateAdSetError, DuplicateAdSetResponse, DuplicateAdData, DuplicateAdError, DuplicateAdResponse, GetAdSetDetailsData, GetAdSetDetailsError, GetAdSetDetailsResponse, UpdateAdSetData, UpdateAdSetError, UpdateAdSetResponse, UpdateAdSetStatusData, UpdateAdSetStatusError, UpdateAdSetStatusResponse, GetAdTreeData, GetAdTreeError, GetAdTreeResponse, GetAdsTimelineData, GetAdsTimelineError, GetAdsTimelineResponse, GetAdData, GetAdError, GetAdResponse, UpdateAdData, UpdateAdError, UpdateAdResponse, DeleteAdData, DeleteAdError, DeleteAdResponse, UpdateAdStatusData, UpdateAdStatusError, UpdateAdStatusResponse, GetCampaignAnalyticsData, GetCampaignAnalyticsError, GetCampaignAnalyticsResponse, GenerateAdPreviewsData, GenerateAdPreviewsError, GenerateAdPreviewsResponse, GetAdPreviewsData, GetAdPreviewsError, GetAdPreviewsResponse, GenerateKeywordIdeasData, GenerateKeywordIdeasError, GenerateKeywordIdeasResponse, GenerateKeywordHistoricalMetricsData, GenerateKeywordHistoricalMetricsError, GenerateKeywordHistoricalMetricsResponse, QueryAdInsightsData, QueryAdInsightsError, QueryAdInsightsResponse, CreateAdInsightsReportData, CreateAdInsightsReportError, CreateAdInsightsReportResponse, GetAdInsightsReportData, GetAdInsightsReportError, GetAdInsightsReportResponse, GetAdAnalyticsData, GetAdAnalyticsError, GetAdAnalyticsResponse, GetAdTrackingTagsData, GetAdTrackingTagsError, GetAdTrackingTagsResponse, UpdateAdTrackingTagsData, UpdateAdTrackingTagsError, UpdateAdTrackingTagsResponse, GetAdCommentsData, GetAdCommentsError, GetAdCommentsResponse, ListAdsBusinessCentersData, ListAdsBusinessCentersError, ListAdsBusinessCentersResponse, GetAdsActivityLogData, GetAdsActivityLogError, GetAdsActivityLogResponse, CreateRfPredictionData, CreateRfPredictionError, CreateRfPredictionResponse, GetRfPredictionData, GetRfPredictionError, GetRfPredictionResponse, CancelRfReservationData, CancelRfReservationError, CancelRfReservationResponse, ReserveRfPredictionData, ReserveRfPredictionError, ReserveRfPredictionResponse, ListAdStudiesData, ListAdStudiesError, ListAdStudiesResponse, ListMetaBusinessesData, ListMetaBusinessesError, ListMetaBusinessesResponse, ListAdLabelsData, ListAdLabelsError, ListAdLabelsResponse, ListHighDemandPeriodsData, ListHighDemandPeriodsError, ListHighDemandPeriodsResponse, CreateHighDemandPeriodData, CreateHighDemandPeriodError, CreateHighDemandPeriodResponse, ListAdCreativesData, ListAdCreativesError, ListAdCreativesResponse, CreateAdCreativeData, CreateAdCreativeError, CreateAdCreativeResponse, GetAdCreativeData, GetAdCreativeError, GetAdCreativeResponse, UpdateAdCreativeData, UpdateAdCreativeError, UpdateAdCreativeResponse, DeleteAdCreativeData, DeleteAdCreativeError, DeleteAdCreativeResponse, ListValueRuleSetsData, ListValueRuleSetsError, ListValueRuleSetsResponse, CreateValueRuleSetData, CreateValueRuleSetError, CreateValueRuleSetResponse, GetValueRuleSetData, GetValueRuleSetError, GetValueRuleSetResponse, UpdateValueRuleSetData, UpdateValueRuleSetError, UpdateValueRuleSetResponse, DeleteValueRuleSetData, DeleteValueRuleSetError, DeleteValueRuleSetResponse, GetAdAccountFinanceData, GetAdAccountFinanceError, GetAdAccountFinanceResponse, ListAdAccountsData, ListAdAccountsError, ListAdAccountsResponse, UpdateAdAccountData, UpdateAdAccountError, UpdateAdAccountResponse, GetDsaDefaultsData, GetDsaDefaultsError, GetDsaDefaultsResponse, GetDsaRecommendationsData, GetDsaRecommendationsError, GetDsaRecommendationsResponse, BoostPostData, BoostPostError, BoostPostResponse, CreateStandaloneAdData, CreateStandaloneAdError, CreateStandaloneAdResponse, ListLeadsData, ListLeadsError, ListLeadsResponse, ListLeadFormsData, ListLeadFormsError, ListLeadFormsResponse, CreateLeadFormData, CreateLeadFormError, CreateLeadFormResponse, GetLeadFormData, GetLeadFormError, GetLeadFormResponse, ArchiveLeadFormData, ArchiveLeadFormError, ArchiveLeadFormResponse, ListFormLeadsData, ListFormLeadsError, ListFormLeadsResponse, CreateTestLeadData, CreateTestLeadError, CreateTestLeadResponse, UploadAdImageData, UploadAdImageError, UploadAdImageResponse, ListAdImagesData, ListAdImagesError, ListAdImagesResponse, ListAdVideosData, ListAdVideosError, ListAdVideosResponse, SearchAdInterestsData, SearchAdInterestsError, SearchAdInterestsResponse, SearchAdTargetingData, SearchAdTargetingError, SearchAdTargetingResponse, EstimateAdReachData, EstimateAdReachError, EstimateAdReachResponse, GetLinkedInBidPricingData, GetLinkedInBidPricingError, GetLinkedInBidPricingResponse, GetLinkedInSupplyForecastData, GetLinkedInSupplyForecastError, GetLinkedInSupplyForecastResponse, ListAdCatalogsData, ListAdCatalogsError, ListAdCatalogsResponse, ListAdCatalogProductSetsData, ListAdCatalogProductSetsError, ListAdCatalogProductSetsResponse, ListAdAudiencesData, ListAdAudiencesError, ListAdAudiencesResponse, CreateAdAudienceData, CreateAdAudienceError, CreateAdAudienceResponse, GetAdAudienceData, GetAdAudienceError, GetAdAudienceResponse, UpdateAdAudienceData, UpdateAdAudienceError, UpdateAdAudienceResponse, DeleteAdAudienceData, DeleteAdAudienceError, DeleteAdAudienceResponse, AddUsersToAdAudienceData, AddUsersToAdAudienceError, AddUsersToAdAudienceResponse, ReplaceAdAudienceCompaniesData, ReplaceAdAudienceCompaniesError, ReplaceAdAudienceCompaniesResponse, GetConversionsQualityData, GetConversionsQualityError, GetConversionsQualityResponse, SendConversionsData, SendConversionsError, SendConversionsResponse, AdjustConversionsData, AdjustConversionsError, AdjustConversionsResponse, ListConversionDestinationsData, ListConversionDestinationsError, ListConversionDestinationsResponse, CreateConversionDestinationData, CreateConversionDestinationError, CreateConversionDestinationResponse, GetConversionDestinationData, GetConversionDestinationError, GetConversionDestinationResponse, UpdateConversionDestinationData, UpdateConversionDestinationError, UpdateConversionDestinationResponse, DeleteConversionDestinationData, DeleteConversionDestinationError, DeleteConversionDestinationResponse, ListConversionAssociationsData, ListConversionAssociationsError, ListConversionAssociationsResponse, AddConversionAssociationsData, AddConversionAssociationsError, AddConversionAssociationsResponse, RemoveConversionAssociationsData, RemoveConversionAssociationsError, RemoveConversionAssociationsResponse, GetConversionMetricsData, GetConversionMetricsError, GetConversionMetricsResponse, ListWhatsAppConversionsData, ListWhatsAppConversionsError, ListWhatsAppConversionsResponse, SendWhatsAppConversionData, SendWhatsAppConversionError, SendWhatsAppConversionResponse, CreateMessagingAdData, CreateMessagingAdError, CreateMessagingAdResponse, CreateCallAdData, CreateCallAdError, CreateCallAdResponse, CreateCtwaAdData, CreateCtwaAdError, CreateCtwaAdResponse, ListCustomConversionsData, ListCustomConversionsError, ListCustomConversionsResponse, CreateCustomConversionData, CreateCustomConversionError, CreateCustomConversionResponse, ListTrackingTagsData, ListTrackingTagsError, ListTrackingTagsResponse, CreateTrackingTagData, CreateTrackingTagError, CreateTrackingTagResponse, GetTrackingTagData, GetTrackingTagError, GetTrackingTagResponse, UpdateTrackingTagData, UpdateTrackingTagError, UpdateTrackingTagResponse, ListTrackingTagSharedAccountsData, ListTrackingTagSharedAccountsError, ListTrackingTagSharedAccountsResponse, AddTrackingTagSharedAccountData, AddTrackingTagSharedAccountError, AddTrackingTagSharedAccountResponse, RemoveTrackingTagSharedAccountData, RemoveTrackingTagSharedAccountError, RemoveTrackingTagSharedAccountResponse, GetTrackingTagStatsData, GetTrackingTagStatsError, GetTrackingTagStatsResponse, ListBlogsData, ListBlogsError, ListBlogsResponse, CreateBlogData, CreateBlogError, CreateBlogResponse, GetBlogData, GetBlogError, GetBlogResponse, UpdateBlogData, UpdateBlogError, UpdateBlogResponse, DeleteBlogData, DeleteBlogError, DeleteBlogResponse, ListBlogArticlesData, ListBlogArticlesError, ListBlogArticlesResponse, CreateBlogArticleData, CreateBlogArticleError, CreateBlogArticleResponse, GetBlogArticleData, GetBlogArticleError, GetBlogArticleResponse, UpdateBlogArticleData, UpdateBlogArticleError, UpdateBlogArticleResponse, DeleteBlogArticleData, DeleteBlogArticleError, DeleteBlogArticleResponse, CreateVerificationData, CreateVerificationError, CreateVerificationResponse, GetVerificationData, GetVerificationError, GetVerificationResponse, CheckVerificationData, CheckVerificationError, CheckVerificationResponse } from './types.gen'; export const client = createClient(createConfig()); /** * Validate character count * Check weighted character count per platform and whether the text is within each platform's limit. * * Twitter/X uses weighted counting (URLs = 23 chars via t.co, emojis = 2 chars). All other platforms use plain character length. * * Returns counts and limits for all 15 supported platform variants. * */ export const validatePostLength = (options: OptionsLegacyParser) => { return (options?.client ?? client).post({ ...options, url: '/v1/tools/validate/post-length' }); }; /** * Validate post content * Dry-run the full post validation pipeline without publishing. Catches issues like missing media for Instagram/TikTok/YouTube, hashtag limits, invalid thread formats, Facebook Reel requirements, and character limit violations. * * Accepts the same body as POST /v1/posts. Does NOT validate accounts, process media, or track usage. Account lookups are limit-only: a twitter accountId is resolved, scoped to the caller, only to pick the 280 vs 25000 character limit. Missing, foreign, or invalid ids fall back to 280 and never error. * * Returns errors for failures and warnings for near-limit content (>90% of character limit). * */ export const validatePost = (options: OptionsLegacyParser) => { return (options?.client ?? client).post({ ...options, url: '/v1/tools/validate/post' }); }; /** * Validate media URL * Check if a media URL is accessible and return metadata (content type, file size) plus per-platform size limit comparisons. * * Performs a HEAD request (with GET fallback) to detect content type and size. Rejects private/localhost URLs for SSRF protection. * * Platform limits are sourced from each platform's actual upload constraints. * */ export const validateMedia = (options: OptionsLegacyParser) => { return (options?.client ?? client).post({ ...options, url: '/v1/tools/validate/media' }); }; /** * Check subreddit existence * Check if a subreddit exists and return basic info (title, subscriber count, NSFW status, post types allowed). * * When accountId is provided, uses authenticated Reddit OAuth API with automatic token refresh (recommended). Falls back to Reddit's public JSON API, which may be unreliable from server IPs. Returns exists: false for private, banned, or nonexistent subreddits. * */ export const validateSubreddit = (options: OptionsLegacyParser) => { return (options?.client ?? client).get({ ...options, url: '/v1/tools/validate/subreddit' }); }; /** * Get post analytics * Returns analytics for posts. With postId, returns a single post. Without it, returns a paginated list with overview stats. * Accepts both Zernio Post IDs and External Post IDs (auto-resolved). fromDate defaults to 90 days ago if omitted, max range 366 days. * Single post lookups may return 202 (sync pending) or 424 (all platforms failed). For follower stats, use /v1/accounts/follower-stats. * * LinkedIn personal accounts: Analytics are only available for posts published through Zernio. LinkedIn's API only returns metrics for posts authored by the authenticated user. Organization/company page analytics work for all posts. * */ export const getAnalytics = (options?: OptionsLegacyParser) => { return (options?.client ?? client).get({ ...options, url: '/v1/analytics' }); }; /** * Get YouTube channel insights * Returns channel-scoped aggregate metrics from YouTube Analytics API v2. Saves you * from looping /v1/analytics/youtube/daily-views over every video when you only need * channel totals. * * Response shape matches /v1/analytics/instagram/account-insights so the same client * handling works. Requires yt-analytics.readonly scope (412 with reauthorizeUrl if * missing). Data has a 2-3 day delay (endDate is clamped accordingly). Max 89 days, * defaults to last 30 days. Requires the Analytics add-on. * * NOT exposed: impressions (Studio thumbnail impressions) and impressionsClickThroughRate. * YouTube Analytics API v2 does not expose these for any principal type, not channel * owners, not Partner Program channels, not content owners with CMS access. The only way * to get them is Studio CSV export. This is a Google-side limitation. * */ export const getYouTubeChannelInsights = (options: OptionsLegacyParser) => { return (options?.client ?? client).get({ ...options, url: '/v1/analytics/youtube/channel-insights' }); }; /** * Get LinkedIn org analytics * Returns aggregate analytics for a LinkedIn organization page. Parallel to * /v1/accounts/{id}/linkedin-aggregate-analytics (which handles personal accounts only). * Backed by LinkedIn's organizationalEntityShareStatistics, * organizationalEntityFollowerStatistics, and organizationPageStatistics endpoints. * * Response shape matches /v1/analytics/instagram/account-insights. Max 89 days, * defaults to last 30 days. Requires the Analytics add-on. * * Scope requirements: r_organization_social, r_organization_followers, and * r_organization_admin must all be present on the account. Accounts connected before * these scopes were included in the OAuth flow will return 412 with a reauth hint. * * Enforced by this endpoint: * - Page-view metrics accept only metricType=total_value (LinkedIn omits per-day * segmentation even when the API is called with DAY granularity, so a time-series * response would be meaningless). * - Date range capped at 89 days. * * LinkedIn-side platform limits (not re-enforced here, but worth knowing for larger * ranges in a future release): * - Follower stats: rolling 12-month window, end must be no later than 2 days ago. * - Share stats: rolling 12-month window. * */ export const getLinkedInOrgAggregateAnalytics = (options: OptionsLegacyParser) => { return (options?.client ?? client).get({ ...options, url: '/v1/analytics/linkedin/org-aggregate-analytics' }); }; /** * Get TikTok account-level insights * Returns account-level TikTok insights from /v2/user/info/ (live) plus historical * time series joined from Zernio's daily snapshotter (AccountStats). * * Response shape matches /v1/analytics/instagram/account-insights. Max 89 days, * defaults to last 30 days. Requires the Analytics add-on and the user.info.stats * scope on the account (412 if missing). * * Scope intentionally narrow. TikTok's public API exposes only the four counter * metrics below. The deep metrics that live in TikTok Studio are NOT available on any * public TikTok API, even for Business accounts: * - profile_views * - account-level impressions / reach * - follower inflow / outflow breakdown * - video watch time, average watch time, full-watched rate * - impression_sources (FYP / Following / Hashtag / Search / Personal profile) * * TikTok's Research API doesn't expose those fields either, and is restricted to * non-commercial academic use per TikTok's eligibility policy. There is no public * API workaround. Post-level metrics (views, likes, comments, shares per video) are * available via /v1/analytics?postId=... from TikTok's /v2/video/query/. * */ export const getTikTokAccountInsights = (options: OptionsLegacyParser) => { return (options?.client ?? client).get({ ...options, url: '/v1/analytics/tiktok/account-insights' }); }; /** * Get YouTube daily views * Returns daily view counts for a YouTube video including views, watch time, and subscriber changes. * Requires yt-analytics.readonly scope (re-authorization may be needed). YouTube finalizes analytics * with a ~3-day delay; by default only finalized days are returned, and an explicit endDate can reach * into the delay window (see the endDate parameter). Max 90 days, defaults to last 30 days. * */ export const getYouTubeDailyViews = (options: OptionsLegacyParser) => { return (options?.client ?? client).get({ ...options, url: '/v1/analytics/youtube/daily-views' }); }; /** * Get YouTube video retention curve * Returns the audience retention curve for a single YouTube video, plus the video's * duration for rendering the curve on a time axis. The curve has up to 100 points * (elapsedVideoTimeRatio 0.01-1.0) aggregated over the whole date range; YouTube does * not support per-day retention breakdowns. * * audienceWatchRatio is the absolute share of viewers watching at that point in the * video and can exceed 1 (rewinds and looping, common on Shorts). relativeRetentionPerformance * compares against videos of similar length (0 = worst, 0.5 = median, 1 = best). * YouTube returns an empty curve for videos with very few views or before analytics * processing completes (2-3 day delay). * * Requires yt-analytics.readonly scope (re-authorization may be needed). * */ export const getYouTubeVideoRetention = (options: OptionsLegacyParser) => { return (options?.client ?? client).get({ ...options, url: '/v1/analytics/youtube/video-retention' }); }; /** * Get Facebook Page insights * Returns page-level Facebook insights (media views, views, post engagements, video metrics, * follower counts). Response shape matches /v1/analytics/instagram/account-insights so the * same client handling works across platforms. * * Metric names track the current (post-November 2025) Meta Graph API. The legacy * page_impressions / page_fans / page_fan_adds / page_fan_removes metrics were deprecated * by Meta on November 15, 2025 and are NOT accepted by this endpoint. Use the replacements * below. Because Meta did not provide direct adds/removes replacements, Zernio synthesizes * followers_gained / followers_lost from the daily follower snapshotter. * * Max 89 days, defaults to last 30 days. Requires the Analytics add-on. * */ export const getFacebookPageInsights = (options: OptionsLegacyParser) => { return (options?.client ?? client).get({ ...options, url: '/v1/analytics/facebook/page-insights' }); }; /** * Get Facebook post monetization earnings * Returns lifetime monetization earnings for ONE Facebook post, read live from Meta on every * request. Requires the Analytics add-on. * * Earnings are CUMULATIVE since the post was published, not earnings within a date range, so * this endpoint takes no since/until and the totals must not be summed across dates or across * posts. Page-level daily earnings live on /v1/analytics/facebook/page-insights. * * A post on a Page that is not enrolled in monetization, or that earned nothing, returns * "total": 0 rather than an error: Meta does not distinguish the two. A metric Meta returned no * bucket for at all is reported in "unavailableMetrics" and omitted from "metrics", never as a 0. * * Amounts are the platform's raw numbers in the stated "unit" and are never rescaled by Zernio. * Breakdown dimensions are not exposed and a "breakdown" param is rejected with 400. So are * "since", "until", "period", and "metricType": scoping this endpoint to a window is not * possible, and silently returning the lifetime total for one would let a caller sum a year of * weekly requests into a figure ~52x the post's real earnings. * */ export const getFacebookPostEarnings = (options: OptionsLegacyParser) => { return (options?.client ?? client).get({ ...options, url: '/v1/analytics/facebook/post-earnings' }); }; /** * Get Instagram insights * Returns account-level Instagram insights such as reach, views, accounts engaged, and total interactions. * These metrics reflect the entire account's performance across all content surfaces (feed, stories, explore, profile), * and are fundamentally different from post-level metrics. Data may be delayed up to 48 hours. * Max 90 days, defaults to last 30 days. Requires the Analytics add-on. * */ export const getInstagramAccountInsights = (options: OptionsLegacyParser) => { return (options?.client ?? client).get({ ...options, url: '/v1/analytics/instagram/account-insights' }); }; /** * Get Instagram follower history * Returns a daily running Instagram follower count time series, served from Zernio's * cross-platform daily snapshotter. Exists because Meta removed follower_count from * the /insights endpoint in Graph API v22+ and never exposed a historical daily series * via any public API. * * Response envelope matches /v1/analytics/instagram/account-insights so the same client * handling works. Max 89 days, defaults to last 30 days. Requires the Analytics add-on. * */ export const getInstagramFollowerHistory = (options: OptionsLegacyParser) => { return (options?.client ?? client).get({ ...options, url: '/v1/analytics/instagram/follower-history' }); }; /** * Get Instagram demographics * Returns audience demographic insights for an Instagram account, broken down by age, city, country, and/or gender. * Requires at least 100 followers. Returns top 45 entries per dimension. * Data may be delayed up to 48 hours. Requires the Analytics add-on. * */ export const getInstagramDemographics = (options: OptionsLegacyParser) => { return (options?.client ?? client).get({ ...options, url: '/v1/analytics/instagram/demographics' }); }; /** * Get YouTube demographics * Returns audience demographic insights for a YouTube channel, broken down by age, gender, and/or country. * Pass videoId to get the audience profile of a single video instead of the whole channel. * Age and gender values are viewer percentages (0-100). Country values are view counts. * Data is based on signed-in viewers only, with a 2-3 day delay. YouTube suppresses demographics * for videos with too few signed-in views, so low-traffic videos can return empty breakdowns. * Requires the Analytics add-on. * */ export const getYouTubeDemographics = (options: OptionsLegacyParser) => { return (options?.client ?? client).get({ ...options, url: '/v1/analytics/youtube/demographics' }); }; /** * Get daily aggregated metrics * Returns daily aggregated analytics metrics and a per-platform breakdown. * Each day includes post count, platform distribution, and summed metrics (impressions, reach, likes, comments, shares, saves, clicks, views). * Defaults to the last 180 days. Requires the Analytics add-on. * */ export const getDailyMetrics = (options?: OptionsLegacyParser) => { return (options?.client ?? client).get({ ...options, url: '/v1/analytics/daily-metrics' }); }; /** * Get best times to post * Returns the best times to post based on historical engagement data. * Groups all published posts by day of week and hour (UTC), calculating average engagement per slot. * Use this to auto-schedule posts at optimal times. Requires the Analytics add-on. * */ export const getBestTimeToPost = (options?: OptionsLegacyParser) => { return (options?.client ?? client).get({ ...options, url: '/v1/analytics/best-time' }); }; /** * Get content performance decay * Returns how engagement accumulates over time after a post is published. * Each bucket shows what percentage of the post's total engagement had been reached by that time window. * Useful for understanding content lifespan (e.g. "posts reach 78% of total engagement within 24 hours"). * Requires the Analytics add-on. * */ export const getContentDecay = (options?: OptionsLegacyParser) => { return (options?.client ?? client).get({ ...options, url: '/v1/analytics/content-decay' }); }; /** * Get frequency vs engagement * Returns the correlation between posting frequency (posts per week) and engagement rate, broken down by platform. * Helps find the optimal posting cadence for each platform. Each row represents a specific (platform, posts_per_week) combination * with the average engagement rate observed across all weeks matching that frequency. * Requires the Analytics add-on. * */ export const getPostingFrequency = (options?: OptionsLegacyParser) => { return (options?.client ?? client).get({ ...options, url: '/v1/analytics/posting-frequency' }); }; /** * Get post analytics timeline * Returns a daily timeline of analytics metrics for a specific post, showing how impressions, likes, * and other metrics evolved day-by-day since publishing. Each row represents one day of data per platform. * For multi-platform Zernio posts, returns separate rows for each platform. Requires the Analytics add-on. * */ export const getPostTimeline = (options: OptionsLegacyParser) => { return (options?.client ?? client).get({ ...options, url: '/v1/analytics/post-timeline' }); }; /** * Get GBP performance metrics * Returns daily performance metrics for a Google Business Profile location. * Metrics include impressions (Maps/Search, desktop/mobile), website clicks, * call clicks, direction requests, conversations, bookings, and food orders. * Data may be delayed 2-3 days. Max 18 months of historical data. * Requires the Analytics add-on. * */ export const getGoogleBusinessPerformance = (options: OptionsLegacyParser) => { return (options?.client ?? client).get({ ...options, url: '/v1/analytics/googlebusiness/performance' }); }; /** * Get GBP search keywords * Returns search keywords that triggered impressions for a Google Business Profile location. * Data is aggregated monthly. Keywords below a minimum impression threshold set by Google are excluded. * Max 18 months of historical data. Requires the Analytics add-on. * */ export const getGoogleBusinessSearchKeywords = (options: OptionsLegacyParser) => { return (options?.client ?? client).get({ ...options, url: '/v1/analytics/googlebusiness/search-keywords' }); }; /** * Get inbox messaging volume * Daily inbox messaging volume + breakdowns. Folds the raw messaging * events into three projections so the client can render the volume * chart, KPI strip, and per-platform stacked bar from a single call. * Max date range is 365 days. * */ export const getInboxVolume = (options: OptionsLegacyParser) => { return (options?.client ?? client).get({ ...options, url: '/v1/analytics/inbox/volume' }); }; /** * Get day × hour heatmap * Day-of-week × hour-of-day breakdown of inbox messages. Buckets are * sparse — only cells with at least one event are returned; clients * zero-fill the rest to render the full 7×24 grid. The `dow` field * follows ClickHouse's `toDayOfWeek` convention (1 = Monday … 7 = * Sunday). Max date range is 365 days. * */ export const getInboxHeatmap = (options: OptionsLegacyParser) => { return (options?.client ?? client).get({ ...options, url: '/v1/analytics/inbox/heatmap' }); }; /** * Get inbox source breakdown * Breakdown of inbox messages by their lineage source (the * `metadata.source` field set at ingest time: human / workflow / * sequence / broadcast / comment_automation / api / contact / * platform). Each source row also carries a per-platform sub-split. * Max date range is 365 days. * */ export const getInboxSourceBreakdown = (options: OptionsLegacyParser) => { return (options?.client ?? client).get({ ...options, url: '/v1/analytics/inbox/source-breakdown' }); }; /** * Get inbox response-time stats * Time-to-first-response stats. Pairs each received message with the * next sent message in the same conversation and reports the delta * as both summary statistics and a fixed-bucket histogram suited * for the analytics page's TTR chart. * * `sampleSize` reflects only conversations that received AND got a * reply in the window — received-but-never-answered conversations * are excluded. Compare against /v1/analytics/inbox/volume's * `summary.received` to compute reply rate. * * Max date range is 365 days. * */ export const getInboxResponseTime = (options: OptionsLegacyParser) => { return (options?.client ?? client).get({ ...options, url: '/v1/analytics/inbox/response-time' }); }; /** * Get top accounts by inbox volume * Leaderboard of social accounts by inbox message volume. Decorates * each row with display labels from the live SocialAccount record * (so the UI shows username + displayName, not just an ID). Accounts * that no longer map to a SocialAccount surface as "(disconnected)" * so the row stays visible. Max date range is 365 days. * */ export const getInboxTopAccounts = (options: OptionsLegacyParser) => { return (options?.client ?? client).get({ ...options, url: '/v1/analytics/inbox/top-accounts' }); }; /** * List conversation analytics * Per-conversation listing with per-row totals + first/last message * timestamps. The inbox analog of GET /v1/analytics (posts listing) — * same filter shape, same pagination, same sort/order semantics. * Use as the entry point for the per-conversation analytics drawer * at /v1/analytics/inbox/conversations/{conversationId}. * * Rows are enriched with the conversation's participant info * (`participantName`, `participantUsername`, `participantPicture`) * and last-message preview by joining the Conversation document * scoped to the caller's team. Max date range is 365 days. * */ export const listInboxConversationAnalytics = (options: OptionsLegacyParser) => { return (options?.client ?? client).get({ ...options, url: '/v1/analytics/inbox/conversations' }); }; /** * Get conversation analytics * Per-conversation inbox analytics. The inbox analog of * /v1/analytics/post-timeline — one conversation, daily totals, * source mix. * * The {conversationId} path param accepts EITHER the Mongo `_id` of * the Conversation document OR its `platformConversationId` (the * same identity used by metadata.conversationId at ingest time). * Ownership is verified in MongoDB against the caller's team * before the Tinybird query fires. * * Max date range is 365 days. * */ export const getInboxConversationAnalytics = (options: OptionsLegacyParser) => { return (options?.client ?? client).get({ ...options, url: '/v1/analytics/inbox/conversations/{conversationId}' }); }; /** * List groups * Returns all account groups visible to the authenticated user. Groups can * contain accounts from multiple profiles. For API keys scoped to specific * profiles, only groups whose accounts all live in allowed profiles are * returned. * */ export const listAccountGroups = (options?: OptionsLegacyParser) => { return (options?.client ?? client).get({ ...options, url: '/v1/account-groups' }); }; /** * Create group * Creates a new account group with a name and a list of social account IDs. * Accounts can belong to different profiles; the caller must have access to * every account's profile. Group names must be unique per user. * */ export const createAccountGroup = (options: OptionsLegacyParser) => { return (options?.client ?? client).post({ ...options, url: '/v1/account-groups' }); }; /** * Update group * Updates the name or account list of an existing group. You can rename the group, change its accounts, or both. */ export const updateAccountGroup = (options: OptionsLegacyParser) => { return (options?.client ?? client).put({ ...options, url: '/v1/account-groups/{groupId}' }); }; /** * Delete group * Permanently deletes an account group. The accounts themselves are not affected. */ export const deleteAccountGroup = (options: OptionsLegacyParser) => { return (options?.client ?? client).delete({ ...options, url: '/v1/account-groups/{groupId}' }); }; /** * Get upload URL * Get a presigned URL to upload files directly to cloud storage (up to 5GB). Returns an uploadUrl and publicUrl. PUT your file to the uploadUrl, then use the publicUrl in your posts. */ export const getMediaPresignedUrl = (options: OptionsLegacyParser) => { return (options?.client ?? client).post({ ...options, url: '/v1/media/presign' }); }; /** * Search posts * Search Reddit posts using a connected account. Optionally scope to a specific subreddit. */ export const searchReddit = (options: OptionsLegacyParser) => { return (options?.client ?? client).get({ ...options, url: '/v1/reddit/search' }); }; /** * Get subreddit feed * Fetch posts from a subreddit feed. Supports sorting, time filtering, and cursor-based pagination. */ export const getRedditFeed = (options: OptionsLegacyParser) => { return (options?.client ?? client).get({ ...options, url: '/v1/reddit/feed' }); }; /** * Account billing snapshot (plan, cycle, balance, caps, status) * The billing "wallet/statement" view: current plan, billing cycle, * accrued balance + remaining credits this period, spend caps, and * payment / access status. This is the billing half of the legacy * `/v1/usage-stats` snapshot — the per-product consumption half is metering * and lives on `GET /v1/usage`. * * Usage-based (Metronome) accounts get a populated `balance`; legacy Stripe * accounts get `balance: null` plus a deprecated `legacy.limits` block and, * when payment-blocked, `status.openInvoiceUrl` / `status.declineReason`. * */ export const getBilling = (options?: OptionsLegacyParser) => { return (options?.client ?? client).get({ ...options, url: '/v1/billing' }); }; /** * Get X/Twitter API pricing table * Returns Zernio's canonical X/Twitter API pricing table. Each X action has its * own Metronome product and its own rate, and Zernio passes X API costs through * at exact rates with zero markup. * * The response is identical for every authenticated user (pricing is universal), * so it is safe to cache on the client for the duration of a billing period. * * To compute your own per-operation spend, pair this endpoint with * `GET /v1/usage-stats` — that endpoint returns `usage.xApiCallsByOperation` * keyed by the same `operation` field you get here. * */ export const getXApiPricing = (options?: OptionsLegacyParser) => { return (options?.client ?? client).get({ ...options, url: '/v1/billing/x-pricing' }); }; /** * Usage snapshot (default) or billed-spend metering (with params) * Dual-mode endpoint, selected by query params — fully backward * compatible: * * **Without metering params (the default):** the plan / quota / usage * snapshot — plan name, billing period, limits, usage counts, access * state. Identical to `GET /v1/usage-stats`. Existing integrations keep * working unchanged. * * **With `range`, `granularity`, `from`, or `to`:** usage METERING — * billed spend (USD) by product family (`accounts`, `numbers`, `calls`, * `sms`, `dlc`, `xApi`, `credits`, `other`) over the window, at * `day` / `month` / `total` granularity, from Metronome's invoice * breakdown (the CHARGE view — always reconciles with what gets billed). * Also served at `GET /v1/usage/daily`. Usage-based accounts only — * legacy Stripe accounts get `{ "supported": false, "days": [] }`. * * For per-domain consumption *volumes* use `GET /v1/usage/calls` and * `GET /v1/usage/sms`. For the billing statement (balance, credits, * caps, payment status) use `GET /v1/billing`. * */ export const getUsage = (options?: OptionsLegacyParser) => { return (options?.client ?? client).get({ ...options, url: '/v1/usage' }); }; /** * @deprecated * Get plan and usage snapshot (plan, limits, payment status) * The plan / quota / payment-status snapshot: current plan name, billing * period, plan limits, usage counts, and access state. Identical to a * bare `GET /v1/usage` call (this path is its deprecated alias). For * billed spend by product, call `GET /v1/usage` with `range` / * `granularity` params. The statement view (balance, credits, caps, * payment status) lives at `GET /v1/billing`. * * The response shape depends on the account's `billingSystem`: * * Stripe users: per-period `usage.uploads` / `usage.profiles` counters. * * Metronome (usage-based) users: `usage.connectedAccounts`, * `usage.xApiCallsByOperation` (per-operation X API call counts — * resolve keys via `GET /v1/billing/x-pricing`), plus a `spend` * block with `currentPeriodCents`, `xSpendCents`, and * `xSpendLimitCents`. The legacy `usage.xApiCalls` 3-tier * aggregate is still emitted for back-compat but excludes the * $0.200 URL tier and any future tiers — new clients should * consume `xApiCallsByOperation` only. * */ export const getUsageStats = (options?: OptionsLegacyParser) => { return (options?.client ?? client).get({ ...options, url: '/v1/usage-stats' }); }; /** * Calling usage and cost * Aggregated calling usage across your numbers, both channels * (WhatsApp Business Calling + regular phone/PSTN): call counts, * answered counts, minutes, and cost. Use it for cost visibility or to * rebill your own customers per number. * * Costs come from each call's billing snapshot, so this endpoint always * agrees with the invoice: `billableUSD` is what Zernio bills; * `metaUSD` is the WhatsApp per-minute charge Meta bills directly to * your WABA (display only, never billed by Zernio). * * Optional `groupBy` returns a breakdown by UTC day, by your number, or * by channel. Defaults to the last 30 days. * */ export const getCallsUsage = (options?: OptionsLegacyParser) => { return (options?.client ?? client).get({ ...options, url: '/v1/usage/calls' }); }; /** * SMS usage (volumes) * Aggregated SMS/MMS volumes across your numbers: sent, received, and * total message counts, with an optional breakdown by UTC day or by * number. Defaults to the last 30 days. * * Volumes only, deliberately: SMS cost is carrier-rated asynchronously * and billed to your invoice, so per-message cost is not available here. * Calling usage (GET /v1/usage/calls) does include billable cost. * */ export const getSmsUsage = (options?: OptionsLegacyParser) => { return (options?.client ?? client).get({ ...options, url: '/v1/usage/sms' }); }; /** * List posts * Returns a paginated list of posts. Published posts include platformPostUrl with the public URL on each platform. */ export const listPosts = (options?: OptionsLegacyParser) => { return (options?.client ?? client).get({ ...options, url: '/v1/posts' }); }; /** * Create post * Create and optionally publish a post. Immediate posts (`publishNow: true`) include `platformPostUrl` in the response. * Content is optional when media is attached, all platforms have `customContent`, every platform entry is an X Article (`platformSpecificData.article`), or every platform entry is a LinkedIn plain repost (`platformSpecificData.reshareUrl` with no text). See each platform's schema for media constraints. * * ## Idempotency * * Two layers of duplicate-protection apply, so safe-to-retry callers (network blips, n8n / Zapier retries, etc.) don't accidentally double-post. * * **1. Same-request idempotency (5-minute window).** * Pass an `x-request-id` header to mark a logical request. If a second request arrives with the same `x-request-id` while the first is in-flight (or within ~5 minutes of completion), we return **HTTP 200** with the original post in the `existingPost` field — no new post is created. The official Zernio SDKs auto-generate a unique `x-request-id` per call. If you're using a generic HTTP client (curl, n8n's HTTP node, Zapier, custom code), either: * - Set a unique `x-request-id` per logical call (recommended — UUIDv4 is fine) * - Or simply omit the header — we'll treat each request as new * * **Common pitfall**: if your workflow tool uses a single execution-level request ID and reuses it across multiple HTTP nodes (e.g. one ID for the whole run, shared across 6 different platform calls), every call after the first will look like a retry of the first and return its post. Generate a fresh ID per node. * * **2. Content-hash dedup (24-hour window).** * Independently, we hash `(platform, accountId, content + media URLs)` and reject duplicates within 24 hours with **HTTP 409**. This catches genuine "same content posted twice to the same account" cases regardless of `x-request-id`. Returns `error`, `accountId`, `platform`, and `existingPostId` so you can find the original. To intentionally re-post identical content within 24h, change something (the caption, the media, the account) — the dedup is keyed on the full content fingerprint. * * Order: same-`x-request-id` retries (200) are checked first; if no idempotency match, the content-hash dedup (409) runs. * */ export const createPost = (options: OptionsLegacyParser) => { return (options?.client ?? client).post({ ...options, url: '/v1/posts' }); }; /** * Sync an external post * Fetch an account's latest external posts (published directly on the platform, not through Zernio) on demand, so a just-published post is retrievable within seconds instead of waiting for the background sync (which refreshes each account at most every ~90 minutes). * * Primary use case: verifying a submitted post. When a user publishes on the platform and immediately pastes the post URL into your app, call this with `accountId` plus `url` (or `postId`) to confirm the post exists and return its metadata. * * Behavior: * - We check our stored copy first and return immediately if the post is already known (no platform call). * - Otherwise we fetch the account's latest posts live from the platform, then match and return the submitted post. * - Requests are debounced per account (~15s): if the account was just synced, the live fetch is skipped. * * `accountId` is required — a post URL or id alone cannot be resolved to an account, and the account must be connected to Zernio (we use its token to read the platform). Supported for every platform with a listing API (Instagram, Facebook, TikTok, YouTube, X, Threads, Pinterest, Reddit, Bluesky, Google Business, and LinkedIn organization accounts). * * LinkedIn personal profiles: LinkedIn has no listing API for personal profiles, so a `url` is REQUIRED and imports that single post. Pass any LinkedIn post URL (`linkedin.com/posts/…`, `linkedin.com/feed/update/urn:li:activity:…`) or a `urn:li:share:…` / `urn:li:ugcPost:…` URN. Works for posts published outside Zernio and before the account was connected, any age; the post must be authored by the connected member. Imported posts return full analytics (impressions, reach, reactions, comments, reshares, saves) and keep refreshing on the background analytics cycle, but carry no content/media (LinkedIn does not expose them for personal profiles). * * `url` accepts any format the platform uses (e.g. `instagram.com/p/…`, `instagram.com/reel/…`, `youtu.be/…`, `youtube.com/shorts/…`, `tiktok.com/@user/video/…`, and `vm.tiktok.com` short links). Pass `postId` (the platform media/video id) as an alternative locator. * * Note: post-level analytics (reach, impressions) still carry the platform's own delay (e.g. ~24h on Instagram). This endpoint confirms the post exists and returns its metadata plus basic engagement (likes, comments), not delayed insights. * */ export const syncExternalPosts = (options: OptionsLegacyParser) => { return (options?.client ?? client).post({ ...options, url: '/v1/posts/sync-external' }); }; /** * Get post * Fetch a single post by ID. For published posts, this returns platformPostUrl for each platform. * */ export const getPost = (options: OptionsLegacyParser) => { return (options?.client ?? client).get({ ...options, url: '/v1/posts/{postId}' }); }; /** * Update post * Update an existing post. Draft, scheduled, failed, partial, and cancelled posts can be edited. * Published posts can only have their recycling config updated. * * To promote a draft to scheduled, send `isDraft: false` together with `scheduledFor` (or `publishNow: true`, * or `queuedFromProfile`). If `isDraft` is omitted the post keeps its current draft status, so sending only * `scheduledFor` to a draft returns 200 but the post remains a draft. * * Non-draft updates run the same per-platform validation as post creation (media requirements, platform-specific * field rules, etc.) against the resulting platforms, returning 400 on failure. * */ export const updatePost = (options: OptionsLegacyParser) => { return (options?.client ?? client).put({ ...options, url: '/v1/posts/{postId}' }); }; /** * Delete post * Delete a draft or scheduled post from Zernio. Published posts cannot be deleted; use the Unpublish endpoint instead. Upload quota is automatically refunded. */ export const deletePost = (options: OptionsLegacyParser) => { return (options?.client ?? client).delete({ ...options, url: '/v1/posts/{postId}' }); }; /** * Bulk upload from CSV * Create multiple posts by uploading a CSV file. Use dryRun=true to validate without creating posts. */ export const bulkUploadPosts = (options: OptionsLegacyParser) => { return (options?.client ?? client).post({ ...options, ...formDataBodySerializer, headers: { 'Content-Type': null, ...options?.headers }, url: '/v1/posts/bulk-upload' }); }; /** * Retry failed post * Immediately retries publishing a failed post. Returns the updated post with its new status. */ export const retryPost = (options: OptionsLegacyParser) => { return (options?.client ?? client).post({ ...options, url: '/v1/posts/{postId}/retry' }); }; /** * Unpublish post * Deletes a published post from the specified platform. The post record in Zernio is kept but its status is updated to cancelled. * Not supported on Instagram, TikTok, or Snapchat. Threaded posts delete all items. YouTube deletion is permanent. * */ export const unpublishPost = (options: OptionsLegacyParser) => { return (options?.client ?? client).post({ ...options, url: '/v1/posts/{postId}/unpublish' }); }; /** * Edit published post * Edit the text of an already-published post. Supported on X (Twitter), Discord, * Facebook, Reddit, LinkedIn, Telegram, Pinterest, Google Business Profile, YouTube, * and Slack. When a post was published to several accounts on the same platform, * pass `accountId` to pick which account's copy to edit (the first entry is edited * otherwise). Each platform enforces its own rules: * * **X (Twitter)** * - Connected X account must have an active X Premium subscription * - Must be within 1 hour of original publish time * - Maximum 5 edits per tweet (enforced by X) * - Threads cannot be edited, only single tweets * - X assigns a NEW post ID on edit, returned as `id` * * **Discord** * - No time limit and no premium requirement * - The message ID is unchanged after the edit * * **Facebook** * - Graph only permits editing a post that the same app created, so this works on * posts published through Zernio and is rejected for posts created in Meta * Business Suite / Composer or by another tool * - Media cannot be swapped, only the message text * - Reactions, comments, and shares are preserved. The post ID is unchanged * * **Reddit** * - Self-posts only. A link post has no editable body and is rejected before the write * - Body only. Reddit exposes no API to edit a post title, ever * - The post ID is unchanged * * **LinkedIn** * - Text only, no time limit. Media, polls, articles, and reshare targets cannot be * changed * - Works for member and organization posts published through this API. The post * keeps its ID and LinkedIn shows an "edited" marker * - Text is limited to 3,000 characters; mentions and hashtags are preserved * * **Telegram** * - No time limit; messages published through Zernio are editable indefinitely * - Text posts: edits the message text (up to 4096 characters) * - Media posts: edits the caption only (up to 1024 characters). The media itself * cannot be swapped * - For albums, the caption shown on the album (its first message) is edited * - The message ID is unchanged * * **Pinterest** * - Description only, maximum 800 characters. Media, link, and board cannot be * changed, and a pin title derived from the old content's first line at publish * stays as-is * - Pinterest's pin-update endpoint is currently in closed beta; until the app is * allowlisted by Pinterest, edits are rejected with a "beta feature not yet * enabled" error * - The pin ID is unchanged * * **Google Business Profile** * - Post body (summary) text only. Call-to-action, event/offer fields, and media are * untouched * - No time limit and no edit limit. The post ID is unchanged * - The post must still exist on Google: a post deleted from the Business Profile * dashboard, or an event/offer post past its end date, returns a 404 * * **YouTube** * - `content` replaces the video description only. The title is unchanged, even if * it was originally derived from the content's first line at publish time * - Title, tags, thumbnail, and privacy edits belong to `POST /v1/posts/{postId}/update-metadata` * - No time window and no edit cap. The video ID is unchanged * * **Slack** * - Text only, up to 4,000 characters. Media cannot be swapped, and media posts * whose share message reference never resolved cannot be edited * - No time limit unless workspace admins restrict message editing * - The message ID is unchanged * * Media edits are not supported on any platform. The post record in Zernio is updated * with the new content and an edit-history entry. * */ export const editPost = (options: OptionsLegacyParser) => { return (options?.client ?? client).post({ ...options, url: '/v1/posts/{postId}/edit' }); }; /** * Update post metadata * Updates metadata of a published video on the specified platform without re-uploading. * Currently only supported for YouTube. At least one updatable field is required. * * Two modes: * * 1. Post-based (video published through Zernio): pass the Zernio postId in the URL and platform in the body. * 2. Direct video ID (video uploaded outside Zernio, e.g. directly to YouTube): use _ as the postId, * and pass videoId + accountId + platform in the body. The accountId is the Zernio social account ID * for the connected YouTube channel. * */ export const updatePostMetadata = (options: OptionsLegacyParser) => { return (options?.client ?? client).post({ ...options, url: '/v1/posts/{postId}/update-metadata' }); }; /** * List users * Returns all users in the workspace including roles and profile access. Also returns the currentUserId of the caller. */ export const listUsers = (options?: OptionsLegacyParser) => { return (options?.client ?? client).get({ ...options, url: '/v1/users' }); }; /** * Get user * Returns a single user's details by ID, including name, email, and role. */ export const getUser = (options: OptionsLegacyParser) => { return (options?.client ?? client).get({ ...options, url: '/v1/users/{userId}' }); }; /** * List profiles * Returns profiles sorted default-first, then by creation date. Filter with name (exact match) and paginate with limit/skip; without those params the full list is returned unchanged. Use includeOverLimit=true to include profiles that exceed the plan limit. */ export const listProfiles = (options?: OptionsLegacyParser) => { return (options?.client ?? client).get({ ...options, url: '/v1/profiles' }); }; /** * Create profile * Creates a new profile with a name, optional description, and color. Names are unique per workspace: a duplicate returns a 409 whose details.existingProfileId carries the id of the existing profile. Send an Idempotency-Key header to make retries safe: a retried create with the same key and body replays the original 201 (same _id) instead of conflicting. */ export const createProfile = (options: OptionsLegacyParser) => { return (options?.client ?? client).post({ ...options, url: '/v1/profiles' }); }; /** * Get profile * Returns a single profile by ID, including its name, color, and default status. */ export const getProfile = (options: OptionsLegacyParser) => { return (options?.client ?? client).get({ ...options, url: '/v1/profiles/{profileId}' }); }; /** * Update profile * Updates a profile's name, description, color, or default status. */ export const updateProfile = (options: OptionsLegacyParser) => { return (options?.client ?? client).put({ ...options, url: '/v1/profiles/{profileId}' }); }; /** * Delete profile * Permanently deletes a profile. Active connected accounts block deletion (returns 400) - disconnect them first. Any remaining disconnected accounts and provisioned WhatsApp numbers are moved to another of your profiles (a new one is created only if needed), never deleted. */ export const deleteProfile = (options: OptionsLegacyParser) => { return (options?.client ?? client).delete({ ...options, url: '/v1/profiles/{profileId}' }); }; /** * List accounts * Returns connected social accounts. Only includes accounts within the plan limit by default. Follower data requires analytics add-on. * Supports optional server-side pagination via page/limit params. When omitted, returns all accounts (backward-compatible). * page and limit must be supplied together; out-of-range page/limit values are rejected with 400 rather than silently clamped. * */ export const listAccounts = (options?: OptionsLegacyParser) => { return (options?.client ?? client).get({ ...options, url: '/v1/accounts' }); }; /** * Get follower stats * Returns follower count history and growth metrics for connected social accounts. * Requires analytics add-on subscription. Follower counts are refreshed once per day. * */ export const getFollowerStats = (options?: OptionsLegacyParser) => { return (options?.client ?? client).get({ ...options, url: '/v1/accounts/follower-stats' }); }; /** * Update account * Updates a connected social account's display name or username override. * * For X/Twitter accounts on usage-based billing, also accepts an `xCapabilities` * object to toggle background API operations that incur X API pass-through costs. * Both fields are opt-in (default `false`) — when off, no analytics syncs or DM * polling are performed for that account, and no API call is metered for those * operations. Publishing and deleting posts are always available regardless of * these toggles. Setting `xCapabilities` on a non-X account returns 400. * */ export const updateAccount = (options: OptionsLegacyParser) => { return (options?.client ?? client).put({ ...options, url: '/v1/accounts/{accountId}' }); }; /** * Move account to another profile * Moves a connected social account to a different profile owned by the same * user. The target profile must belong to the same user as the account. * * For API keys restricted to specific profiles, BOTH the source account's * current profile AND the target profile must be in the key's allowed set. * Calls with a target profile outside the key's scope return 403. * */ export const moveAccountToProfile = (options: OptionsLegacyParser) => { return (options?.client ?? client).patch({ ...options, url: '/v1/accounts/{accountId}' }); }; /** * Disconnect account * Disconnects and removes a connected social account. */ export const deleteAccount = (options: OptionsLegacyParser) => { return (options?.client ?? client).delete({ ...options, url: '/v1/accounts/{accountId}' }); }; /** * Check accounts health * Returns health status of all connected accounts including token validity, permissions, and issues needing attention. */ export const getAllAccountsHealth = (options?: OptionsLegacyParser) => { return (options?.client ?? client).get({ ...options, url: '/v1/accounts/health' }); }; /** * Register a connected WhatsApp number on the Cloud API * Re-runs Meta's Cloud API registration for a WhatsApp account that is already connected. * Use it when the number has its own two-step verification PIN: the connect flows register * with a default PIN, Meta rejects that with error 133005, and the number then fails every * send with the misleading '(#200) You do not have the necessary permission to send messages' * while the account still shows as connected. The PIN is used for this call only and is not stored. * */ export const registerWhatsAppNumber = (options: OptionsLegacyParser) => { return (options?.client ?? client).post({ ...options, url: '/v1/accounts/{accountId}/whatsapp/register' }); }; /** * Check account health * Returns detailed health info for a specific account including token status, permissions, and recommendations. * * For WhatsApp accounts the response also includes `platformConnection`, a live probe of the * Meta link behind the channel (the same read as `GET /v1/whatsapp/number-info`). The OAuth * token can be perfectly valid while Meta refuses to serve the phone-number object (for * example after a phone-side coexistence disconnect), so `tokenStatus` alone is not a * liveness signal for WhatsApp. When the Meta link is dead, `platformConnection.status` is * `disconnected` and the overall `status` is `error`. * */ export const getAccountHealth = (options: OptionsLegacyParser) => { return (options?.client ?? client).get({ ...options, url: '/v1/accounts/{accountId}/health' }); }; /** * Check whether an Instagram user follows the account * Resolves the follow relationship between an Instagram user and the connected * account, plus their public profile counters. * * `userId` is the Instagram-scoped id (IGSID) Meta gives you on a webhook: * `sender.id` on `message.received`, `comment.author.id` on `comment.received`. * * **Meta only answers for people who have MESSAGED the account.** Commenting grants * no consent, so a commenter who has never DMed you is unresolvable - that is a * platform rule, not a limitation of this endpoint. When it cannot be resolved the * response is still `200` with `isFollower: null` and an `unavailableReason`, because * "unknown" is a normal state to branch on: * * * `consent_required` - the user has never messaged this account. * * `dm_access_disabled` - the account owner turned off Instagram Direct API access. * * `not_messageable` - the id is not a messaging-scoped id. * * `error` - a transient Graph API failure. * * To gate a comment automation on this, use the automation's `audience` rules instead * of calling this per comment - they run the same lookup only on comments that * actually match a keyword, and can ask the commenter to confirm with one tap. * * Answers are cached briefly per (account, user). Pass `refresh=true` right after * asking someone to follow, so a follow from a moment ago is visible. * */ export const getInstagramFollowStatus = (options: OptionsLegacyParser) => { return (options?.client ?? client).get({ ...options, url: '/v1/accounts/{accountId}/follow-status/{userId}' }); }; /** * Get TikTok creator info * Returns TikTok creator details, available privacy levels, posting limits, and commercial content options for a specific TikTok account. Only works with TikTok accounts. */ export const getTikTokCreatorInfo = (options: OptionsLegacyParser) => { return (options?.client ?? client).get({ ...options, url: '/v1/accounts/{accountId}/tiktok/creator-info' }); }; /** * Verify credential * Checks whether the bearer credential on this request is valid, without reading any data. Accepts an API key or an OAuth access token. Intended for clients that must validate a credential before use (for example an MCP server verifying an incoming token) so they do not have to call a data endpoint to do it. */ export const verifyCredential = (options?: OptionsLegacyParser) => { return (options?.client ?? client).get({ ...options, url: '/v1/auth/verify' }); }; /** * List keys * Returns all API keys for the authenticated user. Keys are returned with a preview only, not the full key value. */ export const listApiKeys = (options?: OptionsLegacyParser) => { return (options?.client ?? client).get({ ...options, url: '/v1/api-keys' }); }; /** * Create key * Creates a new API key with an optional expiry. The full key value is only returned once in the response. */ export const createApiKey = (options: OptionsLegacyParser) => { return (options?.client ?? client).post({ ...options, url: '/v1/api-keys' }); }; /** * Delete key * Permanently revokes and deletes an API key. */ export const deleteApiKey = (options: OptionsLegacyParser) => { return (options?.client ?? client).delete({ ...options, url: '/v1/api-keys/{keyId}' }); }; /** * List connected apps * Returns the OAuth clients (AI assistants and MCP connectors) the authenticated * user has authorized and that still hold a live token. * * Requires a session or a full-access API key. A profile-scoped API key, a * restricted (zrk_) API key, or an OAuth access token is rejected with 403: an * app must not be able to enumerate its sibling authorizations, and connected-app * management is admin-plane. * */ export const listConnectedApps = (options?: OptionsLegacyParser) => { return (options?.client ?? client).get({ ...options, url: '/v1/me/connected-apps' }); }; /** * Revoke connected app * Ends an app's access: invalidates the client's pending authorization codes and * revokes every live token it holds for the authenticated user. Takes effect on * the app's next request. * * Idempotent while the authorization is still on record: revoking an app that * was already revoked returns 200 with `revokedTokens: 0`. * * Requires a session or a full-access API key. A profile-scoped API key, a * restricted (zrk_) API key, or an OAuth access token is rejected with 403. * */ export const revokeConnectedApp = (options: OptionsLegacyParser) => { return (options?.client ?? client).delete({ ...options, url: '/v1/me/connected-apps/{clientId}' }); }; /** * Create invite token * Generate a secure invite link to grant team members access to your profiles. * Invites expire after 7 days and are single-use. * * Returns 403 when a requested profile is not found or not owned, or when * called with a restricted (zrk_) API key: invite management is admin-plane. * */ export const createInviteToken = (options: OptionsLegacyParser) => { return (options?.client ?? client).post({ ...options, url: '/v1/invite/tokens' }); }; /** * Get OAuth connect URL * Initiate an OAuth connection flow. Returns an authUrl to redirect the user to. * Standard flow: Zernio hosts the selection UI, then redirects to your redirect_url. Headless mode (headless=true): user is redirected to your redirect_url with OAuth data for custom UI. Use the platform-specific selection endpoints to complete. * */ export const getConnectUrl = (options: OptionsLegacyParser) => { return (options?.client ?? client).get({ ...options, url: '/v1/connect/{platform}' }); }; /** * Complete OAuth callback * Exchange the OAuth authorization code for tokens and connect the account to the specified profile. */ export const handleOAuthCallback = (options: OptionsLegacyParser) => { return (options?.client ?? client).post({ ...options, url: '/v1/connect/{platform}' }); }; /** * Connect ads for a platform * Unified ads connection endpoint. Creates a dedicated ads SocialAccount for the specified platform. * * Same-token platforms (facebook, instagram, linkedin, pinterest): Creates an ads SocialAccount (metaads, linkedinads, pinterestads) with a copied OAuth token from the parent posting account. If the ads account already exists, returns alreadyConnected: true. No extra OAuth needed. * * Separate-token platforms (tiktok, twitter): Starts the platform-specific marketing API OAuth flow and creates an ads SocialAccount (tiktokads, xads) with its own token. If the ads account already exists, returns alreadyConnected: true. * - tiktok: accountId is OPTIONAL. With accountId, the new tiktokads account links to that posting account (parentAccountId set) — Spark Ads + standalone ads using the posting TT_USER identity become available. Without accountId, ads-only mode kicks in: the new tiktokads account has parentAccountId=null and standalone ads use a synthetic CUSTOMIZED_USER ("Brand Identity"); Spark Ads are unavailable because TikTok requires a posting account for them. The Brand Identity is configured separately via PATCH /v1/connect/tiktok-ads (or inline on POST /v1/ads/create via the brandIdentity field). * - twitter (X Ads): accountId is REQUIRED. There's no ads-only mode — tweets need to be authored by a real X user. * * Standalone platforms (googleads): Starts the Google Ads OAuth flow and creates a standalone ads SocialAccount (googleads) with no parent. If the account already exists, returns alreadyConnected: true. * * Ads accounts appear as regular SocialAccount documents with ads platform values (e.g., metaads, tiktokads) in GET /v1/accounts. * */ export const connectAds = (options: OptionsLegacyParser) => { return (options?.client ?? client).get({ ...options, url: '/v1/connect/{platform}/ads' }); }; /** * Get Shopify OAuth connect URL * Initiate the Shopify OAuth flow for a store. Shopify is a connect-only * platform: the connected account does not publish social posts, it powers * the Blogs API (`/v1/accounts/{accountId}/blogs`). Returns an `authUrl` * to redirect the merchant to; after they approve the install, Shopify * redirects their browser to Zernio's callback, the account is created on * the profile (platform `shopify`), and the browser is redirected to * `redirect_url` (or the Zernio dashboard when omitted). Requested scopes * are `read_content` and `write_content` (content only; no customer or * order data). Connecting the same profile to a store again refreshes the * stored token in place. * */ export const getShopifyConnectUrl = (options: OptionsLegacyParser) => { return (options?.client ?? client).get({ ...options, url: '/v1/connect/shopify' }); }; /** * Connect a Shopify store with a custom-app Admin token * Token-paste alternative to the OAuth flow: connect a store using the * Admin API access token of a custom app the merchant created in their * own Shopify admin (Settings → Apps and sales channels → Develop apps, * with the `read_content`/`write_content` scopes). Use this when the * one-click OAuth connect is unavailable or when your users prefer not * to install a third-party app on their store. The token is validated * against the store before anything is saved; custom-app tokens do not * expire. Connecting the same profile to a store again replaces the * stored token in place. * */ export const connectShopifyWithToken = (options: OptionsLegacyParser) => { return (options?.client ?? client).post({ ...options, url: '/v1/connect/shopify/token' }); }; /** * Set TikTok brand identity * Set or update the Brand Identity (display name + avatar) for a * `tiktokads` SocialAccount. TikTok requires every ad to carry an * `identity_id + identity_type` pair. The Brand Identity is the * CUSTOMIZED_USER alternative to attributing ads to a real @username * (TT_USER). This route uploads the supplied image to TikTok, creates * the identity via `/v2/identity/create/`, and caches the resulting * `identity_id` on the account so subsequent `POST /v1/ads/create` * calls can opt into it via `identityType: 'CUSTOMIZED_USER'`. * * Configurable on every `tiktokads` account, including linked-mode ones * (those with a posting account on the same profile). Configuration is * idempotent and harmless when posting is also connected: the default * ad-create path still prefers TT_USER, and CUSTOMIZED_USER is only used * per-ad when the caller explicitly opts in. * * TikTok identities are immutable post-creation. Re-saving creates a new * identity on TikTok and swaps the cached id; the old identity stays * orphaned on TikTok's side (harmless, no billing impact). * * Alternative: pass `brandIdentity` directly on `POST /v1/ads/create` to * configure on first ad creation in a single round-trip. * */ export const configureTikTokAdsBrandIdentity = (options: OptionsLegacyParser) => { return (options?.client ?? client).patch({ ...options, url: '/v1/connect/tiktok-ads' }); }; /** * List Facebook pages * Returns the list of Facebook Pages the user can manage after OAuth. Extract tempToken and userProfile from the OAuth redirect params and pass them here. Use the X-Connect-Token header if connecting via API key. */ export const listFacebookPages = (options: OptionsLegacyParser) => { return (options?.client ?? client).get({ ...options, url: '/v1/connect/facebook/select-page' }); }; /** * Select Facebook page * Complete the headless flow by saving the user's selected Facebook page. Pass the userProfile from the OAuth redirect and use X-Connect-Token if connecting via API key. */ export const selectFacebookPage = (options: OptionsLegacyParser) => { return (options?.client ?? client).post({ ...options, url: '/v1/connect/facebook/select-page' }); }; /** * List Pages with a linked Instagram account * Completes the `loginMethod=facebook_login` Instagram flow, i.e. "Instagram API with Facebook Login". * * After the user authorizes on Facebook, extract `tempToken` from the redirect params (headless mode adds `step=select_account`) and pass it here to list the Facebook Pages they manage. Only Pages that have a linked Instagram professional account are returned, so an empty array means the user has no eligible Page. Use the X-Connect-Token header if connecting via API key. * * Not used by the default `instagram_login` flow, which creates the account without a selection step. * */ export const listInstagramPages = (options: OptionsLegacyParser) => { return (options?.client ?? client).get({ ...options, url: '/v1/connect/instagram/select-account' }); }; /** * Select the Page whose Instagram account to connect * Saves the selected Page as an Instagram account connected via Facebook Login. The Page access token becomes the account's access token, so every Instagram call for it runs against the Facebook Graph host. * * One Instagram account per profile: if the profile already has an Instagram account, this replaces it, and picking a different Instagram identity purges the previous account's conversations, external posts and stats. * */ export const selectInstagramAccount = (options: OptionsLegacyParser) => { return (options?.client ?? client).post({ ...options, url: '/v1/connect/instagram/select-account' }); }; /** * List GBP locations * For headless flows. Returns the list of GBP locations the user can manage. Use pendingDataToken (from the OAuth callback redirect) to list locations without consuming the token, so it remains available for select-location. Use X-Connect-Token header if connecting via API key. * */ export const listGoogleBusinessLocations = (options?: OptionsLegacyParser) => { return (options?.client ?? client).get({ ...options, url: '/v1/connect/googlebusiness/locations' }); }; /** * Select GBP location * Complete the headless GBP flow by saving the user's selected location. The pendingDataToken is returned in your redirect URL after OAuth completes (step=select_location). Tokens and profile data are stored server-side, so only the pendingDataToken is needed here. Use X-Connect-Token header if connecting via API key. * */ export const selectGoogleBusinessLocation = (options: OptionsLegacyParser) => { return (options?.client ?? client).post({ ...options, url: '/v1/connect/googlebusiness/select-location' }); }; /** * Get reviews * Returns reviews for a GBP account including ratings, comments, and owner replies. Use nextPageToken for pagination. */ export const getGoogleBusinessReviews = (options: OptionsLegacyParser) => { return (options?.client ?? client).get({ ...options, url: '/v1/accounts/{accountId}/gmb-reviews' }); }; /** * Get verification state * Returns the location's Voice of Merchant state plus its verification history. `voiceOfMerchantState.hasVoiceOfMerchant` tells you whether the listing is verified and published; when it is false, `verify` reports whether a verification is already pending. Each entry in `verifications` has a `state` of PENDING, COMPLETED, or FAILED. */ export const getGoogleBusinessVerifications = (options: OptionsLegacyParser) => { return (options?.client ?? client).get({ ...options, url: '/v1/accounts/{accountId}/gmb-verifications' }); }; /** * Start a verification * Starts a verification for the location. This is a mutating action: depending on `method`, Google mails a postcard, places a call, or sends an SMS/email to the business. Submit the resulting code with POST /gmb-verifications/{verificationId}/complete. Use POST /gmb-verifications/options first to discover which methods are eligible. */ export const startGoogleBusinessVerification = (options: OptionsLegacyParser) => { return (options?.client ?? client).post({ ...options, url: '/v1/accounts/{accountId}/gmb-verifications' }); }; /** * Fetch verification options * Reports the verification methods Google currently offers for the location. Non-mutating (nothing is sent to the business). `languageCode` is required; service-area ("CUSTOMER_LOCATION_ONLY") businesses also require `context.address`, otherwise Google returns 400. */ export const fetchGoogleBusinessVerificationOptions = (options: OptionsLegacyParser) => { return (options?.client ?? client).post({ ...options, url: '/v1/accounts/{accountId}/gmb-verifications/options' }); }; /** * Complete a verification * Completes a PENDING verification by submitting the PIN/code Google sent the business (postcard code, SMS PIN, etc.). On success the verification moves to COMPLETED. */ export const completeGoogleBusinessVerification = (options: OptionsLegacyParser) => { return (options?.client ?? client).post({ ...options, url: '/v1/accounts/{accountId}/gmb-verifications/{verificationId}/complete' }); }; /** * Get food menus * Returns food menus for a GBP location including sections, items, pricing, and dietary info. Only for locations with food menu support. */ export const getGoogleBusinessFoodMenus = (options: OptionsLegacyParser) => { return (options?.client ?? client).get({ ...options, url: '/v1/accounts/{accountId}/gmb-food-menus' }); }; /** * Update food menus * Updates food menus for a GBP location. Send the full menus array. Use updateMask for partial updates. */ export const updateGoogleBusinessFoodMenus = (options: OptionsLegacyParser) => { return (options?.client ?? client).put({ ...options, url: '/v1/accounts/{accountId}/gmb-food-menus' }); }; /** * Get location details * Returns detailed GBP location info (hours, description, phone, website, categories, services). Use readMask to request specific fields. */ export const getGoogleBusinessLocationDetails = (options: OptionsLegacyParser) => { return (options?.client ?? client).get({ ...options, url: '/v1/accounts/{accountId}/gmb-location-details' }); }; /** * Update location details * Updates GBP location details. The updateMask field is required and specifies which fields to update. * This endpoint proxies Google's Business Information API locations.patch, so any valid updateMask field is supported. * Common fields: regularHours, specialHours, profile.description, websiteUri, phoneNumbers, categories, serviceItems. * */ export const updateGoogleBusinessLocationDetails = (options: OptionsLegacyParser) => { return (options?.client ?? client).put({ ...options, url: '/v1/accounts/{accountId}/gmb-location-details' }); }; /** * List media * Lists media items (photos) for a Google Business Profile location. * Returns photo URLs, descriptions, categories, and metadata. * */ export const listGoogleBusinessMedia = (options: OptionsLegacyParser) => { return (options?.client ?? client).get({ ...options, url: '/v1/accounts/{accountId}/gmb-media' }); }; /** * Upload photo * Creates a media item (photo) for a location from a publicly accessible URL. * * Categories determine where the photo appears: CATEGORY_UNSPECIFIED, COVER, PROFILE, LOGO, EXTERIOR, INTERIOR, PRODUCT, FOOD_AND_DRINK, MENU, COMMON_AREA, ROOMS, TEAMS, AT_WORK, ADDITIONAL. * */ export const createGoogleBusinessMedia = (options: OptionsLegacyParser) => { return (options?.client ?? client).post({ ...options, url: '/v1/accounts/{accountId}/gmb-media' }); }; /** * Delete photo * Deletes a photo or media item from a GBP location. */ export const deleteGoogleBusinessMedia = (options: OptionsLegacyParser) => { return (options?.client ?? client).delete({ ...options, url: '/v1/accounts/{accountId}/gmb-media' }); }; /** * Get attribute metadata * Returns metadata about which Google Business Profile attributes are available for * a location or business category. Use this endpoint to discover valid attribute names, * value types, and allowed enum values before reading or writing via gmb-attributes. * * Two mutually exclusive query modes: * * **Location mode**: pass `locationId` (or rely on the account's stored `selectedLocationId`). * Google returns attributes valid for that specific location. * * **Category mode**: pass `categoryName` (must start with `categories/`) and `regionCode`. * Google returns attributes valid for that category across the given region. * `languageCode` is optional in category mode. * * Both modes support `pageSize` and `pageToken` for pagination. * */ export const getGmbAttributeMetadata = (options: OptionsLegacyParser) => { return (options?.client ?? client).get({ ...options, url: '/v1/accounts/{accountId}/gmb-attribute-metadata' }); }; /** * Get attributes * Returns GBP location attributes (amenities, services, accessibility, payment types). Available attributes vary by business category. */ export const getGoogleBusinessAttributes = (options: OptionsLegacyParser) => { return (options?.client ?? client).get({ ...options, url: '/v1/accounts/{accountId}/gmb-attributes' }); }; /** * Update attributes * Updates location attributes (amenities, services, etc.). * * The attributeMask specifies which attributes to update (comma-separated). * */ export const updateGoogleBusinessAttributes = (options: OptionsLegacyParser) => { return (options?.client ?? client).put({ ...options, url: '/v1/accounts/{accountId}/gmb-attributes' }); }; /** * List action links * Lists place action links for a Google Business Profile location. * * Place actions are the booking, ordering, and reservation buttons that appear on your listing. * */ export const listGoogleBusinessPlaceActions = (options: OptionsLegacyParser) => { return (options?.client ?? client).get({ ...options, url: '/v1/accounts/{accountId}/gmb-place-actions' }); }; /** * Create action link * Creates a place action link for a location. * * Available action types: APPOINTMENT, ONLINE_APPOINTMENT, DINING_RESERVATION, FOOD_ORDERING, FOOD_DELIVERY, FOOD_TAKEOUT, SHOP_ONLINE. * */ export const createGoogleBusinessPlaceAction = (options: OptionsLegacyParser) => { return (options?.client ?? client).post({ ...options, url: '/v1/accounts/{accountId}/gmb-place-actions' }); }; /** * Delete action link * Deletes a place action link (e.g. booking or ordering URL) from a GBP location. */ export const deleteGoogleBusinessPlaceAction = (options: OptionsLegacyParser) => { return (options?.client ?? client).delete({ ...options, url: '/v1/accounts/{accountId}/gmb-place-actions' }); }; /** * Update action link * Updates a place action link (change URL or action type). * Only the fields included in the request body will be updated. * */ export const updateGoogleBusinessPlaceAction = (options: OptionsLegacyParser) => { return (options?.client ?? client).patch({ ...options, url: '/v1/accounts/{accountId}/gmb-place-actions' }); }; /** * Get services * Gets the services offered by a Google Business Profile location. * Returns an array of service items (structured or free-form with optional price). * */ export const getGoogleBusinessServices = (options: OptionsLegacyParser) => { return (options?.client ?? client).get({ ...options, url: '/v1/accounts/{accountId}/gmb-services' }); }; /** * Replace services * Replaces the entire service list for a location. * Google's API requires full replacement; individual item updates are not supported. * Each service can be structured (using a predefined serviceTypeId) or free-form (custom label). * */ export const updateGoogleBusinessServices = (options: OptionsLegacyParser) => { return (options?.client ?? client).put({ ...options, url: '/v1/accounts/{accountId}/gmb-services' }); }; /** * Batch get reviews * Fetches reviews across multiple locations in a single request. * More efficient than calling GET /gmb-reviews per location for multi-location businesses. * Returns a flat locationReviews array (not grouped by location): each item carries * the location resource name it belongs to (`name`) plus the review object (`review`), * whose identity is `review.reviewId`. * Reviews are requested from Google ordered by `orderBy` (default `updateTime desc`, * newest first), so callers polling for recent reviews can stop paginating once they * cross their date window. * Note: this endpoint does not return aggregate metrics (averageRating / totalReviewCount). * For those, use the single-location GET /gmb-reviews endpoint. * */ export const batchGetGoogleBusinessReviews = (options: OptionsLegacyParser) => { return (options?.client ?? client).post({ ...options, url: '/v1/accounts/{accountId}/gmb-reviews/batch' }); }; /** * Reply to a review * Posts (or updates) the business owner reply to a Google Business review. * The reply is associated with the account's currently selected location (set via /v1/accounts/{accountId}/gmb-locations). * Calling this endpoint a second time on the same review overwrites the previous reply (PUT semantics on Google's side). * */ export const replyToGoogleBusinessReview = (options: OptionsLegacyParser) => { return (options?.client ?? client).post({ ...options, url: '/v1/accounts/{accountId}/gmb-reviews/{reviewId}/reply' }); }; /** * Delete a review reply * Removes the business owner reply from a Google Business review. The review itself remains. */ export const deleteGoogleBusinessReviewReply = (options: OptionsLegacyParser) => { return (options?.client ?? client).delete({ ...options, url: '/v1/accounts/{accountId}/gmb-reviews/{reviewId}/reply' }); }; /** * Get pending OAuth data * Fetch pending OAuth data for headless mode using the pendingDataToken from the redirect URL. * * **Scope**: This endpoint is used for LinkedIn organizations, Snapchat profiles, and Pinterest boards, where the selection list is too large to fit in URL params. The redirect carries a `pendingDataToken` instead of the full payload; the response includes the corresponding selection array (e.g. `boards` for Pinterest). WhatsApp, Facebook, Google Business and other platforms pass selection state directly via URL query params on the redirect (`profileId`, `tempToken`, `step`), no pending record is created, so this endpoint will return 404 for those flows. Use the platform-specific selection endpoint instead (e.g. `/v1/connect/whatsapp/select-phone-number`). * * Token is one-time use and expires after 10 minutes. No authentication required. * */ export const getPendingOAuthData = (options: OptionsLegacyParser) => { return (options?.client ?? client).get({ ...options, url: '/v1/connect/pending-data' }); }; /** * List LinkedIn orgs * Fetch full LinkedIn organization details (logos, vanity names, websites) for custom UI. No authentication required, just the tempToken from OAuth. */ export const listLinkedInOrganizations = (options: OptionsLegacyParser) => { return (options?.client ?? client).get({ ...options, url: '/v1/connect/linkedin/organizations' }); }; /** * Select LinkedIn org * Complete the LinkedIn connection flow. Set accountType to "personal" or "organization" to connect as a company page. Use X-Connect-Token if connecting via API key. */ export const selectLinkedInOrganization = (options: OptionsLegacyParser) => { return (options?.client ?? client).post({ ...options, url: '/v1/connect/linkedin/select-organization' }); }; /** * List Pinterest boards * For headless flows. Returns Pinterest boards the user can post to. Use X-Connect-Token from the redirect URL. */ export const listPinterestBoardsForSelection = (options: OptionsLegacyParser) => { return (options?.client ?? client).get({ ...options, url: '/v1/connect/pinterest/select-board' }); }; /** * Select Pinterest board * Complete the Pinterest connection flow. After OAuth, use this endpoint to save the selected board and complete the account connection. Use the X-Connect-Token header if you initiated the connection via API key. * */ export const selectPinterestBoard = (options: OptionsLegacyParser) => { return (options?.client ?? client).post({ ...options, url: '/v1/connect/pinterest/select-board' }); }; /** * List Snapchat profiles * For headless flows. Returns Snapchat Public Profiles the user can post to. Use X-Connect-Token from the redirect URL. */ export const listSnapchatProfiles = (options: OptionsLegacyParser) => { return (options?.client ?? client).get({ ...options, url: '/v1/connect/snapchat/select-profile' }); }; /** * Select Snapchat profile * Complete the Snapchat connection flow by saving the selected Public Profile. Snapchat requires a Public Profile to publish content. Use X-Connect-Token if connecting via API key. */ export const selectSnapchatProfile = (options: OptionsLegacyParser) => { return (options?.client ?? client).post({ ...options, url: '/v1/connect/snapchat/select-profile' }); }; /** * Connect Bluesky account * Connect a Bluesky account using identifier (handle or email) and an app password. * To get your userId for the state parameter, call GET /v1/users which includes a currentUserId field. * */ export const connectBlueskyCredentials = (options: OptionsLegacyParser) => { return (options?.client ?? client).post({ ...options, url: '/v1/connect/bluesky/credentials' }); }; /** * Connect an OpenAI Ads account * Connect an OpenAI Ads account using an API key from ChatGPT Ads Manager. * * The key grants full campaign write access on OpenAI's side (OpenAI does * not offer a read-only key scope). Zernio uses it to read ads and * performance, and to create and manage campaigns you set up through * Zernio (create, status, budget, and cancel). Campaigns created * directly in ChatGPT Ads Manager can still be managed there. * */ export const connectOpenAiAdsCredentials = (options: OptionsLegacyParser) => { return (options?.client ?? client).post({ ...options, url: '/v1/connect/openai-ads/credentials' }); }; /** * Connect WhatsApp via credentials * Connect a WhatsApp Business Account by providing Meta credentials directly. * This is the headless alternative to the Embedded Signup browser flow. * * To get the required credentials: * 1. Go to Meta Business Suite (business.facebook.com) * 2. Create or select a WhatsApp Business Account * 3. In Business Settings > System Users, create a System User * 4. Assign it the whatsapp_business_management and whatsapp_business_messaging permissions * 5. Generate a permanent access token * 6. Get the WABA ID from WhatsApp Manager > Account Tools > Phone Numbers * 7. Get the Phone Number ID from the same page (click on the number) * */ export const connectWhatsAppCredentials = (options: OptionsLegacyParser) => { return (options?.client ?? client).post({ ...options, url: '/v1/connect/whatsapp/credentials' }); }; /** * List numbers for selection * Fetch the WhatsApp phone numbers available across the user's WhatsApp Business Accounts (WABAs) after a headless OAuth flow. * * WhatsApp OAuth grants access at the WABA level. When a connected WABA has 2 or more phone numbers, you must call this endpoint to list them and then `POST /v1/connect/whatsapp/select-phone-number` to bind one to the Zernio profile. Single-phone WABAs auto-complete during the OAuth callback and never reach this endpoint. * * Use the `profileId` and `tempToken` returned in the headless redirect (`step=select_phone_number`). * * Alternative: if you already know `wabaId` and `phoneNumberId` (e.g. from Meta Business Suite), use `connectWhatsAppCredentials` instead, which skips this two-step flow. * */ export const listWhatsAppPhoneNumbers = (options: OptionsLegacyParser) => { return (options?.client ?? client).get({ ...options, url: '/v1/connect/whatsapp/select-phone-number' }); }; /** * Complete number selection * Bind a specific WhatsApp phone number to the Zernio profile after the user picks one from `listWhatsAppPhoneNumbers`. Exchanges the short-lived OAuth token for a long-lived token, subscribes the WABA to webhooks, and creates the SocialAccount. * */ export const completeWhatsAppPhoneSelection = (options: OptionsLegacyParser) => { return (options?.client ?? client).post({ ...options, url: '/v1/connect/whatsapp/select-phone-number' }); }; /** * Generate Telegram code * Generate an access code (valid 15 minutes) for connecting a Telegram channel or group. Add the bot as admin, then send the code + @yourchannel to the bot. Poll PATCH /v1/connect/telegram to check status. */ export const getTelegramConnectStatus = (options: OptionsLegacyParser) => { return (options?.client ?? client).get({ ...options, url: '/v1/connect/telegram' }); }; /** * Connect Telegram directly * Connect a Telegram channel/group directly using the chat ID. Alternative to the access code flow. The bot must already be an admin in the channel/group. */ export const initiateTelegramConnect = (options: OptionsLegacyParser) => { return (options?.client ?? client).post({ ...options, url: '/v1/connect/telegram' }); }; /** * Check Telegram status * Poll this endpoint to check if a Telegram access code has been used to connect a channel/group. Recommended polling interval: 3 seconds. * Status values: pending (waiting for user), connected (channel/group linked), expired (generate a new code). * */ export const completeTelegramConnect = (options: OptionsLegacyParser) => { return (options?.client ?? client).patch({ ...options, url: '/v1/connect/telegram' }); }; /** * List Facebook pages * Returns all Facebook pages the connected account has access to, including the currently selected page. */ export const getFacebookPages = (options: OptionsLegacyParser) => { return (options?.client ?? client).get({ ...options, url: '/v1/accounts/{accountId}/facebook-page' }); }; /** * Update Facebook page * Switch which Facebook Page is active for a connected account. */ export const updateFacebookPage = (options: OptionsLegacyParser) => { return (options?.client ?? client).put({ ...options, url: '/v1/accounts/{accountId}/facebook-page' }); }; /** * List LinkedIn orgs * Returns LinkedIn organizations (company pages) the connected account has admin access to. */ export const getLinkedInOrganizations = (options: OptionsLegacyParser) => { return (options?.client ?? client).get({ ...options, url: '/v1/accounts/{accountId}/linkedin-organizations' }); }; /** * Get LinkedIn aggregate stats * Returns aggregate analytics across all posts for a LinkedIn personal account. Only includes posts published through Zernio (LinkedIn API limitation). Org accounts should use /v1/analytics instead. Requires r_member_postAnalytics scope. Saves (POST_SAVE) and sends (POST_SEND) are available for personal accounts; organization pages always return 0 for these two metrics because LinkedIn does not expose them on the organization analytics endpoint. */ export const getLinkedInAggregateAnalytics = (options: OptionsLegacyParser) => { return (options?.client ?? client).get({ ...options, url: '/v1/accounts/{accountId}/linkedin-aggregate-analytics' }); }; /** * Get LinkedIn post stats * Returns analytics for a specific LinkedIn post by URN. Works for both personal and organization accounts. Saves and sends are only populated for personal accounts (LinkedIn does not expose these metrics on the organization analytics endpoint). */ export const getLinkedInPostAnalytics = (options: OptionsLegacyParser) => { return (options?.client ?? client).get({ ...options, url: '/v1/accounts/{accountId}/linkedin-post-analytics' }); }; /** * Get LinkedIn post reactions * Returns individual reactions for a specific LinkedIn post, including reactor profiles * (name, headline/job title, profile picture, profile URL, reaction type). * Only works for organization/company page accounts. LinkedIn restricts reaction * data for personal profiles (r_member_social_feed is a closed permission). * */ export const getLinkedInPostReactions = (options: OptionsLegacyParser) => { return (options?.client ?? client).get({ ...options, url: '/v1/accounts/{accountId}/linkedin-post-reactions' }); }; /** * Switch LinkedIn account type * Switch a LinkedIn account between personal profile and organization (company page) posting. */ export const updateLinkedInOrganization = (options: OptionsLegacyParser) => { return (options?.client ?? client).put({ ...options, url: '/v1/accounts/{accountId}/linkedin-organization' }); }; /** * Resolve LinkedIn mention * Converts a LinkedIn profile or company URL to a URN for @mentions in posts. * * How to use LinkedIn @mentions (2-step workflow): * * 1. Call this endpoint with the LinkedIn profile/company URL to get the mention URN and format. * 2. Embed the returned mentionFormat (e.g. @[Vincent Jong](urn:li:person:xxx)) directly in your post's content field. * * Example: * - Resolve: GET /v1/accounts/{id}/linkedin-mentions?url=linkedin.com/in/vincentjong&displayName=Vincent Jong * - Returns: mentionFormat: "@[Vincent Jong](urn:li:person:xxx)" * - Use in post content: "Great talk with @[Vincent Jong](urn:li:person:xxx) today!" * * Important: The mentions array field in POST /v1/posts is stored for reference only and does NOT trigger @mentions on LinkedIn. You must embed the mention format directly in the content text. * * Requirements: * - Person mentions require the LinkedIn account to be admin of at least one organization. This is a LinkedIn API limitation: the only endpoints that resolve profile URLs to member URNs (vanityUrl, peopleTypeahead) are scoped to organization followers. There is no public LinkedIn API to resolve a vanity URL without organization context. * - Organization mentions (e.g. @Microsoft) work without this requirement. * - For person mentions to be clickable, the displayName parameter must exactly match the name shown on their LinkedIn profile. * - Person mentions DO work when published from personal profiles (the URN just needs to be valid). The limitation is only in the resolution step (URL to URN), not in publishing. * */ export const getLinkedInMentions = (options: OptionsLegacyParser) => { return (options?.client ?? client).get({ ...options, url: '/v1/accounts/{accountId}/linkedin-mentions' }); }; /** * List active Instagram stories * Returns the IG Business/Creator account's currently-active stories. * Meta keeps stories live for 24h; expired stories are not returned. * * Limitations propagated from Meta (these are NOT bugs): * - 24h window only * - Live videos excluded * - Reshared stories not returned * - `mediaUrl` may be null if Meta flagged the story for copyright * - `caption`, `likeCount`, `commentsCount` do not apply to story media * */ export const listInstagramStories = (options: OptionsLegacyParser) => { return (options?.client ?? client).get({ ...options, url: '/v1/accounts/{accountId}/instagram/stories' }); }; /** * Get Instagram publishing limit * Returns the account's remaining content-publishing quota for Instagram's rolling * 24-hour window, so you can pace publishing and warn before the cap is reached. * * `quotaUsage` counts containers published since the start of the window. * Always compare against the returned `quotaTotal` rather than hardcoding a number: * Meta's prose documentation and the live API disagree on the value, and the live * value is authoritative. * */ export const getInstagramPublishingLimit = (options: OptionsLegacyParser) => { return (options?.client ?? client).get({ ...options, url: '/v1/accounts/{accountId}/instagram/publishing-limit' }); }; /** * Search Instagram audio * Search Instagram's audio catalog (licensed music or original sounds), * or list what is currently trending by omitting `q`. Returns up to ~30 * assets; Meta exposes no pagination on this edge. * * Pass the returned `audioId` as * `platformSpecificData.audioConfiguration.audioId` when creating a Reel * to publish it with that track. * * Requires an Instagram account connected via **Facebook Login**. Meta * hosts this catalog on graph.facebook.com only, so accounts connected * with classic Instagram Login receive a 400 * (`instagram_audio_requires_facebook_login`) and must be reconnected * choosing the Facebook option. * */ export const searchInstagramAudio = (options: OptionsLegacyParser) => { return (options?.client ?? client).get({ ...options, url: '/v1/accounts/{accountId}/instagram/audio' }); }; /** * Get Instagram audio metadata * Fetch one audio asset's metadata by ID. Use it to re-validate a stored * `audioId` before a scheduled Reel publishes, or to refresh the preview * `downloadUrl` (Meta expires preview URLs after roughly 1.5 days). * * Same connection requirement as the search endpoint: Facebook-Login * Instagram accounts only. * */ export const getInstagramAudio = (options: OptionsLegacyParser) => { return (options?.client ?? client).get({ ...options, url: '/v1/accounts/{accountId}/instagram/audio/{audioId}' }); }; /** * Get Instagram story insights * Returns metrics for a single story. The `source` field discriminates * between three states: * * - `live` — fetched from Meta in real time (story is still active) * - `cached` — fetched from a persisted `story_insights` webhook payload * (story has expired but we received its final-state metrics from Meta) * - `unavailable` — story has expired and we never received its webhook * payload (for example, the account connected after the story expired) * * Meta can report an expired story as an empty successful result rather * than an error, so an expired story resolves to `cached` or `unavailable` * even though the upstream request itself succeeded. * * Field semantics follow Meta's API. Counts below 5 may be returned as 0 * due to Meta's privacy floor on small audiences. The `navigation` field * is the sum of `tapsForward + tapsBack + exits + swipesForward`. * */ export const getInstagramStoryInsights = (options: OptionsLegacyParser) => { return (options?.client ?? client).get({ ...options, url: '/v1/accounts/{accountId}/instagram/stories/{storyId}/insights' }); }; /** * List Pinterest boards * Returns the boards available for a connected Pinterest account. Use this to get a board ID when creating a Pinterest post. */ export const getPinterestBoards = (options: OptionsLegacyParser) => { return (options?.client ?? client).get({ ...options, url: '/v1/accounts/{accountId}/pinterest-boards' }); }; /** * Set default Pinterest board * Sets the default board used when publishing pins for this account. */ export const updatePinterestBoards = (options: OptionsLegacyParser) => { return (options?.client ?? client).put({ ...options, url: '/v1/accounts/{accountId}/pinterest-boards' }); }; /** * Create Pinterest board * Creates a new board on the connected Pinterest account. The returned board ID can be used immediately as `platformSpecificData.boardId` when creating a Pinterest post. */ export const createPinterestBoard = (options: OptionsLegacyParser) => { return (options?.client ?? client).post({ ...options, url: '/v1/accounts/{accountId}/pinterest-boards' }); }; /** * List YouTube playlists * Returns the playlists available for a connected YouTube account. Use this to get a playlist ID when creating a YouTube post with the playlistId field. */ export const getYoutubePlaylists = (options: OptionsLegacyParser) => { return (options?.client ?? client).get({ ...options, url: '/v1/accounts/{accountId}/youtube-playlists' }); }; /** * Set default YouTube playlist * Sets the default playlist used when publishing videos for this account. When a post does not specify a playlistId, the default playlist is not automatically used (it is stored for client-side convenience). */ export const updateYoutubeDefaultPlaylist = (options: OptionsLegacyParser) => { return (options?.client ?? client).put({ ...options, url: '/v1/accounts/{accountId}/youtube-playlists' }); }; /** * List GBP locations * Returns Google Business Profile locations the connected account can access, plus the currently selected location. The list is bounded (see hasMore); for accounts that own many locations, use the search or filter query params to find a specific one instead of loading them all, or raise limit to enumerate an account with more than 100 locations. * */ export const getGmbLocations = (options: OptionsLegacyParser) => { return (options?.client ?? client).get({ ...options, url: '/v1/accounts/{accountId}/gmb-locations' }); }; /** * Update GBP location * Switch which GBP location is active for a connected account. */ export const updateGmbLocation = (options: OptionsLegacyParser) => { return (options?.client ?? client).put({ ...options, url: '/v1/accounts/{accountId}/gmb-locations' }); }; /** * Assign GBP location to another profile * 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 (browser OAuth), then bulk-assign the rest onto each client's profile via this endpoint. The path `accountId` is a SOURCE connected GBP account (the token holder); the body `profileId` is the TARGET profile. Returns 409 if the target profile already has a Google Business connection (switch its location with PUT gmb-locations instead). * */ export const assignGoogleBusinessLocation = (options: OptionsLegacyParser) => { return (options?.client ?? client).post({ ...options, url: '/v1/accounts/{accountId}/gmb-locations/assign' }); }; /** * Get Facebook post reactions * Returns the reaction breakdown for a Facebook Page post: a count per reaction type * plus the overall total. * * The whole breakdown is fetched in a single Graph call. Note that the post analytics * endpoint reports only an aggregate reaction count (surfaced there as `likes`), so use * this endpoint when you need per-type counts. * */ export const getFacebookPostReactions = (options: OptionsLegacyParser) => { return (options?.client ?? client).get({ ...options, url: '/v1/accounts/{accountId}/facebook-post-reactions' }); }; /** * List Reddit subreddits * Returns the subreddits the connected Reddit account can post to. Use this to get a subreddit name when creating a Reddit post. */ export const getRedditSubreddits = (options: OptionsLegacyParser) => { return (options?.client ?? client).get({ ...options, url: '/v1/accounts/{accountId}/reddit-subreddits' }); }; /** * Set default subreddit * Sets the default subreddit used when publishing posts for this Reddit account. */ export const updateRedditSubreddits = (options: OptionsLegacyParser) => { return (options?.client ?? client).put({ ...options, url: '/v1/accounts/{accountId}/reddit-subreddits' }); }; /** * Get subreddit rules * Returns a subreddit's posting rules plus Reddit's site-wide rules, so you can check * them before submitting and avoid a removal. * * Use this alongside `POST /v1/tools/validate/subreddit`, which only confirms that a * subreddit exists and reports its basic posting settings. * */ export const getSubredditRules = (options: OptionsLegacyParser) => { return (options?.client ?? client).get({ ...options, url: '/v1/accounts/{accountId}/reddit-subreddits/{subreddit}/rules' }); }; /** * Vote on a Reddit post or comment * Cast, change, or clear the connected account's vote on a Reddit post or comment. * * **Reddit requires that votes be cast by humans.** Reddit's API terms permit a client * to proxy a human's action one-for-one, and prohibit a bot from deciding how to vote * or from amplifying a human's vote. Call this endpoint only in direct response to an * explicit action by the account owner. Automated or agent-decided voting is * vote manipulation and puts API access at risk. * */ export const voteRedditThing = (options: OptionsLegacyParser) => { return (options?.client ?? client).post({ ...options, url: '/v1/accounts/{accountId}/reddit-vote' }); }; /** * List subreddit flairs * Returns available post flairs for a subreddit. Some subreddits require a flair when posting. */ export const getRedditFlairs = (options: OptionsLegacyParser) => { return (options?.client ?? client).get({ ...options, url: '/v1/accounts/{accountId}/reddit-flairs' }); }; /** * Set Reddit post flair * Applies a flair to a post the connected account already published. Use the GET on this * path to list the available `flairTemplateId` values for the subreddit. * * Flair can also be set at submit time by passing `flairId` in `platformSpecificData` * when creating the post. This endpoint is for changing it afterwards. * * The subreddit must allow users to select their own post flair. Setting flair on * another user's post requires moderator permissions, which Zernio does not request. * */ export const setRedditPostFlair = (options: OptionsLegacyParser) => { return (options?.client ?? client).post({ ...options, url: '/v1/accounts/{accountId}/reddit-flairs' }); }; /** * Get Slack account settings * Returns the connected Slack channel details and the default message identity (name and avatar shown as the author on every post, with Slack's APP badge). The identity applies to messages only; the app's own Slack profile is global and cannot be changed per workspace. */ export const getSlackSettings = (options: OptionsLegacyParser) => { return (options?.client ?? client).get({ ...options, url: '/v1/accounts/{accountId}/slack-settings' }); }; /** * Update Slack account settings * Set or clear the default message identity for this channel. Empty string clears a field; per-post platformSpecificData.username/iconUrl still override these defaults. */ export const updateSlackSettings = (options: OptionsLegacyParser) => { return (options?.client ?? client).patch({ ...options, url: '/v1/accounts/{accountId}/slack-settings' }); }; /** * Get Bluesky account settings * Returns the account's default post languages (defaultLangs), applied at publish time whenever a post's platformSpecificData.langs is absent. Null when no default is set. */ export const getBlueskySettings = (options: OptionsLegacyParser) => { return (options?.client ?? client).get({ ...options, url: '/v1/accounts/{accountId}/bluesky-settings' }); }; /** * Update Bluesky account settings * Set or clear the account's default post languages. 1-3 BCP-47 codes (e.g. "pt", "en-US"), the same validation as per-post langs; explicit null clears the default. Per-post platformSpecificData.langs always overrides this default. Applies to posts published after the change; already-published posts cannot be retagged (Bluesky has no post edit). */ export const updateBlueskySettings = (options: OptionsLegacyParser) => { return (options?.client ?? client).patch({ ...options, url: '/v1/accounts/{accountId}/bluesky-settings' }); }; /** * Get Discord account settings * Returns the current Discord account settings including webhook identity (display name and avatar), connected channel, and guild information. */ export const getDiscordSettings = (options: OptionsLegacyParser) => { return (options?.client ?? client).get({ ...options, url: '/v1/accounts/{accountId}/discord-settings' }); }; /** * Update Discord settings * Update Discord account settings. Supports two operations (can be combined): * * 1. **Webhook identity** - Set the default display name and avatar that appear as the message author on every post. These are account-level defaults; individual posts can override them via platformSpecificData.webhookUsername / webhookAvatarUrl. * * 2. **Switch channel** - Move the connection to a different channel in the same guild. A new webhook is automatically created in the target channel. * */ export const updateDiscordSettings = (options: OptionsLegacyParser) => { return (options?.client ?? client).patch({ ...options, url: '/v1/accounts/{accountId}/discord-settings' }); }; /** * List Discord guild channels * Returns the text, announcement, and forum channels in the connected Discord guild. Use this to discover available channels when switching the connected channel via PATCH /v1/accounts/{accountId}/discord-settings. */ export const getDiscordChannels = (options: OptionsLegacyParser) => { return (options?.client ?? client).get({ ...options, url: '/v1/accounts/{accountId}/discord-channels' }); }; /** * List Slack workspace members * Members of the connected Slack workspace that can receive a direct message, for populating a recipient picker. Bots, deactivated members and Slackbot are excluded. Start a DM by passing a member id as `participantId` to POST /v1/inbox/conversations. */ export const listSlackMembers = (options: OptionsLegacyParser) => { return (options?.client ?? client).get({ ...options, url: '/v1/accounts/{accountId}/slack-members' }); }; /** * Send a Discord Direct Message * Send a 1:1 Direct Message from the bot to a Discord user (by snowflake ID). * Supports the same payload shape as channel posts — content, embeds, media * attachments, and TTS. * * Constraints (Discord platform limits): * - The bot can only DM users it shares at least one guild with. * - If the recipient has DMs disabled for non-friends, Discord returns 403 * (surfaces as a 502 platform error). * - `content` capped at 2,000 chars. * - At least one of `content`, `embeds`, or `attachments` is required. * - The recipient must be identified by Discord snowflake ID (not username). * * This is a dedicated endpoint rather than a `POST /v1/posts` variant because * DMs are 1:1 operational messages (onboarding, billing reminders, support * pings) with a different lifecycle than scheduled channel posts. DMs are * not persisted to `Post` / `ExternalPost` and are always sent immediately. * */ export const sendDiscordDirectMessage = (options: OptionsLegacyParser) => { return (options?.client ?? client).post({ ...options, url: '/v1/discord/dms' }); }; /** * List Discord guild roles * Returns all roles in a Discord guild. Useful for building role-mention * pickers, role-permission UIs, or finding the role ID before calling * the role-assign endpoint. * * Roles are returned unordered — sort client-side by `position` if you * need Discord's UI ordering. * * Caller must pass `accountId` of a Discord SocialAccount bound to this * guild (route verifies team access + guild match). * */ export const listDiscordGuildRoles = (options: OptionsLegacyParser) => { return (options?.client ?? client).get({ ...options, url: '/v1/discord/guilds/{guildId}/roles' }); }; /** * Create a Discord guild role * Creates a new role in the guild. * * Requires the bot to hold the Manage Roles permission. Guilds that added the Zernio bot * before role management shipped must re-invite it, because Discord applies the * permission set at invite time. * * Discord's role hierarchy applies: the bot cannot create a role positioned at or above * its own highest role, and cannot grant permissions it does not itself hold. Either * attempt returns a 403 carrying Discord's own error. * */ export const createDiscordGuildRole = (options: OptionsLegacyParser) => { return (options?.client ?? client).post({ ...options, url: '/v1/discord/guilds/{guildId}/roles' }); }; /** * Edit a Discord guild role * Updates a role's name, color, hoist, mentionable flag, or permission bitfield. * At least one field must be supplied. Omitted fields are left unchanged. * * Requires the bot to hold Manage Roles, and the target role must sit below the bot's * highest role. See the create-role operation for the re-invite requirement. * */ export const editDiscordGuildRole = (options: OptionsLegacyParser) => { return (options?.client ?? client).patch({ ...options, url: '/v1/discord/guilds/{guildId}/roles/{roleId}' }); }; /** * Delete a Discord guild role * Permanently deletes a role from the guild and removes it from every member. * This cannot be undone. * * Requires the bot to hold Manage Roles, and the target role must sit below the bot's * highest role. * */ export const deleteDiscordGuildRole = (options: OptionsLegacyParser) => { return (options?.client ?? client).delete({ ...options, url: '/v1/discord/guilds/{guildId}/roles/{roleId}' }); }; /** * List Discord guild members * Cursor-paginated list of guild members. Returns Discord's raw member * objects so callers can build community-ops automation (e.g. "add role * to all members joined in the last 7 days") on the actual platform shape. * * Pagination: pass `after` = the last `user.id` from the previous page. * Omit on the first call. Response includes a `nextCursor` and `hasMore` * flag so callers don't need to know Discord's pagination shape. * */ export const listDiscordGuildMembers = (options: OptionsLegacyParser) => { return (options?.client ?? client).get({ ...options, url: '/v1/discord/guilds/{guildId}/members' }); }; /** * Search Discord guild members * Search guild members whose username or nickname **starts with** the * query (Discord matches prefixes only, not substrings). * * Cheaper than paginating the full member listing when you already know * who you are looking for. * */ export const searchDiscordGuildMembers = (options: OptionsLegacyParser) => { return (options?.client ?? client).get({ ...options, url: '/v1/discord/guilds/{guildId}/members/search' }); }; /** * Get a Discord guild member * Fetch a single guild member by Discord user id. * * Cheaper than paginating the full member listing when you already know * who you are looking for. * */ export const getDiscordGuildMember = (options: OptionsLegacyParser) => { return (options?.client ?? client).get({ ...options, url: '/v1/discord/guilds/{guildId}/members/{userId}' }); }; /** * Assign a role to a guild member * Assign one role to one member. Idempotent on Discord's side — re-running * on a member who already has the role is a 204 no-op. * * Path shape mirrors Discord's own API (`PUT /guilds/{guild}/members/{user}/roles/{role}`) * for zero-translation mental mapping. * * Bot needs MANAGE_ROLES permission in the guild AND its highest role * must be above the target role (Discord hierarchy rule). The * `@everyone` role (where roleId == guildId) cannot be assigned. * */ export const addDiscordMemberRole = (options: OptionsLegacyParser) => { return (options?.client ?? client).put({ ...options, url: '/v1/discord/guilds/{guildId}/members/{userId}/roles/{roleId}' }); }; /** * Remove a role from a guild member * Remove one role from one member. Idempotent — removing a role the * member doesn't have returns 204 no-op. * * Same permission + hierarchy constraints as the PUT counterpart. * */ export const removeDiscordMemberRole = (options: OptionsLegacyParser) => { return (options?.client ?? client).delete({ ...options, url: '/v1/discord/guilds/{guildId}/members/{userId}/roles/{roleId}' }); }; /** * Delete a Discord channel message * Deletes a message from a channel, for moderation and cleanup. This cannot be undone. * * Deleting a message the bot did not send requires the bot to hold the Manage Messages * permission, which the Zernio bot requests at install time. Deleting the bot's own * message needs no extra permission. * * Ownership is verified by resolving the channel's guild and confirming the caller owns * a Discord account bound to it. * */ export const deleteDiscordMessage = (options: OptionsLegacyParser) => { return (options?.client ?? client).delete({ ...options, url: '/v1/discord/channels/{channelId}/messages/{messageId}' }); }; /** * Crosspost Discord message * Publishes a message from an announcement channel so it propagates to every server * following that channel. * * The source channel must be an announcement channel. Calling this on a regular text * channel returns a 400 before Discord is contacted, because Discord's own error for * this case is opaque. * */ export const crosspostDiscordMessage = (options: OptionsLegacyParser) => { return (options?.client ?? client).post({ ...options, url: '/v1/discord/channels/{channelId}/messages/{messageId}/crosspost' }); }; /** * Create a Discord public thread * Creates a public thread in a channel. Pass `messageId` to start the thread from an * existing message, or omit it to create a standalone thread. * * Threads created here are always public. Requires the bot to hold Create Public * Threads, which the Zernio bot requests at install time. * */ export const createDiscordThread = (options: OptionsLegacyParser) => { return (options?.client ?? client).post({ ...options, url: '/v1/discord/channels/{channelId}/threads' }); }; /** * List pinned messages * Returns the channel's pinned messages, sorted most-recently-pinned * first. Discord caps a channel at 50 pinned messages and returns the * full list unpaginated. * * Bot needs READ_MESSAGE_HISTORY in the channel (granted by default * BOT_PERMISSIONS). * */ export const listDiscordPinnedMessages = (options: OptionsLegacyParser) => { return (options?.client ?? client).get({ ...options, url: '/v1/discord/channels/{channelId}/pins' }); }; /** * Pin a Discord message * Pin a specific message in a channel. Path shape mirrors Discord's own * API (`PUT /channels/{cid}/pins/{mid}`). * * Idempotent — re-pinning an already-pinned message is a 204 no-op. * * Constraints: * - Bot needs MANAGE_MESSAGES in the channel. * - 50-pin cap per channel — hitting it returns 400 (Discord-side). * Caller should unpin one first. * */ export const pinDiscordMessage = (options: OptionsLegacyParser) => { return (options?.client ?? client).put({ ...options, url: '/v1/discord/channels/{channelId}/pins/{messageId}' }); }; /** * Unpin a Discord message * Unpin a message. Same MANAGE_MESSAGES permission requirement as pin. * Idempotent — unpinning a non-pinned message is a 204 no-op. * */ export const unpinDiscordMessage = (options: OptionsLegacyParser) => { return (options?.client ?? client).delete({ ...options, url: '/v1/discord/channels/{channelId}/pins/{messageId}' }); }; /** * List Discord scheduled events * Return all scheduled events in the guild. Events are distinct from * messages — they appear in the server's Events panel and Discord * auto-notifies interested members ahead of start time. * * Pass `withUserCount=true` to include `user_count` (number of members * who RSVP'd) on each event. Useful for surfacing engagement. * */ export const listDiscordScheduledEvents = (options: OptionsLegacyParser) => { return (options?.client ?? client).get({ ...options, url: '/v1/discord/guilds/{guildId}/events' }); }; /** * Create a Discord scheduled event * Create a guild scheduled event. Three event types, selected via the * discriminator on `entity.type`: * * - `external` — off-platform (Zoom, in-person, livestream). Requires * both `location` and `endsAt`. Most common type for scheduler * integrations. * - `voice` — hosted in a Discord voice channel. Requires `channelId`. * - `stage` — hosted in a Discord stage channel. Requires `channelId`. * * Bot needs MANAGE_EVENTS in the guild. Existing installs (pre-events * PR) need a re-invite OR a server admin manually granting the * permission — see route header for details. * */ export const createDiscordScheduledEvent = (options: OptionsLegacyParser) => { return (options?.client ?? client).post({ ...options, url: '/v1/discord/guilds/{guildId}/events' }); }; /** * Get a Discord scheduled event */ export const getDiscordScheduledEvent = (options: OptionsLegacyParser) => { return (options?.client ?? client).get({ ...options, url: '/v1/discord/guilds/{guildId}/events/{eventId}' }); }; /** * Update a Discord scheduled event * Patch any subset of fields. Passing `status: 'cancelled'` is how you * cancel an event — Discord doesn't have a dedicated cancel endpoint, * it's a status transition. * * Most status transitions Discord enforces (you can't go SCHEDULED → * COMPLETED directly). The common consumer case is SCHEDULED → CANCELED. * */ export const updateDiscordScheduledEvent = (options: OptionsLegacyParser) => { return (options?.client ?? client).patch({ ...options, url: '/v1/discord/guilds/{guildId}/events/{eventId}' }); }; /** * Delete a Discord scheduled event * Hard-delete an event. Use PATCH with `status: 'cancelled'` instead * if you want the event preserved in the guild's history. * */ export const deleteDiscordScheduledEvent = (options: OptionsLegacyParser) => { return (options?.client ?? client).delete({ ...options, url: '/v1/discord/guilds/{guildId}/events/{eventId}' }); }; /** * List schedules * Returns queue schedules for a profile. Use all=true for all queues, or queueId for a specific one. Defaults to the default queue. */ export const listQueueSlots = (options: OptionsLegacyParser) => { return (options?.client ?? client).get({ ...options, url: '/v1/queue/slots' }); }; /** * Create schedule * Create an additional queue for a profile. The first queue created becomes the default. * Subsequent queues are non-default unless explicitly set. * */ export const createQueueSlot = (options: OptionsLegacyParser) => { return (options?.client ?? client).post({ ...options, url: '/v1/queue/slots' }); }; /** * Update schedule * Create a new queue or update an existing one. Without queueId, creates/updates the default queue. With queueId, updates a specific queue. With setAsDefault=true, makes this queue the default for the profile. * */ export const updateQueueSlot = (options: OptionsLegacyParser) => { return (options?.client ?? client).put({ ...options, url: '/v1/queue/slots' }); }; /** * Delete schedule * Delete a queue from a profile. Pass queueId to delete a specific queue; * omit it to delete all queues for the profile. * If deleting the default queue, another queue will be promoted to default. * */ export const deleteQueueSlot = (options: OptionsLegacyParser) => { return (options?.client ?? client).delete({ ...options, url: '/v1/queue/slots' }); }; /** * Preview upcoming slots * Returns the next N upcoming queue slot times for a profile as ISO datetime strings. */ export const previewQueue = (options: OptionsLegacyParser) => { return (options?.client ?? client).get({ ...options, url: '/v1/queue/preview' }); }; /** * Get next available slot * Returns the next available queue slot for preview purposes. To create a queue post, use POST /v1/posts with queuedFromProfile instead of scheduledFor. */ export const getNextQueueSlot = (options: OptionsLegacyParser) => { return (options?.client ?? client).get({ ...options, url: '/v1/queue/next-slot' }); }; /** * List webhooks * Retrieve all configured webhooks for the authenticated user. Supports up to 50 webhooks per user. */ export const getWebhookSettings = (options?: OptionsLegacyParser) => { return (options?.client ?? client).get({ ...options, url: '/v1/webhooks/settings' }); }; /** * Create webhook * Create a new webhook configuration. Maximum 50 webhooks per user. * * `name`, `url` and `events` are required. `url` must be a valid URL and `events` must contain at least one event. Whitespace is trimmed from `url` before validation. * * Webhooks are automatically disabled after 10 consecutive delivery failures. * * A restricted (zrk_) API key can only subscribe to events whose resource group * the key holds; an event outside the key's groups is rejected with 403, so a * restricted key can never create a subscription broader than itself. * * `disabledResourceGroups` restricts the subscription itself, independently of * which key or session later reads it. Events in a disabled group are dropped * before delivery to this endpoint, on live delivery and on every replay path * (test fire, redelivery, dead-letter requeue), even if they are listed in * `events`. Omit it to receive everything in `events`, which is how existing * subscriptions behave. A restricted key's own disabled groups are always * unioned in. * */ export const createWebhookSettings = (options: OptionsLegacyParser) => { return (options?.client ?? client).post({ ...options, url: '/v1/webhooks/settings' }); }; /** * Update webhook * Update an existing webhook configuration. All fields except `_id` are optional; only provided fields will be updated. * * When provided, `name` must be 1-50 characters, `url` must be a valid URL, and `events` must contain at least one event. Whitespace is trimmed from `url` before validation. * * Webhooks are automatically disabled after 10 consecutive delivery failures. * * A restricted (zrk_) API key can only set `events` to events whose resource * group the key holds; an event outside the key's groups is rejected with 403. * It also cannot widen an existing subscription past its own groups. * * `disabledResourceGroups` replaces the subscription's own denylist, which * applies to delivery regardless of which key or session created it. Send an * empty array to clear it. A restricted key's own disabled groups are unioned * into the stored value on every update, so repointing a legacy unrestricted * subscription with a restricted key also narrows it. * * Timing: the new denylist applies to every event emitted after the update. * Events already queued for delivery when the update landed were filtered * against the previous denylist and can still arrive at your endpoint for up * to five minutes after they were enqueued, because the delivery worker * trusts a five-minute enqueue-time snapshot before re-checking the * subscription. Retries beyond that window, dead-letter replays, test fires, * and redeliveries are all checked against the current denylist. * */ export const updateWebhookSettings = (options: OptionsLegacyParser) => { return (options?.client ?? client).put({ ...options, url: '/v1/webhooks/settings' }); }; /** * Delete webhook * Permanently delete a webhook configuration. */ export const deleteWebhookSettings = (options: OptionsLegacyParser) => { return (options?.client ?? client).delete({ ...options, url: '/v1/webhooks/settings' }); }; /** * List webhook delivery logs * Retrieve recorded webhook delivery attempts for the authenticated user, most recent first. * Logs are retained for 30 days. Supports filtering by status, event type, webhook ID, and event ID, * plus offset-based pagination. * * For a restricted (zrk_) API key, rows for events outside the key's resource * groups are omitted (`pagination.total` may over-count), and an `event` filter * naming such an event is rejected with 403. Events blocked by a subscription's * own `disabledResourceGroups` are dropped before delivery, so they produce no * log rows for anyone; the exception is the five-minute tail after a denylist * change, where an already-queued event can still be delivered and logged. * */ export const getWebhookLogs = (options?: OptionsLegacyParser) => { return (options?.client ?? client).get({ ...options, url: '/v1/webhooks/logs' }); }; /** * Send test webhook * Send a test webhook to verify your endpoint is configured correctly. The test payload includes event: "webhook.test" to distinguish it from real events. * * `webhook.test` belongs to the `webhooks` resource group, so a key with that * group disabled is rejected with 403, as is a test fire on a subscription that * lists `webhooks` in its own `disabledResourceGroups` (a 403, not a reported * delivery failure). Replays of real events (redelivery, dead-letter requeue) run * the same checks as live delivery, against both the key's groups and the * subscription's. * */ export const testWebhook = (options: OptionsLegacyParser) => { return (options?.client ?? client).post({ ...options, url: '/v1/webhooks/test' }); }; /** * List activity logs * Unified logs endpoint. Returns logs for publishing, connections, webhooks, and messaging. * Filter by type, platform, status, and time range. Logs are retained for 90 days. * */ export const listLogs = (options?: OptionsLegacyParser) => { return (options?.client ?? client).get({ ...options, url: '/v1/logs' }); }; /** * List conversations * Fetch conversations (DMs) from all connected messaging accounts in a single API call. Supports filtering by profile and platform. Results are aggregated and deduplicated. * Supported platforms: Facebook, Instagram, Twitter/X, Bluesky, Reddit, Telegram. * * Twitter/X limitation: X has replaced traditional DMs with encrypted "X Chat" for many accounts. Messages sent or received through encrypted X Chat are not accessible via X's API (the /2/dm_events endpoint only returns legacy unencrypted DMs). This means some Twitter/X conversations may show only outgoing messages or appear empty. This is an X platform limitation that affects all third-party applications. See X's docs on encrypted messaging for more details. * * Instagram and Facebook pre-connect history: when one of these accounts is connected, Zernio replays the DM history the account already holds on Meta, so conversations that began before the account was connected appear here. Up to 500 conversations per account are replayed. The replay runs in the background and can finish after a listing you have already taken, and replayed conversations keep their original lastMessageAt, so they sort into date order rather than appearing at the top. If you mirror this endpoint into your own store, re-run the sweep rather than relying on a single pass at connect time. Replayed history emits no webhooks and is stored as already read, so it never affects unread counts. Threads that Meta refuses to serve are skipped, and an account whose Instagram "connected tools" message access is turned off is not replayed at all. * */ export const listInboxConversations = (options?: OptionsLegacyParser) => { return (options?.client ?? client).get({ ...options, url: '/v1/inbox/conversations' }); }; /** * Create conversation * Initiate a new direct message conversation with a specified user. If a conversation already exists with the recipient, the message is added to the existing thread. * * Supported platforms: X/Twitter, Bluesky, Reddit, WhatsApp, SMS, and Slack. Other platforms return PLATFORM_NOT_SUPPORTED. * * Slack: pass a workspace member id as participantId (list them with GET /v1/accounts/{accountId}/slack-members). Zernio opens the DM channel with that member and sends the message; the thread then behaves like any other Slack conversation in the inbox. The member must belong to the connected workspace. * * WhatsApp: this is the endpoint for sending an approved template message to a phone number. Provide templateName, templateLanguage, and templateParams (variable values for the text header, body and dynamic URL buttons, in that order), with the recipient phone in participantId. A template is required because WhatsApp does not permit freeform messages to open a conversation; a missing template returns TEMPLATE_REQUIRED. Templates with media headers (image, video, document) are handled automatically: Zernio reads the approved template definition and fills the header at send time with the template's approved sample asset. To send a DIFFERENT asset per message (e.g. a distinct invoice PDF for each recipient), pass the headerMedia field with a public link (or a Meta media id); it overrides the sample for that send. Calling this for a number you already have a thread with simply sends the template into that thread, which also makes it the way to re-engage a contact after the 24-hour customer-service window has closed. Once the recipient replies (opening the 24h window), send freeform messages with the send-message endpoint (POST /v1/inbox/conversations/{conversationId}/messages). Template fields are accepted on the JSON body only, not on multipart requests. Alternatively, WhatsApp Business Accounts eligible for Meta Direct Send can open a conversation with a business-initiated utility text message and no template: pass category: 'utility' together with message (and no templateName). See the category field below. * * DM eligibility (X/Twitter): Before sending, the endpoint checks if the recipient accepts DMs from your account (via the receives_your_dm field). If not, a 422 error with code DM_NOT_ALLOWED is returned. You can skip this check with skipDmCheck: true if you have already verified eligibility. * * X API tier requirement: DM write endpoints require X API Pro tier ($5,000/month) or Enterprise access. This applies to BYOK (Bring Your Own Key) users who provide their own X API credentials. * * Rate limits (X/Twitter only): X's DM API enforces 200 requests per 15 minutes, 1,000 per 24 hours per connected X account, and 15,000 per 24 hours per X developer app (shared across all DM endpoints). These limits do NOT apply to other platforms. WhatsApp sends are governed by Meta's per-number messaging tiers (unique business-initiated conversations per 24 hours) and per-number throughput instead. * */ export const createInboxConversation = (options: OptionsLegacyParser) => { return (options?.client ?? client).post({ ...options, url: '/v1/inbox/conversations' }); }; /** * Search conversations * Search your conversations two ways at once, and get back the matching conversations, most-recent match first: * * - Message text: matches words inside message bodies. Case-insensitive and accent-insensitive, exact tokens only (no substrings, no stemming). Each hit carries up to 3 most-recent matching messages. With direction=outgoing you can collect examples of how you write to customers, for example to teach an AI agent your tone of voice. * - Contact identity: matches the participant's name, username, or phone number as a case-insensitive substring. These hits have matchCount 0 and an empty matches array. * * A conversation that matches both ways is returned once, carrying its message matches. * * Only platforms whose messages are stored by Zernio are searchable: WhatsApp, SMS, Telegram, Facebook, Instagram, Twitter/X and Reddit. Bluesky conversations are fetched live from the platform and cannot be searched; those accounts are listed in meta.accountsSkipped. * */ export const searchInboxConversations = (options: OptionsLegacyParser) => { return (options?.client ?? client).get({ ...options, url: '/v1/inbox/conversations/search' }); }; /** * Get conversation * Retrieve details and metadata for a specific conversation. Requires accountId query parameter. */ export const getInboxConversation = (options: OptionsLegacyParser) => { return (options?.client ?? client).get({ ...options, url: '/v1/inbox/conversations/{conversationId}' }); }; /** * Update conversation status * Archive or activate a conversation. Requires accountId in request body. */ export const updateInboxConversation = (options: OptionsLegacyParser) => { return (options?.client ?? client).put({ ...options, url: '/v1/inbox/conversations/{conversationId}' }); }; /** * List messages * Fetch messages for a specific conversation, with cursor-based pagination * and ordering control. * * Pagination: pass `pagination.nextCursor` from a prior response back as * the `cursor` query param to fetch the next page. The cursor is opaque; * do not parse or construct it client-side. * * Sort order: defaults to `asc` (oldest first, chat style). For the * "show me the latest messages" pattern, pass `?sortOrder=desc&limit=N`. * Twitter, Instagram, Telegram, WhatsApp and Reddit honor the requested * order from the local message store. For Facebook and Bluesky, the * upstream APIs only return newest-first and have no order parameter — * sort order is best-effort and only reverses items within a single page * (pages still walk newest→oldest). The response field `sortOrderApplied` * tells you what was actually applied. * * Reddit threads are paginated client-side because Reddit's API has no * per-thread cursor. Very long threads may be upstream-truncated by * Reddit's inbox/sent windows (~100 most-recent items each); this is a * Reddit platform limitation. * * Instagram and Facebook conversations include history from before the * account was connected, replayed from Meta. That replay covers the 500 * most recent messages per conversation: a longer thread keeps its newest * 500 and older messages are not retrievable. Messages that arrived after * the account was connected are unaffected. Replayed messages are stored * as already read and emit no webhooks. * * Twitter/X limitation: X's encrypted "X Chat" messages are not accessible via the API. Conversations where the other participant uses encrypted X Chat may only show your outgoing messages. See the list conversations endpoint for more details. * * This endpoint is read-only and does NOT mark messages as read or send * read receipts. To mark a conversation read (and send WhatsApp blue ticks * on eligible accounts), call `POST /v1/inbox/conversations/{conversationId}/read`. * */ export const getInboxConversationMessages = (options: OptionsLegacyParser) => { return (options?.client ?? client).get({ ...options, url: '/v1/inbox/conversations/{conversationId}/messages' }); }; /** * Send message * Send a message in a conversation. Supports text, attachments, quick replies, * buttons, templates, and message tags. Attachment and interactive message * support varies by platform. * * WhatsApp template messages: to send an approved template into this * conversation (required when the 24-hour customer-service window is * closed), use the `template` field with a single element carrying the * template reference: `{ "elements": [{ "name": ..., "language": ..., "components": [...] }] }`. * See the `template` field below for the exact shape. To send a template * to a phone number you have no conversation with yet, use the * create-conversation endpoint (POST /v1/inbox/conversations) instead. * * WhatsApp rich interactive messages (list, CTA URL, Flow, location request) * are available via the `interactive` field. Tap events are delivered through * the `message.received` webhook with WhatsApp-specific `metadata` fields * (`interactiveType`, `interactiveId`, `flowResponseJson`, `flowResponseData`). * * **Idempotency:** send an `Idempotency-Key` header to make retries safe * (e.g. after a client-side timeout where delivery is unknown): same key + * same body replays the original response (with `Idempotent-Replayed: true`) * instead of sending the message a second time; same key + different body * returns 422; a key still in flight returns 409. Works for JSON and * multipart (file upload) requests alike. Keys are retained for 24 hours. * * Only successful (2xx) responses are stored for replay: if the request * throws or returns a non-2xx status, the key is released so the same key * can be retried once the problem is fixed. The header therefore protects * the "request succeeded but the response was lost" case. For an ambiguous * failure (a 5xx or a network timeout), reconcile before retrying: a * failure after the platform already accepted the message also releases * the key, and a blind retry could send it twice. List the conversation's * messages first, and treat an empty result as inconclusive rather than * as proof nothing was sent, since a send that failed while being recorded * leaves no trace on our side. * */ export const sendInboxMessage = (options: OptionsLegacyParser) => { return (options?.client ?? client).post({ ...options, url: '/v1/inbox/conversations/{conversationId}/messages' }); }; /** * Download WhatsApp media * Streams the binary for a WhatsApp attachment. This is the endpoint the * `url` on a WhatsApp `attachments[]` entry points at, in both the * `message.received` webhook and the List messages response. * * **This is an authenticated endpoint, not a public link.** Send * `Authorization: Bearer ` exactly as you would for any other * call. Passing the URL straight to a browser, an LLM vision API, or a * no-code "download file" step without the header returns `401`. This is * the most common integration mistake on this endpoint, and it differs from * Instagram, Facebook and Telegram, whose `attachments[].url` is a direct * CDN link that needs no header. * * **Fetch on receipt, not lazily.** WhatsApp media lives in Meta's media * store, not ours, and it is removed after a limited retention window * (currently 7 days, and Meta has been dropping some inbound media sooner). * Once Meta drops it the media is unrecoverable and this endpoint answers * `400` permanently, so retrying will never succeed. Download and store the * bytes when the webhook arrives. * */ export const getWhatsAppMedia = (options: OptionsLegacyParser) => { return (options?.client ?? client).get({ ...options, url: '/v1/whatsapp/media/{mediaId}' }); }; /** * Edit message * Edit the text and/or reply markup of a previously sent Telegram message. * Only supported for Telegram. Returns 400 for other platforms. * */ export const editInboxMessage = (options: OptionsLegacyParser) => { return (options?.client ?? client).patch({ ...options, url: '/v1/inbox/conversations/{conversationId}/messages/{messageId}' }); }; /** * Delete message * Delete a message from a conversation. Platform support varies: * - Telegram: Full delete (bot's own messages anytime, others if admin) * - X/Twitter: Full delete (own DM events only) * - Bluesky: Delete for self only (recipient still sees it) * - Reddit: Delete from sender's view only * - Facebook, Instagram, WhatsApp: Not supported (returns 400) * */ export const deleteInboxMessage = (options: OptionsLegacyParser) => { return (options?.client ?? client).delete({ ...options, url: '/v1/inbox/conversations/{conversationId}/messages/{messageId}' }); }; /** * Send typing indicator * Show a typing indicator in a conversation. Platform support: * - Facebook Messenger: Shows "Page is typing..." for 20 seconds * - Instagram: Shows "typing..." to the recipient (works for both Instagram Login and Facebook Login accounts). The recipient must be signed in to Instagram to see it. * - Telegram: Shows "Bot is typing..." for 5 seconds * - WhatsApp: Shows "typing..." for up to 25 seconds. Requires a recent inbound message in the conversation (Meta references the inbound message id) and also marks that message as read as a side-effect. * - All others: Returns 200 but no-op (platform doesn't support it) * * Typing indicators are best-effort. The endpoint always returns 200 even if the platform call fails; `success` reports whether a typing indicator was actually sent to the platform (`false` on unsupported platforms or when the platform call failed). * */ export const sendTypingIndicator = (options: OptionsLegacyParser) => { return (options?.client ?? client).post({ ...options, url: '/v1/inbox/conversations/{conversationId}/typing' }); }; /** * Mark a conversation as read * Marks all unread incoming messages in the conversation as read. * * For WhatsApp, this also sends read receipts (blue ticks) to the contact, * EXCEPT on coexistence accounts (where the WhatsApp Business app on the * customer's phone owns read state and we never override it). * * This is the explicit, human-driven counterpart to `GET .../messages`, * which is side-effect-free and does NOT mark anything read. Call this when * a user actually views the conversation. * */ export const markConversationRead = (options: OptionsLegacyParser) => { return (options?.client ?? client).post({ ...options, url: '/v1/inbox/conversations/{conversationId}/read' }); }; /** * Add reaction * Add an emoji reaction to a message. Platform support: * - Telegram: Supports a subset of Unicode emoji reactions * - WhatsApp: Supports any standard emoji (one reaction per message per sender) * - Instagram and Facebook Messenger: Any standard emoji, subject to Meta's 24h messaging window * - Slack: The emoji must have a Slack name (e.g. `:thumbsup:`); unnamed characters return 400 * - All others: Returns 400 (not supported) * */ export const addMessageReaction = (options: OptionsLegacyParser) => { return (options?.client ?? client).post({ ...options, url: '/v1/inbox/conversations/{conversationId}/messages/{messageId}/reactions' }); }; /** * Remove reaction * Remove a reaction from a message. Platform support: * - Telegram: Send empty reaction array to clear * - WhatsApp: Send empty emoji to remove * - Instagram and Facebook Messenger: Sends Meta's `unreact` action; the emoji does not need to be repeated * - Slack: Removes the reaction we previously sent on that message * - All others: Returns 400 (not supported) * */ export const removeMessageReaction = (options: OptionsLegacyParser) => { return (options?.client ?? client).delete({ ...options, url: '/v1/inbox/conversations/{conversationId}/messages/{messageId}/reactions' }); }; /** * Upload media file * Upload a media file using API key authentication and get back a publicly accessible URL. * The URL can be used as attachmentUrl when sending inbox messages. * * Files are stored in temporary storage and auto-delete after 7 days. * Maximum file size is 25MB. * * Unlike /v1/media/upload (which uses upload tokens for end-user flows), * this endpoint uses standard Bearer token authentication for programmatic use. * */ export const uploadMediaDirect = (options: OptionsLegacyParser) => { return (options?.client ?? client).post({ ...options, ...formDataBodySerializer, headers: { 'Content-Type': null, ...options?.headers }, url: '/v1/media/upload-direct' }); }; /** * Get FB persistent menu * Get the persistent menu configuration for a Facebook Messenger account. */ export const getMessengerMenu = (options: OptionsLegacyParser) => { return (options?.client ?? client).get({ ...options, url: '/v1/accounts/{accountId}/messenger-menu' }); }; /** * Set FB persistent menu * Set the persistent menu for a Facebook Messenger account. Max 3 top-level items, max 5 nested items. */ export const setMessengerMenu = (options: OptionsLegacyParser) => { return (options?.client ?? client).put({ ...options, url: '/v1/accounts/{accountId}/messenger-menu' }); }; /** * Delete FB persistent menu * Removes the persistent menu from Facebook Messenger conversations for this account. */ export const deleteMessengerMenu = (options: OptionsLegacyParser) => { return (options?.client ?? client).delete({ ...options, url: '/v1/accounts/{accountId}/messenger-menu' }); }; /** * Get IG ice breakers * Get the ice breaker configuration for an Instagram account. */ export const getInstagramIceBreakers = (options: OptionsLegacyParser) => { return (options?.client ?? client).get({ ...options, url: '/v1/accounts/{accountId}/instagram-ice-breakers' }); }; /** * Set IG ice breakers * Set ice breakers for an Instagram account. Max 4 ice breakers, question max 80 chars. */ export const setInstagramIceBreakers = (options: OptionsLegacyParser) => { return (options?.client ?? client).put({ ...options, url: '/v1/accounts/{accountId}/instagram-ice-breakers' }); }; /** * Delete IG ice breakers * Removes the ice breaker questions from an Instagram account's Messenger experience. */ export const deleteInstagramIceBreakers = (options: OptionsLegacyParser) => { return (options?.client ?? client).delete({ ...options, url: '/v1/accounts/{accountId}/instagram-ice-breakers' }); }; /** * Get TG bot commands * Get the bot commands configuration for a Telegram account. */ export const getTelegramCommands = (options: OptionsLegacyParser) => { return (options?.client ?? client).get({ ...options, url: '/v1/accounts/{accountId}/telegram-commands' }); }; /** * Set TG bot commands * Set bot commands for a Telegram account. */ export const setTelegramCommands = (options: OptionsLegacyParser) => { return (options?.client ?? client).put({ ...options, url: '/v1/accounts/{accountId}/telegram-commands' }); }; /** * Delete TG bot commands * Clears all bot commands configured for a Telegram bot account. */ export const deleteTelegramCommands = (options: OptionsLegacyParser) => { return (options?.client ?? client).delete({ ...options, url: '/v1/accounts/{accountId}/telegram-commands' }); }; /** * Resolve message attachment * Resolve one attachment on a message to a media url that works right now. * * Instagram and Facebook sign DM media urls per request and expire them, so * the `url` on a message is a snapshot: it works when you read the message * and stops working later. This endpoint checks the stored url and, when it * has gone stale, re-mints the message's media from Meta and persists it * before answering. The message id never expires, so this URL is the one to * store — it is returned on each attachment as `refreshUrl`. * * By default it responds `302` to the live media url, so it can be used * directly as an `` on a browser session. API-key integrators * should pass `?format=json` and read `url` off the body, since a browser * cannot attach an Authorization header to an image request. * * Only Instagram and Facebook media can be re-minted. On other platforms * the stored url is returned as-is when it still resolves, and `404` * otherwise. * */ export const getMessageAttachment = (options: OptionsLegacyParser) => { return (options?.client ?? client).get({ ...options, url: '/v1/inbox/conversations/{conversationId}/messages/{messageId}/attachments/{index}' }); }; /** * List commented posts * Returns posts with comment counts from all connected accounts. Aggregates data across multiple accounts. * * For users with the Ads add-on (Metronome plans always qualify), the user's Meta ads * (boosted/dark posts) are included too. There's one row per (ad, placement-with-comments): * an ad that runs on both Facebook feed and Instagram feed produces up to two rows (the * Page dark post and the IG media have separate comment threads), each flagged * `isAd: true` with `adId` and `placement` (`id` is `{adId}:{placement}`). Use * `?platform=metaads` to return *only* ad rows; passing `facebook`/`instagram` returns * *organic* posts only (no ads); omitting `platform` returns both. Fetch a row's thread * from GET /v1/ads/{adId}/comments?placement={placement}. Ad comment counts are read with * the Marketing API token (Facebook side) or the connected Instagram account's token * (Instagram side); a row whose count can't be read is omitted. * * Pagination walks each account's platform listing. Following `nextCursor` reaches past * the first page on Facebook, Instagram, Threads, LinkedIn and YouTube, since they are * the platforms that support a server-side date window; on the others the listing stops * at its first page. Cursor pagination is only coherent for the default sort * (`sortBy=date`, `sortOrder=desc`): with `sortOrder=asc`, or with `sortBy=comments`, * the cursor filter does not match the sort order and the second page is unreliable. * * `nextCursor` is opaque: pass it back verbatim, never construct or parse it, its * composition may change without notice. Because each page re-queries a live window, * results can still shift between requests, so dedupe by `id` on the client. * * `commentCount` semantics differ by platform: YouTube's includes replies, Facebook's counts * top-level comments only. * */ export const listInboxComments = (options?: OptionsLegacyParser) => { return (options?.client ?? client).get({ ...options, url: '/v1/inbox/comments' }); }; /** * Get post comments * Fetch comments for a specific post. Requires accountId query parameter. * * On Facebook and Instagram, passing a COMMENT id as `postId` is also supported and * returns that comment's replies instead of the post's top-level comments. This is not * available on YouTube, where `postId` must be a video id. * */ export const getInboxPostComments = (options: OptionsLegacyParser) => { return (options?.client ?? client).get({ ...options, url: '/v1/inbox/comments/{postId}' }); }; /** * Reply to comment * Post a reply to a post or specific comment. Requires accountId in request body. */ export const replyToInboxPost = (options: OptionsLegacyParser) => { return (options?.client ?? client).post({ ...options, url: '/v1/inbox/comments/{postId}' }); }; /** * Delete comment * Delete a comment on a post. Supported by Facebook, Instagram, Bluesky, Reddit, YouTube, and LinkedIn. * Requires accountId and commentId query parameters. * */ export const deleteInboxComment = (options: OptionsLegacyParser) => { return (options?.client ?? client).delete({ ...options, url: '/v1/inbox/comments/{postId}' }); }; /** * Edit comment * 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`. * */ export const editInboxComment = (options: OptionsLegacyParser) => { return (options?.client ?? client).patch({ ...options, url: '/v1/inbox/comments/{postId}/{commentId}' }); }; /** * Set comment moderation status * Set a comment's moderation status. Supported on YouTube only. * * Use this to work a moderation queue: approve a held comment (`published`), reject it * (`rejected`), or send it back for review (`heldForReview`). * * The request must be authorized by the owner of the channel or video the comment * belongs to. You cannot moderate comments on videos you do not own. * * This is distinct from `POST /v1/inbox/comments/{postId}/{commentId}/hide`, which * covers Facebook, Instagram, Threads, and X/Twitter and does not apply to YouTube. * */ export const setCommentModeration = (options: OptionsLegacyParser) => { return (options?.client ?? client).post({ ...options, url: '/v1/inbox/comments/{postId}/{commentId}/moderation' }); }; /** * Hide comment * Hide a comment on a post. Supported by Facebook, Instagram, Threads, and X/Twitter. * Hidden comments are only visible to the commenter and page admin. * For X/Twitter, the reply must belong to a conversation started by the authenticated user. * */ export const hideInboxComment = (options: OptionsLegacyParser) => { return (options?.client ?? client).post({ ...options, url: '/v1/inbox/comments/{postId}/{commentId}/hide' }); }; /** * Unhide comment * Unhide a previously hidden comment. Supported by Facebook, Instagram, Threads, and X/Twitter. * */ export const unhideInboxComment = (options: OptionsLegacyParser) => { return (options?.client ?? client).delete({ ...options, url: '/v1/inbox/comments/{postId}/{commentId}/hide' }); }; /** * Like comment * Like or upvote a comment on a post. Supported platforms: Facebook, Twitter/X, * Bluesky, Reddit, LinkedIn. For Bluesky, the cid (content identifier) is required * in the request body. For LinkedIn, pass the composite comment URN returned by the * comments endpoints as commentId; an optional reactionType picks the reaction * (defaults to LIKE), and accounts connected before the social-feed scopes were * requested get a 403 with code `linkedin_reconnect_required`. * */ export const likeInboxComment = (options: OptionsLegacyParser) => { return (options?.client ?? client).post({ ...options, url: '/v1/inbox/comments/{postId}/{commentId}/like' }); }; /** * Unlike comment * Remove a like from a comment. Supported platforms: Facebook, Twitter/X, Bluesky, * Reddit, LinkedIn. For Bluesky, the likeUri query parameter is required. * */ export const unlikeInboxComment = (options: OptionsLegacyParser) => { return (options?.client ?? client).delete({ ...options, url: '/v1/inbox/comments/{postId}/{commentId}/like' }); }; /** * Like post * Like (or react to) a post as a connected account. Supported platforms: LinkedIn, * Twitter/X, Facebook, YouTube, Bluesky. Instagram, Threads, TikTok and Pinterest * expose no like endpoint in their APIs and return 400. Reddit returns 400 too, * pointing at `POST /v1/accounts/{accountId}/reddit-vote`, which covers upvote, * downvote and clear on both posts and comments. * * The account does not have to be the one that published the post, which is what * makes executive engagement possible: pass an exec's `accountId` and the brand * post's ID. `postId` accepts either a Zernio post ID or the platform's native post * ID. A Zernio post ID resolves to the entry for `accountId`, falling back to the * post's single entry on the same platform (two entries on that platform is a 400, * so pass the native ID). * * LinkedIn requires the `w_member_social_feed` / `w_organization_social_feed` * scopes, which are not retroactive: accounts connected before those were requested * get a 403 with code `linkedin_reconnect_required` until the user reconnects the * account. YouTube spends 50 quota units per call. * */ export const likePost = (options: OptionsLegacyParser) => { return (options?.client ?? client).post({ ...options, url: '/v1/inbox/posts/{postId}/like' }); }; /** * Unlike post * Remove this account's like from a post. Supported platforms: LinkedIn, Twitter/X, * Facebook, YouTube, Bluesky. On YouTube this clears the rating. For Bluesky, * `likeUri` (returned when the post was liked) is required. Reddit uses * `POST /v1/accounts/{accountId}/reddit-vote` with `direction: 0`. * */ export const unlikePost = (options: OptionsLegacyParser) => { return (options?.client ?? client).delete({ ...options, url: '/v1/inbox/posts/{postId}/like' }); }; /** * Send private reply * Send a private message to the author of a comment. Supported on Instagram and Facebook only. * One reply per comment, must be sent within 7 days. Optionally attach interactive elements: * `quickReplies` (chips above the keyboard, max 13) or `buttons` (1-3 inline postback/url * buttons rendered in the same bubble via Meta's button_template). Buttons are recommended * for cold reach since chips do not render in the Instagram Message Requests folder. * `quickReplies` and `buttons` are mutually exclusive. * */ export const sendPrivateReplyToComment = (options: OptionsLegacyParser) => { return (options?.client ?? client).post({ ...options, url: '/v1/inbox/comments/{postId}/{commentId}/private-reply' }); }; /** * Retweet a post * Retweet (repost) a tweet by ID. * Rate limit: 50 requests per 15-min window. Shares the 300/3hr creation limit with tweet creation. * */ export const retweetPost = (options: OptionsLegacyParser) => { return (options?.client ?? client).post({ ...options, url: '/v1/twitter/retweet' }); }; /** * Undo retweet * Undo a retweet (un-repost a tweet). * */ export const undoRetweet = (options: OptionsLegacyParser) => { return (options?.client ?? client).delete({ ...options, url: '/v1/twitter/retweet' }); }; /** * Bookmark a tweet * Bookmark a tweet by ID. * Requires the bookmark.write OAuth scope. * Rate limit: 50 requests per 15-min window. * */ export const bookmarkPost = (options: OptionsLegacyParser) => { return (options?.client ?? client).post({ ...options, url: '/v1/twitter/bookmark' }); }; /** * Remove bookmark * Remove a bookmark from a tweet. * */ export const removeBookmark = (options: OptionsLegacyParser) => { return (options?.client ?? client).delete({ ...options, url: '/v1/twitter/bookmark' }); }; /** * Follow a user * Follow a user on X/Twitter. * Requires the follows.write OAuth scope. * For protected accounts, a follow request is sent instead (pending_follow will be true). * */ export const followUser = (options: OptionsLegacyParser) => { return (options?.client ?? client).post({ ...options, url: '/v1/twitter/follow' }); }; /** * Unfollow a user * Unfollow a user on X/Twitter. * */ export const unfollowUser = (options: OptionsLegacyParser) => { return (options?.client ?? client).delete({ ...options, url: '/v1/twitter/follow' }); }; /** * Search recent tweets * Search public tweets from the last 7 days matching an X search query, e.g. to discover tweets to reply to. * The query string is passed through to X unchanged and supports X's search operators * (`from:user`, `-is:retweet`, `is:reply`, `lang:en`, `"exact phrase"`, `conversation_id:123`, boolean `OR`, ...). * Note that standalone operators like `is:` / `has:` / `lang:` must be combined with a keyword or `from:` clause. * * To reply to a found tweet, pass its `id` as the twitter platform entry's `platformSpecificData.replyToTweetId` when creating a post. * * Rate limit: 300 requests per 15-min window per connected account. * */ export const searchTweets = (options: OptionsLegacyParser) => { return (options?.client ?? client).get({ ...options, url: '/v1/twitter/search' }); }; /** * Look up a tweet * Resolve a single tweet by ID or URL into its text, author and public metrics. * * Use this to render a post you are referencing, e.g. the tweet quoted by a quote-style post. * Unlike `/v1/twitter/search` this is not limited to the last 7 days and works for any tweet * visible to the connected account. * * Billed as an X posts read ($0.005). Repeat lookups of the same tweet within the same UTC day * are charged once. * */ export const getTweet = (options: OptionsLegacyParser) => { return (options?.client ?? client).get({ ...options, url: '/v1/twitter/tweet' }); }; /** * List mentions * Returns mentions of your connected organization accounts, delivered via platform webhooks. * Currently supports LinkedIn organization mentions. * * Requires Inbox addon. * */ export const listInboxMentions = (options?: OptionsLegacyParser) => { return (options?.client ?? client).get({ ...options, url: '/v1/inbox/mentions' }); }; /** * Reply to a mention * Reply to a mention of the connected account. Supported on Instagram only. * * Two shapes, selected by whether `commentId` is present: * * - **Comment mention** (someone @mentioned the account inside a comment): pass both * `mediaId` and `commentId`. Instagram posts a reply under that comment. * - **Caption mention** (someone @mentioned the account in their media caption, so no * comment exists): pass `mediaId` only. Instagram posts a comment on their media. * * Story mentions are not supported by Instagram's API. * * Note that `GET /v1/inbox/mentions` currently returns LinkedIn mentions only and does * not surface Instagram mentions. Source `mediaId` and `commentId` from Instagram's * `comments` webhook, which is where mention notifications are delivered for accounts * connected through Instagram Login. * */ export const replyToMention = (options: OptionsLegacyParser) => { return (options?.client ?? client).post({ ...options, url: '/v1/inbox/mentions/reply' }); }; /** * List reviews * Fetch reviews from all connected Facebook Pages and Google Business accounts. Aggregates data with filtering and sorting options. * Supported platforms: Facebook, Google Business. * */ export const listInboxReviews = (options?: OptionsLegacyParser) => { return (options?.client ?? client).get({ ...options, url: '/v1/inbox/reviews' }); }; /** * Reply to review * Post a reply to a review. Requires accountId in request body. */ export const replyToInboxReview = (options: OptionsLegacyParser) => { return (options?.client ?? client).post({ ...options, url: '/v1/inbox/reviews/{reviewId}/reply' }); }; /** * Delete review reply * Delete a reply to a review (Google Business only). Requires accountId in request body. */ export const deleteInboxReviewReply = (options: OptionsLegacyParser) => { return (options?.client ?? client).delete({ ...options, url: '/v1/inbox/reviews/{reviewId}/reply' }); }; /** * List templates * List all message templates for the WhatsApp Business Account (WABA) associated with the given account. * Templates are fetched directly from the WhatsApp Cloud API. * */ export const getWhatsAppTemplates = (options: OptionsLegacyParser) => { return (options?.client ?? client).get({ ...options, url: '/v1/whatsapp/templates' }); }; /** * Create template * Create a new message template. Supports two modes: * * Custom template: Provide components with your own content. Submitted to Meta for review (can take up to 24h). * * Library template: Provide library_template_name instead of components to use a pre-built template * from Meta's template library. Library templates are pre-approved (no review wait). You can optionally * customize parameters and buttons via library_template_body_inputs and library_template_button_inputs. * * Browse available library templates at: https://business.facebook.com/wa/manage/message-templates/ * */ export const createWhatsAppTemplate = (options: OptionsLegacyParser) => { return (options?.client ?? client).post({ ...options, url: '/v1/whatsapp/templates' }); }; /** * Get template * Retrieve a single message template by name. * */ export const getWhatsAppTemplate = (options: OptionsLegacyParser) => { return (options?.client ?? client).get({ ...options, url: '/v1/whatsapp/templates/{templateName}' }); }; /** * Update template * Update a message template's components. Only certain fields can be updated depending on * the template's current approval state. Approved templates can only have components updated. * */ export const updateWhatsAppTemplate = (options: OptionsLegacyParser) => { return (options?.client ?? client).patch({ ...options, url: '/v1/whatsapp/templates/{templateName}' }); }; /** * Delete template * Permanently delete a message template by name. * */ export const deleteWhatsAppTemplate = (options: OptionsLegacyParser) => { return (options?.client ?? client).delete({ ...options, url: '/v1/whatsapp/templates/{templateName}' }); }; /** * Get calling config for an account * Returns the local calling configuration snapshot for the connected * WhatsApp account: whether calling is enabled, the forward-to * destination URI, recording opt-in state, the phone number record id * (use as `{id}` on the read-write calling sub-resource at * /v1/phone-numbers/{id}/whatsapp/calling) and whether SIP digest * credentials are stored (the encrypted password itself is never * returned). Also carries account-level extras (billing eligibility, * current-period spend) that the number-keyed GET does not. * */ export const getWhatsAppCallingConfig = (options: OptionsLegacyParser) => { return (options?.client ?? client).get({ ...options, url: '/v1/whatsapp/calling' }); }; /** * @deprecated * Enable calling on a number * Deprecated alias of `/v1/phone-numbers/{id}/whatsapp/calling`; same contract. New * integrations should use that path. * * Enable WhatsApp Business Calling on a connected number. Configures * Meta calling.status=ENABLED with our Telnyx SIP endpoint, fetches and * stores the Meta-issued SIP password (encrypted), and snapshots the * customer's forward-to destination. * */ export const enableWhatsAppCallingLegacy = (options: OptionsLegacyParser) => { return (options?.client ?? client).post({ ...options, url: '/v1/whatsapp/phone-numbers/{id}/calling' }); }; /** * @deprecated * Update calling config * Deprecated alias of `/v1/phone-numbers/{id}/whatsapp/calling`; same contract. New * integrations should use that path. * * Update fields on an already-enabled number. Only fields present in * the body are written; `undefined` leaves the stored value alone, * explicit `null` clears a nullable field. No Meta side effect, this * only changes local routing state consumed by the Telnyx webhook * handler. * */ export const updateWhatsAppCallingLegacy = (options: OptionsLegacyParser) => { return (options?.client ?? client).patch({ ...options, url: '/v1/whatsapp/phone-numbers/{id}/calling' }); }; /** * @deprecated * Disable calling on a number * Deprecated alias of `/v1/phone-numbers/{id}/whatsapp/calling`; same contract. New * integrations should use that path. * * Disable calling. Sends calling.status=DISABLED to Meta (best-effort) * and flips the local `callingEnabled` flag off. forwardTo and SIP * creds are preserved so a re-enable does not lose the destination. * */ export const disableWhatsAppCallingLegacy = (options: OptionsLegacyParser) => { return (options?.client ?? client).delete({ ...options, url: '/v1/whatsapp/phone-numbers/{id}/calling' }); }; /** * Check call permission * Returns the permission state and the list of available actions for * a given consumer wa_id (e.g. `start_call`, `send_call_permission_request`). * Use this before placing a call to decide whether to prompt for * consent first. * */ export const getWhatsAppCallPermissions = (options: OptionsLegacyParser) => { return (options?.client ?? client).get({ ...options, url: '/v1/whatsapp/call-permissions' }); }; /** * Initiate outbound call * Initiates an outbound Business-Initiated Call. The Telnyx-side SIP * leg is originated server-side (Option B: SIP-first). Telnyx INVITEs * Meta directly over TLS:5061 with the SIP digest credentials we * captured at calling-enablement time). No client-side SDP is * required; pass only `accountId` and `to`. * * To send the consumer the call-consent prompt instead of placing a * call, pass `action: "send_call_permission_request"` (+ optional * `bodyText`). The consumer must tap Allow in WhatsApp before * `start_call` is permitted; Meta limits the prompt to 1 per consumer * per 24h (2 per 7 days) and requires an open 24h service window. * * **Idempotency:** send an `Idempotency-Key` header to make retries * safe; same key + same body replays the original response instead of * dialing (and billing) a second call. * */ export const initiateWhatsAppCall = (options: OptionsLegacyParser) => { return (options?.client ?? client).post({ ...options, url: '/v1/whatsapp/calls' }); }; /** * List call history for an account * Compact history listing for a single connected account. Results are * scoped to the resolved SocialAccount; profile-scoped team members * cannot read calls on sibling accounts. * * Cursor pagination: pass the returned `nextCursor` as `before` to fetch * the next page (same scheme as `GET /v1/calls`). `since`/`until` remain * as absolute range filters and combine with the cursor. * */ export const listWhatsAppCalls = (options: OptionsLegacyParser) => { return (options?.client ?? client).get({ ...options, url: '/v1/whatsapp/calls' }); }; /** * Get a single call */ export const getWhatsAppCall = (options: OptionsLegacyParser) => { return (options?.client ?? client).get({ ...options, url: '/v1/whatsapp/calls/{id}' }); }; /** * Get a call recording * Resolves a fresh, playable MP3 URL for the call's recording. * Provider-signed recording URLs expire ~10 minutes after signing, so the * `recordingUrl` stored on the call is usually stale by the time it is * played; this endpoint re-signs on demand. Default responds `302 Found` * redirecting to the fresh URL (point an `