Changelog
Every change to the Zernio API, newest first.
Significant changes are announced here, on the Telegram channel and on X.
Nothing below breaks a working integration. Every endpoint is versioned in the URL path, currently /v1, and a breaking change ships only as a new path version: /v1 keeps working. New endpoints, new response fields and new error codes arrive inside /v1 at any time, which is why error handling asks you to branch on code. An operation on its way out is marked deprecated: true in the OpenAPI spec and announced here before it is removed.
New Feature
You can now create YouTube playlists via
This creates an empty playlist on the connected YouTube channel and returns the playlist object; use the returned
Request body:
•
•
•
Notes: requires YouTube OAuth scope
POST /v1/accounts/{accountId}/youtube-playlists.This creates an empty playlist on the connected YouTube channel and returns the playlist object; use the returned
playlist.id as platformSpecificData.playlistId when publishing a video.Request body:
•
title (required)•
description (optional)•
privacy: private | public | unlisted (default private)Notes: requires YouTube OAuth scope
youtube or youtube.force-ssl; costs 50 YouTube quota units; not idempotent (retrying can create duplicate playlists).New Feature
New endpoint GET /v1/ads/campaigns/{campaignId} returns a single Meta campaign live, so you no longer need to page
Call it with
Key params:
•
•
•
Note: if the resolved connection can’t see the campaign, Meta returns
Also: PUT /v1/ads/{adId} may now return
GET /v1/ads/campaigns just to find one campaign.Call it with
campaignId (Meta platformCampaignId) and accountId. Optionally override the Graph projection with fields (passed through to Meta; unknown fields return Meta’s 400).Key params:
•
campaignId (path)•
accountId (query, required)•
fields (query, optional; e.g. id,name,status,daily_budget)Note: if the resolved connection can’t see the campaign, Meta returns
400 (not 404).Also: PUT /v1/ads/{adId} may now return
429 when Meta rate-limits writes to metered objects (notably ad creatives). Retry after ~30 seconds.Breaking Change
GET
Use
Meta lead forms are now strictly scoped to the Page managed by
Also applies to DELETE
/v1/ads/lead-forms/{formId} now returns the full Meta form (including thankyoupage) and can be customized with a Meta Graph field selection.Use
fields (Meta only) to override the default projection so newly-added Meta fields are reachable without an API change (e.g. name,thankyoupage{title,body,buttontype,websiteurl}). Invalid selections are rejected with 400.Meta lead forms are now strictly scoped to the Page managed by
accountId: reading or archiving a form on any other Page returns 404.Also applies to DELETE
/v1/ads/lead-forms/{formId} (archive): you may now receive 404 when the form isn’t on the managed Page.Breaking Change
TikTok connect behavior changed on
Connecting TikTok on a profile that already has one will replace it; reconnecting the same account keeps history, but authorizing a different account can delete the previous account’s analytics/inbox/DM history.
Handle the new redirect error
Key fields:
• Path:
• Query:
• Redirect error enum now includes:
GET /v1/connect/{platform}: TikTok connections now go through the TikTok for Business app, and each profile supports one TikTok account.Connecting TikTok on a profile that already has one will replace it; reconnecting the same account keeps history, but authorizing a different account can delete the previous account’s analytics/inbox/DM history.
Handle the new redirect error
missingtiktokpermissions (user-fixable): the authorization omitted required permissions, so nothing changes and the existing connection keeps working. Re-run the flow and accept all permissions.Key fields:
• Path:
platform=tiktok• Query:
profileId, redirect_url, optional headless• Redirect error enum now includes:
missingtiktokpermissionsBreaking Change
Google Ads behavior changed for campaign status + targeting.
Campaign status on Google now cascades on resume via
Setting
Campaign targeting reads now return real device bid adjustments via
Device targeting edits are stricter on Google via
When sending
Campaign status on Google now cascades on resume via
PUT /v1/ads/campaigns/{campaignId}/status.Setting
status to active turns on the campaign and the ad groups/ads Zernio tracks under it in one mutate (Google’s most-restrictive switch wins). Setting paused still writes the campaign only.Campaign targeting reads now return real device bid adjustments via
GET /v1/ads/campaigns/{campaignId}/targeting.devices[].bidModifier is now Google’s bid adjustment (null = none, 0 = device switched off), and the returned device list depends on channel (Search: MOBILE/DESKTOP/TABLET; Display also includes CONNECTED_TV).Device targeting edits are stricter on Google via
PUT /v1/ads/campaigns/{campaignId}/targeting.When sending
targeting.devices, any supported device you omit is switched off (written with bidModifier: 0). Sending a device the campaign channel doesn’t support, or switching every device off, now returns 422.New Feature
New endpoints let you inspect and re-subscribe a connected Facebook Page’s webhook field subscription (read live from Meta), so you can detect and fix cases where
Read current subscription:
•
Returns
Re-subscribe (re-sends full field set to Meta and reads back what was actually granted):
•
Returns
Both endpoints can return
leadgen is missing and lead ads stop arriving.Read current subscription:
•
GET /v1/accounts/{accountId}/webhook-subscriptionReturns
appSubscribed, leadgen, subscribedFields, and warning (only when leadgen is missing).Re-subscribe (re-sends full field set to Meta and reads back what was actually granted):
•
POST /v1/accounts/{accountId}/webhook-subscriptionReturns
resubscribed, plus appSubscribed, leadgen, subscribedFields, and optional warning.Both endpoints can return
409 when the connection has no selected Page; the POST can return 502 if Meta rejects the subscription.Breaking Change
Meta Promotion is no longer supported in the Ads API.
Meta validates promotion metadata on create, but discards it, so Zernio now rejects it to avoid false positives. Any request that sends a Promotion object will now fail.
Changes:
•
•
•
•
Use
Meta validates promotion metadata on create, but discards it, so Zernio now rejects it to avoid false positives. Any request that sends a Promotion object will now fail.
Changes:
•
POST /v1/ads/create: promotion is rejected (400) on all shapes•
POST /v1/ads/creatives: promotion is rejected (400)•
PUT /v1/ads/{adId}: creative.promotion is not supported; any object is rejected (400)•
GET /v1/ads/{adId}: removed query param refreshPromotionUse
creativeFeatures (values OPTIN | OPTOUT) for Advantage+ enhancements; set Promotions directly in Meta Ads Manager instead.Improvement
Analytics post items now include ad detection via
Use this flag to exclude paid delivery from organic reporting when a post’s metrics include ads (e.g., LinkedIn dark posts, TikTok Spark/boosted promotions).
New field:
•
isAd in AnalyticsListResponse.posts[].Use this flag to exclude paid delivery from organic reporting when a post’s metrics include ads (e.g., LinkedIn dark posts, TikTok Spark/boosted promotions).
New field:
•
isAd (boolean) — true when metrics include paid delivery; false otherwise (note: some TikTok uploaded-asset ads can’t be detected and may still report false).Improvement
TikTok posting behavior is now clarified for TikTok for Business accounts when using
For TikTok for Business connections,
Also,
Key fields:
•
•
•
videoCoverImageUrl and videoMadeWithAi.For TikTok for Business connections,
videoCoverImageUrl is passed to TikTok as the cover directly (no stitching), and the URL must resolve on a TikTok-verified domain.Also,
videoMadeWithAi disclosure is supported on video posts only for TikTok for Business: setting videoMadeWithAi=true on a direct photo post will be rejected. Use draft=true to send the photo to Creator Inbox and set the disclosure in the TikTok app.Key fields:
•
videoCoverImageUrl•
videoMadeWithAi•
draftNew Feature
PUT
Google creative updates are now dispatched by the ad’s
Key request fields:
• Google Search + Display:
• Google Display (additional under
• Google Performance Max:
Channel validation highlights:
• Display rejects pinned positions (no
•
/v1/ads/{adId} now supports updating Google Ads creatives beyond Search: you can update Display responsive ads and Performance Max asset groups.Google creative updates are now dispatched by the ad’s
advertisingChannelType, and unsupported creative fields for a channel return 422.Key request fields:
• Google Search + Display:
headlines, descriptions, finalUrls• Google Display (additional under
creative): creative.longHeadline, creative.businessName, creative.imageUrl, creative.squareImageUrl• Google Performance Max:
assetGroup (schema GooglePmaxAssetGroupUpdate) with fields like finalUrl, headlines, descriptions, businessName, images.landscape/images.square/images.logo, youtubeVideoIdsChannel validation highlights:
• Display rejects pinned positions (no
pinnedField)•
assetGroup returns 422 on non-Performance-Max ads; other Google-only creative fields return 422 when the channel can’t carry themNew Feature
Google Ads ad updates now support editing campaign-level geo + language targeting via
You can now replace the campaign’s
Key fields:
•
• Location/language updates are applied atomically in one Google mutate (no half-applied sets); negative (excluded) locations are left untouched
PUT /v1/ads/{adId}.You can now replace the campaign’s
targeting.locations (or top-level geo fields) and targeting.languages from the ad update call; each provided list replaces the full set, and omitted fields are left unchanged.Key fields:
•
targeting.locations: country-code array or { countries, regions, cities, zips, metros• targeting.languages: ["en", "de", "zh_CN", ...] (full replacement)
Behavior/validation:
• Empty locations now returns 400 (omit locations to leave targeting unchanged)
• customLocations (radius targeting) returns 422
• Don’t mix targeting.locations with top-level geo fields (countries/regions/cities/zips/metros): mixing returns 400`• Location/language updates are applied atomically in one Google mutate (no half-applied sets); negative (excluded) locations are left untouched
New Feature
LinkedIn B2B targeting facets can now be searched via
This lets you resolve LinkedIn facet values to the exact URNs to pass into
Use
•
•
•
•
Returned
GET /v1/ads/targeting/search using new dimension values.This lets you resolve LinkedIn facet values to the exact URNs to pass into
TargetingSpec, so you don’t need to assemble URNs manually.Use
dimension:•
industry → feeds TargetingSpec.industries•
jobFunction → feeds TargetingSpec.jobFunctions•
seniority → feeds TargetingSpec.seniorities•
companySize → feeds TargetingSpec.companySizesReturned
results[].id is the full URN (e.g. urn:li:industry:4, urn:li:function:8, urn:li:seniority:6, urn:li:staffCountRange:(51,200)).Breaking Change
comment.received webhook payloads now include Threads and no longer include X/Twitter.You can now receive comment events for Threads posts. If you validate or route by platform, update your handling to match the new enum and note that X/Twitter does not fire this event.
Update
comment.platform enum to:•
instagram, facebook, threads, youtube, linkedin, bluesky, reddit, tiktokImprovement
Meta campaign budget updates via
This prevents stale local ABO/CBO state from incorrectly blocking updates, and a successful update will also repair local mirrored budget fields.
Key behavior:
• If the live campaign is CBO, sending
• A live ABO campaign still returns
PUT /v1/ads/campaigns/{campaignId} now check the live campaign budget mode before applying changes.This prevents stale local ABO/CBO state from incorrectly blocking updates, and a successful update will also repair local mirrored budget fields.
Key behavior:
• If the live campaign is CBO, sending
budget (with type = daily | lifetime) can succeed even if local data previously indicated ABO.• A live ABO campaign still returns
409 for campaign-level budget edits (use the ad-set budget endpoint instead).New Feature
Meta placement asset customization on
You can pin different
Use:
•
•
•
•
•
•
Also updated:
POST /v1/ads/create now supports per-placement copy overrides.You can pin different
headline, body, and description per placement rule while keeping linkUrl and callToAction shared.Use:
•
placementAssets.defaultImageUrl•
placementAssets.rules[].placements•
placementAssets.rules[].imageUrl•
placementAssets.rules[].headline•
placementAssets.rules[].body•
placementAssets.rules[].descriptionAlso updated:
validateOnly: true now supports validating placementAssets for all-image setups (video placement validation still requires uploads).New Feature
You can now purchase an exact phone number by passing
Pick a number from
New request field:
•
New purchase failure case:
•
•
•
Note: regulated countries that return
phoneNumber to POST /v1/phone-numbers/purchase (and the deprecated alias POST /v1/whatsapp/phone-numbers/purchase).Pick a number from
GET /v1/phone-numbers/available, then buy that specific E.164 number instead of getting an auto-assigned one.New request field:
•
phoneNumber (E.164, e.g. +14155552671)New purchase failure case:
•
409 with code = PHONENUMBERUNAVAILABLE (number no longer available / rejected at buy-time)GET /v1/phone-numbers/available results now include:•
numbers[].locality•
numbers[].bestEffort (true if carrier returned a number outside your requested filters)Note: regulated countries that return
202 status = kyc_required do not support buying an exact phoneNumber.Improvement
GET
This lets you rank posts by TikTok (TikTok for Business app) completion and profile traffic directly in analytics queries.
Use
•
•
The delta feed
/v1/analytics now supports sorting by two new metrics: completionrate and profileviews.This lets you rank posts by TikTok (TikTok for Business app) completion and profile traffic directly in analytics queries.
Use
sortBy:•
completion_rate•
profile_viewsThe delta feed
GET /v1/analytics/delta also includes these fields in metrics: completionRate, profileViews.New Feature
POST
This lets you override per-card variables, header media, and buttons when sending a carousel template (card body variables restart at
Use
•
•
•
•
Invalid carousel card overrides now return
/v1/inbox/conversations now supports WhatsApp CAROUSEL template sends via templateCards.This lets you override per-card variables, header media, and buttons when sending a carousel template (card body variables restart at
{{1}} per card, so they can’t be represented in the flat templateParams list).Use
templateCards (max 10), each item:•
cardIndex (0–9)•
params (card body variable values)•
headerMedia: type = image | video | document + (link or id)•
buttons (max 2): subType = quick_reply | urlInvalid carousel card overrides now return
400 with code = INVALIDTEMPLATECARD_PARAM.Breaking Change
Meta Ads business login connection flow now supports an automatic Page picker when multiple Facebook Pages are granted.
If a business-login connect results in multiple Pages,
Facebook Page selection endpoints now support a new business-login grant token:
•
•
Notes:
•
• Business selection requires session/bearer auth for the initiating user;
If a business-login connect results in multiple Pages,
GET /v1/connect/meta-ads/callback may redirect to the Page picker instead of completing immediately.Facebook Page selection endpoints now support a new business-login grant token:
•
GET /v1/connect/facebook/select-page can be called with selectionToken (instead of profileId + tempToken)•
POST /v1/connect/facebook/select-page now accepts { selectionToken, pageId } to complete the business-login Meta Ads connectionNotes:
•
selectionToken is encrypted and expires in 10 minutes• Business selection requires session/bearer auth for the initiating user;
X-Connect-Token is not accepted for the business selectionToken flowImprovement
POST /v1/ads/campaigns and POST /v1/ads/create now default Meta buyingType to AUCTION.If you omit
buyingType, Zernio will treat it as AUCTION and explicitly send it on new Meta campaigns (including validateOnly requests).Set
buyingType to:•
AUCTION•
RESERVED (Reach & Frequency; requires rfPredictionId on /v1/ads/create)New Feature
You can now pin/unpin TikTok comments (TikTok for Business–connected accounts only).
Pinning lets you keep a top-level comment at the top of the post’s comment section.
New endpoints:
•
•
TikTok Inbox support was also expanded:
•
•
Behavior change to handle:
•
Webhook schema update:
•
Pinning lets you keep a top-level comment at the top of the post’s comment section.
New endpoints:
•
POST /v1/inbox/comments/{postId}/{commentId}/pin with body accountId•
DELETE /v1/inbox/comments/{postId}/{commentId}/pin with query accountIdTikTok Inbox support was also expanded:
•
GET /v1/inbox/comments/{postId} now supports TikTok (TikTok for Business). Use commentId (query) to page a comment’s full replies; each top-level comment may include up to 3 inline replies.•
POST /v1/inbox/comments/{postId}/{commentId}/hide and DELETE /v1/inbox/comments/{postId}/{commentId}/hide now support TikTok (TikTok for Business).Behavior change to handle:
•
POST /v1/posts/sync-external may now return 409 with code adsconnectionrequired when the account is inactive/needs reconnection (stop scheduled retries until reconnected).Webhook schema update:
•
WebhookPayloadComment.comment.platform now includes tiktok (TikTok events may include only author id, without username/picture/owner flag).New Feature
New Tools endpoint:
It lets you fetch a TikTok video download URL or list available formats (uses Tools API access + Tools rate limit).
Key params:
•
•
•
Notes:
• Provider-side blocks/gateway failures can return
Also: several Analytics/Accounts/Inbox endpoints now explicitly return
GET /v1/tools/tiktok/download.It lets you fetch a TikTok video download URL or list available formats (uses Tools API access + Tools rate limit).
Key params:
•
url (required)•
action: download | formats (default download)•
formatId (optional; from formats response)Notes:
• Provider-side blocks/gateway failures can return
503; unavailable video returns 404.Also: several Analytics/Accounts/Inbox endpoints now explicitly return
502/503 for upstream/temporary failures (handle retry/backoff). LinkedIn like endpoints now return alreadyReacted/reactionType on 200, and may return 409 when a different reaction already exists.Minor
Added 4 new endpoints:
GET /v1/ads/pixels - List TikTok ad pixelsGET /v1/ads/partnership-content - List partnership ad contentGET /v1/ads/partnership-permissions - List partnership permissionsPOST /v1/ads/partnership-permissions - Set partnership permissionNew Feature
Meta Ads now supports Business Login (system-user tokens) as an opt-in flow.
Use it to connect/reconnect
Start the flow via
•
•
• optional
Complete the OAuth redirect via the new callback endpoint:
•
• On success it redirects (307) with
Notable behavior/errors:
• Business login always returns an
• Reconnect grant mismatches can return
•
• Lead forms/leads endpoints now accept a
Use it to connect/reconnect
metaads without creating/requiring a posting parent account, and to select a granted Facebook Page for creatives/lead forms.Start the flow via
GET /v1/connect/{platform}/ads with:•
platform: facebook | instagram•
loginMode: business (default: classic)• optional
pageId: ^\d+$Complete the OAuth redirect via the new callback endpoint:
•
GET /v1/connect/meta-ads/callback with state (required) and code (or error)• On success it redirects (307) with
connected=metaads, profileId, accountIdNotable behavior/errors:
• Business login always returns an
authUrl; returns 503 if not configured• Reconnect grant mismatches can return
409•
GET /v1/ads/accounts adds Meta fields businessId and businessName• Lead forms/leads endpoints now accept a
metaads business-login accountId (uses system-user token)New Feature
Google Ads now supports Performance Max creation via
This lets you create a PAUSED PMax campaign + asset group atomically (with optional
Create PMax by setting:
•
•
•
•
Key
•
•
•
•
•
•
•
List asset groups:
•
POST /v1/ads/create, and you can list created asset groups via GET /v1/ads/campaigns/{campaignId}/asset-groups.This lets you create a PAUSED PMax campaign + asset group atomically (with optional
validateOnly), then read back the linked text/image/YouTube assets for a campaign.Create PMax by setting:
•
campaignType = pmax•
assetGroup (required)•
budgetType = daily | lifetime•
status = PAUSED (PMax rejects ACTIVE)Key
assetGroup fields:•
finalUrl•
headlines (3-15)•
longHeadline•
descriptions (2-5)•
businessName•
images.landscape, images.square, images.logo (logo required)•
youtubeVideoId (optional)List asset groups:
•
GET /v1/ads/campaigns/{campaignId}/asset-groups returns assetGroups, cachedAt, stale (may serve cached data when Google quota is exhausted).New Feature
New endpoint: POST /v1/ads/accounts creates a durable Meta ad account in the user’s business portfolio using their connected Meta Ads token.
Use it to self-serve ad account creation (note: the account starts without a payment method; the user must add payment/invoicing in Ads Manager before ads can deliver).
Required body fields:
Optional fields include
Response
Important: this operation is not idempotent. After a timeout or
Use it to self-serve ad account creation (note: the account starts without a payment method; the user must add payment/invoicing in Ads Manager before ads can deliver).
Required body fields:
accountId, businessId, name, currency, timezoneId (Meta numeric timezone ID).Optional fields include
endAdvertiser, mediaAgency, partner (default NONE), plus invoicing options like invoice, invoiceGroupId, invoicingEmails, io, poNumber, fundingId.Response
201 returns adAccountId (e.g. act_...), connectionUpdated, paymentMethodRequired, adsManagerUrl, nextSteps, warnings.Important: this operation is not idempotent. After a timeout or
502 with details.creationStatus=unknown, check Ads Manager before retrying.New Feature
Phone number availability now indicates when an out-of-stock country/type can be pre-ordered.
If
New fields:
•
•
•
Note:
If
available is false but preOrderable is true, you can still proceed with KYC and the carrier will source the number after review (typically ~3 weeks; not guaranteed). Only document tiers (3/4) qualify.New fields:
•
GET /v1/phone-numbers/availability (+ deprecated alias /v1/whatsapp/phone-numbers/availability): preOrderable•
GET /v1/phone-numbers/countries: preOrderable and per-type types[].preOrderable, plus types[].fulfilment = instant | request (and types[].tier may be null for fulfilment: request)•
POST /v1/phone-numbers/kyc (+ deprecated alias /v1/whatsapp/phone-numbers/kyc): response adds preOrder (true when the submission placed a pre-order; pre-orders are 1 number and aren’t billed until active)Note:
POST /v1/phone-numbers/stock-watches is now mainly for true stock returns; if preOrderable is true, you can submit KYC instead of creating a watch.Improvement
WhatsApp messages can now include location pins in the
This lets you read the shared coordinates directly from the webhook (the message
New field:
•
Also,
•
•
(Existing fields like
message.received webhook via metadata.location.This lets you read the shared coordinates directly from the webhook (the message
text is only the preview like 📍 <name>).New field:
•
metadata.location with latitude, longitude, name, addressAlso,
message.sent webhook metadata now includes WhatsApp context so you can detect non-text sends without a GET:•
metadata.location•
metadata.contacts(Existing fields like
metadata.quotedMessageId and metadata.threadTs are unchanged.)New Feature
Google Ads now supports managing extension assets (sitelinks, callouts, structured snippets) at account, campaign, and ad-group levels, plus richer Google RSA reads/updates.
New asset endpoints:
• Campaign assets:
• Ad-group assets:
• Account assets:
- Callouts:
- Sitelinks:
- Structured snippets:
Key request params:
•
Notes:
• List endpoints may return cache metadata:
• Asset updates are in-place via
• Removes detach only (assets themselves can’t be deleted)
Google RSA changes:
•
•
New asset endpoints:
• Campaign assets:
GET /v1/ads/campaigns/{campaignId}/assets, PUT, DELETE• Ad-group assets:
GET /v1/ads/ad-sets/{adSetId}/assets, POST, PUT, DELETE• Account assets:
- Callouts:
GET /v1/ads/accounts/callouts, POST, PUT, DELETE- Sitelinks:
GET /v1/ads/accounts/sitelinks, POST, PUT, DELETE- Structured snippets:
GET /v1/ads/accounts/structured-snippets, POST, PUT, DELETEKey request params:
•
accountId (required) and optional customerId (required when the connection has multiple customers)Notes:
• List endpoints may return cache metadata:
cachedAt and stale• Asset updates are in-place via
updates[] (max 20) with assetResourceName; assets are shared, so edits affect all attachments• Removes detach only (assets themselves can’t be deleted)
Google RSA changes:
•
GET /v1/ads/{adId} may now include top-level cachedAt and stale for Google RSA enrichment•
PUT /v1/ads/{adId} now supports Google RSA text updates via top-level headlines, descriptions, finalUrls (each array replaces the full list; omit a field to keep it). Pinning uses pinnedField: HEADLINE1 | HEADLINE2 | HEADLINE3 and DESCRIPTION1 | DESCRIPTION_2.New Feature
TikTok Ads now supports ad comment moderation via new endpoints for replying, hiding/unhiding, and deleting comments.
This lets you moderate first-level TikTok ad comments directly from the Ads API (replies-to-replies are rejected; reply is not idempotent).
New endpoints:
•
•
•
All three support TikTok comment lookup window params
Also updated:
Meta Ads additions for app-promotion setup:
•
•
•
Campaign creation update (Meta):
This lets you moderate first-level TikTok ad comments directly from the Ads API (replies-to-replies are rejected; reply is not idempotent).
New endpoints:
•
POST /v1/ads/{adId}/comments/{commentId}/reply with text•
POST /v1/ads/{adId}/comments/{commentId}/hide with hidden (true/false)•
DELETE /v1/ads/{adId}/comments/{commentId}All three support TikTok comment lookup window params
since/until (max 30 days; defaults to last 30 days).Also updated:
GET /v1/ads/{adId}/comments now supports TikTok (adds since/until; placement is Meta-only and returns 400 for TikTok). TikTok replies are returned as separate comments with parentId.Meta Ads additions for app-promotion setup:
•
GET /v1/ads/instagram-accounts (discover IG ad identities)•
GET /v1/ads/advertisable-applications•
GET /v1/ads/ios-fourteen-campaign-limitsCampaign creation update (Meta):
POST /v1/ads/campaigns adds validateOnly, plus SKAdNetwork/app-promotion fields isSkadnetworkAttribution, promotedObject, and buyingType (AUCTION/RESERVED).New Feature
Meta ads now support explicit Promotions and Advantage+ creative enhancements across create/update flows, with a promotion readback status.
You can attach an offer via
Use:
•
•
•
Also:
•
•
You can attach an offer via
promotion and control enhancements via creativeFeatures. When promotion is used, responses include promotionStatus to indicate whether Meta returned promotion metadata.Use:
•
POST /v1/ads/create: promotion, creativeFeatures (also as defaults for creatives[]; per-item override supports promotion: null)•
POST /v1/ads/creatives: promotion, creativeFeatures (response adds promotion, promotionStatus)•
PUT /v1/ads/{adId}: creative.promotion, creative.creativeFeatures (send promotion: null to remove)promotionStatus values: applied | not_returned | unavailableAlso:
•
GET /v1/ads/{adId} adds refreshPromotion=true|false to read live Meta promotion metadata and include promotionStatus.•
POST /v1/ads/{adId}/duplicate now preserves Meta-returned creative settings (including promotion metadata and creativeFeatures) when a native copy requires a creative rebuild.Breaking Change
Meta messaging boosts via
This prevents accidental messaging-boost behavior when you only intended to set an ad-set destination, and makes CTA/destination handling more predictable.
Key changes:
• Messaging boosts must use
•
•
Also:
POST /v1/ads/boost now require an explicit messaging CTA; destinationType no longer auto-selects one.This prevents accidental messaging-boost behavior when you only intended to set an ad-set destination, and makes CTA/destination handling more predictable.
Key changes:
• Messaging boosts must use
goal=engagement with callToAction in WHATSAPPMESSAGE | MESSAGEPAGE | INSTAGRAM_MESSAGE•
destinationType is now independent for non-messaging CTAs; if you do use a messaging callToAction, any explicit destinationType must match•
whatsappPhoneNumber now requires callToAction=WHATSAPP_MESSAGE (not just destinationType=WHATSAPP)Also:
Ad.creative.whatsappPhoneNumber is now returned (when provided) so you can read back the explicit WhatsApp number used on Meta boosts/messaging ads.New Feature
Google Ads now supports shared negative keyword lists (create/list/get/update/delete), including attaching lists to campaigns and replacing the full keyword set.
Use:
•
•
•
•
•
•
•
•
Key inputs:
•
•
•
• Keyword
Google campaign updates now support daily budget via
Campaign analytics for Google now includes impression share ratios in
Meta boosts now support messaging boosts and WhatsApp number selection:
•
Messaging/CTWA ad creation now supports reusing existing posts:
•
Use:
•
GET /v1/ads/accounts/negative-keyword-lists (cached ~10 min; may return stale=true)•
POST /v1/ads/accounts/negative-keyword-lists (not idempotent)•
GET /v1/ads/accounts/negative-keyword-lists/{listId}•
PUT /v1/ads/accounts/negative-keyword-lists/{listId} (rename)•
DELETE /v1/ads/accounts/negative-keyword-lists/{listId}•
PUT /v1/ads/accounts/negative-keyword-lists/{listId}/keywords (replace full set; keywords max 5000)•
GET /v1/ads/campaigns/{campaignId}/negative-keyword-lists•
PUT /v1/ads/campaigns/{campaignId}/negative-keyword-lists (replace associations; listIds max 20)Key inputs:
•
accountId (required on account-scoped routes)•
customerId (required when a connection has multiple customers)•
platform=google (other platforms return 501)• Keyword
matchType: broad | phrase | exactGoogle campaign updates now support daily budget via
PUT /v1/ads/campaigns/{campaignId}. Shared budgets return 409 unless allowSharedBudgetUpdate=true is explicitly provided.Campaign analytics for Google now includes impression share ratios in
analytics.summary (searchImpressionShare, searchBudgetLostImpressionShare, searchRankLostImpressionShare, searchTopImpressionShare, searchAbsoluteTopImpressionShare) plus analytics.impressionShareCache.{cachedAt,stale}; may return 429 if quota is exhausted without a cached result.Meta boosts now support messaging boosts and WhatsApp number selection:
•
POST /v1/ads/boost adds destinationType=INSTAGRAMDIRECT and whatsappPhoneNumber (E.164), and supports messaging CTAs (WHATSAPPMESSAGE | MESSAGEPAGE | INSTAGRAMMESSAGE) with goal=engagement.Messaging/CTWA ad creation now supports reusing existing posts:
•
POST /v1/ads/messaging and POST /v1/ads/ctwaexistingPostId or objectStoryId (mutually exclusive with fresh creative fields), plus optional whatsappPhoneNumber` for WhatsApp destination.Improvement
The bootstrap flow for
To avoid missing changes during initial sync, first call
Key behavior:
• Bootstrap: omit
• Resume: pass
• Retention: cursors older than ~6 days return
GET /v1/analytics/delta has changed: take the cursor before loading your baseline.To avoid missing changes during initial sync, first call
GET /v1/analytics/delta with no cursor to capture nextCursor, then load the baseline via GET /v1/analytics, then resume the delta feed from that captured nextCursor.Key behavior:
• Bootstrap: omit
cursor → response has empty data + nextCursor• Resume: pass
cursor=<nextCursor> verbatim (opaque)• Retention: cursors older than ~6 days return
400; recover by repeating the same bootstrap orderNew Feature
Google Ads now supports the attach flow on
You can add a new ad into an existing Google Ads ad group (inherits the ad group’s budget/targeting), instead of creating a new campaign/ad group.
Use:
•
•
Notes:
•
• In attach mode,
• For Search ad groups, set
POST /v1/ads/create via adSetId.You can add a new ad into an existing Google Ads ad group (inherits the ad group’s budget/targeting), instead of creating a new campaign/ad group.
Use:
•
adSetId (Google AD GROUP id)•
campaignType: search | display (default display)Notes:
•
goal is still required even though budget/targeting are inherited• In attach mode,
budgetAmount/budgetType and bidding fields (bidStrategy, bidAmount, portfolioBidStrategyId) return 400• For Search ad groups, set
campaignType to search (otherwise it’s treated as Display and requires images.landscape + images.square + businessName)Breaking Change
GET
This field still contains fresh data from the underlying ads platform API (when available), but clients must now read
Also clarified across audience endpoints that
/v1/ads/audiences/{audienceId} response renamed metaData to platformData.This field still contains fresh data from the underlying ads platform API (when available), but clients must now read
platformData instead of metaData.Also clarified across audience endpoints that
{audienceId} is the Zernio audience id (audiences[].id from GET /v1/ads/audiences), not the platform segment id. audiences[].id can be null when the audience wasn’t created through Zernio.New Feature
WhatsApp BYO numbers now support Meta re-verification via 2 new endpoints:
Use this when Meta demotes a migrated/bring-your-own number to re-verification: request an OTP to the customer handset, then submit it to verify with Meta (and call
Request a code with optional:
•
•
Verify with:
•
Note:
POST /v1/accounts/{accountId}/whatsapp/request-code and POST /v1/accounts/{accountId}/whatsapp/verify-code.Use this when Meta demotes a migrated/bring-your-own number to re-verification: request an OTP to the customer handset, then submit it to verify with Meta (and call
POST /v1/accounts/{accountId}/whatsapp/register afterward to complete Cloud API activation).Request a code with optional:
•
method: SMS | VOICE•
language (Meta locale, e.g. en_US)Verify with:
•
code (6 digits; separators like 749-456 are accepted)Note:
request-code is rate-limited to 1 request per 10 minutes per account and may return 429 with Retry-After.Breaking Change
Reddit posting behavior has changed for
forceSelf and nativeVideo in RedditPlatformData.forceSelf now creates a text-only self post and skips native media uploads even if media is provided. For native video with body text, omit forceSelf and provide content plus a video mediaItem.nativeVideo fallback behavior is now stricter when body text is present: if the post includes body text, upload failures or subreddit video restrictions will fail the post instead of falling back to a link. Automatic link fallback applies only when there is no body text.New Feature
WhatsApp connect now supports a hosted Embedded Signup flow to avoid double-selection when a Facebook login can access multiple WhatsApp numbers.
Start it with
You can co-brand the hosted page using:
•
•
•
New endpoint:
• Auth via API key or
• Request adds
• Response adds
Hosted flow adds two WhatsApp error values you may see on redirect:
Start it with
GET /v1/connect/whatsapp + signup=hosted (optional onboarding: api | business_app). The returned authUrl points to a Zernio-hosted page (no state in the response).You can co-brand the hosted page using:
•
brandName•
primaryColor (hex #RRGGBB)•
language: en | esNew endpoint:
GET /v1/connect/whatsapp/sdk-config returns the Embedded Signup config (appId, configId, branding). Auth via API key or X-Connect-Token.POST /v1/connect/whatsapp/embedded-signup now also supports the hosted flow:• Auth via API key or
X-Connect-Token• Request adds
redirectUrl and echoConnectToken• Response adds
account and optional redirectUrlHosted flow adds two WhatsApp error values you may see on redirect:
connectioncancelled, sessionexpired.Improvement
PUT
This lets clients distinguish queue slot conflicts from generic validation errors and handle rescheduling explicitly.
If you hit
• choose a different
• omit
• send
/v1/posts/{postId} can now return 409 when the requested scheduledFor collides with another post already occupying that slot in the same queue (code: queueslotconflict).This lets clients distinguish queue slot conflicts from generic validation errors and handle rescheduling explicitly.
If you hit
409, you can:• choose a different
scheduledFor• omit
scheduledFor to let the queue pick the next open slot• send
queueId: null to schedule outside the queueNew Feature
Meta generic templates now support setting the image aspect ratio on Facebook via
This lets you control how Messenger renders carousel element images (default is horizontal). Note: Instagram does not support this setting and will return
Use
•
•
Applies to:
•
•
imageAspectRatio.This lets you control how Messenger renders carousel element images (default is horizontal). Note: Instagram does not support this setting and will return
400 if it’s included.Use
template.imageAspectRatio with:•
horizontal•
squareApplies to:
•
POST /v1/inbox/conversations/{conversationId}/messages (generic template)•
CommentAutomationTemplate.imageAspectRatioNew Feature
WhatsApp templates now support configuring a per-template delivery TTL via
This lets you set a validity window for template messages (messages not delivered within the TTL are dropped) and read back the custom TTL when it’s set.
Set
•
•
•
Read
•
•
•
TTL ranges by category:
•
•
•
Use
messagesendttl_seconds.This lets you set a validity window for template messages (messages not delivered within the TTL are dropped) and read back the custom TTL when it’s set.
Set
messagesendttl_seconds on:•
POST /v1/whatsapp/templates•
PATCH /v1/whatsapp/templates/{templateName}•
PATCH /v1/whatsapp/templates/id/{templateId}Read
messagesendttl_seconds from:•
GET /v1/whatsapp/templates•
GET /v1/whatsapp/templates/{templateName}•
GET /v1/whatsapp/templates/id/{templateId}TTL ranges by category:
•
AUTHENTICATION: 30–900•
UTILITY: 30–43200•
MARKETING: 43200–2592000Use
-1 to restore the category default (for AUTHENTICATION and UTILITY).Breaking Change
GET /v1/ads/library now supports searching Meta’s Ad Library without a connected account.Meta searches run using Zernio’s developer access, so you can call the endpoint with
platform=meta and omit accountId. LinkedIn searches still require a connected accountId.Use:
•
platform: meta | linkedin (required unless accountId is provided)•
accountId: required for LinkedIn; optional for Meta (passing a Meta account also selects Meta)Meta-specific behavior changes:
• Requests may return
429 due to shared Meta quota (back off ~1 minute)• New
503 when Meta Ad Library is unavailable on Zernio’s side (PLATFORM_DISABLED)Also updated:
403 no longer includes Meta identity confirmation errors; it’s now “Ads access required” (legacy plans may need the Ads add-on).Improvement
Several Google Ads endpoints now serve cached data (with fallback when Google quota is exhausted), and responses include freshness metadata.
These reads may no longer be strictly live; when quota is exhausted, the API can return the last-good copy instead of failing.
New response fields:
•
•
Applies to:
•
•
•
•
•
•
•
•
Also:
These reads may no longer be strictly live; when quota is exhausted, the API can return the last-good copy instead of failing.
New response fields:
•
cachedAt (date-time | null) — when the data was fetched from Google•
stale (boolean) — true when quota was exhausted and the response is a cached fallbackApplies to:
•
GET /v1/ads/search-terms•
GET /v1/ads/bid-strategies•
GET /v1/ads/campaigns/{campaignId}/bidding•
GET /v1/ads/campaigns/{campaignId}/negative-keywords•
GET /v1/ads/campaigns/{campaignId}/targeting•
GET /v1/ads/accounts/callouts•
GET /v1/ads/accounts (Google only)•
GET /v1/ads/conversions/actionsAlso:
PUT /v1/ads/campaigns/{campaignId}/targeting now returns the refreshed devices/locations/languages state read back from Google after the edit (and invalidates the cached GET copy).New Feature
Google Ads now supports portfolio bid strategies and live campaign bidding reads.
You can list/create/update shared (portfolio) bidding strategies and attach them to campaigns, plus fetch a campaign’s current bidding setup for pre-filling edit forms.
New endpoints:
•
•
•
•
Attach a portfolio strategy to Google campaigns using
•
•
•
Portfolio strategy
•
Also on Google ad updates,
You can list/create/update shared (portfolio) bidding strategies and attach them to campaigns, plus fetch a campaign’s current bidding setup for pre-filling edit forms.
New endpoints:
•
GET /v1/ads/bid-strategies (accountId, optional customerId, fromDate, toDate)•
POST /v1/ads/bid-strategies (accountId, name, type, optional targetCpa/targetRoas)•
PATCH /v1/ads/bid-strategies/{strategyId} (accountId, optional name, type, targetCpa, targetRoas)•
GET /v1/ads/campaigns/{campaignId}/bidding (accountId, platform=google, optional customerId)Attach a portfolio strategy to Google campaigns using
portfolioBidStrategyId on:•
POST /v1/ads/campaigns•
PUT /v1/ads/campaigns/{campaignId}•
POST /v1/ads/createPortfolio strategy
type values:•
TARGETCPA, TARGETROAS, MAXIMIZECONVERSIONS, MAXIMIZECONVERSION_VALUEAlso on Google ad updates,
PUT /v1/ads/{adId} now supports device bid adjustments via targeting.devices with devices MOBILE, DESKTOP, TABLET, CONNECTED_TV (optional bidModifier 0.1–10).New Feature
Google Ads now supports editing more campaign and account assets via new endpoints.
You can now read/update campaign targeting (devices, locations, languages), create/list standalone ad groups, manage account-level callout extensions, and list/create conversion actions (with tag snippets).
Campaign targeting:
•
•
•
Ad groups (ad sets):
•
•
Account-level callouts (Google-only):
•
•
•
Conversion actions (Google-only):
•
•
Also updated:
•
•
You can now read/update campaign targeting (devices, locations, languages), create/list standalone ad groups, manage account-level callout extensions, and list/create conversion actions (with tag snippets).
Campaign targeting:
•
GET /v1/ads/campaigns/{campaignId}/targeting (platform=google)•
PUT /v1/ads/campaigns/{campaignId}/targeting body: platform=google, targeting with any of devices/locations/languages (each provided field replaces existing)•
devices enum: MOBILE, DESKTOP, TABLET, CONNECTED_TVAd groups (ad sets):
•
GET /v1/ads/ad-sets (filters: accountId, campaignId, platform)•
POST /v1/ads/ad-sets (Google-only) with optional Idempotency-KeyAccount-level callouts (Google-only):
•
GET /v1/ads/accounts/callouts•
POST /v1/ads/accounts/callouts (callouts: 1–25 chars, up to 20)•
DELETE /v1/ads/accounts/callouts (remove by assetId)Conversion actions (Google-only):
•
GET /v1/ads/conversions/actions (optional customerId, optional type)•
POST /v1/ads/conversions/actions create type=WEBPAGE and returns tagSnippetsAlso updated:
•
POST /v1/ads/create now supports Google campaign language targeting via languages (e.g. ["en","de"]; unknown codes return 400)•
GET /v1/ads/targeting/search adds dimension=language (Google-only) to resolve Google language_constant optionsNew Feature
Google Ads Search keyword management is now available via new endpoints.
You can add keywords without replacing the existing set, pause/enable a keyword, remove a keyword, and manage campaign-level negative keywords separately from ad-group negatives.
Add keywords to an ad group with
•
•
•
•
Pause/enable a keyword with
•
Remove a keyword with
Campaign-level negative keywords:
• List:
• Replace full set:
Also updated:
You can add keywords without replacing the existing set, pause/enable a keyword, remove a keyword, and manage campaign-level negative keywords separately from ad-group negatives.
Add keywords to an ad group with
POST /v1/ads/keywords using:•
accountId•
adSetId•
keywords (string = BROAD, or { text, matchType } where matchType is exact/phrase/broad)•
negative (boolean) to add ad-group-level negativesPause/enable a keyword with
PATCH /v1/ads/keywords/{keywordId}:•
status: active or paused (note: negative keywords can’t be paused/enabled)Remove a keyword with
DELETE /v1/ads/keywords/{keywordId}.Campaign-level negative keywords:
• List:
GET /v1/ads/campaigns/{campaignId}/negative-keywords• Replace full set:
PUT /v1/ads/campaigns/{campaignId}/negative-keywords with keywords (send an empty array to clear all)Also updated:
POST /v1/ads/create (Google Search) now accepts match types for keywords and negativeKeywords via KeywordEntry, and adds campaignNegativeKeywords for campaign-level negatives at create time.Improvement
GET /v1/ads/timeline now returns allConversions per day, and clarifies Google customer-level reporting when adAccountId is a Google customer id.This lets you chart Google’s “all conversions” alongside
conversions (tracked conversions), matching Google Ads’ metrics.all_conversions.New field in each
rows[] item:•
allConversions - all conversions (0 on platforms without this concept)Google note:
• With
adAccountId set to a Google customer id, the timeline represents customer-level performance (clicks, cost, impressions, conversions, allConversions per day).New Feature
New endpoint
It supports both Meta and LinkedIn and returns ads in the platform’s raw shape under
Required:
Common query params:
Meta-only:
LinkedIn-only:
Notes: Passing a platform-unsupported param returns
Also:
GET /v1/ads/library is available to search the public Ad Library for competitor/market research using your connected account token.It supports both Meta and LinkedIn and returns ads in the platform’s raw shape under
data, with pagination via paging.after (cursor; null when exhausted).Required:
accountIdCommon query params:
q, countries, since, until, limit, afterMeta-only:
pageIds, adType (ALL/POLITICALANDISSUEADS/HOUSINGADS/EMPLOYMENTADS/FINANCIALPRODUCTSANDSERVICESADS), status (ACTIVE/INACTIVE/ALL), platforms, mediaType (ALL/IMAGE/MEME/VIDEO/NONE), languages, searchType (KEYWORDUNORDERED/KEYWORDEXACTPHRASE), fieldsLinkedIn-only:
advertiser (and limit max is 25)Notes: Passing a platform-unsupported param returns
400 naming the param. Meta may return 403 metaidentityconfirmation_required until the Facebook user behind the connection confirms identity/location.Also:
POST /v1/inbox/comments/{postId}/{commentId}/private-reply now documents an additional Instagram failure case as a non-retryable 400 when Meta declines the private reply (subcode 2534066).Breaking Change
POST /v1/phone-numbers/stock-watches now supports watching stock by number type via numberType.You can create a watch for a specific type in a country, or omit
numberType to watch all types. Watches are now unique per country + numberType (up to 20 watches total).Request body:
•
country•
numberType (optional): local | mobile | national | toll_freeResponse schema
PhoneNumberStockWatch now includes required numberType (nullable; null means “all types”).Improvement
POST
This clarifies that Zernio does not retry sends internally, and that retrying after a
Key response:
•
/v1/inbox/conversations/{conversationId}/messages now includes a documented 500 response when the upstream platform rejects or fails the send.This clarifies that Zernio does not retry sends internally, and that retrying after a
500 can result in duplicate messages because upstream failures may be ambiguous.Key response:
•
500 - platform rejected/failed the send; retry only if duplicates are acceptable (use Idempotency-Key to protect the “response lost after success” case, but it won’t guarantee safety after an upstream-accepted failure).New Feature
Meta ad sets now support minimum spend targets via
This lets you reserve a share of a CBO (campaign-level) budget for a specific ad set using Meta’s
Set one of:
•
•
Notes:
•
• Requires campaign-level budget (CBO); on an ABO ad set this is rejected (PUT returns 409; create returns 400)
• Read back via
platformSpecificData on PUT /v1/ads/ad-sets/{adSetId} and when creating Meta ads via POST /v1/ads/create.This lets you reserve a share of a CBO (campaign-level) budget for a specific ad set using Meta’s
dailyminspendtarget / lifetimeminspendtarget.Set one of:
•
platformSpecificData.dailyMinSpendTarget•
platformSpecificData.lifetimeMinSpendTargetNotes:
•
dailyMinSpendTarget and lifetimeMinSpendTarget are mutually exclusive (400)• Requires campaign-level budget (CBO); on an ABO ad set this is rejected (PUT returns 409; create returns 400)
• Read back via
GET /v1/ads/ad-sets/{adSetId}?fields=dailyminspendtarget (or lifetimeminspendtarget)Improvement
GET /v1/ads/tree: the ads array in AdTreeAdSet is now explicitly defined as the 100 highest-spending ads over the requested date range when an ad set has more than 100 ads.This means the contents of
AdTreeAdSet.ads can change based on fromDate/toDate; use adCount for the true total size of the ad set.Key fields:
AdTreeAdSet.ads, fromDate, toDate, adCountNew Feature
WhatsApp Flows now supports managing the Flows endpoint encryption key via new endpoints.
You can check whether a business RSA public key is registered (and retrieve it) and register/replace the key used for endpoint-backed flows (
New endpoints:
•
•
Note: only one key is active per phone number; replacing the key requires your flow endpoint to serve the matching private key or
You can check whether a business RSA public key is registered (and retrieve it) and register/replace the key used for endpoint-backed flows (
flowaction: dataexchange).New endpoints:
•
GET /v1/whatsapp/flows/encryption-key (query accountId) returns publicKey, registered, signatureStatus (VALID | MISMATCH)•
POST /v1/whatsapp/flows/encryption-key body: accountId, businessPublicKey (PEM)Note: only one key is active per phone number; replacing the key requires your flow endpoint to serve the matching private key or
data_exchange flows will fail at runtime.New Feature
Meta ads created via
This lets you send 1–5 variations of primary text/headlines (and optionally descriptions) and have Meta optimize delivery across them, without using full
Use:
•
•
•
Requires
POST /v1/ads/create now support Multiple Text Options (Advantage+ Flexible Format) using new top-level fields bodies, headlines, and optional descriptions.This lets you send 1–5 variations of primary text/headlines (and optionally descriptions) and have Meta optimize delivery across them, without using full
dynamicCreative.Use:
•
bodies (1–5)•
headlines (1–5)•
descriptions (0–5)Requires
imageUrl or video, plus linkUrl and callToAction. When set, top-level body and headline are used for preview text (objectstoryspec), and this mode is mutually exclusive with dynamicCreative, placementAssets, carouselCards, and creatives[].New Feature
Instagram publishing now supports paid partnership, sponsor tagging, comment toggling, and location tagging via new fields in
Use:
•
•
•
•
platformSpecificData.Use:
•
isPaidPartnership (boolean) — adds the “Paid partnership” label (feed/Reels/carousels; Stories return 400). Requires Facebook Login connection.•
brandedContentSponsors (array, max 2) — tag sponsor brands by username (optional @) or numeric IG user ID; implies isPaidPartnership.•
commentsEnabled (boolean, default true) — when false, turns off comments after publish (best-effort; Stories ignored).•
locationId (string digits) — tags the post location using a Facebook Page ID with location data (feed/Reels/carousels; Stories rejected with 400).Improvement
Meta ads now support regional regulation identity mapping via
When you include
Set:
•
•
If omitted, Meta may use Ads Manager defaults when configured.
regionalRegulationIdentities on POST /v1/ads/create and POST /v1/ads/boost.When you include
regionalRegulatedCategories (e.g. BRAZILREGULATION, SINGAPOREUNIVERSAL, TAIWANUNIVERSAL, THAILANDUNIVERSAL, AUSTRALIAFINSERV, INDIAFINSERV, TAIWAN_FINSERV), you can now pass the required Meta-verified beneficiary/payer entity IDs in regionalRegulationIdentities.Set:
•
regionalRegulatedCategories: [...]•
regionalRegulationIdentities: { universalbeneficiary: 123, universalpayer: 456, ... }If omitted, Meta may use Ads Manager defaults when configured.
New Feature
New endpoint
This lets integrations stay in sync with far fewer calls than polling
Key params:
•
•
•
•
Response fields:
•
•
•
New webhook event available for subscriptions:
GET /v1/analytics/delta returns a cursor-based feed of analytics snapshots that changed across all readable accounts.This lets integrations stay in sync with far fewer calls than polling
GET /v1/analytics per account; bootstrap once from GET /v1/analytics, then poll deltas.Key params:
•
cursor (opaque; omit to start “from now” and receive data: [] + nextCursor)•
limit (1–100, default 50; out-of-range = 400)•
platform (optional)•
profileId (default all)Response fields:
•
data (oldest-first changed snapshots)•
nextCursor (always present, even on empty pages)•
hasMore (if true, call again immediately)New webhook event available for subscriptions:
analytics.synced. It’s a trigger (no metrics/cursor); on receipt, call GET /v1/analytics/delta using your last stored nextCursor.Breaking Change
Instagram comment-to-DM private replies may now fail for non-followers when you include interactive elements.
Since late Aug 2026, Instagram can refuse
If you need to reach non-followers, send plain text using
Since late Aug 2026, Instagram can refuse
buttons (and other attachments/cards) to commenters who don’t follow the account, returning a non-retryable 400 (Meta code 2, subcode 1545133). The failed call still consumes the comment’s single private reply.If you need to reach non-followers, send plain text using
message only, then add buttons after they reply. (quickReplies and buttons remain mutually exclusive.)Improvement
Meta ads now accept regional regulated categories on ad set creation/boosting.
Use this when targeting countries where Meta requires extra regulation flags (e.g. Singapore/Taiwan/Thailand/Australia/India). Without it, Meta may reject the ad set.
Send
•
•
Example values include
Use this when targeting countries where Meta requires extra regulation flags (e.g. Singapore/Taiwan/Thailand/Australia/India). Without it, Meta may reject the ad set.
Send
regionalRegulatedCategories (array of strings) on:•
POST /v1/ads/create•
POST /v1/ads/boostExample values include
SINGAPOREUNIVERSAL, TAIWANUNIVERSAL, THAILANDUNIVERSAL, AUSTRALIAFINSERV, INDIA_FINSERV.Improvement
Several Ads endpoints can now return
This happens when a team has no payment method on file and has reached the 500 free live ads limit; add a card to resume/continue.
You may see this on:
•
•
•
•
•
•
•
•
•
•
•
•
•
If you have retry logic, treat
403 with code adsallowanceexceeded.This happens when a team has no payment method on file and has reached the 500 free live ads limit; add a card to resume/continue.
You may see this on:
•
PUT /v1/ads/campaigns/{campaignId}/status•
PUT /v1/ads/campaigns/{campaignId}•
POST /v1/ads/campaigns/bulk-status•
POST /v1/ads/campaigns/{campaignId}/duplicate•
POST /v1/ads/ad-sets/{adSetId}/duplicate•
POST /v1/ads/{adId}/duplicate•
PUT /v1/ads/ad-sets/{adSetId} and PUT /v1/ads/ad-sets/{adSetId}/status•
PUT /v1/ads/{adId} and PUT /v1/ads/{adId}/status•
PATCH /v1/ads/{adId}/tracking-tags•
POST /v1/ads/create•
POST /v1/ads/boost•
POST /v1/ads/messaging, POST /v1/ads/call, POST /v1/ads/ctwa•
POST /v1/ads/campaigns/{campaignId}/assetsIf you have retry logic, treat
403 adsallowanceexceeded as non-retryable until billing is set up.Improvement
GET /v1/connect/pending-data pending OAuth tokens are now repeatable and expire 1 hour after issuance (previously one-time use, 10 minutes).This makes headless OAuth selection flows more resilient (you can re-fetch the pending selection data until the user completes selection; completion deletes the pending record).
Use
token (query) from pendingDataToken on the redirect. selectionType can be: organizations, pages, boards, locations, profiles.Scope note: this pending-data flow now covers Google Business locations and Slack channels in addition to existing large selection lists.
New Feature
GET
This makes it easier to build adset-centric dashboards by listing only ads within a specific platform ad set.
Use
PUT
Meta creative updates are now patch-style: send any subset of
Key Meta-only options in
•
•
•
GET
These ids can be used in
•
•
•
Also,
/v1/ads now supports filtering by Meta ad set via adSetId.This makes it easier to build adset-centric dashboards by listing only ads within a specific platform ad set.
Use
adSetId (platform ad set id) alongside existing filters like campaignId.PUT
/v1/ads/{adId} now supports richer creative updates, especially for Meta.Meta creative updates are now patch-style: send any subset of
creative fields and omitted fields are preserved (including existing media), and you can also repoint an ad to an existing creative.Key Meta-only options in
creative:•
description (link description)•
videoId (reuse an already-uploaded video)•
existingCreativeId (repoint to a creative from GET /v1/ads/creatives; ignores other creative fields)GET
/v1/ads/targeting/search now supports Meta work demographics via dimension values workPosition, workEmployer, workIndustry.These ids can be used in
TargetingSpec as:•
workPositions•
workEmployers•
workIndustriesAlso,
TargetingSpec audience lists are now supported: audienceInclude and audienceExclude accept platform audience IDs (not supported on OpenAI Ads; returns 400).Breaking Change
WhatsApp template sends via
If you provide fewer values than the template’s required header + body + dynamic URL-button slots, the request is rejected with
Key fields:
•
•
POST /v1/inbox/conversations now validate templateParams more strictly for positional templates.If you provide fewer values than the template’s required header + body + dynamic URL-button slots, the request is rejected with
400 (INVALIDTEMPLATEPARAMS) instead of sending a message with misaligned values.Key fields:
•
templateParams - must cover all required slots for positional templates•
templateButtonParams - can override a button’s value; overridden buttons may not need a value in templateParams unless later uncovered dynamic URL buttons followImprovement
Creating or retrying posts can now return 403 with
This happens when the target
Applies to:
•
•
code=ACCOUNTNOTENABLEDFORPOSTING.This happens when the target
accountId is connected for ads only (i.e., enabled: false) and cannot be posted to. Connect the account as a posting account, refresh IDs via GET /v1/accounts, then retry.Applies to:
•
POST /v1/posts (403 code enum now includes ACCOUNTNOTENABLEDFORPOSTING)•
POST /v1/posts/{postId}/retry (403 may include ACCOUNTNOTENABLEDFORPOSTING, PROFILEOVERLIMIT, or insufficient_permissions)Breaking Change
Some endpoints now return additional billing/capability errors you may need to handle.
Posts listing may now fail with
OpenAI Ads connect may now return
SMS sender ID creation now has explicit billing-related failures on
•
•
Posts listing may now fail with
403 when X analytics isn’t enabled for the account (code XANALYTICSNOT_ENABLED) on GET /v1/accounts/{accountId}/posts.OpenAI Ads connect may now return
402 PaymentRequired on POST /v1/connect/openai-ads/credentials.SMS sender ID creation now has explicit billing-related failures on
POST /v1/sms/sender-ids:•
402 (code payment_required) when no payment method is on file•
409 (code billingsetupincomplete) when billing setup is incompleteBreaking Change
PostAnalytics.follows can now be null in GET /v1/analytics responses.This affects Instagram where Meta doesn’t expose
follows for Reels and non-Reels video; in those cases Zernio will return null instead of 0. Update your parsing/aggregation to handle follows: null (treat as “unknown/not available”, not zero).Breaking Change
WhatsApp templates now support working with a specific Meta template variant via its Meta template id.
New endpoints:
•
•
•
Template listing and name-based operations are now language-aware (Meta stores templates per
•
•
•
Delete behavior change:
•
New endpoints:
•
GET /v1/whatsapp/templates/id/{templateId} (requires accountId)•
PATCH /v1/whatsapp/templates/id/{templateId} (body: accountId, components)•
DELETE /v1/whatsapp/templates/id/{templateId} (requires accountId)Template listing and name-based operations are now language-aware (Meta stores templates per
name + language):•
GET /v1/whatsapp/templates adds filters: name, language, status (APPROVED/REJECTED/PENDING/PAUSED/DISABLED/INAPPEAL/PENDINGDELETION)•
GET /v1/whatsapp/templates/{templateName} adds optional language; if multiple variants exist and no language is provided, it can return 409 ambiguous_template with details.languages•
PATCH /v1/whatsapp/templates/{templateName} now takes language in the request body (required when multiple variants exist); missing language can return 409 ambiguous_templateDelete behavior change:
•
DELETE /v1/whatsapp/templates/{templateName} without language deletes all language variants; with language it deletes only that variant and returns scope = all_languages or language.Improvement
WhatsApp sends via
If you send bursts to the same recipient, WhatsApp may reject excess messages with Meta error code
If you see
POST /v1/inbox/conversations/{conversationId}/messages now document a per-recipient rate limit.If you send bursts to the same recipient, WhatsApp may reject excess messages with Meta error code
131056 ("Too many messages sent to this recipient"). Pace sends to a single recipient to ~10/min; sends to other recipients are unaffected.If you see
400 responses with platformError.code = 131056, throttle per recipient and retry later.New Feature
Phone number stock watches are now available via new endpoints:
You can watch an out-of-stock country and get notified the first time deliverable numbers are available again (email + webhook). Watches are checked every 6h, consumed when they fire, and you can watch up to 20 countries.
Create a watch with
Webhooks now support the new event
GET /v1/phone-numbers/stock-watches, POST /v1/phone-numbers/stock-watches, DELETE /v1/phone-numbers/stock-watches/{id}.You can watch an out-of-stock country and get notified the first time deliverable numbers are available again (email + webhook). Watches are checked every 6h, consumed when they fire, and you can watch up to 20 countries.
Create a watch with
country (ISO 3166-1 alpha-2). Responses: 201 created, 200 if it already existed, 409 if the country is currently in stock or the 20-country limit is reached.Webhooks now support the new event
phonenumber.stockavailable in events (for POST /v1/webhooks/settings and PUT /v1/webhooks/settings). Payload includes stock.country and stock.types[] with numberType (local/mobile/national/toll_free) and availableCount.New Feature
GET /v1/usage metering mode now supports spend attribution by profile/account, plus optional projection onto a single profile or account.This lets you break down billed spend (USD) across profiles/accounts for a given window, or fetch the metering payload scoped to one group for chargeback/reporting.
New query params (metering mode):
•
groupBy: profile | account (adds attribution)•
profileId: project results onto one profile (pair with range)•
accountId: project results onto one account (pair with range)New response fields in
UsageMetering:•
attribution (with groupBy, groups, unattributed, totals, restricted)•
scope (present with profileId/accountId)Note: when
profileId or accountId is set, peaks, callUsage, and tax return null (workspace-level only).New Feature
New endpoint:
Supports cached reads to avoid repeated YouTube calls; response includes
Key params:
•
•
•
•
•
Response highlights:
GET /v1/accounts/{accountId}/youtube-captions to fetch a YouTube video transcript (plain text plus timed cues), including auto-generated (asr) and uploaded (standard) tracks.Supports cached reads to avoid repeated YouTube calls; response includes
source = cache | youtube and fetchedAt. Use refresh=true only when captions changed (re-downloads from YouTube).Key params:
•
accountId (path)•
videoId (query, required)•
language (query, optional; BCP-47)•
format (query) = json | srt (default json)•
refresh (query, boolean)Response highlights:
trackKind = asr | standard, cues (when format=json), srt (when format=srt), and availableTracks for language selection.New Feature
New endpoint:
Provide the ad set ID via
Response includes
DELETE /v1/ads/ad-sets/{adSetId} deletes an ad set on the platform and cancels its ads locally (it never deletes the campaign).Provide the ad set ID via
adSetId (path).Response includes
deleted and adCount (number of local Ad documents marked status: cancelled). Returns 501 if deletion isn’t supported on the target platform.New Feature
Instagram is now supported (limited release) for liking/unliking posts and comments via:
•
•
This enables engagement actions on Instagram feed media (posts/reels/carousels) and their comments/replies, but access is currently restricted by Meta permissions.
Instagram requirements/behavior:
• Requires permission
• Only works for accounts connected via Facebook Login; Instagram Login returns
• Accounts whose token predates the permission return
• Limited release: non-allowed accounts return
• Burst limit: 50 like/unlike calls per 5s per Instagram account; exceeding can lock likes for 1 hour
Also note: these endpoints’
•
POST /v1/inbox/posts/{postId}/like, DELETE /v1/inbox/posts/{postId}/like•
POST /v1/inbox/comments/{postId}/{commentId}/like, DELETE /v1/inbox/comments/{postId}/{commentId}/likeThis enables engagement actions on Instagram feed media (posts/reels/carousels) and their comments/replies, but access is currently restricted by Meta permissions.
Instagram requirements/behavior:
• Requires permission
instagrammanageengagement• Only works for accounts connected via Facebook Login; Instagram Login returns
400 with code instagramlikesrequirefacebooklogin• Accounts whose token predates the permission return
403 with code reconnect_required• Limited release: non-allowed accounts return
403 with code PLATFORMBETARESTRICTED• Burst limit: 50 like/unlike calls per 5s per Instagram account; exceeding can lock likes for 1 hour
Also note: these endpoints’
403 may now indicate missing platform permission (not only Inbox add-on).Breaking Change
PlatformTarget.platformSpecificData is now returned as a free-form object (with additionalProperties: true) instead of a oneOf union of *PlatformData schemas.This avoids generated clients failing to deserialize
PlatformTarget responses when platformSpecificData doesn’t match exactly one variant. Treat platformSpecificData as an untyped JSON object on responses (it may be omitted rather than returned as an empty object).New Feature
WhatsApp inbox conversation creation now supports sending values for template buttons that carry their own payload (copy-code / flow tokens), via
Use this when your approved WhatsApp template has buttons that need a per-send value (e.g. Pix copy-and-paste code, coupon code, Flow token). This complements
Set
•
•
•
Message lineage is now exposed as
Note: existing stored messages are not backfilled; treat
templateButtonParams on POST /v1/inbox/conversations.Use this when your approved WhatsApp template has buttons that need a per-send value (e.g. Pix copy-and-paste code, coupon code, Flow token). This complements
templateParams (text variables + dynamic URL buttons).Set
templateButtonParams as an array of:•
index (0-9)•
subType: url | copy_code | flow•
valueMessage lineage is now exposed as
sentVia on GET /v1/inbox/conversations/{conversationId}/messages and on the message.sent webhook, with values:human | api | broadcast | sequence | workflow | comment_automation | bulk-api | nullNote: existing stored messages are not backfilled; treat
sentVia: null as “unknown”.New Feature
DELETE /v1/ads/campaigns/{campaignId} now accepts platform=google.You can delete Google campaigns using the same endpoint and request body shape as Facebook/Instagram.
Set
platform to one of: facebook, instagram, googleFor empty campaigns (0 ads), include
accountId to allow deletion: accountId (Zernio SocialAccount id).Improvement
GET
This lets you request a desired locale (e.g.
New:
• Query param:
• Response field:
/v1/whatsapp/template-library now supports selecting a specific language variant via an optional language query param.This lets you request a desired locale (e.g.
es, en_US); if it’s not available, the API returns the first available variant and reports the actual variant in template.language.New:
• Query param:
language• Response field:
template.availableLanguages (array of supported language codes)Improvement
GET
This lets you track recent workspace sign-ins (stamped at most once per hour). The field may be omitted for users with no recorded sign-in since it was introduced.
New field:
•
The
Updated enum:
•
New optional fields:
•
•
/v1/users now returns lastLoginAt for each user.This lets you track recent workspace sign-ins (stamped at most once per hour). The field may be omitted for users with no recorded sign-in since it was introduced.
New field:
•
users[].lastLoginAtThe
message.sent webhook payload now supports more platforms and can include send context in a new metadata object.Updated enum:
•
message.platform: instagram, facebook, telegram, whatsapp, twitter, reddit, bluesky, slackNew optional fields:
•
metadata.quotedMessageId•
metadata.threadTs (Slack only)New Feature
You can now fetch a single Google Business Profile review by ID via
This returns one review in the same shape as the items from
Key params:
•
•
•
Response includes
GET /v1/accounts/{accountId}/gmb-reviews/{reviewId}.This returns one review in the same shape as the items from
GET /v1/accounts/{accountId}/gmb-reviews, which is useful for checking the latest state before replying (replies are overwritten in place).Key params:
•
accountId (path)•
reviewId (path, ID portion only)•
locationId (query, optional override; otherwise uses the account’s selected location)Response includes
review (GoogleBusinessReview).New Feature
Instagram now supports publishing videos muted via
When
Use:
•
Applies to Reels, Stories, and video carousel slides; ignored for images. Videos above 200MB cannot be muted (mute before uploading).
muteAudio in platformSpecificData.When
muteAudio=true, we strip the audio track before sending the video to Instagram, so the published video is permanently silent.Use:
•
muteAudio (boolean, default false)Applies to Reels, Stories, and video carousel slides; ignored for images. Videos above 200MB cannot be muted (mute before uploading).
New Feature
Threads posts now support an optional first comment via
This lets you automatically publish a reply immediately after the main post is published; when using
Key field:
•
Note: the first comment is a Threads post and counts toward the profile’s 250 posts / 24h limit.
platformSpecificData.threads.firstComment.This lets you automatically publish a reply immediately after the main post is published; when using
threadItems, it replies to the root post.Key field:
•
firstComment (string, max 500)Note: the first comment is a Threads post and counts toward the profile’s 250 posts / 24h limit.
New Feature
Pinterest posts now support targeting a board section and optionally disclosing AI-modified content via
Use
Set
PinterestPlatformData.Use
boardSectionId to publish into a specific section within boardId (omit it to post to the board). Pinterest rejects the pin if the section doesn’t belong to the board, so send both together.Set
isAiGenerated to true to include Pinterest’s AI_MODIFIED disclosure (shows an “AI modified” label); false (default) omits the disclosure.New Feature
Google Search ads created via
You can create and attach sitelink assets during ad creation (Google reviews them asynchronously; approval is not immediate).
Use
• 2–20 items
• each item:
• optional:
Applies when
POST /v1/ads/create now support campaign-level sitelinks.You can create and attach sitelink assets during ad creation (Google reviews them asynchronously; approval is not immediate).
Use
sitelinks (Google Search only):• 2–20 items
• each item:
text (max 25) + linkUrl (URI)• optional:
description1, description2 (each max 35)Applies when
campaignType is search.New Feature
Meta ads now support the Page Likes objective via
This lets you create campaigns/ads optimized for Page Likes (Meta maps this to Engagement/Page Likes) using the same create flows.
Use
•
•
Notes for
goal = page_likes.This lets you create campaigns/ads optimized for Page Likes (Meta maps this to Engagement/Page Likes) using the same create flows.
Use
goal with:•
POST /v1/ads/campaigns: goal now includes page_likes•
POST /v1/ads/create: goal now includes page_likesNotes for
page_likes (Meta): promotedObject.pageId is optional (auto-filled from the connected Page). Creative fields like headline, body, linkUrl, callToAction, imageUrl/video are optional (Meta derives the Like button + destination from the Page).Improvement
Inbox list endpoints now return which connected accounts were skipped during aggregation via
This helps you distinguish between accounts that failed (
New response field:
•
Applies to:
•
•
•
meta.accountsSkipped.This helps you distinguish between accounts that failed (
meta.failedAccounts) vs accounts that were intentionally not queried because the platform/account doesn’t support the feature or isn’t enabled.New response field:
•
meta.accountsSkipped: array of { accountId, platform }Applies to:
•
GET /v1/inbox/conversations•
GET /v1/inbox/comments•
GET /v1/inbox/reviewsBreaking Change
GET /v1/ads/tree now supports scoping the tree to multiple ad accounts via adAccountId.You can pass a comma-separated list to limit results to a subset of connected accounts (useful for agency profiles).
Use
adAccountId=act1,act2,act_3 (max 50 per request). Single values keep the old behavior.Note: the plural aliases
adAccountIds and platformAdAccountIds are now rejected with 400 (to avoid silently returning unfiltered results).Improvement
GET
This lets you preview a video before selecting it (useful when multiple videos share the same first frame).
Key field:
•
/v1/ads/videos now returns a playable video URL via source in the default response.This lets you preview a video before selecting it (useful when multiple videos share the same first frame).
Key field:
•
data[].source — signed CDN MP4 URL (expires; use for preview only and re-list to refresh)New Feature
WhatsApp interactive messages now support native address capture via
This lets you prompt users with Meta’s structured shipping-address form in-chat; submissions arrive as
To send, set:
•
•
•
Webhook parsing update:
interactive.type = address_message on POST /v1/inbox/conversations/{conversationId}/messages.This lets you prompt users with Meta’s structured shipping-address form in-chat; submissions arrive as
metadata.interactiveType = nfm_reply on the message.received webhook.To send, set:
•
interactive.type: address_message•
interactive.action.name: address_message•
interactive.action.parameters.country: ISO 3166-1 alpha-2 (required)Webhook parsing update:
WebhookPayloadMessage.metadata now includes nfmReplyName (e.g. flow vs address_message) to distinguish address submissions from Flow submissions; address fields are included in flowResponseData.New Feature
New endpoint
Use it to fetch short-lived asset URLs on demand (don’t cache long-term; re-fetch before serving/downloading).
Key details:
• Path param:
• Response:
• Errors:
GET /v1/ads/{adId}/media returns direct signed URLs for every image/video asset used by an ad’s live creative (normalized across single, carousel, Reels/Story, and dynamic creative).Use it to fetch short-lived asset URLs on demand (don’t cache long-term; re-fetch before serving/downloading).
Key details:
• Path param:
adId (Zernio 24-char hex or platform ad id)• Response:
platform (facebook | instagram), media[] items with type (image | video), url, and for videos: thumbnailUrl, videoId, length, index• Errors:
422 (no active Meta connection), 501 (only supported on Meta)New Feature
CTWA ad creation now supports a custom chat welcome message via
This lets you override Meta’s default greeting and prefilled user text when the WhatsApp chat opens, so ads can steer the first message toward what they promote.
Set
•
•
welcomeMessage.This lets you override Meta’s default greeting and prefilled user text when the WhatsApp chat opens, so ads can steer the first message toward what they promote.
Set
welcomeMessage (single-creative) or creatives[].welcomeMessage (multi-creative) with:•
text•
prefillTextImprovement
GET /v1/inbox/comments and GET /v1/inbox/comments/{postId} responses are now cached for up to 10 minutes.This means comment counts/threads may lag new comments within that window; for real-time updates, use the
comment.received webhook instead of polling.Notes:
• Cache window:
up to 10 minutes• Recommended real-time mechanism:
comment.received• On
GET /v1/inbox/comments/{postId}, your own writes (create/reply/delete) refresh the cache immediately.Breaking Change
Meta ad videos can now be managed directly via the API: upload standalone videos and delete them from an ad account’s video library.
Use
Upload params:
•
• Exactly one of
• Optional
Returns
Use
Breaking change (CTWA): when using multi-creative
Also added for sorting:
Use
POST /v1/ads/videos to upload before an ad exists (for reuse in POST /v1/ads/create via video.id or in POST /v1/ads/preview).Upload params:
•
accountId, adAccountId• Exactly one of
videoUrl (https) or videoBase64• Optional
filename (only for videoBase64)Returns
video.id and optional video.thumbnailUrl.Use
DELETE /v1/ads/videos/{videoId} with accountId + adAccountId to remove a video; returns { success: true } on success.Breaking change (CTWA): when using multi-creative
creatives[], setting any top-level headline/body/imageUrl/video now returns 400 (previously those fields could be silently ignored).Also added for sorting:
createdTime on AdTreeCampaign and AdTreeAdSet (proxy based on earliest ad creation time).Improvement
CTWA ad creation request (
You can now pass
Video fields:
•
•
New status controls:
•
•
CtwaAdRequestBody) now supports reusing existing videos and more control over activation.You can now pass
video.id to reuse a video already uploaded to the ad account (instead of uploading via video.url). video.thumbnailUrl is now optional (Meta may auto-pick a thumbnail).Video fields:
•
video.url or video.id (either; id wins)•
video.thumbnailUrl (optional)New status controls:
•
status: ACTIVE | PAUSED (ad-level; default ACTIVE)•
campaignStatus: ACTIVE | PAUSED (campaign-level; default ACTIVE; rejected when using adSetId)Breaking Change
Connecting WhatsApp via
Response updates:
•
•
•
POST /v1/connect/whatsapp/credentials now explicitly subscribes your Meta app to the WABA with a Zernio override callback, cutting over webhook delivery immediately (no overlap). Unsubscribing the app from the WABA afterward will also stop Zernio delivery and requires calling this endpoint again.Response updates:
•
webhookNotice may be returned on success with cutover/unsubscribe guidance•
account.phoneNumber replaces account.selectedPhoneNumber•
account.verifiedName and account.qualityRating are now included when availableNew Feature
WhatsApp Flows now let you set a data exchange endpoint via
This enables configuring the HTTPS callback endpoint directly on DRAFT flows (and it will be used only if your uploaded Flow JSON declares
New field:
•
•
Notes:
•
• Settable only while the flow is in
endpointUri when creating or updating a flow.This enables configuring the HTTPS callback endpoint directly on DRAFT flows (and it will be used only if your uploaded Flow JSON declares
dataapiversion "3.0").New field:
•
POST /v1/whatsapp/flows: endpointUri•
PATCH /v1/whatsapp/flows/{flowId}: endpointUriNotes:
•
endpointUri is uri format and HTTPS-only• Settable only while the flow is in
DRAFTImprovement
LinkedIn reshare behavior is now explicitly defined for
When you set
Note: a text-free reshare is not LinkedIn’s one-click “Repost” feed treatment (LinkedIn’s API doesn’t support creating that), so it will still appear authored by you.
Use:
platformSpecificData.reshareUrl on POST /v1/posts.When you set
reshareUrl, the published post is always a reshare authored by your account with the original embedded underneath: with content it’s a quote-style reshare, and with no content it’s a text-free reshare.Note: a text-free reshare is not LinkedIn’s one-click “Repost” feed treatment (LinkedIn’s API doesn’t support creating that), so it will still appear authored by you.
Use:
platformSpecificData.reshareUrl (mutually exclusive with media).New Feature
SIP trunking is now available for Voice: you can create SIP trunks and attach Zernio numbers to route inbound/outbound calls through an external SIP platform.
New endpoints:
•
•
•
•
•
•
•
Create trunk request fields:
Phone number responses now include
Behavior change: when a number is attached to a trunk, these endpoints can return
•
•
•
New endpoints:
•
GET /v1/phone-numbers/sip-trunks (returns trunks, plus enabled)•
POST /v1/phone-numbers/sip-trunks (create; returns digestPassword only once)•
GET /v1/phone-numbers/sip-trunks/{id}•
DELETE /v1/phone-numbers/sip-trunks/{id}•
POST /v1/phone-numbers/sip-trunks/{id}/rotate-credentials (returns new digestPassword only once)•
POST /v1/phone-numbers/{id}/sip-trunk (attach with trunkId)•
DELETE /v1/phone-numbers/{id}/sip-trunk (detach)Create trunk request fields:
label, sipHost, optional sipPort, transport (tls/tcp/udp).Phone number responses now include
sipTrunkId (null when not trunked) on GET /v1/phone-numbers and GET /v1/phone-numbers/{id} (and the deprecated WhatsApp aliases).Behavior change: when a number is attached to a trunk, these endpoints can return
409 (invalidresourcestate) and require detaching first:•
DELETE /v1/phone-numbers/{id}•
POST /v1/phone-numbers/{id}/voice•
POST /v1/phone-numbers/{id}/whatsapp/callingNew Feature
LinkedIn company_list audiences can now be updated after creation via
This uploads a new full company list and replaces the existing list (not appended/merged). Matching is asynchronous and the audience stays
Send:
•
•
Notes:
•
•
POST /v1/ads/audiences/{audienceId}/companies.This uploads a new full company list and replaces the existing list (not appended/merged). Matching is asynchronous and the audience stays
processing while LinkedIn processes the update.Send:
•
audienceId (path)•
companies (array, 1..300000) — each row must include at least one of name, domain, website, linkedinPageUrlNotes:
•
company_list is filled at creation from companies on POST /v1/ads/audiences; use the new endpoint for later changes•
POST /v1/ads/audiences/{audienceId}/users is customerlist-only and returns 422 for companylist (use /companies instead)New Feature
New endpoint GET /v1/ads/videos is available.
It lists a Meta ad account’s existing ad video library so you can reuse an already-uploaded video when creating ads (avoid duplicate uploads and share one upload across multiple ads).
Call with:
•
•
•
•
•
Use returned
Note: this endpoint is Meta-only; other platforms return
It lists a Meta ad account’s existing ad video library so you can reuse an already-uploaded video when creating ads (avoid duplicate uploads and share one upload across multiple ads).
Call with:
•
accountId (required)•
adAccountId (required, act_<n>)•
fields (optional Graph field override)•
limit (optional, default 25, max 100)•
after (optional cursor)Use returned
data[].id as video.id on POST /v1/ads/create. A video is only usable once status.video_status is ready.Note: this endpoint is Meta-only; other platforms return
501.Improvement
POST
This improves client-side handling when creating a Reddit DM thread fails (missing user, DM not allowed, or rate limiting).
Key response changes:
•
•
•
/v1/inbox/conversations now returns more specific Reddit error details.This improves client-side handling when creating a Reddit DM thread fails (missing user, DM not allowed, or rate limiting).
Key response changes:
•
404 now includes code: PARTICIPANTNOTFOUND when the u/username doesn’t exist•
422 now also applies to Reddit when the recipient doesn’t accept private messages (still code: DMNOTALLOWED)•
429 may now be returned for Reddit rate limits with code: rate_limitedNew Feature
POST /v1/posts/{postId}/edit now supports editing published posts on more platforms and can target a specific account’s copy of a post.You can now edit on
platform: twitter, discord, facebook, reddit, linkedin, telegram, pinterest, googlebusiness, youtube, slack.When a post was published to multiple accounts on the same platform, pass
accountId to choose which one to edit (defaults to the first).Request body:
platform, content, optional accountId.Improvement
External Instagram posts and analytics responses now include additional Instagram-specific metadata fields.
This helps you distinguish content types like Reels vs Feed, detect AI-generated labels, and see whether a Reel was shared to the main feed.
New optional fields:
•
•
•
•
Also,
This helps you distinguish content types like Reels vs Feed, detect AI-generated labels, and see whether a Reel was shared to the main feed.
New optional fields:
•
mediaProductType (Instagram only; e.g. FEED, REELS, STORY, AD)•
isAiGenerated (Instagram only)•
isSharedToFeed (Instagram Reels only)•
mediaAudioType (Instagram only; MUSIC or ORIGINAL_SOUND)Also,
GET /v1/whatsapp/account-events events now include accountId in each event item.Improvement
Phone number listings now include WhatsApp Business Calling status via
This lets you detect whether Calling is enabled per number when listing purchased numbers and connected (bring-your-own) numbers.
New fields:
•
-
-
•
-
-
callingEnabled.This lets you detect whether Calling is enabled per number when listing purchased numbers and connected (bring-your-own) numbers.
New fields:
•
GET /v1/phone-numbers-
numbers[].callingEnabled-
connected[].callingEnabled•
GET /v1/whatsapp/phone-numbers (deprecated alias)-
numbers[].callingEnabled-
connected[].callingEnabledNew Feature
New endpoint:
You can now poll a WhatsApp account’s Meta-originated event history (newest first), including template review outcomes and WABA status changes. This complements webhook push events with a queryable feed.
Required query:
Optional query:
Response:
GET /v1/whatsapp/account-events.You can now poll a WhatsApp account’s Meta-originated event history (newest first), including template review outcomes and WABA status changes. This complements webhook push events with a queryable feed.
Required query:
accountIdOptional query:
limit (1–200, default 50)Response:
events[] with id, type (e.g. templateapproved, templaterejected, accountrestricted, accountdisconnected), severity (info/success/warning/critical), title, detail, createdAt.New Feature
New endpoint: POST
Use this when OAuth connect isn’t available or when users prefer not to install a third-party app; the token is validated against the store before saving and reconnecting replaces the stored token.
Request body:
•
•
•
Response includes
/v1/connect/shopify/token to connect a Shopify store using a merchant-created custom-app Admin token (token-paste alternative to OAuth).Use this when OAuth connect isn’t available or when users prefer not to install a third-party app; the token is validated against the store before saving and reconnecting replaces the stored token.
Request body:
•
profileId•
shop (e.g. your-store.myshopify.com)•
accessToken (starts with shpat_)Response includes
account with platform = shopify and username = store domain.Improvement
WhatsApp connect endpoints now return a clearer
This lets you distinguish between “number pinned to another profile” vs “number already connected elsewhere” and handle the conflict programmatically.
On
•
•
409 conflict when the selected number is already connected on another profile/workspace.This lets you distinguish between “number pinned to another profile” vs “number already connected elsewhere” and handle the conflict programmatically.
On
POST /v1/connect/whatsapp/credentials and POST /v1/connect/whatsapp/select-phone-number, 409 may include:•
WHATSAPPNUMBERPINNEDTOPROFILE — move it first with PATCH /v1/whatsapp/phone-numbers/{id}/profile•
WHATSAPPNUMBERALREADY_CONNECTED — disconnect it from the other profile/workspace first (a number can only be live on one profile)Improvement
mediaStatus on external/synced post media items can now be explicitly returned as available.This makes media availability explicit instead of relying only on the field being absent, while keeping backward compatibility (older synced items may still omit
mediaStatus).Updated enums:
•
ExternalPostMediaItem.mediaStatus: available | unavailable•
AnalyticsSinglePostResponse.mediaItems[].mediaStatus: available | unavailableNotes:
unavailable indicates the media file couldn’t be retrieved (often url is null, or LinkedIn video may return a cover image), and unavailableReason may be platform_withheld.Improvement
In
This lets you distinguish a real downloadable media file from a thumbnail/cover-image fallback.
Check:
•
•
•
post.external.* webhooks, ExternalPostMediaItem now clarifies LinkedIn video behavior: when LinkedIn returns no video file, url falls back to the cover image and the item includes mediaStatus.This lets you distinguish a real downloadable media file from a thumbnail/cover-image fallback.
Check:
•
url (may be null, or for LinkedIn videos may be a cover image)•
mediaStatus: unavailable (present only when the media file could not be retrieved)•
unavailableReason: platform_withheldNew Feature
X (Twitter) posts now support long-form X Articles via
This lets you create and publish an X Article (or save it as a draft) from the same post creation endpoint. When posting only Articles,
Use
•
•
•
• optional
Billing/pricing:
platformSpecificData.article in POST /v1/posts.This lets you create and publish an X Article (or save it as a draft) from the same post creation endpoint. When posting only Articles,
content can be omitted.Use
platforms[].platformSpecificData.article with:•
title•
content_state•
mode: publish | draft• optional
cover.url (+ cover.altText)Billing/pricing:
GET /v1/billing/x-pricing now includes articledraft and articlepublish (both in tier xapi010, $0.010/call).New Feature
POST
This lets you boost a post with a CTA that drives clicks to an Instagram profile (typically paired with the profile URL).
Use:
•
•
Applies to Meta boosts only; other platforms will reject Meta-only options.
/v1/ads/boost now accepts a Meta-only CTA value VIEWINSTAGRAMPROFILE in callToAction.This lets you boost a post with a CTA that drives clicks to an Instagram profile (typically paired with the profile URL).
Use:
•
callToAction: VIEWINSTAGRAMPROFILE•
linkUrl: (Instagram profile URL)Applies to Meta boosts only; other platforms will reject Meta-only options.
Improvement
PlatformTarget.errorCategory now includes more granular failure types: platformratelimit and quota_exhausted.Use these to distinguish platform throttling vs. shared daily API quota exhaustion and adjust retry/backoff or alerting logic accordingly.
Updated
errorCategory values now include: authexpired, usercontent, userabuse, accountissue, platformrejected, platformerror, platformratelimit, quotaexhausted, systemerror, unknown.Improvement
WhatsApp messages sent via the inbox endpoints can now disable link previews using
This lets you send WhatsApp text without the URL thumbnail/preview (useful for cleaner utility messages or when previews are undesirable).
Set
•
•
Available on:
•
•
linkPreview.This lets you send WhatsApp text without the URL thumbnail/preview (useful for cleaner utility messages or when previews are undesirable).
Set
linkPreview (boolean, default true):•
false - send without link preview•
true - send with link previewAvailable on:
•
POST /v1/inbox/conversations (WhatsApp Direct Send with category = utility)•
POST /v1/inbox/conversations/{conversationId}/messages (WhatsApp; ignored on other platforms)Improvement
Comment webhooks now include
This lets you detect when a
New field:
•
comment.author.isOwnAccount in WebhookPayloadComment.This lets you detect when a
comment.received event was authored by the connected account itself (Meta can re-deliver the account’s own replies as comment events), so you can filter or route them differently.New field:
•
comment.author.isOwnAccount (boolean) — populated on Instagram/Facebook realtime webhooks only; if absent, it was not evaluated (don’t treat missing as false).Improvement
Webhook
This flag indicates the inbound message contains nothing you can render (e.g., a
New field:
message.received payload now may include metadata.noRenderableContent (Instagram / Facebook Messenger only).This flag indicates the inbound message contains nothing you can render (e.g., a
template attachment with no text/parseable content, or Meta’s is_unsupported flag). Use it to suppress/handle “empty” messages in your UI.New field:
metadata.noRenderableContent (boolean)New Feature
Bluesky now supports default post languages at the account level, and per-post language tagging for feed-generator filtering.
Use the new endpoints to get/set an account default that applies at publish time when a post omits
•
•
On
Use the new endpoints to get/set an account default that applies at publish time when a post omits
platformSpecificData.langs:•
GET /v1/accounts/{accountId}/bluesky-settings → returns defaultLangs•
PATCH /v1/accounts/{accountId}/bluesky-settings with defaultLangs = 1–3 BCP-47 codes (or null to clear)On
POST /v1/posts, Bluesky posts now accept platformSpecificData.langs (1–3 BCP-47 codes, e.g. pt, en-US). Per-post langs overrides the account default; if neither is set, the field is omitted. When using threadItems, the same langs applies to every item in the thread.Improvement
Meta ad creation now supports setting campaign status independently via
This lets you create a campaign ACTIVE while keeping the ad set/ad PAUSED (or the reverse), instead of using a single
Set
Ads now also return Meta’s creative id as
campaignStatus on POST /v1/ads/create.This lets you create a campaign ACTIVE while keeping the ad set/ad PAUSED (or the reverse), instead of using a single
status value for all levels.Set
campaignStatus to ACTIVE or PAUSED (Meta only). If omitted, it follows status.Ads now also return Meta’s creative id as
ad.creative.creativeId, which you can reuse via existingCreativeId on POST /v1/ads/create.Improvement
Campaign/ad set status toggles now return clearer, more explicit results and always confirm what switch was written.
For
Response additions:
•
•
For ad sets (
Response additions/changes:
•
•
•
Also clarified: on Meta, resuming a campaign doesn’t automatically unpause ad sets paused independently—pair with
For
PUT /v1/ads/campaigns/{campaignId}/status, the API writes the campaign on/off switch and returns the confirmed status (active | paused). updated/skipped now describe only ads whose stored status changed alongside the campaign switch, so updated: 0 can be a normal successful resume.Response additions:
•
status (active | paused)•
skippedReasons (why ads were skipped)For ad sets (
PUT /v1/ads/ad-sets/{adSetId} and PUT /v1/ads/ad-sets/{adSetId}/status), status toggling is now defined as writing the ad set’s own switch where supported (Meta/LinkedIn). Where a platform has no ad-set switch, the API may return no status and instead return a message when nothing was written.Response additions/changes:
•
status may be absent (see statusMessage/message)•
statusSkippedReasons / skippedReasons•
statusMessage / messageAlso clarified: on Meta, resuming a campaign doesn’t automatically unpause ad sets paused independently—pair with
PUT /v1/ads/ad-sets/{adSetId}/status when needed.New Feature
GET
This adds a live probe of the Meta link behind the channel (same read as
New WhatsApp-only fields:
•
•
•
•
/v1/accounts/{accountId}/health now includes platformConnection for WhatsApp accounts.This adds a live probe of the Meta link behind the channel (same read as
GET /v1/whatsapp/number-info), so WhatsApp liveness can be detected even when tokenStatus is valid. When the Meta link is dead, platformConnection.status is disconnected and overall status is error.New WhatsApp-only fields:
•
platformConnection.status: connected | disconnected | unknown•
platformConnection.checkedAt•
platformConnection.phoneStatus•
platformConnection.metaError (when disconnected: code, subcode, message)New Feature
Webhooks now support the new WhatsApp event template category updates via
This fires when Meta reclassifies a WhatsApp template’s category on a connected WABA, so you can react to upcoming (
Subscribe by adding
Payload highlights:
•
•
•
•
•
whatsapp.template.category_updated.This fires when Meta reclassifies a WhatsApp template’s category on a connected WABA, so you can react to upcoming (
scheduled) and applied (applied) changes.Subscribe by adding
whatsapp.template.category_updated to events when calling POST /v1/webhooks/settings or PUT /v1/webhooks/settings.Payload highlights:
•
event: whatsapp.template.category_updated•
template.changeType: scheduled | applied•
template.category: UTILITY | MARKETING | AUTHENTICATION•
template.previousCategory (only when applied)•
template.scheduledCategory, template.effectiveAt (only when scheduled)Breaking Change
Meta ad creation changed how the default Instagram identity is chosen when
If you don’t pass
To force a specific IG identity, set
instagramAccountId is omitted on POST /v1/ads/create.If you don’t pass
instagramAccountId, we now use the Instagram actor Meta already runs the Page’s other ads as, falling back to the Page’s page-backed Instagram account.To force a specific IG identity, set
instagramAccountId to the desired Instagram Business Account ID (e.g. 17841...).Breaking Change
Meta conversion setup on
Meta rejects
Key fields:
•
•
• For pixel custom events:
POST /v1/ads/create is now stricter/clearer around promotedObject.Meta rejects
promotedObject.pixelId unless promotedObject.customEventType is also provided (even if promotedObject.customConversionId is set). If you want to optimize for a Custom Conversion without sending pixel/event, you can now send promotedObject.customConversionId alone.Key fields:
•
promotedObject.pixelId + promotedObject.customEventType (required together for Meta pixel-based conversion optimization)•
promotedObject.customConversionId (can be sent alone; if pixelId is also sent, customEventType is still required)• For pixel custom events:
promotedObject.customEventType = OTHER + promotedObject.customEventStrBreaking Change
SMS is now supported in inbox webhooks, and SMS sends now explicitly fail for opted-out recipients.
Inbound SMS replies arrive via
When sending via
• Opted-out recipients (replied STOP) are refused with
•
Webhook platform enum now includes
Inbound SMS replies arrive via
message.received with message.platform = sms, and outgoing SMS outcomes arrive via message.delivered / message.failed (carrier error details included on failure).When sending via
POST /v1/sms/messages:• Opted-out recipients (replied STOP) are refused with
409•
409 can also mean the same Idempotency-Key is still in flightWebhook platform enum now includes
sms (alongside instagram, facebook, telegram, whatsapp).Improvement
KYC address payloads now accept an optional
•
•
(and the deprecated
Use
Set
extended_address field on:•
POST /v1/phone-numbers/kyc•
POST /v1/phone-numbers/kyc/validate-address(and the deprecated
/v1/whatsapp/... aliases).Use
extendedaddress for address complements like apartment/suite/unit (or quadra/lote in some countries). This can help pass address validation without changing streetaddress.Set
address.extendedaddress (KYC submit) or top-level extendedaddress (validate-address).New Feature
Webhooks now support referral click events via the new
This lets you capture Meta referral payloads even when a click opens an existing thread without generating a message (previously you could only see referrals on
To subscribe, include
•
•
The webhook payload is
•
•
•
referral.received event.This lets you capture Meta referral payloads even when a click opens an existing thread without generating a message (previously you could only see referrals on
message.received when Meta attached them).To subscribe, include
referral.received in events when creating/updating a webhook:•
POST /v1/webhooks/settings events•
PUT /v1/webhooks/settings eventsThe webhook payload is
WebhookPayloadReferral with:•
event = referral.received•
referral (Meta object forwarded verbatim; may include ref, source, type, refereruri, adid, adscontextdata)•
sender, conversation, account, timestampNew Feature
You can now attach a Meta multi-language ad when adding an ad to an existing ad set via
In
Use:
•
•
•
Note:
POST /v1/ads/create.In
adSetId (attach) mode, you can provide a per-locale asset feed so one ad carries localized copy (and optional localized media) while keeping social proof on a single post.Use:
•
adSetId (attach mode)•
defaultLocale (e.g. en, pt_BR)•
translations[] with required fields: locale, headline, body, description (optional per-locale: linkUrl, imageUrl, videoUrl, thumbnailUrl)Note:
translations is mutually exclusive with dynamicCreative, placementAssets, carouselCards, existingCreativeId and creatives[].New Feature
POST
If every platform entry is a LinkedIn repost using
Use
/v1/posts now supports LinkedIn plain reposts (no commentary).If every platform entry is a LinkedIn repost using
platformSpecificData.reshareUrl, you can omit top-level content (and omit customContent) to publish LinkedIn’s one-click “Repost” with no text.Use
platformSpecificData.reshareUrl (LinkedIn post URL or urn:li:share/urn:li:ugcPost/urn:li:groupPost). If you include content, it becomes a quote-reshare (repost with your thoughts).Improvement
Workflow nodes now support an optional display label via
This lets you set a human-friendly name shown in the builder canvas/inspector (falls back to the node
Use
WorkflowNode.label.This lets you set a human-friendly name shown in the builder canvas/inspector (falls back to the node
type when omitted). If you update a workflow, remember the nodes array is replaced wholesale—so resend label to keep it.Use
label (string, 1–80 chars) on WorkflowNode.Improvement
GET /v1/ads/targeting/search expands geo targeting with a new geoType option all and additional geo types.You can now search across multiple geo levels in one call (relevance-ranked) instead of picking a single level.
Updated
geoType enum:•
all, country, region, city, subcity, neighborhood, place, zip, metroarea, geomarketPinterest notes:
city is treated as an alias for all (Pinterest has no city-level catalog), and some geo types (e.g. zip) return 400 because Pinterest doesn’t expose those catalogs.Improvement
GET /v1/inbox/comments/{postId} now supports passing a Facebook/Instagram comment ID as postId to fetch that comment’s replies (instead of the post’s top-level comments).This lets you page deeper reply threads without a separate endpoint; on YouTube,
postId must still be a video id.Use:
•
GET /v1/inbox/comments/{postId}?accountId=... where {postId} can be a post ID or (FB/IG only) a comment ID• New response signal (Facebook only):
repliesHasMore (when true, fetch remaining replies by calling the same endpoint with that comment’s id as postId)Also updated for
GET /v1/inbox/comments pagination:•
nextCursor pagination can continue past the first page on facebook, instagram, threads, linkedin, youtube• Cursor paging is only coherent for
sortBy=date + sortOrder=desc; other sort combinations can make page 2 unreliable• Treat
nextCursor as opaque and dedupe results by idNew Feature
Shopify is now supported as a connect-only platform, with a new Blogs API to manage store blogs and articles.
Start the OAuth flow with
Manage blogs on a connected Shopify account (
•
•
•
Manage articles within a blog:
•
•
•
Notes: a future
Also added:
Start the OAuth flow with
GET /v1/connect/shopify using profileId, shop (e.g. your-store.myshopify.com), and optional redirect_url. This creates an account on the profile with platform shopify.Manage blogs on a connected Shopify account (
accountId):•
GET /v1/accounts/{accountId}/blogs (limit 1-50, cursor)•
POST /v1/accounts/{accountId}/blogs (title, optional handle)•
GET/PATCH/DELETE /v1/accounts/{accountId}/blogs/{blogId} (blogId is platform-native numeric)Manage articles within a blog:
•
GET /v1/accounts/{accountId}/blogs/{blogId}/articles (limit 1-50, cursor)•
POST /v1/accounts/{accountId}/blogs/{blogId}/articles (supports bodyHtml, tags, image.url, seo.title/seo.description, isPublished, publishDate)•
GET/PATCH/DELETE /v1/accounts/{accountId}/blogs/{blogId}/articles/{articleId} (articleId is platform-native numeric)Notes: a future
publishDate schedules publication natively on Shopify; DELETE on a blog deletes the blog and all its articles permanently.Also added:
GET /v1/connect/{platform} now accepts WhatsApp-only onboarding = api | business_app.Improvement
Campaign listing and tree endpoints now support filtering by delivery/spend within a date window.
On
New query params:
•
•
On
On
GET /v1/ads/campaigns, you can now return only campaigns that actually delivered in fromDate–toDate, or that reached a spend threshold.New query params:
•
hasDelivery (boolean) — only campaigns with delivery in the window (spend > 0 or impressions at zero spend)•
minSpend (number, min 0) — only campaigns whose spend in the window reaches this amount (in the campaign’s own currency); implies hasDeliveryOn
GET /v1/ads/tree, the same filters were added, and fromDate is clarified: it affects metrics only unless you also pass hasDelivery or minSpend. Pagination totals reflect the filtered campaign set.New Feature
Google Local Services Ads lead insights are now available via two new endpoints.
You can fetch LSA leads live from Google (newest first) and then pull the conversation history (calls/messages) for a specific lead. Note: data is not persisted (Google is the source of truth), and requests draw from the shared Google Ads operations budget.
Get leads with
Required:
Optional filters:
Key fields:
Get lead conversations with
Params:
Conversation fields include
You can fetch LSA leads live from Google (newest first) and then pull the conversation history (calls/messages) for a specific lead. Note: data is not persisted (Google is the source of truth), and requests draw from the shared Google Ads operations budget.
Get leads with
GET /v1/ads/local-services/leadsRequired:
accountIdOptional filters:
customerId, fromDate, toDate, leadType (PHONECALL/MESSAGE/BOOKING), leadStatus (e.g. NEW, BOOKED, WIPEDOUT), chargedOnly, pageTokenKey fields:
id, contact (null when WIPED_OUT), charged, creditState, createdTimeGet lead conversations with
GET /v1/ads/local-services/leads/{leadId}/conversationsParams:
leadId, accountId (+ optional customerId, pageToken)Conversation fields include
channel (PHONECALL/MESSAGE/SMS/EMAIL/WHATSAPP/ADSAPI), participantType (ADVERTISER/CONSUMER), plus phoneCall.callRecordingUrl / message.attachmentUrls when applicable.New Feature
TikTok ads now populate engagement breakdown in
You can now read paid engagement interactions for TikTok directly from
Key fields:
•
•
•
•
Other platforms are unchanged (still 0 where not supported).
AdEngagementCounts (previously Meta-only).You can now read paid engagement interactions for TikTok directly from
engagementBreakdown (while TikTok follows remain in actions).Key fields:
•
AdMetrics.engagementBreakdown.reactions (TikTok paid likes)•
AdMetrics.engagementBreakdown.comments•
AdMetrics.engagementBreakdown.shares•
AdMetrics.actions.follow (TikTok followers objective result)Other platforms are unchanged (still 0 where not supported).
New Feature
POST
When
Key fields:
• Enable:
• Supported
• Web Smart+ requires
• App Smart+ requires
• Budget is campaign-level;
• Cannot be combined with
/v1/ads/create now supports TikTok Smart+ campaigns via smartPlus.When
smartPlus: true, Zernio creates an Upgraded Smart+ campaign where TikTok automates targeting/bidding/delivery (within your provided geo targeting).Key fields:
• Enable:
smartPlus (boolean)• Supported
goal: conversions, leadgeneration, apppromotion• Web Smart+ requires
promotedObject.pixelId + promotedObject.customEventType• App Smart+ requires
promotedObject.applicationId• Budget is campaign-level;
budgetType=lifetime requires endDate• Cannot be combined with
adSetIdNew Feature
LinkedIn posts now support polls via
Use this to publish a LinkedIn poll with your post. Polls cannot be combined with media or
Set
•
•
•
poll in LinkedInPlatformData.Use this to publish a LinkedIn poll with your post. Polls cannot be combined with media or
reshareUrl, cannot be edited after publishing, and API-created polls are non-sponsored only.Set
poll with:•
question (1-140 chars)•
options (2-4 items, 1-30 chars each)•
duration (optional): ONEDAY | THREEDAYS | SEVENDAYS | FOURTEENDAYS (default SEVEN_DAYS)Improvement
Ad analytics now include additional Meta click + cost breakdown fields in
This adds per-action cost metrics and separates different click definitions (attributed vs in-session vs outbound), making it easier to build accurate dashboards and optimize campaigns.
New fields:
•
•
•
•
Note: these click fields are Meta-only; other platforms return
AdMetrics.This adds per-action cost metrics and separates different click definitions (attributed vs in-session vs outbound), making it easier to build accurate dashboards and optimize campaigns.
New fields:
•
costPerAction (object) — derived spend / actions[type] per action type•
outboundClicks, outboundClicksCtr•
inlineLinkClicks, inlineLinkClickCtr•
uniqueClicks, uniqueCtrNote: these click fields are Meta-only; other platforms return
0 (and costPerAction may be {} when not applicable).New Feature
Google Ads now supports keyword edits on existing ads via
You can update Google Search ad group keywords by sending
Use:
•
•
Also added for Google Search creation via
•
PUT /v1/ads/{adId}.You can update Google Search ad group keywords by sending
targeting.keywords and/or targeting.negativeKeywords. Each list you send becomes the FULL new set of that kind (items not included are removed); omit a kind to leave it unchanged.Use:
•
targeting.keywords (Google only) — items are "string" (defaults to broad) or { text, matchType } where matchType is exact | phrase | broad•
targeting.negativeKeywords (Google only) — same shape as aboveAlso added for Google Search creation via
POST /v1/ads/create:•
negativeKeywords — broad-match negative keywords on the new ad group (editable later via PUT /v1/ads/{adId} targeting.negativeKeywords).Improvement
TikTok ad reporting now populates additional video metrics in
This lets you use the same video KPI fields across Meta and TikTok when reading ad/campaign metrics.
TikTok sources now apply to:
•
•
•
•
•
•
Meta-only remains:
•
•
•
•
AdMetrics (previously documented as Meta-only).This lets you use the same video KPI fields across Meta and TikTok when reading ad/campaign metrics.
TikTok sources now apply to:
•
videoPlayActions•
videoP25WatchedActions•
videoP50WatchedActions•
videoP75WatchedActions•
videoP100WatchedActions•
videoAvgTimeWatchedActionsMeta-only remains:
•
video30SecWatchedActions•
videoThruplayWatchedActions•
videoP95WatchedActions•
costPerThruplayNew Feature
Instagram now supports attaching catalog audio to Reels via
You can search/trend audio with
Search params:
•
•
To publish with audio, set:
•
•
•
Requires an Instagram account connected via Facebook Login. Classic Instagram Login accounts will get a 400
platformSpecificData.audioConfiguration.You can search/trend audio with
GET /v1/accounts/{accountId}/instagram/audio (omit q for trending) and fetch metadata with GET /v1/accounts/{accountId}/instagram/audio/{audioId}. Use the returned audioId when creating a Reel.Search params:
•
audioType: music | original_sound•
q: optional keywords (omit for trending)To publish with audio, set:
•
platformSpecificData.audioConfiguration.audioId (required)•
platformSpecificData.audioConfiguration.audioVolume (0-100)•
platformSpecificData.audioConfiguration.videoVolume (0-100)Requires an Instagram account connected via Facebook Login. Classic Instagram Login accounts will get a 400
instagramaudiorequiresfacebooklogin and must be reconnected using the Facebook option.Improvement
GET /v1/broadcasts/{broadcastId}/recipients now returns errorTraceId for failed recipients.Use
errorTraceId (Meta fbtrace_id) to correlate a failed send and quote it when escalating to Meta Direct Support. It’s only populated when status is failed (Meta platforms).New field:
errorTraceId (string | null)Improvement
GET /v1/accounts/{accountId}/instagram/stories/{storyId}/insights can now return 502 when Instagram rejects the upstream request.This makes upstream failures explicit so clients can distinguish Instagram-side rejection from successful-but-empty expired story results.
Handle these responses:
•
200 with data.source = live | cached | unavailable•
502 - Instagram rejected the requestNew Feature
Meta ads now support scheduled budget increases via
This lets you pre-schedule temporary budget bumps (e.g. Black Friday) instead of editing budgets manually on the day.
Send:
•
• exactly one of
•
•
•
• optional
Meta custom conversions are now manageable via the API:
• List:
• Create or reuse:
Creating supports reuse (to avoid duplicates) by matching
Meta creatives now accept
•
•
Use
POST /v1/ads/high-demand-periods.This lets you pre-schedule temporary budget bumps (e.g. Black Friday) instead of editing budgets manually on the day.
Send:
•
accountId• exactly one of
campaignId / adSetId•
budgetValueType: ABSOLUTE | MULTIPLIER•
budgetValue•
timeStart, timeEnd (Unix seconds, 15-min boundaries)• optional
recurrenceType: ONE_TIME | WEEKLY | MONTHLYMeta custom conversions are now manageable via the API:
• List:
GET /v1/accounts/{accountId}/custom-conversions (requires adAccountId)• Create or reuse:
POST /v1/accounts/{accountId}/custom-conversionsCreating supports reuse (to avoid duplicates) by matching
name + pixelId; reused conversions return 200 with reused: true, otherwise 201. The response includes customConversionId for promotedObject.customConversionId on POST /v1/ads/create.Meta creatives now accept
multiAdvertiser: OPTIN | OPTOUT on:•
POST /v1/ads/creatives•
POST /v1/ads/createUse
OPT_OUT to disable Meta’s multi-advertiser ads behavior (Meta auto-enrolls by default).Breaking Change
POST /v1/media/presign no longer accepts the permanent request field.If you were sending
permanent: true to write uploads to non-expiring storage, remove this parameter from your request payload. Uploads will now follow the default behavior described by the endpoint.Removed parameter:
permanent (boolean)New Feature
POST
By default, presigned uploads go to temporary storage and auto-delete 7 days after upload (so the
New request field:
When
/v1/media/presign now supports permanent uploads.By default, presigned uploads go to temporary storage and auto-delete 7 days after upload (so the
publicUrl stops working after that). Set permanent: true to write to permanent storage with no expiry—useful for media that must remain reachable longer (e.g., covers/thumbnails for posts scheduled >7 days out).New request field:
permanent (boolean, default false)When
permanent is true, the response publicUrl and key use the media/ prefix instead of temp/.New Feature
You can now like/unlike posts via the Inbox API.
This enables executive engagement (like/react as any connected
New endpoints:
•
•
Also updated: comment like/unlike now supports LinkedIn on
This enables executive engagement (like/react as any connected
accountId on a brand post). postId accepts a Zernio post ID or the platform native post ID.New endpoints:
•
POST /v1/inbox/posts/{postId}/like with accountId (+ optional reactionType for LinkedIn: LIKE, PRAISE, EMPATHY, INTEREST, APPRECIATION, ENTERTAINMENT; optional cid for Bluesky)•
DELETE /v1/inbox/posts/{postId}/like with accountId (+ likeUri required for Bluesky)Also updated: comment like/unlike now supports LinkedIn on
POST /v1/inbox/comments/{postId}/{commentId}/like and DELETE /v1/inbox/comments/{postId}/{commentId}/like. For LinkedIn likes, pass the composite comment URN as commentId and you can set reactionType (same enum as above).New Feature
Workflow AI nodes now support
This lets you route AI steps through OpenRouter using your BYOK setup (set
Set
openrouter as an ai.provider value.This lets you route AI steps through OpenRouter using your BYOK setup (set
provider + model).Set
ai.provider to one of: anthropic, openai, google, mistral, groq, openrouter.New Feature
Inbox messages now support refreshable attachment URLs for Instagram/Facebook DMs.
Meta DM media URLs expire; attachments now include a stable
New endpoint:
Key params:
Response behavior:
• Default
•
Also added on
•
•
Meta DM media URLs expire; attachments now include a stable
refreshUrl you can store and call later to get a working media URL.New endpoint:
GET /v1/inbox/conversations/{conversationId}/messages/{messageId}/attachments/{index}Key params:
accountId (required), format (redirect | json, default redirect)Response behavior:
• Default
format=redirect returns 302 to the live media URL•
format=json returns { url, refreshed }Also added on
GET /v1/inbox/conversations/{conversationId}/messages attachments:•
attachments[].refreshUrl (Instagram/Facebook only)•
attachments[].url is now documented as expiring on Instagram/Facebook (use immediately; don’t store).New Feature
Comment-to-DM automations can now also trigger from inbound DMs via
Set
Key details:
•
• Requires at least one
• Not allowed for
Responses now include:
•
•
alsoMatchInDms.Set
alsoMatchInDms: true on a trigger=comment automation to answer users who DM the keyword instead of commenting it. Deduplication is per source (comment vs DM).Key details:
•
alsoMatchInDms (boolean, default false) on POST /v1/comment-automations and PATCH /v1/comment-automations/{automationId}• Requires at least one
keywords entry (empty keyword lists are rejected when enabling)• Not allowed for
trigger=story_replyResponses now include:
•
alsoMatchInDms on automation objects (list/get/create/update)•
source on log entries: comment | story_reply | dmNew Feature
Instagram connect now supports headless mode for
This lets you run the Facebook Login-based Instagram flow with your own UI: after OAuth, the callback redirects to your
Use:
•
Callback includes:
loginMethod=facebook_login on GET /v1/connect/instagram.This lets you run the Facebook Login-based Instagram flow with your own UI: after OAuth, the callback redirects to your
redirect_url with selection-step params, and you finish by calling GET /v1/connect/instagram/select-account.Use:
•
GET /v1/connect/instagram?loginMethod=facebook_login&headless=trueCallback includes:
platform=instagram, step=selectaccount, profileId, tempToken, connecttokenBreaking Change
WhatsApp connect now rejects Zernio-provisioned numbers that are pinned to a different profile with a
This prevents accidentally connecting a provisioned WhatsApp number from the wrong
New
•
•
•
Remediation:
• Move the number with
409 conflict.This prevents accidentally connecting a provisioned WhatsApp number from the wrong
profileId. If you need to connect it on another profile, move the number first, then retry the connect.New
409 responses:•
GET /v1/connect/whatsapp (when profileId doesn’t match the provisioned number’s profile)•
POST /v1/connect/whatsapp/credentials•
POST /v1/connect/whatsapp/select-phone-numberRemediation:
• Move the number with
PATCH /v1/whatsapp/phone-numbers/{id}/profile, then connect again.Improvement
Google Business location selection/updates now validate the provided Google account resource name and will reject mismatches.
If you pass an account identifier to speed up location resolution for large GBP accounts, the API will now return
Applies to:
•
•
•
If you pass an account identifier to speed up location resolution for large GBP accounts, the API will now return
400 when the provided account is not one of the accounts the connection manages.Applies to:
•
POST /v1/connect/googlebusiness/select-location with accountId•
PUT /v1/accounts/{accountId}/gmb-locations with googleAccountId (legacy body field accountId still accepted)•
POST /v1/accounts/{accountId}/gmb-locations/assign with googleAccountIdBreaking Change
POST
This lets you refresh/boost an existing post while keeping the ad set’s learning phase (attach to an existing ad set instead of provisioning a new campaign/ad set).
Key changes:
• Meta-only attach: send
• When
•
• Meta-only additions:
•
POST
• Send
Schema note (breaking for generated clients):
/v1/ads/boost now supports attaching a boost to an existing Meta ad set via adSetId, and adds Meta support for CTA destinations via linkUrl + callToAction.This lets you refresh/boost an existing post while keeping the ad set’s learning phase (attach to an existing ad set instead of provisioning a new campaign/ad set).
Key changes:
• Meta-only attach: send
adSetId to attach under an existing ad set• When
adSetId is set, budget/schedule/targeting are owned by the ad set and sending any of them is a 400•
budget is now required only when adSetId is NOT set• Meta-only additions:
instagramAccountId, destinationType (INSTAGRAMPROFILE, WEBSITE, ONAD, MESSENGER, WHATSAPP)•
linkUrl is now supported on Meta boosts (still supported on TikTok); callToAction must be sent together with linkUrl (sending either alone is a 400)POST
/v1/ads/ctwa (deprecated) also adds an attach flow:• Send
adSetId to add creatives to an existing messaging ad set; then budgetAmount/budgetType/endDate/objective/countries/interests/audienceId are rejected with 400Schema note (breaking for generated clients):
CtwaAdRequestBody no longer lists budgetAmount/budgetType as always-required (they’re required unless adSetId is set).New Feature
New endpoint:
This re-homes the number and any associated accounts together (so you don’t split the number across profiles by moving only the social account).
Send
Response may include
PATCH /v1/whatsapp/phone-numbers/{id}/profile to move a provisioned number to another profile.This re-homes the number and any associated accounts together (so you don’t split the number across profiles by moving only the social account).
Send
{ "profileId": "..." } where id is the phone number record id from GET /v1/phone-numbers (not an account id).Response may include
movedPlatforms with values phone, sms, whatsapp. Conflicts return 409 if the destination profile already has an account on any of those platforms.New Feature
PUT /v1/ads/campaigns/{campaignId} now supports Google campaigns (in addition to Meta) and enforces platform-level field support (unsupported fields return 400, not silently ignored).For Google, you can set campaign bidding via:
•
platform: google•
bidStrategy (Meta + Google)•
bidAmount (Google only; required for LOWESTCOSTWITHBIDCAP and COST_CAP)•
roasAverageFloor (Google only; required for LOWESTCOSTWITHMINROAS)The
200 response now echoes back only the fields you sent, plus updated (and may include bidAmount/roasAverageFloor when provided).New Feature
Message reactions are now supported on Instagram, Facebook Messenger, and Slack via the existing reactions endpoints.
You can add/remove reactions across more inbox channels, and reaction webhooks now cover these platforms too.
Use:
•
•
Notes:
• Slack reactions require a Slack emoji name like
• Instagram/Messenger reactions are subject to Meta’s 24h messaging window
•
Comment-to-DM automations now support sending a Meta generic template (product card) as the first DM.
Set
•
•
Constraints:
•
• For
Also: for
You can add/remove reactions across more inbox channels, and reaction webhooks now cover these platforms too.
Use:
•
POST /v1/inbox/conversations/{conversationId}/messages/{messageId}/reactions with emoji•
DELETE /v1/inbox/conversations/{conversationId}/messages/{messageId}/reactions with accountId (query)Notes:
• Slack reactions require a Slack emoji name like
:thumbsup: (unnamed Unicode emoji returns 400)• Instagram/Messenger reactions are subject to Meta’s 24h messaging window
•
reaction.received webhook now includes Slack/Instagram/Messenger (sender can be the business platform id; compare reaction.sender.id with conversation.participantId)Comment-to-DM automations now support sending a Meta generic template (product card) as the first DM.
Set
template on POST /v1/comment-automations / PATCH /v1/comment-automations/{automationId}:•
template.type: generic•
template.elements: 1–10 cards (each supports title, subtitle, imageUrl, buttons)Constraints:
•
template is mutually exclusive with buttons (sending both is a 400)• For
PATCH, pass template: null to clear the cardAlso: for
POST /v1/inbox/conversations/{conversationId}/messages on Instagram/Facebook, top-level buttons are now explicitly mutually exclusive with template (sending both returns 400).New Feature
Instagram follow checks are now available via
This resolves the follow relationship (when Meta allows it) and returns
Key fields:
•
•
Comment-to-DM automations now support follower-based targeting + optional follow verification (Instagram only), and delayed sends.
New request fields on
•
•
•
•
Automation logs (
Webhook comment payloads now may include Instagram-only
GET /v1/accounts/{accountId}/follow-status/{userId}.This resolves the follow relationship (when Meta allows it) and returns
isFollower (can be null when unknown) plus basic public counters; use refresh=true to bypass the short cache.Key fields:
•
isFollower: true | null (null = unknown, never “no”)•
unavailableReason: consentrequired | dmaccessdisabled | notmessageable | error | nullComment-to-DM automations now support follower-based targeting + optional follow verification (Instagram only), and delayed sends.
New request fields on
POST /v1/comment-automations and PATCH /v1/comment-automations/{automationId}:•
dmDelaySeconds (0–86400)•
commentReplyDelaySeconds (0–86400)•
audience (followerStatus: any | follower | non_follower; whenUnknown: send | skip | verify; optional minFollowerCount)•
followGate (message, buttonLabel, optional notFollowingMessage)Automation logs (
GET /v1/comment-automations/{automationId} and .../logs) add new status values pending and gated, plus fields like audienceOutcome (passed | blocked | gatesent | gatepassed | gate_failed) and nextDueAt while queued.Webhook comment payloads now may include Instagram-only
comment.author.instagramProfile (best-effort) with isFollower/followerCount when available; absence means “unknown”.Improvement
GET /v1/comment-automations/{automationId}/logs now returns non-matching comment diagnostics via a new misses object.Use
misses to see how many comments reached the automation but matched none of its keywords (and a few recent samples) to troubleshoot keyword/exclusion setups.New response fields:
•
misses.total•
misses.retentionDays•
misses.samples[]: commentText, commenterName, excludedBy, atRequest validation tightened:
•
limit now 1..200•
skip now >= 0• Invalid values may return
400 BadRequestImprovement
PATCH
You can switch an automation between comment-based and story-reply-based triggers; changing it will detach the automation from its currently bound post/story unless the same request sets a new binding.
Set
/v1/comment-automations/{automationId} now accepts trigger to control what fires an automation.You can switch an automation between comment-based and story-reply-based triggers; changing it will detach the automation from its currently bound post/story unless the same request sets a new binding.
Set
trigger to: • comment • story_reply (Instagram only)New Feature
Ads analytics now includes funnel step counts, an engagement breakdown, and cost per ThruPlay.
This makes it easier to report on Meta ads without parsing/deduping raw
New fields in
•
•
•
Also new in
•
This makes it easier to report on Meta ads without parsing/deduping raw
actions keys (and avoids double-counting), and adds a direct cost metric for ThruPlays.New fields in
AdMetrics:•
costPerThruplay•
funnel (ref AdFunnelCounts): landingPageViews, contentViews, searches, wishlistAdds, cartAdds, checkoutsInitiated, paymentInfoAdds, purchases, leads, registrationsCompleted, appInstalls, messagingConversationsStarted, messagingFirstReplies•
engagementBreakdown (ref AdEngagementCounts): postEngagement, pageEngagement, reactions, comments, shares, saves, pageLikes, videoViews, linkClicksAlso new in
Ad:•
creativeType: carousel | video | document | image | nullNew Feature
Comment-to-DM automations now support more precise keyword matching and exclusions via
This lets you avoid accidental triggers (e.g., keyword
Use
•
New fields:
•
•
Available on
matchMode=word, plus new fields excludeKeywords and typoTolerance.This lets you avoid accidental triggers (e.g., keyword
app no longer firing on happy) and optionally match close misspellings when using word-based matching.Use
matchMode:•
contains • word • exactNew fields:
•
excludeKeywords: string[] - comments matching these never trigger (compared using the same matchMode)•
typoTolerance: boolean - only with matchMode=wordAvailable on
POST /v1/comment-automations, PATCH /v1/comment-automations/{automationId}, and returned by GET endpoints.Improvement
LinkedIn connected accounts now include additional profile details in
This lets you read LinkedIn bio/headline and vanity slug info directly from the account object without extra lookups.
Key fields:
•
•
•
SocialAccount.metadata.profileData (refreshed on each daily snapshot).This lets you read LinkedIn bio/headline and vanity slug info directly from the account object without extra lookups.
Key fields:
•
metadata.profileData.bio (nullable)•
metadata.profileData.extraData.vanityName (personal accounts)•
metadata.organizationInfo.vanityName (organization accounts)New Feature
Meta ads creation now supports optimizing for pixel custom events via
Use this when you want
Set:
•
•
promotedObject.customEventStr on POST /v1/ads/create.Use this when you want
goal=conversions (or lead_conversion) to optimize against a custom event name from Events Manager (instead of a standard event or a Custom Conversion).Set:
•
promotedObject.customEventType = OTHER•
promotedObject.customEventStr = <youreventname>customEventStr is case-sensitive, and OTHER requires customEventStr (and vice versa).Improvement
Incoming message.received webhooks now include quote-reply context via
This lets you correlate an inbound message that quotes/replies to an earlier message (WhatsApp/Instagram/Facebook Messenger) using the original message’s
New field:
•
Also clarified
• WhatsApp: pass
• Telegram: pass Telegram message ID
• Instagram: outbound quote replies are not supported by Meta's Send API;
• Slack: threads the reply (uses
• Silently ignored on platforms without reply support (incl. Facebook Messenger)
metadata.quotedMessageId.This lets you correlate an inbound message that quotes/replies to an earlier message (WhatsApp/Instagram/Facebook Messenger) using the original message’s
platformMessageId.New field:
•
metadata.quotedMessageId - platformMessageId of the message being quoted/replied toAlso clarified
POST /v1/inbox/conversations/{conversationId}/messages replyTo behavior:• WhatsApp: pass
wamid• Telegram: pass Telegram message ID
• Instagram: outbound quote replies are not supported by Meta's Send API;
replyTo is ignored. Quote context is available on inbound messages only.• Slack: threads the reply (uses
thread_ts)• Silently ignored on platforms without reply support (incl. Facebook Messenger)
Improvement
Meta multi-language ads now support per-locale destination URLs via
This lets you send users to different landing pages per language while keeping a single ad (shared social proof).
Set
translations[].linkUrl on POST /v1/ads/create.This lets you send users to different landing pages per language while keeping a single ad (shared social proof).
Set
translations[].linkUrl to override the destination for that locale; when omitted it inherits the top-level linkUrl (which must be present if any locale uses translations[].linkUrl).New Feature
Webhooks now support per-subscription event blocking via
This lets you prevent specific resource-group events (e.g. private messages) from ever being delivered to a webhook endpoint, even if those events are listed in
Use
• Create: include
• Update: sending
Restricted
New/expanded
•
•
•
Note: after updating a webhook denylist, already-queued events may still arrive for up to ~5 minutes.
disabledResourceGroups on POST /v1/webhooks/settings and PUT /v1/webhooks/settings.This lets you prevent specific resource-group events (e.g. private messages) from ever being delivered to a webhook endpoint, even if those events are listed in
events. The same filtering applies to replay paths (test fire, redelivery, dead-letter requeue).Use
disabledResourceGroups (enum: publishing, engagement, messages, contacts, analytics, ads, telephony, accounts, billing, webhooks):• Create: include
disabledResourceGroups to opt out; omit or [] to receive everything in events• Update: sending
disabledResourceGroups replaces the denylist; send [] to clear; omit to leave unchangedRestricted
zrk_ keys: the key’s own disabled groups are always unioned into the subscription, so a restricted key can’t create or widen a subscription beyond itself.New/expanded
403 behavior (ResourceGroupForbidden) was added to:•
GET /v1/webhooks/settings•
DELETE /v1/webhooks/settings•
POST /v1/webhooks/test (note: webhook.test is in the webhooks group)Note: after updating a webhook denylist, already-queued events may still arrive for up to ~5 minutes.
Breaking Change
Non-draft updates to
If the resulting post violates platform rules (media requirements, platform-specific fields, etc.), the update will return
Also,
PUT /v1/posts/{postId} now run the same per-platform validation as post creation.If the resulting post violates platform rules (media requirements, platform-specific fields, etc.), the update will return
400 instead of succeeding.Also,
PostUpdateResponse may now include warnings (array of strings).New Feature
WhatsApp now supports requesting a user’s contact info via interactive messages.
You can send a consent-based “share contact” prompt, and inbound replies include contact cards plus an origin flag so you can distinguish consented self-share vs address-book shares.
Send with
•
On
•
•
You can send a consent-based “share contact” prompt, and inbound replies include contact cards plus an origin flag so you can distinguish consented self-share vs address-book shares.
Send with
interactive.type:•
requestcontactinfoOn
message.received, check:•
metadata.contacts (array)•
metadata.contactsOrigin: contact_request | otherNew Feature
Slack DMs are now supported in the Inbox API.
You can list eligible Slack workspace members (for a recipient picker) via
Key params:
•
•
Also updated:
You can list eligible Slack workspace members (for a recipient picker) via
GET /v1/accounts/{accountId}/slack-members, then start a DM by passing the member id as participantId to POST /v1/inbox/conversations.Key params:
•
GET /v1/accounts/{accountId}/slack-members: query, limit•
POST /v1/inbox/conversations: participantId (Slack member id, e.g. U01ABCDEF)Also updated:
POST /v1/inbox/conversations now lists supported platforms as X/Twitter, Bluesky, Reddit, WhatsApp, SMS, and Slack.Improvement
OAuth connect now surfaces upstream token-exchange failures via a new
This helps distinguish platform-side rejections from internal errors; handle
Key response codes:
•
•
Lead form creation can now return
Key response code:
•
502 response on POST /v1/connect/{platform}.This helps distinguish platform-side rejections from internal errors; handle
502 separately from 500 (the upstream 4xx is forwarded as platform_error).Key response codes:
•
502 - platform rejected token exchange (platform_error)•
500 - internal error while connecting the accountLead form creation can now return
422 on POST /v1/ads/lead-forms when Meta rejects the form (error code 3), typically because the Facebook Page hasn’t accepted the Lead Ads Terms.Key response code:
•
422 - Meta rejected lead form (often requires a Page admin to accept Lead Ads Terms)Improvement
Contacts listing now supports filtering by SocialAccount via
Use
New query param:
Also,
accountId on GET /v1/contacts.Use
accountId to list contacts through their channel; when accountId is set, the profileId contact filter is not applied (a provided profileId is still access-checked and still scopes filters.tags).New query param:
accountIdAlso,
POST /v1/contacts 409 is now explicitly documented as: Duplicate channel. The platformIdentifier is already bound to a channel on this accountId.New Feature
New endpoint
Use it to confirm a credential is valid before proceeding (e.g., when verifying an incoming token).
On
GET /v1/auth/verify was added to validate a bearer credential (API key or OAuth access token) without calling a data endpoint.Use it to confirm a credential is valid before proceeding (e.g., when verifying an incoming token).
On
200 it returns valid, userId, authType (api_key | oauth | session), and scope (space-separated OAuth scopes; null for API keys). 401 if invalid.Improvement
POST
This lets you explicitly set the ad set
Set
/v1/ads/boost now supports Meta-only optimization goal overrides via optimizationGoal.This lets you explicitly set the ad set
optimization_goal instead of relying on the default derived from goal, which can be useful when Meta requires a different optimization goal for the boost objective.Set
optimizationGoal to a Meta-compatible value for the objective derived from your goal (e.g. boost goal engagement maps to Meta objective OUTCOMEAWARENESS, so values like REACH, IMPRESSIONS, ADRECALLLIFT, or THRUPLAY-class are accepted; POSTENGAGEMENT is rejected under that objective).New Feature
Meta Ads now supports Value Rule Sets (bid adjustments by audience segment) via new endpoints under
You can create/list/read/replace/delete value rule sets, then attach one to an ad set to increase/decrease bids for matching segments (rule order matters: first match wins).
New endpoints:
•
•
•
•
•
Attach/detach on ad sets (Meta only) via
• Attach/replace: send
• Detach: send
• Sending
Also supported on create (Meta only) via
• Attach to the new ad set with
•
Eligibility (Meta enforced): only ad sets on
/v1/ads/value-rule-sets.You can create/list/read/replace/delete value rule sets, then attach one to an ad set to increase/decrease bids for matching segments (rule order matters: first match wins).
New endpoints:
•
GET /v1/ads/value-rule-sets•
POST /v1/ads/value-rule-sets•
GET /v1/ads/value-rule-sets/{valueRuleSetId}•
PUT /v1/ads/value-rule-sets/{valueRuleSetId} (full replace, not patch)•
DELETE /v1/ads/value-rule-sets/{valueRuleSetId}Attach/detach on ad sets (Meta only) via
PUT /v1/ads/ad-sets/{adSetId}:• Attach/replace: send
valueRuleSetId (optionally valueRulesApplied: true)• Detach: send
valueRulesApplied: false and omit valueRuleSetId• Sending
valueRulesApplied: false with valueRuleSetId returns 400 mutuallyexclusivefieldsAlso supported on create (Meta only) via
POST /v1/ads/create:• Attach to the new ad set with
valueRuleSetId (optional valueRulesApplied: true)•
valueRulesApplied: false is rejected on createEligibility (Meta enforced): only ad sets on
LOWESTCOSTWITHOUTCAP or COSTCAP.New Feature
WhatsApp template creation now supports NAMED variables via
This lets you use {{customer_name}}-style tokens (in addition to the existing positional {{1}} style) and provide per-variable examples for Meta review.
Set
For NAMED templates, include examples in:
•
•
Each named param example uses:
parameter_format on POST /v1/whatsapp/templates.This lets you use {{customer_name}}-style tokens (in addition to the existing positional {{1}} style) and provide per-variable examples for Meta review.
Set
parameter_format to POSITIONAL | NAMED (also accepts positional | named).For NAMED templates, include examples in:
•
components[].example.bodytextnamed_params•
components[].example.headertextnamed_paramsEach named param example uses:
{ param_name, example }.Breaking Change
Ad.creative.thumbnailUrl can now be null (was always a string).This affects ad preview/rendering: don’t assume a thumbnail URL is always available; fall back to other creative fields when it’s missing.
Key change:
creative.thumbnailUrl: string | nullImprovement
Connect flow redirects now include the created social account id in the success
•
•
•
•
This lets you reliably capture the connected account immediately after OAuth without extra lookup.
Look for
redirect_url for these endpoints:•
POST /v1/connect/facebook/select-page•
POST /v1/connect/googlebusiness/select-location•
POST /v1/connect/pinterest/select-board•
POST /v1/connect/snapchat/select-profileThis lets you reliably capture the connected account immediately after OAuth without extra lookup.
Look for
accountId in the returned redirect_url query params (e.g. ...&accountId=...).Breaking Change
PUT
If you include
/v1/posts/{postId} now requires platforms when sending root-level tiktokSettings or facebookSettings.If you include
tiktokSettings or facebookSettings in an update request without a platforms array, the API will return 400. Include the target platforms (with platform + accountId) in the same request when updating these settings.Breaking Change
In
A
Key field:
PUT /v1/posts/{postId}, platforms[].platformSpecificData update behavior is now explicitly defined.A
Settings namespace (e.g. facebookSettings, tiktokSettings) that is omitted from the request is preserved from the stored post. If you send the key, it replaces the entire namespace (it is not deep-merged).Key field:
platforms[].platformSpecificDataNew Feature
GET /v1/inbox/conversations now supports filtering by platform=whatsapp, and conversation objects can include new ad attribution data via metadata.This lets you identify conversations that started from Meta ads (Click-to-Message / Click-to-Direct, and WhatsApp Click-to-WhatsApp) and store/route them differently.
Use
platform enum values: facebook, instagram, twitter, bluesky, reddit, telegram, whatsappNew field:
metadata (nullable). Keys are optional and may include:• WhatsApp CTWA:
ctwaclid, ctwasourceid, ctwasourcetype, ctwasourceurl, ctwaheadline, ctwacapturedat• IG/FB ads:
metaadid, metaadsource, metaadtype, metaadref, metaadtitle, metaadphotourl, metaadvideourl, metaadpostid, metaadproductid, metaadflowid, metaadcapturedatGET /v1/inbox/conversations/{conversationId} also returns metadatametaad*` family there.Breaking Change
POST /v1/sms/sender-ids no longer enforces global uniqueness for alphanumeric sender IDs.The same
senderId can now be registered by multiple workspaces, so creating a sender ID should no longer fail due to another workspace already having it.Changes:
• Removed
409 (senderidtaken) conflict response• Increased active sender ID limit in
403 from 5 to 1,000 (senderidlimit_reached)Request remains:
senderIdNew Feature
WhatsApp now supports Meta Direct Send utility messages via
Eligible WhatsApp Business Accounts can start or send a business-initiated
Use
•
•
Notes:
•
•
•
category on inbox messaging endpoints.Eligible WhatsApp Business Accounts can start or send a business-initiated
utility text message without an approved template (Meta matches/auto-creates a template asynchronously). If the WABA isn’t eligible, the request fails and you must use an approved template.Use
category:•
POST /v1/inbox/conversations: set category=utility with message and no templateName (WhatsApp only)•
POST /v1/inbox/conversations/{conversationId}/messages: set category=utility to send as a business-initiated utility message (WhatsApp only)Notes:
•
category enum: utility•
category cannot be combined with templateName (create-conversation) or template (send-message)•
category is accepted on application/json only (not multipart/form-data)Improvement
Sending inbox messages now supports idempotent retries via
This prevents duplicate sends when you retry after timeouts: same key + same body replays the original response (with
Use:
•
New error responses:
•
•
Idempotency-Key on POST /v1/inbox/conversations/{conversationId}/messages.This prevents duplicate sends when you retry after timeouts: same key + same body replays the original response (with
Idempotent-Replayed: true) instead of sending again. Works for both application/json and multipart/form-data requests; keys are retained for 24 hours.Use:
•
Idempotency-Key (header)New error responses:
•
409 - Idempotency key still in flight•
422 - Idempotency key reused with a different bodyImprovement
PostAnalytics now includes videoDurationSeconds (nullable integer).This exposes the video length (currently Instagram Reels only) so you can estimate retention when combined with
igReelsAvgWatchTime (ms).New field:
•
videoDurationSeconds - video length in seconds; null when unknown (non-video/other platforms, or when Instagram doesn’t expose the media URL, e.g. some copyrighted-audio reels).New Feature
LinkedIn ads now return delivery diagnostics and effective bidding details via new fields on
This lets you distinguish “configured active” vs “actually serving” and understand why a LinkedIn campaign/creative is not delivering (holds, review, budget, etc.).
New fields:
•
•
•
•
Also added to tree responses:
•
Note: for LinkedIn,
Ad and GET /v1/ads/tree nodes.This lets you distinguish “configured active” vs “actually serving” and understand why a LinkedIn campaign/creative is not delivering (holds, review, budget, etc.).
New fields:
•
Ad.costType (LinkedIn only): CPM | CPC | CPV•
Ad.servingStatuses (LinkedIn only): array of serving/hold reasons (e.g. RUNNABLE, ACCOUNTTOTALBUDGET_HOLD)•
Ad.creative.isServing (LinkedIn only): true | false | null•
Ad.creative.servingHoldReasons (LinkedIn only): creative-level hold reasons (e.g. UNDER_REVIEW, REJECTED)Also added to tree responses:
•
AdTreeAdSet.costType, AdTreeAdSet.servingStatusesNote: for LinkedIn,
bidAmount represents the campaign’s effective unitCost and can be 0 (a real, delivery-stopping value), independent of bidStrategy.Improvement
GET
Use
New parameter:
/v1/contacts now supports filtering by multiple tags via a new tags query parameter.Use
tags as a comma-separated list to match contacts that have any of the provided tags.New parameter:
tags (comma-separated, OR match). Example: tags=vip,lead,partnerImprovement
GET
This lets you distinguish platform-specific states (e.g., Meta disabled vs. billing vs. review) and show better “why can’t I run ads?” messaging instead of relying only on
New fields in
•
•
•
Note: LinkedIn continues to use
/v1/ads/accounts now returns additional platform status details for ad accounts.This lets you distinguish platform-specific states (e.g., Meta disabled vs. billing vs. review) and show better “why can’t I run ads?” messaging instead of relying only on
selectable.New fields in
accounts[]:•
accountStatus (platform status; type varies by platform)•
disableReason (Meta only; integer code when accountStatus indicates disabled)•
approvalStatus (X only; observed: ACCEPTED, PENDING, REJECTED)Note: LinkedIn continues to use
status (filtered to active accounts).Improvement
POST /v1/contacts/bulk now treats missing platformIdentifier as a per-contact failure when importing in channel mode.If you send a top-level
accountId, each contact is expected to include platformIdentifier; rows missing it are rejected individually and returned in errors[] under an HTTP 200 (the whole import no longer fails with HTTP 400 for that case).Key behavior:
• Channel mode:
accountId set + contact missing platformIdentifier → HTTP 200 with errors[]• Request-level
400 still applies for missing required name, or when a row includes channel data but there is no top-level accountId to attach it toImprovement
GET
Use
PUT
/v1/ads/campaigns now supports listing Meta empty campaigns (campaigns with adCount: 0), which were previously invisible because campaigns are aggregated from ad documents.Use
includeEmpty=true to include these campaigns (metrics will be zeroed). Requires accountId and adAccountId when includeEmpty=true.PUT
/v1/ads/campaigns/{campaignId} can now update an empty Meta campaign by sending accountId in the request body (otherwise it may 404 due to no local ad docs). The response will return updated: 0 for empty campaigns.Improvement
Meta ads campaign create/delete behavior has been tightened and now supports deleting “empty” campaign shells.
On
On
On
On
POST /v1/ads/campaigns, budgetAmount is explicitly in WHOLE currency units (not cents), and bidStrategy now requires a campaign budget (budgetAmount + budgetType = daily | lifetime). Sending bidStrategy without a campaign budget is now a 400, and invalid bid states (e.g. missing bidAmount for LOWESTCOSTWITHBIDCAP/COST_CAP) are rejected up front.On
DELETE /v1/ads/campaigns/{campaignId}, you can now delete a campaign with zero ads by including accountId in the body (alongside platform = facebook | instagram). accountId is only required for empty campaigns.On
POST /v1/ads/create, budgetAmount is also clarified as WHOLE currency units (not cents) to avoid Meta minor-unit mixups.Breaking Change
POST /v1/contacts/bulk request requirements and account/platform resolution have changed.You can now do a CRM-only import without channels, but when importing channel identifiers you must attach them to an account; duplicates are still skipped, but any new
tags are merged onto the existing contact.Key changes:
• Top-level required fields are now
profileId and contacts (no longer always requiring accountId/platform)•
accountId is required when contacts include platformIdentifier (or a row-level accountId); otherwise the request returns 400•
platform is ignored when accountId is set; the platform is derived from the resolved account, and a mismatch can 404 as account not foundBreaking Change
Facebook post options have been reorganized: draft/carousel/colored-background text settings are now grouped under
This makes Facebook-specific options consistent and clarifies which fields belong under
Move these fields under
•
•
•
•
Applies to
facebookSettings.This makes Facebook-specific options consistent and clarifies which fields belong under
platformSpecificData vs nested settings.Move these fields under
facebookSettings (root-level or per-platform):•
draft•
carouselCards•
carouselLink•
textFormatPresetIdApplies to
POST /v1/posts and PUT /v1/posts/{postId}. Also note: firstComment is skipped when facebookSettings.draft is true.Improvement
GET
This lets you distinguish “insufficient LinkedIn org/page role” from invalid input (
New response:
•
/v1/accounts/{accountId}/linkedin-mentions now returns 403 when LinkedIn blocks person-mention lookup due to missing company page posting privileges.This lets you distinguish “insufficient LinkedIn org/page role” from invalid input (
400) or not found (404), and show a clearer remediation message.New response:
•
403 - account has no organization role that can resolve person mentions (error message indicates needing Admin/Content Admin on a page, or mentioning an organization instead)Breaking Change
Create/duplicate ads endpoints now support safe retries via an
This prevents accidental duplicate campaigns/ad sets/ads when your client retries after timeouts or network errors.
Now supported on:
•
•
•
•
Behavior:
• same
• same key + different body →
• key still processing →
Also: metrics
Idempotency-Key header.This prevents accidental duplicate campaigns/ad sets/ads when your client retries after timeouts or network errors.
Now supported on:
•
POST /v1/ads/campaigns•
POST /v1/ads/campaigns/{campaignId}/duplicate•
POST /v1/ads/ad-sets/{adSetId}/duplicate•
POST /v1/ads/{adId}/duplicateBehavior:
• same
Idempotency-Key + same body → replay original 2xx response• same key + different body →
422• key still processing →
409Also: metrics
conversions is now a number (can be fractional) in GET /v1/ads/timeline and the AdMetrics schema (attribution splitting + Google modeled conversions).New Feature
You can now manage OAuth connected apps for the authenticated user: list authorized clients and revoke their access.
List connected apps with
Revoke an app with
Auth note: requires a session or full-scope API key; profile-scoped API keys and OAuth access tokens get
List connected apps with
GET /v1/me/connected-apps. Each item includes clientId, clientName, redirectHost, scopes, authorizedAt, lastUsedAt, tokenCount.Revoke an app with
DELETE /v1/me/connected-apps/{clientId} (idempotent). Response includes revoked, revokedTokens, invalidatedCodes.Auth note: requires a session or full-scope API key; profile-scoped API keys and OAuth access tokens get
403 (insufficient_permissions).New Feature
WhatsApp now supports re-registering a connected number on Meta Cloud API, and the credentials connect flow can include a 2-step verification PIN.
If a number has its own 2-step PIN, Meta can reject registration (error 133005) and sends may fail with misleading permission errors. You can now pass the PIN during connect, or re-run registration later.
Use:
•
• New:
Connect response may include
Inbox comments endpoints now also return platform/upstream error responses you may want to handle:
If a number has its own 2-step PIN, Meta can reject registration (error 133005) and sends may fail with misleading permission errors. You can now pass the PIN during connect, or re-run registration later.
Use:
•
POST /v1/connect/whatsapp/credentials with optional pin (6 digits, ^\d{6}$)• New:
POST /v1/accounts/{accountId}/whatsapp/register with optional pinConnect response may include
registrationWarning when the account was created but Cloud API registration was rejected (number can’t send until resolved).Inbox comments endpoints now also return platform/upstream error responses you may want to handle:
403 (platform permission), 429 (rate limited), 502 (upstream platform error) on GET/POST/DELETE /v1/inbox/comments/{postId}.Improvement
Facebook text background posts via
Previously, requests were rejected with
Use
textFormatPresetId no longer reject long captions.Previously, requests were rejected with
400 when content exceeded ~130 characters. Now, length is not rejected; Facebook may publish and return a warning instead.Use
textFormatPresetId (numeric string, ^\d+$) for Pages + text-only feed posts. Requests are still rejected with 400 if mediaItems or carouselCards are present, if contentType is story or reel, or if content is empty.New Feature
WhatsApp Calling config now supports a call duration cap and forwarded caller ID control on calling endpoints.
You can limit forwarded call length (to prevent dead-air billing) and, for SIP forwards, choose whether the destination sees the business number or the end-caller.
New fields:
•
•
Available on:
•
•
•
You can limit forwarded call length (to prevent dead-air billing) and, for SIP forwards, choose whether the destination sees the business number or the end-caller.
New fields:
•
maxCallDurationSeconds (request/response) — 30..14400 or null (no cap / clear cap)•
forwardCallerId (request/response) — business | callerAvailable on:
•
GET /v1/whatsapp/calling•
GET /v1/phone-numbers/{id}/whatsapp/calling•
POST/PATCH /v1/phone-numbers/{id}/whatsapp/calling (and the deprecated alias /v1/whatsapp/phone-numbers/{id}/calling)Breaking Change
Bulk contact import (
When
Check your handling of
POST /v1/contacts/bulk) now validates and normalizes phone identifiers for phone platforms.When
platform is whatsapp or sms, each contact’s platformIdentifier is normalized to digits; values that aren’t phone-shaped are rejected per contact and reported in errors[] (not imported).Check your handling of
errors[] in the 200 response (now returned as strings describing per-contact failures).New Feature
WhatsApp Calling now supports caller-ID verification for customer-brought (BYO) phone numbers, so
Start (or resend) a verification code via
Confirm the code via
•
•
tel: call forwards can present the business number as caller ID.Start (or resend) a verification code via
POST /v1/phone-numbers/{id}/whatsapp/caller-id-verification with optional method: sms | call.Confirm the code via
POST /v1/phone-numbers/{id}/whatsapp/caller-id-verification/verify with required code.GET /v1/whatsapp/calling now returns:•
callerIdMode: business | platform•
callerIdVerified: true | falseImprovement
WhatsApp Calling endpoints now return caller ID details for tel: forwards via
This helps you understand whether the PSTN forward-leg will present the business number or a Zernio platform number when caller ID isn’t verified.
New response fields:
•
•
Also clarified:
callerIdMode (and verification status on GET).This helps you understand whether the PSTN forward-leg will present the business number or a Zernio platform number when caller ID isn’t verified.
New response fields:
•
POST /v1/phone-numbers/{id}/whatsapp/calling and POST /v1/whatsapp/phone-numbers/{id}/calling: callerIdMode = business | platform•
GET /v1/phone-numbers/{id}/whatsapp/calling: callerIdMode = business | platform, callerIdVerified (boolean)Also clarified:
400 can be returned if forwardTo is set to the number itself.New Feature
Facebook posts now support large-text background presets via
This lets you render a text-only feed post as large text on a colored background using Meta’s
Use
• Text-only feed posts only: request is rejected if
• Not allowed with
•
• If a URL is present in
textFormatPresetId in FacebookPlatformData.This lets you render a text-only feed post as large text on a colored background using Meta’s
textformatpreset_id.Use
textFormatPresetId (numeric string, pattern ^\d+$). Constraints:• Text-only feed posts only: request is rejected if
mediaItems or carouselCards are present• Not allowed with
contentType story or reel•
content must be non-empty and ≤ 130 chars (otherwise rejected)• If a URL is present in
content, no link preview is attached while a preset is setImprovement
Rate limit guidance has been updated: the per-account velocity limit referenced in
•
•
If you throttle based on the error text for
429 responses is now 25 posts/hour per account (was 15) for:•
POST /v1/posts•
POST /v1/posts/{postId}/retryIf you throttle based on the error text for
429, update your assumptions to match the new 25 posts/hour limit.Improvement
Meta ad URL tags now preserve Meta dynamic macros in tracking parameters.
When setting Meta
Applies to:
•
•
•
When setting Meta
urlTags, macros like {{ad.id}}, {{campaign.id}}, {{placement}} are sent through unescaped so Meta can expand them; all other characters are percent-encoded.Applies to:
•
PATCH /v1/ads/{adId}/tracking-tags via urlTags•
POST /v1/ads/boost via tracking.urlTags•
POST /v1/ads/create via tracking.urlTagsImprovement
Meta ads reporting now supports filtering by Facebook Page via
This lets you scope results and rolled-up metrics to ads whose creative is backed by a specific Page (useful when one Meta ad account serves multiple Pages).
New query param:
•
Available on:
•
•
•
Matches against
pageId on ads, campaigns, and the campaign tree.This lets you scope results and rolled-up metrics to ads whose creative is backed by a specific Page (useful when one Meta ad account serves multiple Pages).
New query param:
•
pageId (Meta only)Available on:
•
GET /v1/ads•
GET /v1/ads/campaigns•
GET /v1/ads/treeMatches against
ad.creative.pageId (may be null for non-Meta ads and rare IG-only creatives).New Feature
Facebook analytics now supports per-post monetization earnings via
This returns lifetime cumulative earnings for a single post, read live from Meta on each request (no date range supported).
Required params:
•
•
Optional:
•
Notes:
•
• Money metrics include
• Metrics Meta can’t serve are omitted from
Also:
GET /v1/analytics/facebook/post-earnings.This returns lifetime cumulative earnings for a single post, read live from Meta on each request (no date range supported).
Required params:
•
accountId•
postId (e.g. {pageId}_{postId} or bare Reels video ID)Optional:
•
metrics = contentmonetizationearnings, monetizationapproximateearnings (defaults to both)Notes:
•
since/until/period/metricType/breakdown are rejected with 400 (lifetime only).• Money metrics include
unit (micro_amount | unspecified) and currency (string | null), and are never rescaled.• Metrics Meta can’t serve are omitted from
metrics and listed in unavailableMetrics (HTTP 200).Also:
GET /v1/analytics/facebook/page-insights now accepts monetization metrics (contentmonetizationearnings, monetizationapproximateearnings) and may return unavailableMetrics with the same omit-vs-zero behavior.New Feature
POST /v1/media/presign now supports generating presigned upload URLs for audio files.You can upload audio by setting
contentType to one of: audio/mpeg, audio/mp4, audio/aac, audio/ogg, audio/wav, audio/webm, audio/x-m4a.Improvement
Phone number purchases can now be auto-assigned to a different profile when the requested
Send
Check the assigned profile in:
•
•
profileId already has a number.Send
profileId as the preferred profile, but the API may assign the number to the next free profile (or create one) and will return the actual assignment.Check the assigned profile in:
•
phoneNumber.profileId (when a number is provisioned)•
profileId in the { status: "already_purchased" } response when using purchaseIntentIdBreaking Change
Instagram user tagging behavior has changed for
• Photos:
• Reels/videos: username-only tags; any
• Stories:
Carousel targeting still uses
platformSpecificData.instagram.userTags.userTags items now require only username (previously username + x + y were required). Tag handling now depends on media type:• Photos:
x/y are required; photo tags without valid coordinates are skipped• Reels/videos: username-only tags; any
x/y provided are ignored• Stories:
x/y are optionalCarousel targeting still uses
mediaIndex (defaults to 0); out-of-range indices are ignored.New Feature
GET /v1/inbox/conversations/search now supports filtering by Slack via platform=slack.This lets you search conversations and restrict results to Slack accounts the same way as other stored-message platforms.
Use
platform = facebook | instagram | telegram | whatsapp | sms | slackThe
conversation.started webhook payload also adds conversation.platform=slack in WebhookPayloadConversationStarted.New Feature
POST /v1/posts/sync-external now supports LinkedIn personal profiles.Because LinkedIn personal profiles don’t have a listing API, you must provide
url to import a single post authored by the connected member (works even for posts published before the account was connected).Use
accountId + url (required for LinkedIn personal), where url can be:•
linkedin.com/posts/…•
linkedin.com/feed/update/urn:li:activity:…•
urn:li:share:… / urn:li:ugcPost:…Imported LinkedIn personal posts return analytics (impressions, reach, reactions, comments, reshares, saves) but no content/media.
New Feature
Slack accounts now support reading and updating per-channel default message identity via new endpoints
This lets you set default author name/avatar for Slack messages on an account (per-post
Update with
The settings response includes channel details like
GET /v1/accounts/{accountId}/slack-settings and PATCH /v1/accounts/{accountId}/slack-settings.This lets you set default author name/avatar for Slack messages on an account (per-post
platformSpecificData.username/platformSpecificData.iconUrl can still override).Update with
defaultUsername (max 80) and defaultIconUrl. Send an empty string to clear either field.The settings response includes channel details like
channelId, channelName, channelType (public or private), plus teamId/teamName.Improvement
POST
You can distinguish missing-recipient cases and, for Instagram/Facebook, get Meta’s diagnostic fields to debug rejected sends.
New
•
•
/v1/inbox/conversations/{conversationId}/messages now returns more detailed send failures in 400 responses.You can distinguish missing-recipient cases and, for Instagram/Facebook, get Meta’s diagnostic fields to debug rejected sends.
New
400 fields:•
code: PLATFORMLIMITATION | MISSINGPARTICIPANT•
platformError (Instagram/Facebook only): code, subcode, fbtraceId, typeNew Feature
Slack is now supported as a posting platform via
You can schedule/publish Slack messages to the channel tied to the connected Slack
Use
•
•
•
•
•
Slack is also available for filtering in
POST /v1/posts and POST /v1/tools/validate/post, and can be connected via GET /v1/connect/{platform}.You can schedule/publish Slack messages to the channel tied to the connected Slack
accountId (one connected account per channel).Use
platform=slack and Slack options in platformSpecificData:•
threadTs•
unfurlLinks•
unfurlMedia•
username•
iconUrlSlack is also available for filtering in
GET /v1/accounts/health via platform=slack.New Feature
New Google Ads keyword research endpoints are available, and
You can now pull Keyword Planner data via:
•
•
Key request fields:
•
•
•
•
•
• Meta: use
• Google Ads: use
Note: Google responses return raw rows (camelCase) with int64 counters encoded as strings; bid/CPC values are in micros.
GET /v1/ads/insights now supports Google Ads queries.You can now pull Keyword Planner data via:
•
POST /v1/ads/keywords/ideas (generate ideas from seedKeywords and/or seedUrl)•
POST /v1/ads/keywords/historical-metrics (metrics for up to keywords = 1000)Key request fields:
•
accountId (required)•
customerId (optional; needed if the connection has several Google Ads accounts)•
countries (optional; omitted = worldwide)•
languageConstantId (default 1000)•
network: GOOGLESEARCH | GOOGLESEARCHANDPARTNERSGET /v1/ads/insights now routes by platform:• Meta: use
objectId (+ fields/breakdowns/filtering as before)• Google Ads: use
query (GAQL SELECT), with paging via pageToken and paging.nextPageToken (fixed 10,000 rows/page)Note: Google responses return raw rows (camelCase) with int64 counters encoded as strings; bid/CPC values are in micros.
Improvement
Instagram and Facebook Inbox now include pre-connect DM history replay in
When an IG/FB account is connected, Zernio replays existing Meta DM history in the background so older threads can appear in listings; if you sync into your own store, you should re-sweep rather than relying on a single pass at connect time.
Key behavior:
•
• Replayed history emits
•
GET /v1/inbox/conversations and GET /v1/inbox/conversations/{conversationId}/messages.When an IG/FB account is connected, Zernio replays existing Meta DM history in the background so older threads can appear in listings; if you sync into your own store, you should re-sweep rather than relying on a single pass at connect time.
Key behavior:
•
GET /v1/inbox/conversations: up to 500 conversations per account are replayed; replayed threads keep their original lastMessageAt/updatedTime so they sort into date order (not bumped to the top)• Replayed history emits
no webhooks and is stored as read (does not affect unreadCount)•
GET /v1/inbox/conversations/{conversationId}/messages: replay includes the 500 most recent messages per conversation; older messages beyond that are not retrievableBreaking Change
POST /v1/sms/registrations no longer requires phoneNumbers in the request body.For 10DLC registrations, if
phoneNumbers is omitted or an empty array, it now defaults to your active SMS-enabled US local numbers that aren’t already covered by another registration.Key request fields:
•
registrationType: standard10dlc | soleprop10dlc | tollfree•
phoneNumbers: optional (new defaulting behavior for 10DLC)New Feature
Webhooks now support
This event fires when Zernio’s background sync detects a platform post (published via Zernio) was later deleted on the platform (poll-driven ~hourly, not real-time).
Subscribe by including
In
•
•
• New field on delete events:
post.platform.deleted in events for POST /v1/webhooks/settings and PUT /v1/webhooks/settings.This event fires when Zernio’s background sync detects a platform post (published via Zernio) was later deleted on the platform (poll-driven ~hourly, not real-time).
Subscribe by including
post.platform.deleted in events.In
WebhookPayloadPostPlatform:•
event: post.platform.published | post.platform.failed | post.platform.deleted | post.tiktok.url_resolved•
platform.status: published | failed | deleted• New field on delete events:
platform.deletedAt (ISO 8601)New Feature
Instagram is now supported for typing indicators via
Calling this endpoint can now show
Request:
Response:
The endpoint may now also return
POST /v1/inbox/conversations/{conversationId}/typing.Calling this endpoint can now show
typing... to the Instagram recipient (works for both Instagram Login and Facebook Login accounts; recipient must be signed in to Instagram to see it).Request:
{ "accountId": "..." }Response:
{ "success": true|false } where success indicates whether the typing indicator was actually sent (false on unsupported platforms or if the platform call failed).The endpoint may now also return
400 (BadRequest) in addition to existing responses.New Feature
Meta conversion goals on
This lets you run
Use
•
•
POST /v1/ads/create now support optimizing against a Custom Conversion.This lets you run
goal conversions or lead_conversion without providing a standard event type, by pointing the ad set at an existing Custom Conversion.Use
promotedObject.customConversionId (instead of promotedObject.customEventType) for:•
goal=conversions•
goal=lead_conversionNew Feature
SMS carrier registrations now support a preflight dry-run and change-request replies.
Use
Request:
Response:
Use
Also added to
Use
POST /v1/sms/registrations/preflight to validate and preview the exact 10DLC brand/campaign payloads that would be stored, without creating a registration. It returns compliance findings (including AI review when available) so you can fix issues before submitting.Request:
registrationType (standard10dlc | soleprop_10dlc), brand, campaign, optional phoneNumbers, optional messagingBrandNameResponse:
composed, advisories (severity block | warn), verdict (pass | warn | fail | unreviewed), aiUnavailableUse
POST /v1/sms/registrations/{id}/respond to reply to a reviewer change request (when a registration is in changes_requested) with a note, files (hosted URLs), or both—without resubmitting the full registration. On success, status returns to requested.Also added to
POST /v1/sms/registrations: messagingBrandName (2–60 chars) to brand samples/auto-replies when it differs from the legal brand.displayName.Improvement
Webhook limits have increased: you can now configure up to 50 webhooks per user (was 10).
This lets you split event subscriptions across more endpoints/environments without hitting the previous cap.
Applies to:
•
•
This lets you split event subscriptions across more endpoints/environments without hitting the previous cap.
Applies to:
•
GET /v1/webhooks/settings (list up to 50)•
POST /v1/webhooks/settings (create up to 50; 400 if max reached)New Feature
New endpoint
Use it with:
•
•
•
•
•
•
•
To reply to a found tweet, set
Rate limit: 300 requests per 15 minutes per connected account.
GET /v1/twitter/search lets you search public tweets from the last 7 days using an X query (passed through unchanged) to discover tweets to reply to.Use it with:
•
accountId (required)•
query (required, 1–512 chars)•
limit (10–100, default 10)•
cursor (pagination)•
sinceId / untilId (numeric tweet IDs)•
startTime / endTime (ISO 8601 UTC, within last 7 days)•
sortOrder: recency | relevancy (default recency)To reply to a found tweet, set
platformSpecificData.replyToTweetId to the returned tweets[].id when creating a post.Rate limit: 300 requests per 15 minutes per connected account.
Improvement
KYC submission now enforces stricter validation when requesting multiple numbers with an
If you pass
Applies to:
•
•
Key params:
areaCode.If you pass
areaCode and request quantity above that area’s live inventory, the request is rejected with 400 (instead of best-effort skipping).Applies to:
•
POST /v1/phone-numbers/kyc•
POST /v1/whatsapp/phone-numbers/kycKey params:
areaCode, quantity (1–5).Breaking Change
GET /v1/accounts now enforces stricter pagination and query validation.If you use server-side pagination, you must send
page and limit together; sending only one now returns 400. Out-of-range page/limit values are also rejected with 400 (instead of being silently clamped).Key params:
•
page (1-based) + limit (1–100) must be supplied together•
profileId must be a valid ObjectIdImprovement
POST /v1/tools/validate/post now accepts accountId per platform to validate against account-specific limits.For
platform=twitter, providing platforms[].accountId lets validation apply the correct character limit (280 vs 25000 for X Premium). Missing/invalid/foreign IDs fall back to 280 and won’t error.Use:
•
platforms[].platform (enum: twitter, instagram, tiktok, youtube, facebook, linkedin, bluesky, threads, reddit, pinterest, telegram, snapchat, googlebusiness, discord)•
platforms[].accountIdNew Feature
New endpoint
Keywords are populated by the periodic ads discovery sweep (~every 3 hours per account). Campaign-level negative keywords aren’t included (ad-group-level only).
Filter with:
•
•
•
•
•
•
Response returns
GET /v1/ads/keywords is available to list Google Search keyword criteria (positive and negative) synced from connected Google Ads accounts (one row per ad-group keyword).Keywords are populated by the periodic ads discovery sweep (~every 3 hours per account). Campaign-level negative keywords aren’t included (ad-group-level only).
Filter with:
•
accountId, adAccountId (Google customer ID), profileId•
campaignId, adSetId (Google ad group)•
status: active | paused•
matchType: exact | phrase | broad | unknown•
negative: true (negative only) | false (positive only)•
search (substring match), plus pagination via page/limit (1–500)Response returns
keywords with fields like keyword, matchType, status, negative, and syncedAt, plus pagination.Improvement
WhatsApp template endpoints can now return
This makes Meta-side failures explicit so you can distinguish platform outages/rejections from Zernio validation/auth errors.
Applies to:
•
•
•
Also added ad review state support via
502 when Meta rejects the request or is unreachable.This makes Meta-side failures explicit so you can distinguish platform outages/rejections from Zernio validation/auth errors.
Applies to:
•
GET /v1/whatsapp/templates/{templateName}•
PATCH /v1/whatsapp/templates/{templateName}•
DELETE /v1/whatsapp/templates/{templateName}Also added ad review state support via
reviewStatus (schema AdReviewStatus: inreview, approved, rejected, withissues) and Meta-only configuredStatus on Ad to separate delivery status from the ad’s on/off toggle.Improvement
message.sent webhook payload now includes message.source for WhatsApp.This lets you tell whether a sent message came from the WhatsApp Business phone app on a Coexistence number vs being sent through Zernio (dashboard/API/broadcasts).
New field:
message.source = whatsappbusinessapp | cloud_apiOnly present for WhatsApp; absent on non-WhatsApp platforms.
New Feature
New endpoint:
You can send a reviewer message and/or submit corrected requirement data in a single call; submitting corrections re-submits the number back to review, while message-only posts to the reviewer thread.
Request body supports:
•
•
•
•
•
Response includes
POST /v1/phone-numbers/{id}/remediate/respond lets you respond to a regulatory reviewer for a phone number awaiting remediation.You can send a reviewer message and/or submit corrected requirement data in a single call; submitting corrections re-submits the number back to review, while message-only posts to the reviewer thread.
Request body supports:
•
message (max 2000)•
documents[] keyed by requirementId (use base64 or documentId)•
address (corrected address record)•
entityType: individual | business | null•
attachments[] (max 5; each filename + base64)Response includes
status: resubmitted | replied, plus posted, phoneNumber, and siblingsResubmitted.Improvement
GET /v1/profiles now supports exact-name filtering and pagination, and the default sort order is now default-first (then creation date).Use
name (exact match) to find a profile by name, and limit/skip to paginate. When limit or skip is provided, the response may include total, skip, and limit.New query params:
name, limit (1..1000), skip (>=0)POST /v1/profiles now supports idempotent retries via Idempotency-Key (header).With
Idempotency-Key: same key + same body replays the original 201; same key + different body returns 422; key still processing returns 409. Duplicate name conflicts (409) now include details.existingProfileId.New Feature
New endpoint:
Use it when the reviewer asks for info the structured remediation form can’t express; posting a reply can move the number back to
Request body:
•
•
Response
•
•
Errors:
POST /v1/phone-numbers/{id}/remediate/reply lets you reply to a regulatory reviewer on a phone number awaiting remediation, including optional file attachments.Use it when the reviewer asks for info the structured remediation form can’t express; posting a reply can move the number back to
in review (comment-style asks). Requires text or at least one attachments item.Request body:
•
text (string, max 2000)•
attachments (array, max 5) items: { filename, base64 } (PDF/JPG/PNG/WEBP, max 10MB each)Response
200:•
posted (boolean)•
attachments (integer uploaded)Errors:
400, 401, 404, 502 (retry).New Feature
You can now view KYC documents on file via
This streams the stored verification document inline as
KYC spec responses now include
•
•
Use that
GET /v1/phone-numbers/kyc/document/{documentId}.This streams the stored verification document inline as
application/pdf, so users can confirm what’s on file before reusing a verification. Access is auth-scoped (returns 404 if the documentId isn’t referenced by one of your numbers).KYC spec responses now include
documentId on document rows:•
GET /v1/phone-numbers/kyc → reusable.options[].details[].documentId•
GET /v1/whatsapp/phone-numbers/kyc → reusable.options[].details[].documentIdUse that
documentId as the {documentId} path parameter when fetching the PDF.Improvement
KYC reuse selection now supports a stable option identifier via
This makes selecting which prior verification to reuse unambiguous (a phone number may label multiple verifications) and lets you know whether reuse activates in minutes or still queues for carrier review.
Use on submit:
•
•
Returned on form spec:
•
reusable.options[].id, and KYC reuse options now include instant to indicate activation speed.This makes selecting which prior verification to reuse unambiguous (a phone number may label multiple verifications) and lets you know whether reuse activates in minutes or still queues for carrier review.
Use on submit:
•
POST /v1/phone-numbers/kyc: pass reuseOptionId (preferred)•
reuseFrom is now a legacy fallback and may be ambiguousReturned on form spec:
•
GET /v1/phone-numbers/kyc (and the deprecated WhatsApp alias): reusable.options[] now includes id and instantNew Feature
PUT /v1/ads/ad-sets/{adSetId} now supports updating bid strategy for platform=openai.You can set OpenAI ad group bidding via
bidStrategy + bidAmount; bidAmount is converted to micros (× 1,000,000).Use
bidStrategy:•
LOWESTCOSTWITHBIDCAP (requires bidAmount)•
COST_CAP (requires bidAmount)LOWESTCOSTWITHMINROAS is not supported on OpenAI and will return 422.New Feature
Zernio Ads now supports OpenAI Ads (ChatGPT Ads), including connect, create/manage, reporting, and conversions.
Connect an OpenAI Ads account via
Ads endpoints now accept
•
• Campaign/ad set status updates:
Creating ads:
•
• Required creative fields:
• Budget:
Conversions:
• Use
• If no pixel exists yet, create one via
Note:
Connect an OpenAI Ads account via
POST /v1/connect/openai-ads/credentials with apiKey and profileId (requires Ads add-on).Ads endpoints now accept
platform=openai:•
GET /v1/ads, GET /v1/ads/campaigns, GET /v1/ads/tree, GET /v1/ads/timeline• Campaign/ad set status updates:
PUT /v1/ads/campaigns/{campaignId}/status, POST /v1/ads/campaigns/bulk-status, PUT /v1/ads/ad-sets/{adSetId}, PUT /v1/ads/ad-sets/{adSetId}/statusCreating ads:
POST /v1/ads/create supports OpenAI Ads with:•
goal: traffic | awareness | conversions• Required creative fields:
headline, body, imageUrl, linkUrl• Budget:
budgetType must be lifetime (sending daily returns 422) and requires endDateConversions:
POST /v1/ads/conversions now supports accountId platform openaiads.• Use
GET /v1/accounts/{accountId}/conversion-destinations to get the OpenAI pixel destinationId (pixel wire id)• If no pixel exists yet, create one via
POST /v1/accounts/{accountId}/tracking-tags (OpenAI Ads may return 422 if pixel management isn’t enabled)Note:
PUT /v1/ads/{adId} supports OpenAI Ads for status + budget only (no targeting/creative updates). DELETE /v1/ads/{adId} archives OpenAI Ads (no platform delete API).Improvement
YouTube analytics endpoints now support returning provisional (not-yet-finalized) days when you set an explicit
By default,
Applies to:
•
•
•
New response field:
•
endDate up to today.By default,
endDate still resolves to the newest fully finalized day (~3-day delay). If you request into the delay window, the response indicates which days may still change.Applies to:
•
GET /v1/analytics/youtube/daily-views•
GET /v1/analytics/youtube/video-retention•
GET /v1/analytics/youtube/demographicsNew response field:
•
provisionalSince — first date in the returned range whose numbers are provisional and may be revised by YouTube.New Feature
WhatsApp template creation now supports carousel and limited time offer components in
This lets you build richer MARKETING templates (multi-card carousels and offer labels/countdowns) using the same template creation endpoint.
New component types:
•
•
Phone number purchase adds
components on POST /v1/whatsapp/templates.This lets you build richer MARKETING templates (multi-card carousels and offer labels/countdowns) using the same template creation endpoint.
New component types:
•
type=carousel with cards (2–10), each card components can include type=header, type=body, type=buttons•
type=limitedtimeoffer with limitedtimeoffer.text (<=16 chars) and optional limitedtimeoffer.has_expirationPhone number purchase adds
wantsWhatsapp on POST /v1/phone-numbers/purchase (used when connectWhatsapp=false) to ensure the assigned number is WhatsApp-eligible by swapping it during purchase if needed.Improvement
Profile create/update now return 409 Conflict when the profile name is already taken.
This lets clients distinguish name collisions from generic validation errors and handle retries/renames cleanly.
New response:
•
•
Several Ads read endpoints now also document 400 Bad Request for invalid query input:
•
•
•
•
This lets clients distinguish name collisions from generic validation errors and handle retries/renames cleanly.
New response:
•
POST /v1/profiles → 409 (code: profilenameconflict)•
PUT /v1/profiles/{profileId} → 409 (code: profilenameconflict)Several Ads read endpoints now also document 400 Bad Request for invalid query input:
•
GET /v1/ads → 400•
GET /v1/ads/catalogs → 400•
GET /v1/ads/catalogs/{catalogId}/product-sets → 400•
GET /v1/ads/conversions/quality → 400Improvement
Meta ads now support additional promoted-object fields, and some payloads are validated more strictly.
In
•
•
In
In
In
POST /v1/ads/create, promotedObject adds:•
offlineConversionDataSetId•
whatsappPhoneNumberIn
PUT /v1/ads/ad-sets/{adSetId}, platformSpecificData.promotedObject adds the same fields and now rejects unknown keys (additionalProperties: false), returning 400.In
POST /v1/ads/boost and POST /v1/ads/create, Meta adds specialAdCategoryCountry (array of 2-letter ISO codes). It requires specialAdCategories to be set (400 otherwise).GET /v1/ads/audiences and POST /v1/ads/audiences add a new audience type value: meta_engagement.Breaking Change
Discord: new member lookup + search endpoints, and guild member listing now fails fast when the Server Members Intent is missing.
You can now fetch a single member or prefix-search members without the privileged intent (useful when full listing is blocked).
New endpoints:
•
•
Breaking change:
•
SMS: alphanumeric Sender IDs are now supported.
Create and manage branded one-way SMS sender IDs (3–11 chars; letters/digits/spaces; at least one letter) and view the workspace-wide daily cap.
New endpoints:
•
•
•
•
Ads (Meta): new campaign/creative utilities + validate-only dry runs.
You can now create campaign shells, manage the creative library, duplicate ad sets/ads, and run a Meta validation pass without creating anything.
New endpoints:
•
•
•
•
New option on
•
You can now fetch a single member or prefix-search members without the privileged intent (useful when full listing is blocked).
New endpoints:
•
GET /v1/discord/guilds/{guildId}/members/{userId} (accountId)•
GET /v1/discord/guilds/{guildId}/members/search (accountId, query, optional limit) — query is prefix-onlyBreaking change:
•
GET /v1/discord/guilds/{guildId}/members now returns 403 if the Server Members Intent is not enabled (previously Discord could return an empty array).SMS: alphanumeric Sender IDs are now supported.
Create and manage branded one-way SMS sender IDs (3–11 chars; letters/digits/spaces; at least one letter) and view the workspace-wide daily cap.
New endpoints:
•
GET /v1/sms/sender-ids (returns senderIds + budget with cap/usedToday/level/pendingRequest)•
POST /v1/sms/sender-ids (senderId)•
DELETE /v1/sms/sender-ids/{id}•
POST /v1/sms/sender-ids/limit-request (requestedCap, reason)Ads (Meta): new campaign/creative utilities + validate-only dry runs.
You can now create campaign shells, manage the creative library, duplicate ad sets/ads, and run a Meta validation pass without creating anything.
New endpoints:
•
POST /v1/ads/campaigns (standalone campaign)•
POST /v1/ads/creatives, GET /v1/ads/creatives, GET /v1/ads/creatives/{creativeId}, PUT /v1/ads/creatives/{creativeId}, DELETE /v1/ads/creatives/{creativeId}•
GET /v1/ads/images, GET /v1/ads/labels, GET /v1/ads/businesses, GET /v1/ads/high-demand-periods•
POST /v1/ads/ad-sets/{adSetId}/duplicate, POST /v1/ads/{adId}/duplicateNew option on
POST /v1/ads/create:•
validateOnly: true (Meta only; single standalone shape only) — returns 200 with validation results instead of creating entities.Breaking Change
WhatsApp inbound attachments can now be downloaded via
This streams the binary for a WhatsApp attachment referenced by
Call it with:
•
•
•
Important behavior:
• Download on receipt: WhatsApp media expires on Meta’s side (typically ~7 days). After expiry this endpoint returns
Removed endpoints:
•
•
•
•
•
•
•
•
Breaking change (SMS):
GET /v1/whatsapp/media/{mediaId}.This streams the binary for a WhatsApp attachment referenced by
attachments[].url in message.received webhooks and List messages.Call it with:
•
mediaId (path) from attachments[].payload.id•
accountId (query) = the WhatsApp account that received the media•
Authorization: Bearer <your API key> (required; otherwise 401)Important behavior:
• Download on receipt: WhatsApp media expires on Meta’s side (typically ~7 days). After expiry this endpoint returns
400 permanently (do not retry).Removed endpoints:
•
GET /v1/sms/sender-ids, POST /v1/sms/sender-ids, POST /v1/sms/sender-ids/limit-request, DELETE /v1/sms/sender-ids/{id}•
POST /v1/ads/campaigns•
POST /v1/ads/ad-sets/{adSetId}/duplicate•
POST /v1/ads/{adId}/duplicate•
GET /v1/ads/labels•
GET /v1/ads/high-demand-periods•
GET/POST/GET/PUT/DELETE /v1/ads/creatives (and /v1/ads/creatives/{creativeId})•
GET /v1/ads/imagesBreaking change (SMS):
POST /v1/sms/messages no longer supports alphanumeric sender IDs in from (now requires an SMS-enabled number).New Feature
Meta Ads: new endpoints for campaign/creative management, duplication, and library access.
You can now create a standalone campaign shell, duplicate ad sets/ads via Meta native copy, manage creatives separately from ads, and query labels/images/creative library + budget schedules.
New endpoints:
• Create campaign (ODAX shell):
• Duplicate ad set:
• Duplicate ad:
• Creative library + CRUD:
• Libraries/metadata:
• Budget schedules:
Audience updates expanded:
You can now create a standalone campaign shell, duplicate ad sets/ads via Meta native copy, manage creatives separately from ads, and query labels/images/creative library + budget schedules.
New endpoints:
• Create campaign (ODAX shell):
POST /v1/ads/campaigns with goal (engagement, traffic, awareness, videoviews, leadgeneration, leadconversion, jobapplicants, conversions, apppromotion, catalogsales), optional CBO via budgetAmount + budgetType (daily|lifetime), status (ACTIVE|PAUSED)• Duplicate ad set:
POST /v1/ads/ad-sets/{adSetId}/duplicate with deepCopy, campaignId, statusOption (ACTIVE|PAUSED|INHERITEDFROMSOURCE), syncAfter• Duplicate ad:
POST /v1/ads/{adId}/duplicate with adSetId, statusOption (ACTIVE|PAUSED|INHERITEDFROMSOURCE), syncAfter• Creative library + CRUD:
GET /v1/ads/creatives, POST /v1/ads/creatives (use imageUrl or imageHash or carouselCards), GET /v1/ads/creatives/{creativeId}, PUT /v1/ads/creatives/{creativeId} (rename via name), DELETE /v1/ads/creatives/{creativeId}• Libraries/metadata:
GET /v1/ads/images, GET /v1/ads/labels• Budget schedules:
GET /v1/ads/high-demand-periods (exactly one of campaignId or adSetId)Audience updates expanded:
PUT /v1/ads/audiences/{audienceId} now supports updating platform audiences’ name/description (Meta only). spec is still allowed only for saved_targeting; sending spec for a platform audience returns 400. Non-Meta platform audience updates return 501.New Feature
SMS now supports alphanumeric sender IDs (e.g.
Create/manage sender IDs via:
•
•
•
•
Sending:
Notes for sender IDs: text-only (no MMS), recipients can’t reply, and sends to US/CA/PR are blocked.
New responses to handle on
•
•
•
ZERNIO) for one-way international texting.Create/manage sender IDs via:
•
GET /v1/sms/sender-ids (includes workspace daily budget: budget.cap, budget.usedToday, budget.level, budget.pendingRequest)•
POST /v1/sms/sender-ids with senderId (3–11 chars; letters/digits/spaces; at least one letter)•
DELETE /v1/sms/sender-ids/{id}•
POST /v1/sms/sender-ids/limit-request with requestedCap + reasonSending:
POST /v1/sms/messages now accepts from as either an SMS-enabled number or an approved sender ID.Notes for sender IDs: text-only (no MMS), recipients can’t reply, and sends to US/CA/PR are blocked.
New responses to handle on
POST /v1/sms/messages:•
403 (gate: registration/MMS not supported/alpha to US-CA-PR)•
429 (alphanumeric daily cap reached; code alphadailylimit_reached; resets midnight UTC)•
404 now covers “no SMS-enabled number or sender ID matches from”.New Feature
Meta ads now support Reach & Frequency (RESERVED) buying via new R&F prediction + reservation endpoints.
You can request a quote (no spend, no entities created), reserve it to lock price/inventory, then create an ad using the reserved prediction.
New endpoints:
•
•
•
•
•
•
Notes: RESERVED uses the single-ad shape only (no
You can request a quote (no spend, no entities created), reserve it to lock price/inventory, then create an ad using the reserved prediction.
New endpoints:
•
POST /v1/ads/rf-predictions (create quote; provide date range + exactly one of budgetAmount or reach)•
GET /v1/ads/rf-predictions/{predictionId}•
POST /v1/ads/rf-predictions/{predictionId}/reserve (returns a NEW RESERVED predictionId + expiresAt)•
DELETE /v1/ads/rf-predictions/{predictionId} (cancel reservation)POST /v1/ads/create adds:•
buyingType: AUCTION | RESERVED•
rfPredictionId (required when buyingType=RESERVED)Notes: RESERVED uses the single-ad shape only (no
creatives[], adSetId, existingCampaignId, or dynamicCreative). Budget/schedule/pricing come from the reservation; bid fields are ignored.New Feature
New Meta Ads endpoints are available for live reads, auditing, studies, finances, and asset upload.
You can now:
• Read a live Meta ad set (incl. learning phase) via
• Fetch account audit/activity log via
• List A/B tests & lift studies via
• Read ad account finances via
• Upload ad images from base64 via
Also updated:
•
•
•
You can now:
• Read a live Meta ad set (incl. learning phase) via
GET /v1/ads/ad-sets/{adSetId} with accountId and optional fields (default includes learningstageinfo).• Fetch account audit/activity log via
GET /v1/ads/activity using accountId, adAccountId, optional since/until, optional objectId (client-side filter), plus paging limit/after.• List A/B tests & lift studies via
GET /v1/ads/studies with accountId, adAccountId, optional fields, and paging limit/after.• Read ad account finances via
GET /v1/ads/accounts/finance (balance, amountSpent, spendCap, fundingSource).• Upload ad images from base64 via
POST /v1/ads/images with accountId, adAccountId, imageBase64 (optional filename); returns image.hash and image.url.Also updated:
•
specialAdCategories now includes FINANCIALPRODUCTSSERVICES and ONLINEGAMBLINGAND_GAMING on POST /v1/ads/boost and POST /v1/ads/create.•
callToAction enums expanded on POST /v1/ads/create (and Meta creatives[].callToAction) to include values like ADDTOCART, APPLYNOW, BOOKNOW, ORDERNOW, INSTALLMOBILEAPP, USEAPP (plus others).•
POST /v1/ads/conversions: consent now affects Meta too — if adUserData or adPersonalization is DENIED, events are sent with Meta Limited Data Use (LDU); GRANTED (or omitted) uses default processing.Deprecation
New Meta Ads capabilities are available: ad previews, click-to-message ads, and click-to-call ads.
You can now render embeddable preview iframes before creating an ad via
Click-to-message ads are now unified under
•
•
•
Click-to-call ads are supported via
You can now render embeddable preview iframes before creating an ad via
POST /v1/ads/preview (provide exactly one of existingCreativeId or creativeSpec) or for an existing ad via GET /v1/ads/{adId}/preview. Use formats (Meta ad_format values, max 10) to control placements.Click-to-message ads are now unified under
POST /v1/ads/messaging with destination:•
whatsapp•
messenger•
instagram_directClick-to-call ads are supported via
POST /v1/ads/call using phoneNumber (E.164) and linkUrl.POST /v1/ads/ctwa is now deprecated; use POST /v1/ads/messaging with destination: whatsapp (the old endpoint remains for back-compat).New Feature
Meta insights now supports action-level segmentation and attribution controls on both the sync and async insights APIs.
You can segment
New on
•
•
•
•
New on
•
•
•
•
Meta ad creation also adds hand-built carousels via
You can segment
actions[] and control attribution windows/reporting time without changing endpoints.New on
GET /v1/ads/insights:•
actionBreakdowns•
actionAttributionWindows•
actionReportTime•
useUnifiedAttributionSettingNew on
POST /v1/ads/insights/reports:•
actionBreakdowns•
actionAttributionWindows (array of strings)•
actionReportTime•
useUnifiedAttributionSettingMeta ad creation also adds hand-built carousels via
carouselCards on POST /v1/ads/create (2–10 cards, deterministic order).Improvement
Google Business batch reviews now supports server-side sorting via
This lets you fetch newest-updated reviews first (default) and stop paginating earlier when polling for recent changes.
Use
•
•
•
Also note:
Responses now clarify attribution: each
Inbox reviews (
orderBy on POST /v1/accounts/{accountId}/gmb-reviews/batch.This lets you fetch newest-updated reviews first (default) and stop paginating earlier when polling for recent changes.
Use
orderBy:•
updateTime desc (default)•
rating•
rating descAlso note:
locationNames is now capped at maxItems: 50 per request (chunk larger sets).Responses now clarify attribution: each
locationReviews[].name is the LOCATION resource name (not the review resource name), and review includes review.name (full review resource name) plus reviewMediaItems (photos/videos).Inbox reviews (
GET /v1/inbox/reviews) now includes Google Business location attribution fields: locationId (bare id) and locationName (display name).New Feature
POST
This lets you publish a photo comment alongside the text reply. The image URL must be publicly accessible so Meta can fetch it.
New request field:
•
If
/v1/inbox/comments/{postId} now supports attaching an image when replying on Facebook via attachmentUrl.This lets you publish a photo comment alongside the text reply. The image URL must be publicly accessible so Meta can fetch it.
New request field:
•
attachmentUrl (uri) — Facebook onlyIf
attachmentUrl is sent for non-Facebook platforms, the API now returns 400 (e.g. code PLATFORMNOTSUPPORTED).Improvement
POST
You can create TikTok ads/campaigns in a paused state and use additional geo targeting fields for TikTok.
Key changes:
•
• Geo targeting fields now support TikTok:
GET
/v1/ads/create now supports more TikTok controls and targeting.You can create TikTok ads/campaigns in a paused state and use additional geo targeting fields for TikTok.
Key changes:
•
status now applies to Meta and TikTok: ACTIVE | PAUSED (TikTok pauses campaign > ad group > ad)• Geo targeting fields now support TikTok:
cities, regions, metrosGET
/v1/ads/targeting/search: TikTok geo searches now return mixed levels in one list (type can be country | region | city | district | metro), and geoType is not applied for TikTok results.Improvement
TikTok conversion ads created via
This makes it easier to configure
Use
•
Applies to TikTok only;
POST /v1/ads/create now accept TikTok Events Manager event names in promotedObject.customEventType (auto-resolved to the underlying optimization_event code).This makes it easier to configure
goal=conversions without manually mapping to TikTok’s internal codes; note that ONWEBORDER is now deprecated.Use
promotedObject.customEventType with one of:•
SHOPPING, ONWEBCART, INITIATEORDER, FORM, ONWEBREGISTER, ONWEB_DETAILApplies to TikTok only;
promotedObject.pixelId is still required for goal=conversions.New Feature
New Meta Ads insights endpoints are available:
You can now run a flexible live insights query that forwards your chosen Meta Graph parameters (fields/breakdowns/filtering) and returns rows in Meta’s raw shape; use the async report flow for long ranges or large accounts.
Sync query:
Async flow:
• Submit:
• Poll:
Meta only (facebook/instagram). Invalid field/breakdown combos return
GET /v1/ads/insights, POST /v1/ads/insights/reports, GET /v1/ads/insights/reports/{reportRunId}.You can now run a flexible live insights query that forwards your chosen Meta Graph parameters (fields/breakdowns/filtering) and returns rows in Meta’s raw shape; use the async report flow for long ranges or large accounts.
Sync query:
GET /v1/ads/insights with accountId, objectId, optional level (ad/adset/campaign/account), fields, breakdowns, filtering, datePreset or fromDate+toDate, timeIncrement, limit, after.Async flow:
• Submit:
POST /v1/ads/insights/reports (body includes accountId, objectId, optional level, fields, breakdowns, filtering, datePreset or fromDate/toDate, timeIncrement (1-90 | monthly | all_days)) → returns reportRunId• Poll:
GET /v1/ads/insights/reports/{reportRunId} with accountId, limit, after → returns status and percentCompletion; when status is Job Completed, includes data + paging.Meta only (facebook/instagram). Invalid field/breakdown combos return
400 with Meta’s error message.New Feature
Google Business Profiles: you can now assign a GBP location to a different profile by reusing an existing OAuth grant (no browser re-auth).
Use
•
•
•
Returns
Phone number purchasing now supports selecting a number type.
On
Country/type discovery updates:
•
•
•
Facebook connect flow:
Use
POST /v1/accounts/{accountId}/gmb-locations/assign with:•
profileId (target profile)•
selectedLocationId (e.g. locations/123)•
googleAccountId (optional, recommended for accounts with many locations)Returns
409 if the target profile already has a Google Business connection.Phone number purchasing now supports selecting a number type.
On
POST /v1/phone-numbers/purchase you can pass numberType: local | mobile | national | toll_free. Availability/KYC can vary by (country, type).Country/type discovery updates:
•
GET /v1/phone-numbers/countries now includes inStock and types[] (per-type monthlyCents, needsKyc, whatsappAvailable, smsAvailable, callsAvailable, inStock)•
GET /v1/phone-numbers/availability adds optional numberType•
GET /v1/phone-numbers/kyc adds optional numberTypeFacebook connect flow:
POST /v1/connect/facebook/select-page can now return 409 with code RECONNECTACCOUNTMISMATCH when a force=true token-recovery re-auth lands on a different user/page than the existing connection.New Feature
Google Business reviews now include attached photos in review responses.
You can display/download reviewer-uploaded images and know how many photos are attached per review.
Added fields:
•
•
Notes:
You can display/download reviewer-uploaded images and know how many photos are attached per review.
Added fields:
•
GET /v1/accounts/{accountId}/gmb-reviews → reviews[].photoCount, reviews[].photos[] (each item: url)•
GET /v1/inbox/reviews → data[].photos[] (Google Business only; each item: url)Notes:
photoCount counts photos only (videos not counted). For non-Google platforms, photos is always an empty array.New Feature
Meta campaign updates now support a lifetime spend cap via
This lets you set or remove a campaign-level spend cap without recreating the campaign.
Use:
•
•
Meta ad set updates now accept post-launch delivery settings via
Set any of:
•
•
•
•
Also added:
platformSpecificData.spendCap on PUT /v1/ads/campaigns/{campaignId}.This lets you set or remove a campaign-level spend cap without recreating the campaign.
Use:
•
platformSpecificData.spendCap = number (sets cap)•
platformSpecificData.spendCap = null (removes cap; Meta rejects 0)Meta ad set updates now accept post-launch delivery settings via
platformSpecificData on PUT /v1/ads/ad-sets/{adSetId}.Set any of:
•
platformSpecificData.optimizationGoal•
platformSpecificData.billingEvent•
platformSpecificData.startDate, platformSpecificData.endDate•
platformSpecificData.promotedObject (e.g. pixelId, customEventType, pageId, applicationId, objectStoreUrl, customConversionId, productCatalogId, productSetId)Also added:
billingEvent (Meta only) on POST /v1/ads/create to explicitly set the ad set’s billing event (defaults to IMPRESSIONS).Breaking Change
Queue schedules:
This lets you clear a profile’s entire queue schedule in one call (previously
Use:
•
•
Sequences:
Response now includes
Also:
DELETE /v1/queue/slots now supports deleting all queues for a profile by omitting queueId.This lets you clear a profile’s entire queue schedule in one call (previously
queueId was required).Use:
•
profileId (required)•
queueId (optional) — omit to delete all queuesSequences:
POST /v1/sequences/{sequenceId}/enroll now returns per-contact outcomes.Response now includes
failed and results (array of { contactId, success, error }) in addition to enrolled.Also:
GET /v1/queue/slots expands all to true | false.Improvement
GET
You can raise the maximum number of Google Business Profile locations returned (default was effectively 100) up to 500 to enumerate accounts with more than 100 locations.
Use
•
Invalid values now return
/v1/accounts/{accountId}/gmb-locations now supports a limit query parameter.You can raise the maximum number of Google Business Profile locations returned (default was effectively 100) up to 500 to enumerate accounts with more than 100 locations.
Use
limit:•
limit (integer, default 100, min 1, max 500)Invalid values now return
400 (e.g. limit out of range).New Feature
YouTube demographics analytics now supports per-video audience breakdowns via
Pass
Use:
•
•
Response adds (video mode only):
Also: Meta city targeting docs were updated to reflect that
videoId on GET /v1/analytics/youtube/demographics.Pass
videoId to fetch demographics for a single video (instead of the whole channel). Note: YouTube may return empty breakdowns for low-traffic videos due to signed-in view thresholds.Use:
•
videoId - YouTube video ID (scoped to the connected channel; otherwise 404)•
startDate - defaults to the video publish date (lifetime) when videoId is providedResponse adds (video mode only):
videoId, title, publishedAt.Also: Meta city targeting docs were updated to reflect that
cities[].radius has a minimum (~17 km / 10 mi). Smaller values can produce a 0-size audience and fail at launch; use customLocations for tighter radius targeting.Improvement
GET
This lets you proactively refresh stored tokens (the endpoint normally returns
Use
New param:
/v1/connect/{platform}/ads now supports force to trigger a fresh OAuth flow even when an ads account is already connected.This lets you proactively refresh stored tokens (the endpoint normally returns
alreadyConnected: true based on account active state, not token liveness).Use
force=true to always receive authUrl; completing OAuth refreshes the stored token on the existing posting + ads accounts in place.New param:
force (true/false, default false)Breaking Change
SMS carrier registration
You may now see pre-submission review and deactivation states when listing, fetching, or reusing registrations, which can affect clients that assume only the previous 3 statuses.
Updated
•
Applies to:
•
•
•
Also added to
•
•
status now includes additional lifecycle states in registration APIs.You may now see pre-submission review and deactivation states when listing, fetching, or reusing registrations, which can affect clients that assume only the previous 3 statuses.
Updated
status enum:•
pending, approved, rejected, requested, changes_requested, deactivatedApplies to:
•
GET /v1/sms/registrations•
GET /v1/sms/registrations/{id}•
POST /v1/phone-numbers/{id}/sms/reuse-registrationAlso added to
POST /v1/sms/registrations request body:•
resubmitRequestId (resubmit an existing registration returned for changes)•
wizardValues (optional raw wizard answers; can be omitted by API integrators)Improvement
conversions in ads metrics now includes non-Meta platforms.You can now rely on
conversions for more platforms when charting or aggregating performance (instead of always getting 0 outside Meta).Applies to:
•
GET /v1/ads/timeline (rows[].conversions)•
AdMetrics.conversionsNew platform behavior:
• Meta: events matching the campaign optimization goal
• Google: tracked conversions
• X / LinkedIn: reported website/lead conversions (added 2026-07)
Improvement
GET
Deactivated (terminated) registrations are hidden by default; pass
New query param:
•
/v1/sms/registrations now supports includeDeactivated (boolean).Deactivated (terminated) registrations are hidden by default; pass
includeDeactivated=true to include them in the results.New query param:
•
includeDeactivated - true to include deactivated registrations (default: hidden)New Feature
New endpoint:
This terminates the carrier registry campaign so recurring monthly campaign fees stop; numbers under the registration can no longer send texts (receiving is unaffected). Deactivation is irreversible and the operation is idempotent.
Key parameter:
Response:
DELETE /v1/sms/registrations/{id} to deactivate an SMS brand/campaign registration.This terminates the carrier registry campaign so recurring monthly campaign fees stop; numbers under the registration can no longer send texts (receiving is unaffected). Deactivation is irreversible and the operation is idempotent.
Key parameter:
id (path, string)Response:
200 returns status = deactivated (enum: deactivated)New Feature
LinkedIn now supports Thought Leader ads on
This lets you create a LinkedIn ad that references an existing post (share/ugcPost) as the creative, while still using the normal
Set:
•
Notes:
• Mutually exclusive with other LinkedIn creative sources in
• When using
POST /v1/ads/create via platformSpecificData.thoughtLeader.This lets you create a LinkedIn ad that references an existing post (share/ugcPost) as the creative, while still using the normal
/v1/ads/create flow for targeting/bidding/schedule.Set:
•
platformSpecificData.thoughtLeader.postUrn (pattern: urn:li:share:\d+ or urn:li:ugcPost:\d+)Notes:
• Mutually exclusive with other LinkedIn creative sources in
platformSpecificData (e.g. carousel, document, conversation, etc.)• When using
thoughtLeader, you don’t need headline/body/imageUrl/organizationId for the creative (the referenced post provides them).New Feature
New LinkedIn-only Ads helpers are available:
• Suggested bid & budget bounds via
Returns suggested bid + allowed bid limits and daily budget bounds for a targeting spec (use before
Key request fields:
Response:
• Impressions/clicks/spend forecast via
Forecasts metrics for a targeting spec over a future time range.
Key request fields:
Response:
For non-LinkedIn accounts, both endpoints return
• Suggested bid & budget bounds via
POST /v1/ads/targeting/bid-pricingReturns suggested bid + allowed bid limits and daily budget bounds for a targeting spec (use before
POST /v1/ads/create).Key request fields:
accountId, adAccountId, spec, optional campaignType (TEXTAD/SPONSOREDUPDATES/SPONSOREDINMAILS), bidType (CPM/CPC/CPV), matchType (EXACT/AUDIENCEEXPANDED), dailyBudget.Response:
available and pricing (may be null).• Impressions/clicks/spend forecast via
POST /v1/ads/targeting/supply-forecastForecasts metrics for a targeting spec over a future time range.
Key request fields:
accountId, adAccountId, spec, timeRangeStart, timeRangeEnd, plus either dailyBudget or totalBudget. For manual bidding, set competingBid with bidType (CPM/CPC/CPV) and amount; for auto-bidding, set optimizationTarget.Response:
available and forecast series with metricType and granularity (DAILY/SEVENDAY/THIRTYDAY/CUSTOM).For non-LinkedIn accounts, both endpoints return
available: false (not an error).New Feature
New endpoint
Send
Response:
Errors:
POST /v1/sms/opt-in-proof uploads SMS opt-in proof (screenshot/PDF) and returns a public URL you can include in a campaign’s messageFlow for carrier review.Send
multipart/form-data with file (PNG/JPG/WebP/GIF/PDF, max 4MB).Response:
{ "url": "..." }Errors:
422 unsupported type/too large, 401 unauthorized. Alias: /v1/sms/registrations/{id}/opt-in-proof.New Feature
POST
Set
New field:
/v1/sms/messages now supports scheduling via sendAt.Set
sendAt (ISO 8601 date-time with offset) to queue an SMS/MMS for a future time; it must be in the future. The message is queued and the message.delivered webhook fires when it actually sends.New field:
sendAtImprovement
SMS carrier registration now accepts optional policy URLs on 10DLC campaigns via
You can include privacy/terms links in the
New
•
•
Also,
POST /v1/sms/registrations.You can include privacy/terms links in the
campaign object to help carrier reviewers validate compliance.New
campaign properties:•
privacyPolicyLink•
termsAndConditionsLinkAlso,
embeddedLink and embeddedPhone now document auto-derivation from your sample messages when omitted.New Feature
New endpoint:
Use it to host proof of consent when submitting
Request:
Response:
Errors:
POST /v1/sms/registrations/{id}/opt-in-proof uploads an SMS opt-in form screenshot/PDF and returns a public URL you can include in an appeal.Use it to host proof of consent when submitting
POST /v1/sms/registrations/{id}/appeal (add the returned URL to your messageFlow).Request:
multipart/form-data with file (PNG/JPG/WebP/GIF/PDF, max 4MB)Response:
{ "url": "..." }Errors:
404 registration not found, 422 unsupported type/too largeNew Feature
New endpoint:
The returned
Request body:
•
•
•
Response (201):
POST /v1/accounts/{accountId}/pinterest-boards to create Pinterest boards on a connected Pinterest account.The returned
board.id can be used immediately as platformSpecificData.boardId when creating a Pinterest post.Request body:
•
name (required)•
description•
privacy: PUBLIC | PROTECTED | SECRET (default PUBLIC)Response (201):
board with id, name, description, privacy, url.New Feature
Website retargeting audiences are now supported via a new audience type:
You can now create and list these audiences using
When creating, set:
•
•
website_retargeting (LinkedIn only).You can now create and list these audiences using
type=website_retargeting on POST /v1/ads/audiences and filter them via GET /v1/ads/audiences.When creating, set:
•
type: website_retargeting•
matchRules: array of URL patterns (matchType: EXACT | STARTSWITH | CONTAINS | ENDSWITH, plus matchValue)New Feature
Custom audiences now support new types:
You can list and filter these via
To create them via
•
•
company_list and engagement on /v1/ads/audiences.You can list and filter these via
GET /v1/ads/audiences using type = customerlist | companylist | engagement | website | lookalike | saved_targeting.To create them via
POST /v1/ads/audiences:•
type=engagement (LinkedIn): requires sourceType (VIDEOADS | LEADGENFORMS | ORGANIZATIONPAGES | EVENTPAGES | SINGLEIMAGE_ADS), plus trigger, lookbackDays (30 | 60 | 90 | 180 | 365), and engagementSources.•
type=company_list (LinkedIn): requires companies (array of company rows like name, domain, website, linkedinPageUrl).New Feature
LinkedIn firmographic breakdowns are now supported in analytics via the
This lets you request LinkedIn aggregated firmographic segments for the requested date range (note: LinkedIn data is aggregated over the whole range, delayed 12–24h, and segments with <3 events are omitted).
Use
•
•
•
LinkedIn breakdown rows include the raw pivot
breakdowns query param on GET /v1/ads/{adId}/analytics and GET /v1/ads/campaigns/{campaignId}/analytics.This lets you request LinkedIn aggregated firmographic segments for the requested date range (note: LinkedIn data is aggregated over the whole range, delayed 12–24h, and segments with <3 events are omitted).
Use
breakdowns with LinkedIn dimensions:•
jobtitle, jobfunction, seniority, industry•
company, company_size•
country, regionLinkedIn breakdown rows include the raw pivot
value plus a resolved name.New Feature
POST /v1/ads/create now supports LinkedIn Jobs ads via a new goal
This lets you create Dynamic Jobs Ads that promote open roles (requires a Company Page with active job postings).
Use:
•
•
Also added for LinkedIn-only creatives on
•
job_applicants.This lets you create Dynamic Jobs Ads that promote open roles (requires a Company Page with active job postings).
Use:
•
goal: job_applicants•
platformSpecificData.jobs with headline and buttonLabel (each takes exactly one of preApproved or custom)Also added for LinkedIn-only creatives on
platformSpecificData:•
conversation (Conversation Ads) with subject, firstMessageId, and messages (buttons use exactly one of nextMessageId or url).New Feature
POST
You can create LinkedIn-only creatives (beyond single image/video) by setting one of these mutually-exclusive options:
•
•
•
•
•
•
Notes:
• Carousel cards use
• Document ads use
• Spotlight ads use
• Follower ads use
Also clarified: on LinkedIn,
/v1/ads/create now supports more LinkedIn ad formats via platformSpecificData.You can create LinkedIn-only creatives (beyond single image/video) by setting one of these mutually-exclusive options:
•
platformSpecificData.carousel (2–10 cards)•
platformSpecificData.document (PDF/PPT/DOC)•
platformSpecificData.spotlight (dynamic spotlight)•
platformSpecificData.follower (dynamic follower)•
platformSpecificData.textAd (right-rail text ad)•
platformSpecificData.event (promote an existing event)Notes:
• Carousel cards use
cards[].imageUrl and optionally cards[].headline / cards[].landingUrl (falls back to ad-level linkUrl).• Document ads use
document.url + document.title.• Spotlight ads use
spotlight.headline + spotlight.callToAction + spotlight.landingUrl.• Follower ads use
follower.callToAction enum: VISITORGANIZATIONCOMPANYPAGE, VISITORGANIZATIONLIFEPAGE, VISITORGANIZATIONJOBSPAGE, VISITORGANIZATIONCAREERSPAGE.Also clarified: on LinkedIn,
imageUrl + linkUrl publishes an ARTICLE-content creative (with longHeadline as the description).New Feature
Sole-prop 10DLC registrations can now re-send the OTP via
Use this when the original code expired or never arrived. It only works while the registration is pending and awaiting OTP, and is rate-limited to 1 request/min.
Request:
Response:
Errors:
Port-in updates:
•
•
10DLC campaign submissions:
•
POST /v1/sms/registrations/{id}/resend-otp.Use this when the original code expired or never arrived. It only works while the registration is pending and awaiting OTP, and is rate-limited to 1 request/min.
Request:
id (path)Response:
{ sent: boolean }Errors:
400 not awaiting OTP, 404 not found, 429 wait 1 minutePort-in updates:
•
POST /v1/phone-numbers/port-in: endUser.accountNumber is now required (and endUser.countryCode is restricted to US/CA)•
POST /v1/phone-numbers/port-in/check: response adds lineType (when known) to indicate if a transfer PIN is required at submit10DLC campaign submissions:
•
POST /v1/sms/registrations: campaign.optinMessage, campaign.optoutMessage, and campaign.helpMessage are now optional; when omitted, compliant templates are generated.New Feature
LinkedIn ads now support campaign bidding & delivery controls via
This lets you set LinkedIn-specific options (manual bids, cost model, audience expansion, Audience Network, etc.) while keeping the platform inferred from
Use
•
•
•
•
•
platformSpecificData on POST /v1/ads/create and POST /v1/ads/boost.This lets you set LinkedIn-specific options (manual bids, cost model, audience expansion, Audience Network, etc.) while keeping the platform inferred from
accountId (sending options for a different platform returns 400).Use
platformSpecificData (LinkedIn only):•
costType: CPM | CPC | CPV•
unitCost (manual bid; requires costType)•
optimizationTargetType (pass-through)•
creativeSelection: OPTIMIZED | ROUND_ROBIN•
audienceExpansionEnabled, offsiteDeliveryEnabled, connectedTelevisionOnlyNew Feature
Comment-to-DM automations now support message variations for random rotation of DM text and public comment replies.
This lets you provide multiple alternate texts so repeat triggers don’t always send identical messages.
New optional fields:
• Create/Update:
• Returned on list/detail/update responses:
Rotation behavior:
• DM text is picked at random from
• Public reply is picked at random from
Applies to:
This lets you provide multiple alternate texts so repeat triggers don’t always send identical messages.
New optional fields:
• Create/Update:
dmMessageVariations (up to 5), commentReplyVariations (up to 5)• Returned on list/detail/update responses:
dmMessageVariations, commentReplyVariationsRotation behavior:
• DM text is picked at random from
[dmMessage, ...dmMessageVariations]• Public reply is picked at random from
[commentReply, ...commentReplyVariations]Applies to:
POST /v1/comment-automations, PATCH /v1/comment-automations/{automationId}, GET /v1/comment-automations, GET /v1/comment-automations/{automationId}.Improvement
Analytics responses now include media accessibility alt text via
Use this to display or audit the alt text set on each media item when available.
New field:
•
Included in:
•
•
mediaItems[].altText.Use this to display or audit the alt text set on each media item when available.
New field:
•
mediaItems[].altText (string)Included in:
•
AnalyticsSinglePostResponse•
AnalyticsListResponseImprovement
GET
This lets you detect and display reviews with attached photos (Google Business only; other platforms return
New fields:
•
•
GET
Use this to validate budget inputs against account-level minimums (field may be absent if the connected token can’t read it).
New field:
•
/v1/inbox/reviews now returns Google Business review photo info via hasPhotos and photoCount.This lets you detect and display reviews with attached photos (Google Business only; other platforms return
hasPhotos=false and photoCount=0).New fields:
•
hasPhotos•
photoCountGET
/v1/ads/accounts now includes Meta’s minimum daily budget via minimumDailyBudget.Use this to validate budget inputs against account-level minimums (field may be absent if the connected token can’t read it).
New field:
•
minimumDailyBudgetNew Feature
Meta ads now support setting EU DSA (Article 26) defaults at the ad-account level, so EU-targeted calls can omit per-ad
Set defaults once via
Read current defaults via
dsaBeneficiary/dsaPayor when defaults are configured.Set defaults once via
PATCH /v1/ads/accounts with accountId, adAccountId, defaultDsaBeneficiary, optional defaultDsaPayor (if omitted, payor defaults to beneficiary).Read current defaults via
GET /v1/ads/dsa-defaults (accountId, adAccountId), and fetch Meta-provided suggestions via GET /v1/ads/dsa-recommendations (accountId, adAccountId).POST /v1/ads/create, POST /v1/ads/boost, and POST /v1/ads/ctwa now treat dsaBeneficiary/dsaPayor as optional for EU targeting when the Meta ad account has defaults set (Meta accounts only; other platforms return 400).Improvement
POST
Use
New/updated request behavior:
•
•
•
/v1/phone-numbers/port-in now supports portType to specify whether you’re porting the entire losing account or only some numbers.Use
portType to avoid ambiguity on ports where the losing account keeps some numbers.New/updated request behavior:
•
portType: full | partial (default: full)•
endUser.billingPhoneNumber: now documented as the number on the losing carrier’s bill; defaults to the ported number itself on single-number orders•
focDatetimeRequested: when omitted, defaults to ~1 week out (shifted off weekends)New Feature
New endpoint:
This is ad-scoped only (does not affect the parent ad set/campaign), and is a thin wrapper around updating the ad
Set
Response includes
PUT /v1/ads/{adId}/status to pause/resume a single ad.This is ad-scoped only (does not affect the parent ad set/campaign), and is a thin wrapper around updating the ad
status.Set
status to active or paused. {adId} accepts Zernio _id, Meta platformAdId, or the creative’s effective story/media IDs.Response includes
updated (1/0) and skipped (1/0); message is returned when the change is skipped (terminal status or already in target state).New Feature
conversation.started webhooks now include SMS conversations via conversation.platform.This lets you receive “new conversation started” events for SMS in the same platform-agnostic subscription.
New enum value:
•
conversation.platform: sms (in addition to existing platforms)New Feature
WhatsApp template sends via
This lets you use a single approved template with an image/video/document header while sending a different asset for each recipient (e.g. a unique invoice PDF), instead of always sending the template’s approved sample asset.
Set
•
• Provide exactly one of
• Optional:
POST /v1/inbox/conversations now support overriding a media-header template asset per message using headerMedia.This lets you use a single approved template with an image/video/document header while sending a different asset for each recipient (e.g. a unique invoice PDF), instead of always sending the template’s approved sample asset.
Set
headerMedia (JSON body only):•
type: image | video | document (must match the template header type)• Provide exactly one of
link (public URL) or id (Meta media id)• Optional:
filename (document only)Improvement
GET
This lets you filter out ad accounts that can’t currently create/run ads and show a user-friendly reason in your UI.
New fields in
•
•
On non-Meta platforms these fields are absent (treat
/v1/ads/accounts now returns Meta ad account usability flags: selectable and unusableReason.This lets you filter out ad accounts that can’t currently create/run ads and show a user-friendly reason in your UI.
New fields in
accounts[] (Meta only):•
selectable (boolean) — whether the account can create/run ads now•
unusableReason (string|null) — reason when selectable is false; null when selectableOn non-Meta platforms these fields are absent (treat
selectable as true).Improvement
GET /v1/inbox/conversations/search now searches by contact identity (participant name/username/phone) in addition to message text, returning matching conversations once (message matches included when present).Contact-identity matches are case-insensitive substrings; they return
matchCount=0 and matches=[]. Note: identity matching is not applied when direction is set (incoming | outgoing).Also updated:
• Searchable platforms now include Twitter/X and Reddit; Bluesky is live-fetched and appears in
meta.accountsSkipped• Response adds
conversation.lastMessage (most recent message preview)Improvement
Meta placements now support delivering ads on WhatsApp by allowing
This lets you explicitly include WhatsApp inventory when using manual placements (instead of relying on automatic placements).
Set
•
Applies to:
•
•
whatsapp in placements.publisherPlatforms.This lets you explicitly include WhatsApp inventory when using manual placements (instead of relying on automatic placements).
Set
placements.publisherPlatforms to include:•
whatsapp (alongside facebook, instagram, threads, messenger, audience_network)Applies to:
•
POST /v1/ads/create•
POST /v1/ads/ctwaImprovement
POST
This lets you restrict CTWA ad delivery to specific Meta surfaces/devices instead of using automatic placements (Meta may still enforce eligibility/co-selection rules and return actionable errors).
Use
•
•
•
/v1/ads/ctwa now supports manual ad placements via placements.This lets you restrict CTWA ad delivery to specific Meta surfaces/devices instead of using automatic placements (Meta may still enforce eligibility/co-selection rules and return actionable errors).
Use
placements with:•
publisherPlatforms: facebook | instagram | threads | messenger | audience_network•
facebookPositions, instagramPositions, messengerPositions, audienceNetworkPositions, threadsPositions, whatsappPositions•
devicePlatforms: mobile | desktopImprovement
GET /v1/usage now includes estimated tax via a new tax field on UsageMetering.This lets you show an estimated tax amount (computed with Stripe Tax) on top of the window’s net spend (
totals.total). tax is null when no billing address is on file, the total is ≤ 0, or the estimate fails.New fields:
•
tax.taxUsd — estimated tax in USD (added on top of totals.total)•
tax.ratePercent — combined rate percentage (nullable)•
tax.jurisdictionLabel — human label like “ES VAT” (nullable)•
tax.reverseCharge — true for EU/UK B2B reverse charge (0 tax added)Improvement
Webhooks now support additional event subscriptions for conversations, calls, leads, and WhatsApp.
You can subscribe to these new
•
•
•
•
SMS carrier registrations (10DLC) now accept
For rejected registrations,
You can subscribe to these new
events via POST /v1/webhooks/settings or PUT /v1/webhooks/settings:•
conversation.started•
call.received, call.ended, call.failed, call.permission_request•
lead.received•
whatsapp.automatic_eventSMS carrier registrations (10DLC) now accept
campaign.subUsecases on POST /v1/sms/registrations (2–5 values): 2FA, ACCOUNTNOTIFICATION, CUSTOMERCARE, DELIVERYNOTIFICATION, FRAUDALERT, HIGHEREDUCATION, MARKETING, POLLINGVOTING, PUBLICSERVICEANNOUNCEMENT, SECURITY_ALERT.For rejected registrations,
GET /v1/sms/registrations/{id} may now include campaignContent (messageFlow, sample1, sample2), and POST /v1/sms/registrations/{id}/appeal can include corrected messageFlow/sample1/sample2 along with appealReason.New Feature
New billing snapshot endpoint:
It returns the billing “statement” view (plan, current cycle, balance/credits, spend caps, and payment/access status). Use this for billing state; use
Key response blocks:
•
•
•
•
•
Also:
• Default (no metering params): same snapshot as before
• Metering mode (billed spend by product) when passing any of:
Metering params:
•
•
•
Stripe accounts calling metering mode receive
GET /v1/billing.It returns the billing “statement” view (plan, current cycle, balance/credits, spend caps, and payment/access status). Use this for billing state; use
GET /v1/usage for usage/metering.Key response blocks:
•
plan (includes isUsageBased)•
period (start/end may be null on Stripe)•
balance (null for Stripe; populated for Metronome)•
caps (e.g. xSpendUsedCents, xSpendLimitCents)•
status (e.g. hasAccess, suspended, openInvoiceUrl, declineReason)Also:
GET /v1/usage is now dual-mode.• Default (no metering params): same snapshot as before
• Metering mode (billed spend by product) when passing any of:
range, granularity, from, toMetering params:
•
range: cycle | prev-cycle | 7d | 14d | 30d | 3mo | 12mo | custom•
granularity: day | month | total•
from/to required when range=customStripe accounts calling metering mode receive
{ "supported": false }.New Feature
POST
You can either upload bytes with
New JSON body option:
•
•
New response to handle:
•
/v1/whatsapp/business-profile/photo now accepts an image via URL in addition to multipart upload.You can either upload bytes with
multipart/form-data (file) or send application/json with url; we fetch the image server-side and upload the bytes to Meta.New JSON body option:
•
accountId•
url (public https image URL; must resolve directly without redirects)New response to handle:
•
422 when the profile photo is locked for WhatsApp coexistence numbers (must be managed in the WhatsApp Business app).Breaking Change
POST
This can break existing integrations that submit
For
For
Webhook post events now echo back your post’s free-form
•
•
/v1/sms/registrations now enforces additional required fields for 10DLC registrations.This can break existing integrations that submit
brand/campaign without the newly-required fields.For
brand, these fields are now required: website, street, city, state, postalCode (in addition to existing required fields).For
campaign, sample2 is now required and must be distinct from sample1.Webhook post events now echo back your post’s free-form
metadata (when provided at create time) in:•
WebhookPayloadPost.post.metadata•
WebhookPayloadPostPlatform.post.metadataBreaking Change
Google Business Profile posts:
If you were sending
•
•
•
offer.offerType has been removed from GoogleBusinessPlatformData.If you were sending
offerType (previously OFFER or BUYONEGET_ONE), remove it from your payloads and use the remaining offer fields instead:•
redeemOnlineUrl•
termsConditions•
couponCodeBreaking Change
The supported
If you request breakdowns that are no longer supported, your call may fail (400) or no longer return those breakdowns.
Affected endpoints:
•
•
breakdowns values were reduced on analytics endpoints.If you request breakdowns that are no longer supported, your call may fail (400) or no longer return those breakdowns.
Affected endpoints:
•
GET /v1/ads/{adId}/analytics: Meta breakdowns now limited to age, gender, country, publisherplatform, deviceplatform, region (removed: platformposition, impressiondevice, videoasset, imageasset, bodyasset, titleasset; also removed aliases placement and singular breakdown)•
GET /v1/ads/campaigns/{campaignId}/analytics: Meta breakdowns now limited to age, gender, country, publisherplatform, deviceplatform, region, platformposition, impressiondevice, videoasset, imageasset, bodyasset, titleasset (removed aliases placement and singular breakdown)New Feature
GET /v1/analytics now supports a new metric: follows.You can read
analytics.follows in responses (Instagram feed posts and stories only: organic accounts that started following from this post; 0 for reels and other platforms).You can also sort analytics results by
sortBy = follows (enum now includes: date, engagement, impressions, reach, likes, comments, shares, saves, clicks, views, follows).Breaking Change
GET /v1/ads/{adId}/analytics and GET /v1/ads/campaigns/{campaignId}/analytics now validate breakdown dimensions and will return 400 for unknown values.This prevents silent/partial breakdown results and makes invalid
breakdowns requests fail fast with a supported-values list.Updates:
•
breakdowns: unknown dimensions now return 400 (instead of being ignored)•
placement is accepted as an alias for publisher_platform•
breakdown (singular) is also acceptedNew Meta breakdown options include:
platformposition, impressiondevice, videoasset, imageasset, bodyasset, titleasset.New Feature
You can now update saved_targeting audiences via
This lets you change an audience’s
Request body (send at least one):
Also:
PUT /v1/ads/audiences/{audienceId}.This lets you change an audience’s
name, description, or fully replace its spec (no merge). Uploaded/derived audiences aren’t updatable and return 422.Request body (send at least one):
name, description, specAlso:
GET /v1/ads/audiences now returns accountId for type=savedtargeting items, and DELETE /v1/ads/audiences/{audienceId} clarifies that savedtargeting deletes only the local record (it exists only on Zernio).Improvement
Ads reporting endpoints may now return
When this happens, the response includes currently-available data plus
Applies to:
•
•
•
•
•
Also,
•
•
202 when part of the requested date range isn’t ingested yet.When this happens, the response includes currently-available data plus
backfillPending: true, and a Retry-After header with the recommended poll interval. Repeat the same request until it returns 200 with the full range.Applies to:
•
GET /v1/ads•
GET /v1/ads/tree•
GET /v1/ads/timeline•
GET /v1/ads/campaigns/{campaignId}/analytics•
GET /v1/ads/{adId}/analyticsAlso,
POST /v1/posts 403 responses now include a stable code to distinguish causes:•
ACCOUNT_DISCONNECTED•
PROFILEOVERLIMITNew Feature
WhatsApp inbox messages now support commerce interactive messages (products, product lists, catalog messages, and carousels) via
This lets you send catalog-driven messages and receive cart submissions / product inquiries back in webhooks.
New
•
Webhook
•
•
Note: interactive messages are session messages (must be sent within WhatsApp’s 24-hour window).
interactive on POST /v1/inbox/conversations/{conversationId}/messages.This lets you send catalog-driven messages and receive cart submissions / product inquiries back in webhooks.
New
interactive.type values:•
product, productlist, catalogmessage, carouselWebhook
message.received metadata adds:•
metadata.order (cart submitted from commerce messages)•
metadata.referredProduct (product inquiry context)Note: interactive messages are session messages (must be sent within WhatsApp’s 24-hour window).
Improvement
GET
You can display richer mention attribution (when available) without extra profile resolution on your side.
New fields:
•
•
•
/v1/inbox/mentions now returns additional LinkedIn author details in each mention.You can display richer mention attribution (when available) without extra profile resolution on your side.
New fields:
•
authorName - display name resolved from authorUrn (can be null)•
authorUsername - LinkedIn vanity name (can be null)•
authorPicture - author profile picture URL (can be null; CDN URLs may expire, so fetch promptly)New Feature
Meta ad metrics now include video performance fields in
You can track video starts, thruplays, 30s views, completion percentiles, and average watch time for hook/hold/drop-off analysis. These fields are Meta video ads only (0 for non-video ads and other platforms).
New fields:
•
•
•
•
•
Note: in
AdMetrics (and therefore AdDailyMetrics).You can track video starts, thruplays, 30s views, completion percentiles, and average watch time for hook/hold/drop-off analysis. These fields are Meta video ads only (0 for non-video ads and other platforms).
New fields:
•
videoPlayActions•
video30SecWatchedActions•
videoThruplayWatchedActions•
videoP25WatchedActions, videoP50WatchedActions, videoP75WatchedActions, videoP95WatchedActions, videoP100WatchedActions•
videoAvgTimeWatchedActionsNote: in
AdDailyMetrics, videoAvgTimeWatchedActions is recomputed per day; don’t sum or plain-average it across days (the range value is play-weighted).New Feature
New endpoint:
You can now search message text across stored inbox conversations and get back matching conversations with up to 3 most-recent matching messages per conversation (useful for finding threads by topic or filtering by sent/received).
Key params:
•
•
•
•
•
Response includes
Requires Inbox addon (
GET /v1/inbox/conversations/search.You can now search message text across stored inbox conversations and get back matching conversations with up to 3 most-recent matching messages per conversation (useful for finding threads by topic or filtering by sent/received).
Key params:
•
query (required, 2–200 chars)•
direction: incoming | outgoing•
platform: facebook | instagram | telegram | whatsapp | sms•
profileId, accountId•
limit (1–50, default 20), cursorResponse includes
data[].conversation, matchCount, matches (up to 3), plus pagination and meta.accountsSkipped for connected accounts on live-fetched platforms that can’t be searched.Requires Inbox addon (
403 if missing).Improvement
Inbox webhook account context now includes optional
Use
profileId in InboxWebhookAccount.Use
profileId (workspace/profile ID) to route or filter inbox webhooks by workspace. It may be omitted for the shared WhatsApp sandbox account or when the account has no resolvable profile.New Feature
New API surfaces for Usage, Calls, Voice (PSTN), SMS, and Phone Numbers are now available, including a unified call history and a new usage “hub”. Several older WhatsApp-prefixed paths are now deprecated aliases of the new canonical endpoints.
Usage snapshot + breakdowns:
•
•
•
Unified call history (WhatsApp + PSTN) + recordings:
•
•
•
WhatsApp call history improvements:
•
•
PSTN Voice calling (new):
•
•
• Call control:
• Browser softphone handshake:
SMS (new):
•
•
• Opt-outs export:
• US carrier registration flows:
Phone Numbers (new canonical resource):
•
• Purchase + inventory:
• Regulated KYC:
• Port-in:
WhatsApp Calling config is now number-keyed:
• New canonical sub-resource:
• Existing
Deprecated aliases (no immediate break, but new integrations should switch):
•
• Many
Usage snapshot + breakdowns:
•
GET /v1/usage (replaces GET /v1/usage-stats; same contract)•
GET /v1/usage/calls with optional groupBy: day | number | channel and filters since, until, channel (whatsapp | pstn), number•
GET /v1/usage/sms with optional groupBy: day | number and filters since, until, numberUnified call history (WhatsApp + PSTN) + recordings:
•
GET /v1/calls filters: channel (whatsapp | pstn), status (ringing | answered | ended | failed), direction (inbound | outbound), number, search, cursor pagination via before + nextCursor•
GET /v1/calls/{id}•
GET /v1/calls/{id}/recording (default 302; as=json returns { url })WhatsApp call history improvements:
•
GET /v1/whatsapp/calls now supports cursor pagination via before and returns nextCursor•
POST /v1/whatsapp/calls now supports idempotent retries via Idempotency-KeyPSTN Voice calling (new):
•
POST /v1/phone-numbers/{id}/voice configure inbound routing (forwardTo), recording/transcription, voicemail, business hours, IVR, blocklist•
DELETE /v1/phone-numbers/{id}/voice• Call control:
POST /v1/voice/calls (supports Idempotency-Key), GET /v1/voice/calls, GET /v1/voice/calls/{id}, POST /v1/voice/calls/{id}/end, POST /v1/voice/calls/{id}/transfer, GET /v1/voice/calls/{id}/recording (as=json), GET /v1/voice/calls/estimate• Browser softphone handshake:
POST /v1/voice/calls/web then POST /v1/voice/calls/web/dialSMS (new):
•
POST /v1/sms/messages (supports Idempotency-Key; MMS via mediaUrls)•
GET /v1/sms/lookup (carrier + line type mobile | landline | voip | toll-free | unknown)• Opt-outs export:
GET /v1/sms/opt-outs with format: json | csv• US carrier registration flows:
GET/POST /v1/sms/registrations, GET /v1/sms/registrations/{id}, POST /v1/sms/registrations/{id}/verify-otp, POST /v1/sms/registrations/{id}/appeal, POST /v1/sms/registrations/sharePhone Numbers (new canonical resource):
•
GET /v1/phone-numbers, GET /v1/phone-numbers/{id}, DELETE /v1/phone-numbers/{id}• Purchase + inventory:
POST /v1/phone-numbers/purchase, GET /v1/phone-numbers/countries, GET /v1/phone-numbers/available, GET /v1/phone-numbers/availability• Regulated KYC:
GET/POST /v1/phone-numbers/kyc, POST /v1/phone-numbers/kyc/upload-document, POST /v1/phone-numbers/kyc/validate-address, POST /v1/phone-numbers/kyc/share, POST /v1/phone-numbers/kyc/review-packet, remediation via GET/POST /v1/phone-numbers/{id}/remediate• Port-in:
GET /v1/phone-numbers/port-in, POST /v1/phone-numbers/port-in, POST /v1/phone-numbers/port-in/check, POST /v1/phone-numbers/port-in/documents, DELETE /v1/phone-numbers/port-in/{id}WhatsApp Calling config is now number-keyed:
• New canonical sub-resource:
GET/POST/PATCH/DELETE /v1/phone-numbers/{id}/whatsapp/calling• Existing
/v1/whatsapp/phone-numbers/{id}/calling endpoints are deprecated aliasesDeprecated aliases (no immediate break, but new integrations should switch):
•
GET /v1/usage-stats → GET /v1/usage• Many
/v1/whatsapp/phone-numbers/* paths → corresponding /v1/phone-numbers/* pathsImprovement
Webhooks now support TikTok URL backfill via the new event
This fires when a published TikTok post’s public URL is resolved/backfilled, using the same payload shape as
Subscribe by including
Also clarified for
post.tiktok.url_resolved.This fires when a published TikTok post’s public URL is resolved/backfilled, using the same payload shape as
post.platform.published/post.platform.failed (see WebhookPayloadPostPlatform).Subscribe by including
post.tiktok.url_resolved in events when calling POST /v1/webhooks/settings or PUT /v1/webhooks/settings.Also clarified for
PUT /v1/posts/{postId}: to promote a draft to scheduled you must send isDraft: false together with scheduledFor (or publishNow: true / queuedFromProfile). If isDraft is omitted, the post keeps its current draft status.Improvement
Webhook payloads now include
This makes it easier to consistently filter all webhook events by a single field (e.g. route staging vs production by account) while keeping
New field:
•
Added to these webhook payloads:
•
•
•
•
•
• inbox webhook payloads via
account.accountId (same value as account.id) as a canonical field for filtering/routing events.This makes it easier to consistently filter all webhook events by a single field (e.g. route staging vs production by account) while keeping
account.id for backward compatibility.New field:
•
account.accountIdAdded to these webhook payloads:
•
comment.received•
lead.received•
review.new•
review.updated•
post.external.created, post.external.updated, post.external.deleted• inbox webhook payloads via
InboxWebhookAccountImprovement
Webhook post events now include the connected social account per platform target via
This lets you route/attribute webhook deliveries by the specific SocialAccount used for each platform publish (useful when a single post spans multiple accounts).
New field:
•
•
post.platforms[].accountId.This lets you route/attribute webhook deliveries by the specific SocialAccount used for each platform publish (useful when a single post spans multiple accounts).
New field:
•
WebhookPayloadPost.post.platforms[].accountId•
WebhookPayloadPostPlatform.post.platforms[].accountId (on post.platform.published / post.platform.failed; see also the top-level account block there)Improvement
WhatsApp KYC form spec now returns an optional audience per requirement in
This lets you show/collect only the fields that apply to the end-user type (business vs individual) and avoid submitting both sets of requirements.
New field:
GET /v1/whatsapp/phone-numbers/kyc.This lets you show/collect only the fields that apply to the end-user type (business vs individual) and avoid submitting both sets of requirements.
New field:
fields[].audience = business | individual | null (applies to both).Improvement
GET /v1/connect/pending-data now supports Pinterest board selection in headless OAuth flows.For Pinterest (in addition to LinkedIn orgs and Snapchat profiles), the OAuth redirect returns
pendingDataToken and you can fetch the full selection payload via token.Use
token (query) from the redirect, and read selectionType (enum: organizations, pages, boards, locations, profiles) to determine which selection array to consume (e.g. boards for Pinterest).Improvement
Workflow send_message nodes now allow omitting
This makes interactive message configs simpler, but payloads with neither
Applies to
interactive.type; it will be inferred from the interactive payload shape.This makes interactive message configs simpler, but payloads with neither
type nor an inferable shape are now rejected.Applies to
send_message.config with messageType = interactive (WhatsApp-only).New Feature
POST /v1/invite/tokens now supports inviting users with the new admin role via role.Use
role=admin to grant team-management permissions (invite/remove members, change roles and access) without billing, ownership transfer, or account deletion permissions.role enum is now: admin, member, billing_admin, viewer.Note:
readOnly (deprecated) can’t be combined with role=billing_admin or role=admin.Improvement
WhatsApp document sends via URL now support setting the displayed filename using
This lets you control the recipient-visible file name for
Use:
•
•
•
Applies to WhatsApp only; ignored for
attachmentName on POST /v1/inbox/conversations/{conversationId}/messages.This lets you control the recipient-visible file name for
attachmentUrl document messages (otherwise WhatsApp derives it from the URL and may show “Untitled”).Use:
•
attachmentType: file•
attachmentUrl: (public URL)•
attachmentName: e.g. Report.pdfApplies to WhatsApp only; ignored for
image/video/audio and for multipart binary uploads.New Feature
New endpoint
Use it to confirm a user-submitted post exists by sending
Request body:
•
•
•
Response:
•
• If
• If no locator provided:
Returned posts use
POST /v1/posts/sync-external lets you sync/verify external (non-Zernio) posts on demand, so a just-published post can be retrieved within seconds instead of waiting for the background sync.Use it to confirm a user-submitted post exists by sending
accountId plus either url or postId.Request body:
•
accountId (required)•
url (optional)•
postId (optional)Response:
•
synced.postsFound, synced.postsSynced, synced.skipped• If
url/postId provided: found + post (or null)• If no locator provided:
posts (recent external posts)Returned posts use
ExternalPostSummary (includes platform, platformPostId, platformPostUrl, publishedAt, and basic analytics like likes/comments).New Feature
WhatsApp Business Profile now supports managing business usernames via new endpoints.
You can fetch the current username + approval state, claim/transfer a username, release it, and retrieve available suggestions.
New endpoints:
•
•
•
•
On username claim failures, expect error codes like
You can fetch the current username + approval state, claim/transfer a username, release it, and retrieve available suggestions.
New endpoints:
•
GET /v1/whatsapp/business-profile/username (accountId query) → returns username (nullable) and status (approved/reserved/none)•
POST /v1/whatsapp/business-profile/username body: accountId, username, optional transferAction (none/force_transfer)•
DELETE /v1/whatsapp/business-profile/username body: accountId•
GET /v1/whatsapp/business-profile/username/suggestions (accountId query) → returns suggestions[]On username claim failures, expect error codes like
whatsappusernameunavailable, whatsappusernameineligible, or whatsappusernametransfer_required.New Feature
New endpoint
Currently supports LinkedIn organization mentions and requires the Inbox addon.
Key query params:
•
•
•
•
•
Response includes
GET /v1/inbox/mentions is available to list mentions of your connected organization accounts delivered via platform webhooks.Currently supports LinkedIn organization mentions and requires the Inbox addon.
Key query params:
•
accountId (filter by social account ID)•
profileId (filter by profile ID)•
sortOrder (asc | desc, default desc)•
limit (1–100, default 25)•
cursor (pagination)Response includes
data items with id, platform (linkedin), content, permalink, authorUrn, organizationalEntity, publishedAt, plus pagination (hasMore, cursor).New Feature
New endpoint
Use query params
GET /v1/ads/campaigns/{campaignId}/analytics returns campaign-level analytics in one call (summary metrics, daily[] timeline, and optional Meta demographic breakdowns).Use query params
fromDate, toDate (defaults to last 90 days; max 730 days) and optional platform to disambiguate IDs. For Meta-only breakdowns, pass breakdowns as a comma-separated list: age, gender, country, publisherplatform, deviceplatform, region, platformposition, impressiondevice, videoasset, imageasset, bodyasset, titleasset.GET /v1/ads/tree now supports daily trends in the same response: set timeIncrement=1 to add daily[] metrics series, and control where it appears with dailyLevel = campaign (default) | adset | ad.New Feature
Instagram Trial Reels are now identifiable per platform target in post responses.
When an Instagram reel is created as a Trial (via
•
•
Use these fields to segment trial reels in analytics; note this reflects creation-time intent (Instagram doesn’t expose a readable trial flag).
When an Instagram reel is created as a Trial (via
platformSpecificData.trialParams), the corresponding PlatformTarget may include:•
isTrialReel (boolean) — present and true only for trial reels•
trialGraduationStrategy — MANUAL | SS_PERFORMANCE (present only when isTrialReel is true)Use these fields to segment trial reels in analytics; note this reflects creation-time intent (Instagram doesn’t expose a readable trial flag).
Breaking Change
Removed endpoint
This endpoint previously returned recent posts for a connected social account by calling the platform API directly (Facebook, Instagram, X/Twitter, Bluesky, Threads, YouTube, LinkedIn, Reddit, TikTok, Pinterest).
If you were using YouTube filtering, note that
GET /v1/accounts/{accountId}/posts.This endpoint previously returned recent posts for a connected social account by calling the platform API directly (Facebook, Instagram, X/Twitter, Bluesky, Threads, YouTube, LinkedIn, Reddit, TikTok, Pinterest).
If you were using YouTube filtering, note that
excludeUnlisted is no longer available via this route.Breaking Change
New endpoint
You can optionally filter YouTube results to avoid returning internal/unlisted content with
Use:
•
•
Also note a targeting spec change:
GET /v1/accounts/{accountId}/posts lists recent posts for a connected social account by calling the platform API directly (Facebook, Instagram, X/Twitter, Bluesky, Threads, YouTube, LinkedIn, Reddit, TikTok, Pinterest).You can optionally filter YouTube results to avoid returning internal/unlisted content with
excludeUnlisted.Use:
•
accountId (path)•
excludeUnlisted (query, boolean; YouTube only)Also note a targeting spec change:
TargetingSpec.cities[].distanceUnit is now TargetingSpec.cities[].distanceunit (with radius still supported). excludedLocations now supports excluded cities with optional radius + distanceunit, plus excluded places, neighborhoods, and excluded customLocations.New Feature
Instagram posts now support AI-generated media self-disclosure via
When
Set:
•
Applies to feed posts, Reels, Stories, and carousels.
isAiGenerated in platformSpecificData.When
isAiGenerated is true, Instagram labels the post as containing AI-generated media (this is for media, not AI-written captions).Set:
•
isAiGenerated (boolean, default false)Applies to feed posts, Reels, Stories, and carousels.
New Feature
POST /v1/whatsapp/phone-numbers/kyc now supports provisioning multiple same-country numbers in a single KYC submission via quantity.Set
quantity (default 1, min 1, max 5) to request several numbers; the same verification covers all of them and each number is billed only when it activates (best-effort if some orders fail).Response now includes
numbers (array) with all provisioned numbers. phoneNumber remains and represents the first/primary number for backward compatibility.status remains: kycsubmitted | kycreused | kycalreadysubmittedNew Feature
WhatsApp phone number provisioning now supports creating a hosted KYC link via
This generates a single-use, 7-day link your end customer can complete without a Zernio login; once submitted, the number provisions under your account (only for regulated/KYC countries).
Request fields:
•
•
•
•
Response fields:
•
•
•
To react when the form is completed, subscribe webhooks to the new event:
POST /v1/whatsapp/phone-numbers/kyc/share.This generates a single-use, 7-day link your end customer can complete without a Zernio login; once submitted, the number provisions under your account (only for regulated/KYC countries).
Request fields:
•
profileId (required)•
country (required, ISO-2; non-KYC countries return 400)•
branding (optional: companyName, logoUrl, brandColor)•
redirect_url (optional; on success appends kyc=submitted and country=<ISO-2>)Response fields:
•
url•
token•
expiresAtTo react when the form is completed, subscribe webhooks to the new event:
whatsapp.number.kyc_submitted (available in POST/PUT /v1/webhooks/settings).Improvement
Comment webhooks (
This lets you detect and render comments that have no text content; note Meta URLs may be ephemeral, so fetch promptly.
Key fields:
•
•
•
comment.received) now include an optional comment.attachment object for Facebook graphic-only comments (sticker, GIF, photo).This lets you detect and render comments that have no text content; note Meta URLs may be ephemeral, so fetch promptly.
Key fields:
•
comment.attachment.type (required)•
comment.attachment.imageUrl•
comment.attachment.urlNew Feature
Meta boosts via
Use
Key rule:
POST /v1/ads/boost now support raw Meta-native targeting with rawTargeting.Use
rawTargeting to pass a Meta targeting spec verbatim (snakecase like geolocations, customaudiences, excludedcustomaudiences, flexiblespec, targeting_automation) to target specific custom/lookalike audiences or clone targeting exactly.Key rule:
rawTargeting is mutually exclusive with targeting (sending both returns 422).New Feature
POST
You can target more precise location types and explicitly exclude locations when creating Meta ads.
New fields:
•
•
•
POST
New fields:
•
•
•
•
•
GET
Use
/v1/ads/create now supports additional Meta geo targeting controls.You can target more precise location types and explicitly exclude locations when creating Meta ads.
New fields:
•
places (Meta geoType place)•
neighborhoods (Meta geoType neighborhood)•
excludedLocations with countries, regions, cities, zipsPOST
/v1/ads/lead-forms adds richer Meta Instant Form configuration.New fields:
•
formType: MOREVOLUME | HIGHERINTENT | RICH_CREATIVE•
blockDisplayForNonTargetedViewer•
allowOrganicLeadGen•
questionPageCustomHeadline•
contextCard (title, content, style: LISTSTYLE | PARAGRAPHSTYLE, buttonText, coverPhoto)GET
/v1/ads/targeting/search geo search now supports geoType=all and returns coordinates.Use
dimension=geo with geoType=all to search across all geo types in one call, and read latitude / longitude from results (when available).Improvement
Moving an account to another profile via
This enforces the rule that a profile can hold only one account per platform.
Handle the new response:
•
PATCH /v1/accounts/{accountId} can now return 409 when the target profile already has an account on the same platform.This enforces the rule that a profile can hold only one account per platform.
Handle the new response:
•
409 - target profile already has an account of the same platform (profileplatformconflict)Improvement
GET
Key fields include
Geo targeting search via
/v1/ads/lead-forms/{formId} now returns a fully detailed form object (enough to duplicate the form via POST /v1/ads/lead-forms).Key fields include
form.status (ACTIVE/ARCHIVED), questions, thankyoupage, and context_card.Geo targeting search via
GET /v1/ads/targeting/search now supports additional geoType values when dimension=geo: subcity, neighborhood, place, metroarea, geomarket (in addition to country, region, city, zip).Improvement
Inbox conversations now include ad-click attribution via a new
This lets you identify when a conversation started from a click-to-message ad (vs organic) and store the associated ad identifiers/details.
New field:
•
Common keys you may see in
• WhatsApp CTWA:
• Meta CTM/CTD:
metadata object in responses from GET /v1/inbox/conversations and GET /v1/inbox/conversations/{conversationId}.This lets you identify when a conversation started from a click-to-message ad (vs organic) and store the associated ad identifiers/details.
New field:
•
metadata (object, nullable) — only present for ad-originated conversations; absent for organicCommon keys you may see in
metadata:• WhatsApp CTWA:
ctwaclid, ctwasourceid, ctwasourceurl, ctwaheadline, ctwasourcetype, ctwacapturedat• Meta CTM/CTD:
metaadid, metaadtitle, metaadsource, metaadtype, metaadref, metaadcapturedat, metaadphotourl, metaadvideourl, metaadpostid, metaadproductid, metaadflowidImprovement
Analytics post items now include
This lets you distinguish ad creatives from organic posts in analytics results (currently only available for LinkedIn posts; false when unavailable).
New field:
•
isAd in AnalyticsListResponse.posts[].This lets you distinguish ad creatives from organic posts in analytics results (currently only available for LinkedIn posts; false when unavailable).
New field:
•
isAd (boolean) — true for ad creatives, otherwise falseImprovement
Meta ads created via
This lets you clone an ad set’s full Meta-native targeting spec via
When both are set,
Key fields:
•
•
•
POST /v1/ads/create now allow combining manual placements with rawTargeting.This lets you clone an ad set’s full Meta-native targeting spec via
rawTargeting while still overriding placements using the structured placements object.When both are set,
placements is converted to Meta snake_case and merged into rawTargeting before sending to Meta.Key fields:
•
rawTargeting (Meta TargetingSpec, snake_case)•
placements.publisherPlatforms: facebook, instagram, threads, messenger, audience_network•
placements.facebookPositions, placements.instagramPositions, placements.messengerPositions, placements.audienceNetworkPositions, placements.threadsPositions, placements.whatsappPositions, placements.devicePlatforms: mobile/desktopImprovement
POST
You can stage Meta campaigns/ads more safely (e.g., create everything paused, or pause just the campaign while keeping the ad set ready), and you can now attach new ads using an existing creative without resending copy/media.
New/updated fields:
•
•
•
/v1/ads/create now supports more control over Meta publish state and creative reuse.You can stage Meta campaigns/ads more safely (e.g., create everything paused, or pause just the campaign while keeping the ad set ready), and you can now attach new ads using an existing creative without resending copy/media.
New/updated fields:
•
status (ACTIVE | PAUSED) now explicitly controls desired publish state; in attach mode (adSetId) it affects only the new ad•
campaignStatus (ACTIVE | PAUSED) lets you set campaign state independently on the legacy create shape (ignored when existingCampaignId is set)•
existingCreativeId can now be used with adSetId to attach an ad that reuses an existing Meta creative (copy/media fields can be omitted)New Feature
POST
This enables safer launches (no immediate spend) and easier multi-ad-set testing while keeping creatives consistent.
New request fields:
•
•
•
/v1/ads/create now supports additional Meta-only creation options: you can create ads/ad sets paused, add a new ad set under an existing campaign, and/or reuse an existing creative.This enables safer launches (no immediate spend) and easier multi-ad-set testing while keeping creatives consistent.
New request fields:
•
status: ACTIVE | PAUSED•
existingCampaignId (Meta only; mutually exclusive with adSetId and creatives[])•
existingCreativeId (Meta only; mutually exclusive with creatives[], dynamicCreative, placementAssets)Improvement
LinkedIn posts now support quote-reshare via
Set
Notes:
•
• Works on personal profiles and organization pages
Error responses are now more structured for programmatic handling:
reshareUrl in LinkedInPlatformData.Set
reshareUrl to a LinkedIn post link ("Copy link to post") or a urn:li:share / urn:li:ugcPost / urn:li:groupPost URN. The published post becomes a repost with your content as commentary and the original post embedded underneath.Notes:
•
reshareUrl is mutually exclusive with media• Works on personal profiles and organization pages
Error responses are now more structured for programmatic handling:
ErrorResponse adds type, code, param, platform, and platformError (raw upstream payload when type is platform_error).type can be: invalidrequesterror, authenticationerror, permissionerror, notfound, ratelimiterror, platformerror, api_error.New Feature
POST /v1/ads/conversions now supports TikTok conversions (platform tiktokads).You can now relay OFFLINE conversion events to TikTok via the Offline Events API. Use
destinationId as the TikTok Offline Event Set ID, and note each event must include an email or phone.Key details:
•
accountId now accepts: metaads, googleads, linkedinads, tiktokads• Response
platform enum now includes tiktokadsGET /v1/accounts/{accountId}/conversion-destinations also now supports tiktokads (platform enum includes tiktokads).GET /v1/ads/tree adds campaignId (platform campaign id) to restrict the tree to a single campaign, independent of pagination.Improvement
Google Ads conversion destination creation via
If a conversion action with the same
Key fields:
•
•
New response:
•
POST /v1/accounts/{accountId}/conversion-destinations is now effectively idempotent by name.If a conversion action with the same
name already exists, the API will reuse it and return the same response as a fresh create. If the same name is used with a different type (category), the API now returns 409 with error code IDEMPOTENCY_CONFLICT.Key fields:
•
name•
typeNew response:
•
409 (Google Ads only) — IDEMPOTENCY_CONFLICTNew Feature
POST /v1/accounts/{accountId}/conversion-destinations now supports creating conversion destinations for Google Ads in addition to LinkedIn.You can create a Google Ads conversion action (created as
UPLOAD_CLICKS for offline uploads). Standard event names are mapped automatically, and actions default to secondary unless you opt in.Key Google Ads fields:
•
countingType: MANYPERCLICK | ONEPERCLICK•
primaryForGoal: true | false (default false)Notes:
•
accountId can now be linkedinads or googleads• Creation is not idempotent — retries can create duplicates
• 201 response
platform now includes linkedinads | googleadsImprovement
WhatsApp regulated-number KYC reuse now supports choosing which prior approved verification to reuse.
When submitting with reuse,
GET /v1/whatsapp/phone-numbers/kyc adds reusable.options (newest first), listing all approved verifications (each with fromPhoneNumber + details).When submitting with reuse,
POST /v1/whatsapp/phone-numbers/kyc adds reuseFrom to select the verification to reuse (set to reusable.options[].fromPhoneNumber). Omit reuseFrom to use the newest; no match returns 409.Improvement
GET
When
New field:
/v1/whatsapp/phone-numbers/kyc now returns a human-readable summary when a reusable KYC verification is available.When
reusable.available is true, you can show what verification is already on file (best-effort; may be empty if provider lookup fails) and let users skip re-entering the form.New field:
reusable.details (array of { label, value }) alongside reusable.fromPhoneNumber.Improvement
GET /v1/connect/{platform}/ads now supports ad sync scoping via adAccountId/adAccountIds across more ads platforms (not just Meta).You can limit discovery/sync to specific ad accounts for
facebook/instagram (Meta), linkedin, googleads, and twitter. Changing the scope will also remove already-synced ads from any de-scoped ad accounts.Use:
•
adAccountId - single ad account id (platform-specific format)•
adAccountIds - multiple ids (repeat param or comma-separated)Notes:
•
tiktok ignores these params (advertisers are scoped during OAuth)•
pinterest ignores these params (no ads discovery)• Meta ids (
act_<digits>) are validated against the connected token; unreachable ids return 400• Omitting both
adAccountId and adAccountIds keeps the previously persisted scope unchangedImprovement
Broadcast recipient results now include a plain-language failure reason via
This helps you interpret WhatsApp
Use:
•
•
Webhooks for delivery status now also include
errorExplanation on GET /v1/broadcasts/{broadcastId}/recipients.This helps you interpret WhatsApp
errorCode values (e.g. opt-out / re-engagement required) without maintaining your own mapping.Use:
•
recipients[].errorCode (integer, nullable)•
recipients[].errorExplanation (string, nullable; null if unmapped — fall back to error)Webhooks for delivery status now also include
error.explanation in WebhookPayloadMessageDeliveryStatus (populated on event=message.failed).Improvement
Google campaign objects now include
This exposes the raw Google Ads
New field:
•
advertisingChannelType in AdCampaign and AdTreeCampaign.This exposes the raw Google Ads
campaign.advertisingchanneltype so you can distinguish campaign serving surfaces (e.g. Search vs Performance Max) when syncing/reporting.New field:
•
advertisingChannelType (string, nullable) — Google-only (e.g. SEARCH, PERFORMANCEMAX, VIDEO, DEMANDGEN, DISPLAY, SHOPPING)New Feature
WhatsApp interactive messages now support voice call buttons when sending inbox messages via
This lets you render WhatsApp’s native “call” CTA in-chat (requires WhatsApp Business Calling enabled on the sending number). Optional attribution data can be echoed back on the
Use
•
•
•
Also,
POST /v1/inbox/conversations/{conversationId}/messages.This lets you render WhatsApp’s native “call” CTA in-chat (requires WhatsApp Business Calling enabled on the sending number). Optional attribution data can be echoed back on the
calls webhook.Use
interactive.type = voicecall and interactive.action.name = voicecall. Optional interactive.action.parameters:•
display_text•
ttl_minutes•
payloadAlso,
GET /v1/whatsapp/calling now returns callDeepLink (nullable) for the public https://wa.me/call/<number> deep link when calling is enabled.New Feature
YouTube analytics now supports video retention curves via
This returns an aggregated (not per-day) audience retention curve for a single video, plus
Call with:
•
•
• optional
Response highlights:
•
• empty
Requires
Also added to
• top-level
• per-day
GET /v1/analytics/youtube/video-retention.This returns an aggregated (not per-day) audience retention curve for a single video, plus
durationSeconds so you can render the curve on a time axis.Call with:
•
videoId•
accountId• optional
startDate, endDateResponse highlights:
•
retentionCurve[] points with elapsedVideoTimeRatio, audienceWatchRatio (can be > 1), relativeRetentionPerformance• empty
retentionCurve for low-view / not-yet-processed videos (2–3 day delay)Requires
yt-analytics.readonly; if missing you may get 412 with code=youtubeanalyticsscope_missing.Also added to
GET /v1/analytics/youtube/daily-views responses:• top-level
durationSeconds• per-day
averageViewPercentage (can exceed 100; don’t clamp client-side)New Feature
GET
This lets you choose whether daily totals are attributed to the post’s publish date or bucketed by the day engagement was actually received (engagement-over-time).
Use
•
•
/v1/analytics/daily-metrics now supports engagement attribution via a new query param attribution.This lets you choose whether daily totals are attributed to the post’s publish date or bucketed by the day engagement was actually received (engagement-over-time).
Use
attribution:•
publish (default) — attribute each post’s lifetime total to its publish date•
received — attribute per-day engagement increases to the day they occurredNew Feature
WhatsApp adds blocked-status lookup for a single contact via
This returns a definitive blocked state using Zernio’s blocklist mirror (kept in sync by block/unblock; first call per account may backfill from Meta’s full list).
Required query params:
•
•
Response
•
GET /v1/whatsapp/block-users/status.This returns a definitive blocked state using Zernio’s blocklist mirror (kept in sync by block/unblock; first call per account may backfill from Meta’s full list).
Required query params:
•
accountId•
user (consumer wa_id or E.164 phone; leading + optional)Response
200:•
blocked (boolean)New Feature
POST
Use this to request consumer permission before placing a Business-Initiated Call; the consumer must tap Allow before
Set:
•
•
/v1/whatsapp/calls now supports sending the WhatsApp call-consent prompt via a new optional action parameter.Use this to request consumer permission before placing a Business-Initiated Call; the consumer must tap Allow before
start_call is permitted (Meta rate-limits prompts and requires an open 24h service window).Set:
•
action: sendcallpermission_request (omit action to place a call)•
bodyText: optional text shown with the consent prompt (max 1024 chars)New Feature
WhatsApp now supports managing your blocklist via
You can list blocked users, block up to 1,000 users per request, and unblock in batches (per-user failures are returned without failing the whole request).
New endpoints:
•
•
•
WhatsApp interactive messages now also support location requests in
/v1/whatsapp/block-users.You can list blocked users, block up to 1,000 users per request, and unblock in batches (per-user failures are returned without failing the whole request).
New endpoints:
•
GET /v1/whatsapp/block-users with accountId, optional limit, after (cursor pagination via nextCursor)•
POST /v1/whatsapp/block-users body: accountId, users (1..1000)•
DELETE /v1/whatsapp/block-users body: accountId, users (1..1000)WhatsApp interactive messages now also support location requests in
POST /v1/inbox/conversations/{conversationId}/messages via interactive.type = locationrequestmessage (and action can be omitted; defaults to {"name":"send_location"}).New Feature
Meta catalog ads are now supported via
This lets you create Advantage+ catalog ads (e.g. vehicle inventory) using a Meta product catalog + product set; Meta renders the visuals per catalog item (no
Discover inputs with:
•
•
Create the ad with:
•
•
•
•
Notes:
goal = catalog_sales on POST /v1/ads/create.This lets you create Advantage+ catalog ads (e.g. vehicle inventory) using a Meta product catalog + product set; Meta renders the visuals per catalog item (no
imageUrl/video sent).Discover inputs with:
•
GET /v1/ads/catalogs (accountId, adAccountId)•
GET /v1/ads/catalogs/{catalogId}/product-sets (catalogId, accountId)Create the ad with:
•
goal: catalog_sales•
promotedObject.productSetId•
promotedObject.pixelId•
promotedObject.customEventTypeNotes:
catalog_sales is single-shape only (no creatives[], adSetId, dynamicCreative, or placementAssets).Improvement
GET
This lets you list Embedded Signup–linked WABA numbers alongside purchased numbers (which remain in
New response field:
•
•
•
•
•
Included only on the default view and when
/v1/whatsapp/phone-numbers now also returns connected (bring-your-own) WhatsApp numbers in a separate connected array.This lets you list Embedded Signup–linked WABA numbers alongside purchased numbers (which remain in
numbers). Connected numbers aren’t provisioned or billed by Zernio and use accountId for messaging/inbox endpoints.New response field:
connected[] with:•
accountId•
phoneNumber•
displayName•
profileId•
connectedAtIncluded only on the default view and when
status=active.Improvement
WhatsApp phone number purchase now supports idempotent retries and duplicate-purchase protection on
Use
A second purchase within 10 minutes is now rejected with
POST /v1/whatsapp/phone-numbers/purchase.Use
purchaseIntentId (optional) as an idempotency key: retry with the same value and, if a number was already bought, the API returns { status: "already_purchased", numberId, phoneNumber } instead of provisioning a second number.A second purchase within 10 minutes is now rejected with
409 and code PURCHASE_VELOCITY. To confirm an intentional additional purchase (e.g. bulk provisioning), set allowMultiple to true.New Feature
New endpoint:
You can now fetch Google Business Profile attribute metadata (valid attribute names, value types, and allowed enum values) before reading/writing via
Query modes (mutually exclusive):
• Location mode:
• Category mode:
Pagination:
Response includes
GET /v1/accounts/{accountId}/gmb-attribute-metadata.You can now fetch Google Business Profile attribute metadata (valid attribute names, value types, and allowed enum values) before reading/writing via
gmb-attributes.Query modes (mutually exclusive):
• Location mode:
locationId (or omit to use the account’s stored selectedLocationId)• Category mode:
categoryName (must start with categories/) + regionCode (optional languageCode)Pagination:
pageSize (1–200) and pageToken.Response includes
attributeMetadata[] with parent, valueType (e.g. BOOL, ENUM, URL, REPEATED_ENUM), displayName, repeatable, deprecated, and valueMetadata[] (enum options), plus nextPageToken when more results are available.Improvement
Meta video ads created via
You can now omit
Applies to:
• Top-level
•
POST /v1/ads/create no longer require a thumbnail.You can now omit
video.thumbnailUrl and Zernio/Meta will auto-generate the poster frame using Meta’s preferred video thumbnail (same candidates as Ads Manager). Provide it only if you need an exact poster frame.Applies to:
• Top-level
video (legacy + attach shapes): video.url required, video.thumbnailUrl optional•
creatives[].video (Meta multi-creative): video.url required, video.thumbnailUrl optionalNew Feature
POST
This lets you set the secondary link text on Facebook/Instagram ads instead of relying on Meta scraping OpenGraph data.
Use:
•
•
Also clarified for Meta lead ads: when using
/v1/ads/create now supports Meta link descriptions via a new description field.This lets you set the secondary link text on Facebook/Instagram ads instead of relying on Meta scraping OpenGraph data.
Use:
•
description (Meta only) — applies to legacy + attach + placementAssets•
creatives[].description (Meta multi-creative) — per-ad override; falls back to top-level descriptionAlso clarified for Meta lead ads: when using
adSetId (attach shape) into a lead ad set, you must pass leadGenFormId on every attached ad.Deprecation
Invite creation now supports a dedicated read-only role via
Use
•
•
•
The
role on POST /v1/invite/tokens.Use
role to control access level:•
member•
billing_admin•
viewer - read-only (no publish/edit/delete/connect)The
readOnly field is now deprecated. If readOnly=true, the invite is created with role=viewer (and it can’t be combined with role=billing_admin).New Feature
Webhooks now support a new WhatsApp number event:
Subscribe to this event to be notified when a WhatsApp number needs additional action to proceed.
Add it to
•
whatsapp.number.action_required.Subscribe to this event to be notified when a WhatsApp number needs additional action to proceed.
Add it to
events when creating/updating a webhook via POST /v1/webhooks/settings or PUT /v1/webhooks/settings:•
events: ["whatsapp.number.action_required", ...]Improvement
Invite token creation now supports assigning an org role and issuing read-only invites via
Use
POST /v1/invite/tokens.Use
role to grant org-level permissions (member | billing_admin, default member), and readOnly to restrict the invitee to view-only access within their scope (default false).Breaking Change
WhatsApp template buttons updated:
This enables WhatsApp marketing “copy code” buttons where the label is fixed by WhatsApp, and the coupon code is provided via
Key changes:
•
•
•
WhatsAppTemplateButton now supports a new button type value copy_code, and text is no longer required for all button types.This enables WhatsApp marketing “copy code” buttons where the label is fixed by WhatsApp, and the coupon code is provided via
example.Key changes:
•
type enum now includes: quickreply, url, phonenumber, otp, copy_code, flow, mpm, catalog•
text: required for all types except copy_code•
example is now string or string[] (URL buttons: string[]; copy_code: coupon code as string)New Feature
Meta Lead Gen ads created via
You can run Meta’s multi-text / multi-asset optimization ("Multiple Text Options") on instant-form lead ads; Zernio will create the required Dynamic Creative ad set automatically and attach the form for you.
Use:
•
•
•
• Optional variations:
• Required:
POST /v1/ads/create now support Dynamic Creative using dynamicCreative together with goal=lead_generation.You can run Meta’s multi-text / multi-asset optimization ("Multiple Text Options") on instant-form lead ads; Zernio will create the required Dynamic Creative ad set automatically and attach the form for you.
Use:
•
goal=lead_generation•
leadGenFormId=<form_id>•
dynamicCreative.imageUrls (send 1+; send a single entry if you only want multi-text)• Optional variations:
dynamicCreative.titles, dynamicCreative.bodies, dynamicCreative.descriptions, dynamicCreative.linkUrls• Required:
dynamicCreative.callToActionTypes (e.g. LEARNMORE, SIGNUP, DOWNLOAD, REGISTER, BUY_NOW)New Feature
X (Twitter) posts now support additional labeling and media safety controls via
You can label posts as paid promotions or AI-generated media, and mark attached media with sensitive-content warnings.
New fields in
•
•
•
Reddit posts now support additional post options via
New fields in
•
•
•
•
platformSpecificData.You can label posts as paid promotions or AI-generated media, and mark attached media with sensitive-content warnings.
New fields in
TwitterPlatformData:•
paidPartnership (boolean)•
madeWithAi (boolean)•
sensitiveMedia with adultContent, graphicViolence, other (booleans; requires media)Reddit posts now support additional post options via
platformSpecificData.New fields in
RedditPlatformData:•
flairText (string; ignored if flairId is set)•
nsfw (boolean)•
spoiler (boolean)•
sendreplies (boolean)Improvement
GET
This lets you fetch only healthy accounts or only accounts that need reconnection (matching the dashboard reconnection check). Pagination totals reflect the filtered result set when used with
Use
•
•
/v1/accounts now supports filtering by connection status via the new status query param.This lets you fetch only healthy accounts or only accounts that need reconnection (matching the dashboard reconnection check). Pagination totals reflect the filtered result set when used with
page/limit.Use
status:•
connected•
disconnectedImprovement
Meta Lead Gen ads created via
You can pin different image/video assets to placement groups (e.g. Stories/Reels vs Feed) while still attaching a Lead Gen form via
Use:
•
•
•
Note:
POST /v1/ads/create now support per-placement creative using placementAssets.You can pin different image/video assets to placement groups (e.g. Stories/Reels vs Feed) while still attaching a Lead Gen form via
leadGenFormId.Use:
•
goal=lead_generation•
leadGenFormId•
placementAssets with defaultImageUrl or defaultVideoUrl + rules[].placementsNote:
dynamicCreative is still not compatible with goal=lead_generation.Improvement
Meta ads tracking-tag updates are now simpler on
This removes the previous requirement to provide the full Meta
Key fields:
•
•
•
•
PATCH /v1/ads/{adId}/tracking-tags: you can send urlTags alone and Zernio will preserve the existing creative verbatim while rebuilding/repointing the ad.This removes the previous requirement to provide the full Meta
creative payload just to add/update click-URL tags. For creatives where Meta strips objectstoryspec (e.g. share/page-post/dark/asset_feed), you may still get 422 and need to include creative explicitly.Key fields:
•
urlTags (Meta)•
creative (Meta, optional; required only when preservation isn’t possible)•
trackingUrlTemplate, finalUrlSuffix (Google)•
dynamicValueParameters, customValueParameters (LinkedIn)New Feature
Meta ads created via
This lets you override the default optimization derived from
Use
POST /v1/ads/create now support explicitly setting the ad set optimization goal via optimizationGoal.This lets you override the default optimization derived from
goal (e.g. traffic defaulting to LINKCLICKS) and pass a Meta-native optimizationgoal value directly; Meta will validate and reject incompatible combinations.Use
optimizationGoal (Meta only), e.g. LANDINGPAGEVIEWS, LINKCLICKS, REACH, IMPRESSIONS, OFFSITECONVERSIONS, THRUPLAY, LEAD_GENERATION.New Feature
Google Ads now supports conversion adjustments via
You can retract, restate value, or enhance an already-uploaded conversion (Google only; other platforms return
Send:
•
•
•
Per adjustment:
• Identify the original conversion with
•
•
•
Also: Meta standalone ad creation adds
POST /v1/ads/conversions/adjustments.You can retract, restate value, or enhance an already-uploaded conversion (Google only; other platforms return
405). Partial failure is supported (check adjustmentsFailed / failures[]).Send:
•
accountId (must be a googleads account)•
destinationId (conversion action resource name)•
adjustments[] with adjustmentType = RETRACTION | RESTATEMENT | ENHANCEMENTPer adjustment:
• Identify the original conversion with
orderId (required for ENHANCEMENT) or gclid + conversionTime•
adjustmentTime (unix seconds)•
restatementValue + currency (for RESTATEMENT)•
user.email/user.phone (+ optional userAgent) for ENHANCEMENTAlso: Meta standalone ad creation adds
goal = leadconversion on POST /v1/ads/create (and Ad.goal) for website pixel lead optimization (distinct from leadgeneration instant forms).Improvement
GET /v1/comment-automations now returns stats.trackedSends for each automation.Use
trackedSends as the CTR denominator for tracked links (divide stats.linkClicks by stats.trackedSends, not stats.dmsSent). It may lag dmsSent for automations that predate click tracking.New field:
•
stats.trackedSends - DMs sent with a trackable (wrapped) linkNew Feature
Webhooks now support external post events for posts created/updated/deleted directly on the platform (not published via Zernio).
Subscribe by including these in
•
•
•
Payload uses
Subscribe by including these in
events when calling POST /v1/webhooks/settings or PUT /v1/webhooks/settings:•
post.external.created•
post.external.updated•
post.external.deletedPayload uses
WebhookPayloadExternalPost with post = ExternalPostWebhookPost (includes post.id = platform-native ID, post.source = external; on post.external.deleted, post.deletedAt is populated). Events are sync-detected (~hourly), not real-time.New Feature
POST
You can explicitly set the created ad’s name (instead of inheriting
New fields:
•
•
•
/v1/ads/create now supports Meta-specific single-ad naming and ad-level tracking.You can explicitly set the created ad’s name (instead of inheriting
name), and attach pixel measurement + URL tags regardless of optimization goal.New fields:
•
adName (Meta only) — overrides the default ad name (default was name)•
tracking.pixelId (Meta only) — attaches pixel measurement via ad tracking_specs•
tracking.urlTags (Meta only) — sets ad url_tags as [{ key, value }]New Feature
New Inbox Analytics endpoints are available to query messaging volume, response times, and conversation-level stats (max date range:
New endpoints:
•
•
•
•
•
•
•
Webhook event enums were expanded in
•
•
•
365 days).New endpoints:
•
GET /v1/analytics/inbox/volume — volume + KPIs + per-platform split (key params: fromDate, toDate, profileId, platform, accountId, source)•
GET /v1/analytics/inbox/heatmap — day-of-week × hour heatmap (key params: fromDate, toDate, action=message.received|message.sent|message.read|all)•
GET /v1/analytics/inbox/source-breakdown — breakdown by metadata.source•
GET /v1/analytics/inbox/response-time — time-to-first-response summary + histogram•
GET /v1/analytics/inbox/top-accounts — top accounts by inbox volume (key param: limit 1–50)•
GET /v1/analytics/inbox/conversations — paginated conversation analytics (key params: limit, page, sortBy=lastMessageAt|firstMessageAt|totalMessages|received|sent|read|failed, order=asc|desc)•
GET /v1/analytics/inbox/conversations/{conversationId} — single-conversation analytics ({conversationId} accepts Mongo _id or platformConversationId)Webhook event enums were expanded in
POST /v1/webhooks/settings, PUT /v1/webhooks/settings, and the Webhook schema to include:•
whatsapp.number.suspended•
whatsapp.number.reactivated•
whatsapp.number.releasedNew Feature
You can now rename Meta ads objects via update endpoints.
This adds name updates without recreating entities (Meta only; other platforms return
•
•
•
Ad creation on Meta also adds more naming control:
•
• In Meta multi-creative mode,
This adds name updates without recreating entities (Meta only; other platforms return
501).•
PUT /v1/ads/campaigns/{campaignId} now accepts name (max 255)•
PUT /v1/ads/ad-sets/{adSetId} now accepts name (max 255)•
PUT /v1/ads/{adId} now supports name (max 255) and propagates it to Meta; non-Meta returns 501Ad creation on Meta also adds more naming control:
•
POST /v1/ads/create now accepts campaignName and adSetName (max 255)• In Meta multi-creative mode,
creatives[].name sets the per-ad name (max 255)New Feature
Meta ads created via
This lets you pass a Meta targeting spec through verbatim (snake_case) to clone an existing campaign’s targeting exactly, including advanced fields not covered by the camelCase targeting fields.
Use
• camelCase targeting fields (e.g.
•
•
Sending both will return
POST /v1/ads/create now support raw Meta-native targeting via rawTargeting.This lets you pass a Meta targeting spec through verbatim (snake_case) to clone an existing campaign’s targeting exactly, including advanced fields not covered by the camelCase targeting fields.
Use
rawTargeting (Meta only) and do not combine it with:• camelCase targeting fields (e.g.
countries, regions, cities, interests, ageMin, ...)•
audienceId•
savedTargetingIdSending both will return
422. If targeting EU users, also provide dsaBeneficiary / dsaPayor separately (not inside rawTargeting).Improvement
POST
This endpoint isn’t idempotent at the ad-platform level (a blind retry can create duplicate campaigns/ad sets/ads). With
Use
• Same key + same body → replays the original response (response header
• Same key + different body →
• Key still processing →
Keys are scoped to your credential and expire after 24h.
/v1/ads/create now supports safe retries via an optional Idempotency-Key header.This endpoint isn’t idempotent at the ad-platform level (a blind retry can create duplicate campaigns/ad sets/ads). With
Idempotency-Key, retries return the original stored response instead of creating duplicates.Use
Idempotency-Key (e.g. a UUID):• Same key + same body → replays the original response (response header
Idempotent-Replayed: true)• Same key + different body →
422• Key still processing →
409 (retry after backoff)Keys are scoped to your credential and expire after 24h.
Improvement
Google Business Profile location listing now supports server-side narrowing for large accounts via
This helps users find a specific location without loading the full list, and responses can now indicate when the list is bounded.
Changes:
•
•
•
•
search and advanced filter.This helps users find a specific location without loading the full list, and responses can now indicate when the list is bounded.
Changes:
•
GET /v1/connect/googlebusiness/locations: new search, filter; response adds hasMore and per-location storeCode•
GET /v1/accounts/{accountId}/gmb-locations: new search, filter; response adds hasMore and per-location storeCode•
POST /v1/connect/googlebusiness/select-location: request body adds optional (recommended) accountId to resolve the location directly for large accounts•
PUT /v1/accounts/{accountId}/gmb-locations: request body adds optional (recommended) accountId when switching locationsImprovement
Meta placement asset customization in
You can now run a single ad with different assets by placement (e.g. 9:16 video on Stories/Reels, 4:5 on Feed). A
Use
• Image mode:
• Video mode:
POST /v1/ads/create now supports pinning video per placement group (not just images).You can now run a single ad with different assets by placement (e.g. 9:16 video on Stories/Reels, 4:5 on Feed). A
placementAssets block must be all-image or all-video (no mixing).Use
placementAssets in one of these modes:• Image mode:
defaultImageUrl + rules[].imageUrl• Video mode:
defaultVideoUrl + rules[].videoUrl (+ optional defaultThumbnailUrl / rules[].thumbnailUrl)placementAssets still can’t be combined with dynamicCreative, creatives[], or adSetId.New Feature
Ads now support unified read/update of click-URL tracking tags via
This lets you audit and set platform-native tracking params across Meta/Google/LinkedIn (platforms without a click-URL tracking surface return
Read returns
• Meta:
• Google:
• LinkedIn:
Update expects only the platform fields:
• Meta:
• Google:
• LinkedIn:
Meta-only: you can now read Event Match Quality (EMQ) + pixel↔CAPI coverage via
Also added Meta advanced matching fields to
GET /v1/ads/{adId}/tracking-tags and PATCH /v1/ads/{adId}/tracking-tags.This lets you audit and set platform-native tracking params across Meta/Google/LinkedIn (platforms without a click-URL tracking surface return
405).Read returns
platform, level (creative | campaign) plus platform fields:• Meta:
urlTags, templateUrlSpec• Google:
trackingUrlTemplate, finalUrlSuffix• LinkedIn:
dynamicValueParameters, customValueParametersUpdate expects only the platform fields:
• Meta:
urlTags (array of {key,value}) + full creative (headline, body, callToAction, linkUrl, imageUrl); may return 422 if the creative can’t be rebuilt• Google:
trackingUrlTemplate and/or finalUrlSuffix• LinkedIn:
dynamicValueParameters and/or customValueParametersMeta-only: you can now read Event Match Quality (EMQ) + pixel↔CAPI coverage via
GET /v1/ads/conversions/quality with accountId and destinationId (non-Meta returns 405).Also added Meta advanced matching fields to
ConversionEvent.user: city, state, zip, dob, gender (Meta-only; hashed server-side).New Feature
POST
This lets you pin a specific image to each placement group (e.g. 9:16 for Stories/Reels and 4:5 for Feed) within a single ad, using Meta’s
Use
•
•
Constraints: Meta-only; cannot be combined with
/v1/ads/create now supports Meta placement asset customization via placementAssets.This lets you pin a specific image to each placement group (e.g. 9:16 for Stories/Reels and 4:5 for Feed) within a single ad, using Meta’s
assetfeedspec + assetcustomizationrules behavior.Use
placementAssets:•
defaultImageUrl (required)•
rules[] (1-10), each with imageUrl + placementsplacements supports: publisherPlatforms, facebookPositions, instagramPositions, messengerPositions, audienceNetworkPositions, threadsPositions, whatsappPositions, devicePlatforms.Constraints: Meta-only; cannot be combined with
dynamicCreative, creatives[], or adSetId.New Feature
Comment-to-DM automations now support Instagram story reply triggers via a new
You can create automations that fire on keyword replies to stories (in addition to post comments). For story replies,
Use
•
•
Applies to:
•
•
trigger field.You can create automations that fire on keyword replies to stories (in addition to post comments). For story replies,
platformPostId is the story media id (omit it to target any story).Use
trigger:•
comment•
story_replyApplies to:
•
POST /v1/comment-automations (new request field trigger, default comment)•
GET /v1/comment-automations and GET /v1/comment-automations/{automationId} (responses now include trigger)Improvement
GET
This lets you report delivery/read performance for comment-to-DM automations (where supported) without extra calls.
New fields:
•
•
/v1/comment-automations now returns additional automation stats: stats.delivered and stats.read.This lets you report delivery/read performance for comment-to-DM automations (where supported) without extra calls.
New fields:
•
stats.delivered - DMs confirmed delivered (Messenger; IG emits no delivery receipt)•
stats.read - DMs confirmed read (IG messagingseen / Messenger messagereads)New Feature
Comment-to-DM automations now support tagging contacts who click tracked links via
When
Use:
•
•
Returned in:
•
•
clickTag.When
linkTracking is enabled, you can set clickTag so anyone who clicks a tracked link button in the DM gets tagged for segmentation (broadcasts/sequences).Use:
•
POST /v1/comment-automations: clickTag (optional; requires linkTracking)•
PATCH /v1/comment-automations/{automationId}: clickTag (empty string clears it)Returned in:
•
GET /v1/comment-automations: clickTag•
GET /v1/comment-automations/{automationId}: clickTagNew Feature
Comment-to-DM automations now support link tracking for DM button URLs, and the list endpoint includes click metrics.
When enabled, link buttons are wrapped in a tracked redirect so you can measure clicks and CTR.
New fields:
• Create/update:
• List:
• List stats:
• Details/create responses:
When enabled, link buttons are wrapped in a tracked redirect so you can measure clicks and CTR.
New fields:
• Create/update:
linkTracking (POST default: true; set false to send links untouched)• List:
automations[].linkTracking• List stats:
automations[].stats.linkClicks, automations[].stats.uniqueClicks• Details/create responses:
automation.linkTrackingNew Feature
X (Twitter) posts now support quote reposting via
Set
Key property:
•
quoteTweetId in TwitterPlatformData.Set
quoteTweetId to a tweet ID (or full status URL) to publish a quote tweet. For threads, it applies to the first tweet only.Key property:
•
quoteTweetId - mutually exclusive with media and poll; X only allows quoting your own posts or posts you’re mentioned in / part of the conversation thread of; billed at the standard create rate ($0.015).Improvement
WhatsApp call billing now includes
This makes it explicit what Zernio bills you (Telnyx + recording) vs what Meta bills directly to your WABA (display-only).
New/updated billing fields:
•
•
•
Applies to:
•
•
• Webhook schema
billableCostUSD in call history, cost estimates, and call.ended webhooks.This makes it explicit what Zernio bills you (Telnyx + recording) vs what Meta bills directly to your WABA (display-only).
New/updated billing fields:
•
billing.billableCostUSD - amount Zernio bills (excludes Meta)•
billing.metaCostUSD - Meta per-minute charge (billed by Meta directly; display only)•
billing.totalCostUSD - full cost including Meta (display only)Applies to:
•
GET /v1/whatsapp/calls•
GET /v1/whatsapp/calls/estimate (in breakdown)• Webhook schema
WebhookPayloadCallEnded (in call.billing)Improvement
GET
You can use these to show engagement stats and recent activity without querying message history.
New fields:
• Contact:
• Channel:
/v1/contacts/{contactId} now returns additional messaging activity fields on the contact and channels.You can use these to show engagement stats and recent activity without querying message history.
New fields:
• Contact:
messagesSentCount, messagesReceivedCount, lastMessageSentAt (nullable), lastMessageReceivedAt (nullable)• Channel:
lastActiveAt (nullable)New Feature
New Workflows API endpoints are available to build and run branching conversation workflows (node/edge graphs), including activation/pausing and execution tracking.
Manage workflows:
•
•
•
•
•
Control lifecycle:
•
•
Track and test runs:
•
•
Manage workflows:
•
GET /v1/workflows (filters: profileId, status = draft|active|paused, limit, skip)•
POST /v1/workflows (create draft; required: profileId, accountId, name; optional: platform = whatsapp|instagram|facebook|telegram|twitter|bluesky|reddit, nodes, edges, entryNodeId)•
GET /v1/workflows/{workflowId}•
PATCH /v1/workflows/{workflowId} (graph edits only when draft or paused)•
DELETE /v1/workflows/{workflowId}Control lifecycle:
•
POST /v1/workflows/{workflowId}/activate (validates runnable graph; idempotent)•
POST /v1/workflows/{workflowId}/pause (stops matching new inbound messages; idempotent)Track and test runs:
•
GET /v1/workflows/{workflowId}/executions (filter status = running|waiting|completed|exited|failed; limit, skip)•
POST /v1/workflows/{workflowId}/executions (manual start; provide conversationId or WhatsApp-only to; optional text seeds lastMessage)New Feature
WhatsApp now supports Sandbox session management via new endpoints:
This lets you activate a test phone against the shared sandbox number (reply-to-activate), list non-expired sessions, and revoke access to block future sends.
Key fields:
• Create/refresh with
• Session object
• Responses include
Also,
GET /v1/whatsapp/sandbox/sessions, POST /v1/whatsapp/sandbox/sessions, DELETE /v1/whatsapp/sandbox/sessions/{sessionId}.This lets you activate a test phone against the shared sandbox number (reply-to-activate), list non-expired sessions, and revoke access to block future sends.
Key fields:
• Create/refresh with
phone (server normalizes to E.164)• Session object
WhatsAppSandboxSession: id, phoneE164, status (pending | active), expiresAt, activatedAt• Responses include
sandboxNumberAlso,
GET /v1/whatsapp/phone-numbers now includes an optional sandbox object (or null) with phoneNumber, accountId, and allowed template (name, language), plus isSandbox = true.Improvement
WhatsApp conversion events no longer require a Facebook Page ID on the account when calling
The endpoint now only requires
If you were setting account metadata with
POST /v1/whatsapp/conversions.The endpoint now only requires
metaCapiDatasetId (provision via POST /v1/whatsapp/dataset) and forwards the WABA ID as userdata.whatsappbusinessaccountid for Meta attribution.If you were setting account metadata with
connectedFacebookPageId, you can stop—missing it will no longer cause 422. Missing metaCapiDatasetId can still return 422.Improvement
Inbox webhooks now include
This lets you join any inbox webhook event back to the Zernio CRM Contact (best-effort), without relying on the sender identity — useful for outgoing and delivery-status style events.
New field:
•
Applies to:
•
•
•
•
contactId on conversation (via InboxWebhookConversation).This lets you join any inbox webhook event back to the Zernio CRM Contact (best-effort), without relying on the sender identity — useful for outgoing and delivery-status style events.
New field:
•
conversation.contactId - Zernio CRM Contact ID for the participant (omitted when no match or participantId is absent)Applies to:
•
message.received•
message.sent•
reaction.received•
conversation.startedImprovement
CTWA ad creation now supports more precise geo targeting in
You can target by city/region/ZIP/metro or point-radius locations (useful for local WhatsApp campaigns) instead of relying only on country targeting.
New optional geo fields:
•
•
•
•
•
Note:
POST /v1/ads/ctwa.You can target by city/region/ZIP/metro or point-radius locations (useful for local WhatsApp campaigns) instead of relying only on country targeting.
New optional geo fields:
•
cities[] (with optional radius + distance_unit: mile | kilometer)•
regions[]•
zips[]•
metros[]•
customLocations[] (requires latitude, longitude, radius, distanceUnit: mile | kilometer)Note:
countries now defaults to ["US"] only when no other geo targeting (cities/regions/zips/metros/customLocations) is provided.New Feature
New webhook event conversation.started is available via
It fires once when a new DM conversation begins (first message exchange) and is platform-agnostic across
Key fields:
•
•
•
•
•
•
WebhookPayloadConversationStarted.It fires once when a new DM conversation begins (first message exchange) and is platform-agnostic across
instagram, facebook, telegram, whatsapp, twitter, reddit, bluesky.Key fields:
•
event=conversation.started•
conversation.id, conversation.platform, conversation.platformConversationId•
conversation.status: active | archived•
account•
startedAt, timestamp•
id (stable webhook event ID)New Feature
POST
When sending an audio attachment, set
Key params:
•
•
•
Note: the audio must be
/v1/inbox/conversations/{conversationId}/messages now supports WhatsApp voice messages (PTT) via voiceNote in the JSON body.When sending an audio attachment, set
voiceNote to true to send it as a WhatsApp voice note (waveform/voice-note UI) instead of a regular audio file.Key params:
•
attachmentType: audio•
attachmentUrl: public URL to the audio file•
voiceNote: true (WhatsApp only)Note: the audio must be
.ogg encoded with OPUS (mono); other formats will be rejected by WhatsApp.New Feature
New endpoint:
You can now fetch recent WhatsApp conversion send events (from delivery logs, ~30-day retention) to power a “recent activity” view without maintaining your own persistence.
Required query:
Optional query:
Response includes per-event fields like
GET /v1/whatsapp/conversions.You can now fetch recent WhatsApp conversion send events (from delivery logs, ~30-day retention) to power a “recent activity” view without maintaining your own persistence.
Required query:
accountIdOptional query:
limit (1–200, default 50)Response includes per-event fields like
timestamp, eventName (LeadSubmitted, Purchase, AddToCart, InitiateCheckout, ViewContent), conversationId, eventsReceived, eventsFailed, traceId, durationMs.Improvement
Inbox webhooks now include a sender
This lets you link inbound messages/reactions directly to a Contact (via the ContactChannel mapping) without an extra Contacts API lookup. The field is omitted when no contact exists (and typically for outgoing/business sender).
New field:
•
•
•
contactId when the sender matches a Zernio CRM Contact.This lets you link inbound messages/reactions directly to a Contact (via the ContactChannel mapping) without an extra Contacts API lookup. The field is omitted when no contact exists (and typically for outgoing/business sender).
New field:
•
message.sender.contactId in message.received and message.sent•
reaction.sender.contactId in reaction.received•
InboxWebhookMessage.sender.contactIdNew Feature
WhatsApp now supports managing the Meta CTWA conversions dataset via two new endpoints.
Use these to detect whether Conversions API reporting is configured for a WhatsApp account and to provision the required Meta dataset (idempotent).
Check current dataset:
Returns
Provision (or fetch existing) dataset:
Returns
If the account token is missing
Use these to detect whether Conversions API reporting is configured for a WhatsApp account and to provision the required Meta dataset (idempotent).
Check current dataset:
GET /v1/whatsapp/dataset with accountId (query)Returns
datasetId (nullable).Provision (or fetch existing) dataset:
POST /v1/whatsapp/dataset with body accountIdReturns
datasetId and created (boolean).If the account token is missing
whatsappbusinessmanage_events, provisioning returns 422 (reconnect required).New Feature
New endpoint:
You can now look up a single pre-approved WhatsApp Template Library template by exact name to inspect its structure before importing—especially the
Required query params:
•
•
Response (
GET /v1/whatsapp/template-library.You can now look up a single pre-approved WhatsApp Template Library template by exact name to inspect its structure before importing—especially the
buttons you must mirror when creating a template (e.g., URL/PHONE_NUMBER inputs).Required query params:
•
accountId (WhatsApp social account ID)•
name (exact library template name)Response (
200): template (object or null) with key fields like bodyparams and buttons where buttons[].type can be QUICKREPLY, URL, PHONE_NUMBER, OTP, FLOW, ...New Feature
WhatsApp Flows now supports listing submitted flow responses via
This lets you fetch customer responses parsed from
Use:
•
•
•
Response items include
Also updated: when sending flows via
GET /v1/whatsapp/flow-responses.This lets you fetch customer responses parsed from
nfm_reply webhook messages (newest first), and optionally scope them to a specific flow.Use:
•
accountId (required)•
flowId (optional)•
limit (optional, max 200, default 50)Response items include
id, receivedAt, from, senderName, conversationId, flowToken, data, raw.Also updated: when sending flows via
POST /v1/whatsapp/flows/send, if flow_token is omitted it is now auto-generated as <flowId>:<uuid> so responses can be attributed to that flow in the Flow Responses view.New Feature
WhatsApp Flows now support flow previews and version history.
You can fetch Meta’s embeddable web preview URL for a flow (drafts included), and list Zernio-tracked versions for a flow’s clone lineage.
New endpoints:
•
•
Flow versioning metadata is now returned:
•
•
Cloning can now stay in the same lineage:
•
You can fetch Meta’s embeddable web preview URL for a flow (drafts included), and list Zernio-tracked versions for a flow’s clone lineage.
New endpoints:
•
GET /v1/whatsapp/flows/{flowId}/preview with accountId and optional invalidate (mint a fresh link)•
GET /v1/whatsapp/flows/{flowId}/versions with accountIdFlow versioning metadata is now returned:
•
GET /v1/whatsapp/flows adds version and lineageId per flow•
POST /v1/whatsapp/flows response adds version and lineageIdCloning can now stay in the same lineage:
•
POST /v1/whatsapp/flows adds asVersion (only when using cloneFlowId)New Feature
Inbox messages now include platform-specific metadata on each message via
This lets you read extra context like quoted replies and WhatsApp interactive/tap details without relying only on webhooks.
New field:
•
WhatsApp multipart sends now support voice note uploads via
Set
New multipart field:
•
messages[].metadata.This lets you read extra context like quoted replies and WhatsApp interactive/tap details without relying only on webhooks.
New field:
•
messages[].metadata (free-form object; commonly includes quotedMessageId, waInteractive, and inbound tap fields like interactiveType / interactiveId)WhatsApp multipart sends now support voice note uploads via
voiceNote.Set
voiceNote to true when uploading an in-browser recorded audio so it’s transcoded to WhatsApp-native ogg/Opus.New multipart field:
•
voiceNote: true (WhatsApp-only)New Feature
Inbox messages now include emoji reactions on supported platforms (WhatsApp / Telegram) via
This lets you display who reacted and when, alongside the message timeline.
New per-message field:
•
Sending messages now supports WhatsApp location and contact cards.
New request fields for
•
•
reactions on each message.This lets you display who reacted and when, alongside the message timeline.
New per-message field:
•
reactions[]: { emoji, fromMe, reactedAt }Sending messages now supports WhatsApp location and contact cards.
New request fields for
POST /v1/inbox/conversations/{conversationId}/messages:•
location: { latitude, longitude, name?, address? }•
contacts[]: contact card objects (requires name.formatted_name; optional phones[], emails[])New Feature
POST /v1/inbox/conversations now supports starting conversations on X/Twitter, Bluesky, Reddit, and WhatsApp (previously X/Twitter only).You can initiate DMs across these platforms using
participantId or participantUsername (platform-dependent).WhatsApp requires an approved template to start a conversation: set
templateName, templateLanguage, and optional templateParams (freeform initial messages aren’t allowed). message is now optional when using a WhatsApp template.skipDmCheck remains X/Twitter only.Improvement
PostAnalytics now includes Instagram Reels watch-time metrics.You can retrieve average and total watch time for Reels (values are
0 for non-Reels media and other platforms).New fields:
•
igReelsAvgWatchTime (ms)•
igReelsVideoViewTotalTime (ms)New Feature
New webhook payload schema added for Meta Lead Gen / Instant Forms: lead.received events now use
This lets webhook consumers reliably parse incoming leads, including form metadata, ad attribution (when available), and a flattened field map.
Key properties:
•
•
•
•
•
•
WebhookPayloadLead.This lets webhook consumers reliably parse incoming leads, including form metadata, ad attribution (when available), and a flattened field map.
Key properties:
•
event: lead.received•
lead.fields: key → value map (multiple-choice values are option keys, e.g. k1)•
lead.isOrganic: boolean•
lead.createdAt: ISO 8601 datetime•
account.platform: facebook•
lead.formName, lead.adId, lead.adsetId, lead.campaignId: nullable when not availableNew Feature
Meta Lead Gen is now supported end-to-end: you can manage Instant Forms and retrieve submitted leads via new Ads endpoints.
New endpoints:
• List all leads (cross-form CRM view):
• Manage forms:
• Form leads + testing:
Ad creation update (Meta only):
New endpoints:
• List all leads (cross-form CRM view):
GET /v1/ads/leads with formId, accountId, since, limit, cursor• Manage forms:
GET /v1/ads/lead-forms, POST /v1/ads/lead-forms, GET /v1/ads/lead-forms/{formId}, DELETE /v1/ads/lead-forms/{formId}• Form leads + testing:
GET /v1/ads/lead-forms/{formId}/leads, POST /v1/ads/lead-forms/{formId}/test-leadsAd creation update (Meta only):
POST /v1/ads/create now accepts leadGenFormId. When goal is lead_generation, leadGenFormId is REQUIRED, and linkUrl is NOT required (the ad opens the form).New Feature
New endpoint POST /v1/ads/targeting/reach-estimate returns a normalized pre-flight audience reach estimate for a targeting spec before creating a campaign.
Send
Response includes
GET /v1/ads/interests is now deprecated. Use GET /v1/ads/targeting/search with
GET /v1/ads/targeting/search is now cross-platform and normalized. Use
POST /v1/ads/create targeting now supports additional fields:
Audiences now support reusable targeting presets: POST /v1/ads/audiences accepts
Send
accountId + spec (same shape as TargetingSpec used by ads create). Optionally pass optimizationGoal.Response includes
available, and when available: lower, upper, optional daily, currency, and Meta-only estimateReady.GET /v1/ads/interests is now deprecated. Use GET /v1/ads/targeting/search with
dimension=interest (the deprecated alias still returns the legacy { interests: [...] } shape).GET /v1/ads/targeting/search is now cross-platform and normalized. Use
dimension = geo | interest | behavior | income, and for geo also geoType = country | region | city | zip | metro. Results return { results: [{ id, name, type, path?, audienceSize? }] }.POST /v1/ads/create targeting now supports additional fields:
zips, metros, customLocations, behaviors, incomeTier (top5 | top10 | top1025 | top2550), languages, savedTargetingId, specialAdCategories (HOUSING | EMPLOYMENT | CREDIT | ISSUESELECTIONSPOLITICS).Audiences now support reusable targeting presets: POST /v1/ads/audiences accepts
type=savedtargeting with spec (no adAccountId), and GET /v1/ads/audiences adds type filter and returns spec when type is savedtargeting.Improvement
GET
This lets you render a post preview in the inbox/comments view without an additional Reddit fetch.
Key fields in
/v1/inbox/comments/{postId} now returns (Reddit only) post metadata alongside comments in a new post object.This lets you render a post preview in the inbox/comments view without an additional Reddit fetch.
post is null/absent for non-Reddit platforms and may be missing if the upstream post listing isn’t available.Key fields in
post: id, fullname, title, selftext, author, subreddit, permalink, url, score, numComments, createdUtc, over18, stickied, flairText, isGallery.New Feature
Google Business Profile verification is now supported via new GMB Verifications endpoints.
You can fetch verification status/history, discover eligible methods, start a verification (SMS/call/email/postcard), and complete it by submitting the PIN.
New endpoints:
•
•
•
•
Note: service-area businesses may require
You can fetch verification status/history, discover eligible methods, start a verification (SMS/call/email/postcard), and complete it by submitting the PIN.
New endpoints:
•
GET /v1/accounts/{accountId}/gmb-verifications (optional locationId) returns voiceOfMerchantState.hasVoiceOfMerchant and verifications[].state = PENDING/COMPLETED/FAILED•
POST /v1/accounts/{accountId}/gmb-verifications/options requires languageCode; returns options[].verificationMethod = ADDRESS/EMAIL/PHONECALL/SMS/AUTO/VETTEDPARTNER•
POST /v1/accounts/{accountId}/gmb-verifications starts verification with method (+ method-specific fields like phoneNumber/emailAddress)•
POST /v1/accounts/{accountId}/gmb-verifications/{verificationId}/complete completes with pinNote: service-area businesses may require
context (service address) when fetching options/starting verification.New Feature
Webhooks now support message reaction events via the new
Subscribe by including
The
reaction.received subscription.Subscribe by including
reaction.received in events when calling POST /v1/webhooks/settings or PUT /v1/webhooks/settings.The
reaction payload includes emoji, action (added | removed), platformMessageId, optional messageId, sender, and reactedAt.New Feature
GET /v1/ads/audiences now supports fetching custom audiences for LinkedIn and X (Twitter) ad accounts.Use the
platform query param with one of:•
facebook, instagram, googleads, tiktok, tiktokads, pinterest, linkedin, linkedinads, twitter, xadsImprovement
GET
This provides the Meta WhatsApp error code to help you classify and handle delivery failures (only populated when
New field:
/v1/broadcasts/{broadcastId}/recipients now returns errorCode per recipient.This provides the Meta WhatsApp error code to help you classify and handle delivery failures (only populated when
status=failed).New field:
errorCode (integer, nullable) — e.g. 131049 (antispam), 131021 (invalid phone), 131026 (re-engagement required).New Feature
Instagram now supports Stories retrieval + insights.
You can list currently-active stories and fetch metrics for a specific story, including a clear indicator of whether insights are live, cached, or unavailable.
New endpoints:
•
Returns active stories (24h window). Key fields:
•
Returns
You can list currently-active stories and fetch metrics for a specific story, including a clear indicator of whether insights are live, cached, or unavailable.
New endpoints:
•
GET /v1/accounts/{accountId}/instagram/storiesReturns active stories (24h window). Key fields:
id, mediaType, mediaUrl (nullable), permalink, thumbnailUrl, timestamp•
GET /v1/accounts/{accountId}/instagram/stories/{storyId}/insightsReturns
data.source = live | cached | unavailable and data.metrics including views, reach, replies, shares, navigation, tapsForward, tapsBack, exits, swipesForward, profileVisits, follows, reposts, totalInteractions.New Feature
WhatsApp is now supported as a
You can now use
•
•
platform for connecting accounts and checking account health.You can now use
whatsapp with:•
GET /v1/connect/{platform} (path param platform enum now includes whatsapp)•
GET /v1/accounts/health (query param platform enum now includes whatsapp)Improvement
GET
This lets you control the order of returned campaigns (by recency or aggregated spend) while keeping pagination at the campaign level.
Use
•
•
•
•
/v1/ads/tree now supports campaign-level sorting via a new sort query parameter.This lets you control the order of returned campaigns (by recency or aggregated spend) while keeping pagination at the campaign level.
Use
sort:•
newest (default)•
oldest•
spend_desc•
spend_ascNew Feature
Facebook now supports multi-link carousel posts via
This lets you publish a 2–5 card carousel where each image has its own click-through link (and optional headline/description).
Set
•
•
•
Optionally set
Constraints:
facebookSettings on POST /v1/posts.This lets you publish a 2–5 card carousel where each image has its own click-through link (and optional headline/description).
Set
facebookSettings.carouselCards (2–5 items) with:•
link (required)•
name (optional)•
description (optional)Optionally set
facebookSettings.carouselLink for the carousel end-card destination.Constraints:
mediaItems must be images only, and must match carouselCards length/order. Mutually exclusive with facebookSettings.contentType = story or reel.Improvement
POST
If you send the same
Use
Duplicate protection still applies separately: identical content to the same
/v1/posts now supports request idempotency via x-request-id.If you send the same
x-request-id again within ~5 minutes (including while the first request is in-flight), the API treats it as a retry and returns HTTP 200 with the original post in existingPost (no duplicate post is created).Use
x-request-id (UUID) per logical create call. If you omit it, each request is treated as new.Duplicate protection still applies separately: identical content to the same
(platform, accountId) within 24h returns HTTP 409 with details.accountId, details.platform, details.existingPostId.Improvement
{adId} in GET /v1/ads/{adId} and GET /v1/ads/{adId}/comments now accepts multiple identifier types.You can call these endpoints using the Zernio internal
id (24-char hex), Meta numeric platformAdId (from comment.received as comment.ad.id), or the creative’s effectiveobjectstoryid / effectiveinstagrammedia_id. Any of these resolves to the same ad, so you don’t need a translation step.New Feature
Webhooks now support per-platform terminal post events:
These fire once per platform target inside a post when that specific platform reaches a terminal state (published or permanent failure), which helps track multi-platform posts more precisely.
Subscribe via
•
•
A new webhook event is also available:
•
post.platform.published and post.platform.failed.These fire once per platform target inside a post when that specific platform reaches a terminal state (published or permanent failure), which helps track multi-platform posts more precisely.
Subscribe via
events on POST /v1/webhooks/settings or PUT /v1/webhooks/settings:•
post.platform.published•
post.platform.failedA new webhook event is also available:
•
whatsapp.template.status_updatedNew Feature
GET /v1/ads/campaigns now supports filtering aggregated campaign metrics by a date range.Use
fromDate and toDate (YYYY-MM-DD, inclusive) to control the metrics window returned for each campaign.New query params:
•
fromDate - start date (defaults to 90 days ago when both date params are omitted)•
toDate - end date (defaults to today; max 730-day range)New Feature
Webhooks now support WhatsApp template status updates via the new event
This fires when Meta completes a (re)review of a template on a connected WABA, so you can react to approvals/rejections and other status transitions.
Subscribe by adding
Payload:
whatsapp.template.status_updated.This fires when Meta completes a (re)review of a template on a connected WABA, so you can react to approvals/rejections and other status transitions.
Subscribe by adding
whatsapp.template.status_updated to Webhook.events.Payload:
WebhookPayloadWhatsAppTemplateStatusUpdated with template.status ∈ APPROVED, REJECTED, PENDING, PAUSED, DISABLED, INAPPEAL, PENDINGDELETION and template.reason (e.g. "NONE" on approval).New Feature
Google Business reviews now support owner replies via new endpoints:
Use
Reply body:
New optional bidding controls on CTWA:
CTWA create response is now a tagged union via
POST /v1/accounts/{accountId}/gmb-reviews/{reviewId}/reply and DELETE /v1/accounts/{accountId}/gmb-reviews/{reviewId}/reply.Use
comment to post/update a reply (calling POST again overwrites the previous reply). Deleting removes only the reply, not the review.Reply body:
{ "comment": "..." }POST /v1/ads/ctwa now supports creating multiple CTWA ads under one campaign/ad set by sending a non-empty creatives[] array (each creative has its own headline, body, and exactly one of imageUrl/video). Single-creative requests still work.New optional bidding controls on CTWA:
bidStrategy (LOWESTCOSTWITHOUTCAP, LOWESTCOSTWITHBIDCAP, COSTCAP, LOWESTCOSTWITHMINROAS) plus bidAmount (required for bid-cap/cost-cap) and roasAverageFloor (required for min-ROAS).CTWA create response is now a tagged union via
adType: single or multi.New Feature
Webhooks now support
Subscribe to get notified when a Meta ads object (campaign/ad set/ad) changes status or enters
Add
Payload highlights:
•
•
•
•
Also,
ad.status_changed.Subscribe to get notified when a Meta ads object (campaign/ad set/ad) changes status or enters
WITH_ISSUES.Add
ad.status_changed to events when creating/updating a webhook via POST /v1/webhooks/settings or PUT /v1/webhooks/settings.Payload highlights:
•
event: ad.status_changed•
adObject.level: CAMPAIGN | AD_SET | AD•
status.raw: platform-native status (e.g. ACTIVE, PAUSED, WITH_ISSUES)•
error: optional diagnostics on most WITH_ISSUES eventsAlso,
GET /v1/ads/{adId}/analytics now returns ad.currency (ISO 4217) so you can interpret money values in summary and daily.Improvement
GET /v1/ads/timeline now supports filtering by a specific platform ad account via adAccountId.Use this when a single Zernio connection wraps multiple platform ad accounts and you want the timeline chart for just one (e.g. Meta
act_…, TikTok advertiser ID).New query param:
•
adAccountId (optional)Note: rows ingested before
2026-05-13 may not include this dimension yet; the recurring 7-day re-sync will repopulate them over time.New Feature
New endpoint
Key params:
•
•
•
Response:
GET /v1/ads/timeline returns a daily time series of aggregate ad metrics for a social account (one row per calendar day), useful for spend/conversions charts without calling /v1/ads/tree per day.Key params:
•
accountId (required)•
fromDate, toDate (YYYY-MM-DD; defaults to last 90 days; max 730 days)•
platform (optional): facebook, instagram, tiktok, linkedin, pinterest, google, twitterResponse:
rows[] with per-day metrics like spend, impressions, clicks, ctr, conversions, actions, purchaseValue, roas. Returns empty rows if no activity. 403 if Ads add-on is required.New Feature
New endpoint:
Send
Request body:
•
Auth/scope note: for profile-restricted API keys, both the account’s current profile and the target
Responses:
PATCH /v1/accounts/{accountId} to move a connected account to a different profile owned by the same user.Send
profileId in the JSON body to set the target profile.Request body:
•
profileId (required)Auth/scope note: for profile-restricted API keys, both the account’s current profile and the target
profileId must be allowed, otherwise you’ll get 403.Responses:
200 returns message and profileId; errors include 400, 401, 403, 404.Improvement
TikTok conversion ads via
For TikTok,
Set:
•
•
•
POST /v1/ads/create now support/require TikTok pixel optimization settings through promotedObject.For TikTok,
goal=conversions uses promotedObject to configure the ad group’s pixel + optimization event (or inherit them by using adSetId).Set:
•
goal: conversions•
promotedObject.pixelId (required for TikTok conversions)•
promotedObject.customEventType (optional TikTok optimizationevent code, e.g. ONWEBORDER, INITIATEORDER, ONWEBCART, ONWEBREGISTER, FORM, ONWEBDETAIL)New Feature
POST
This creates a LinkedIn Single Image Ad backed by a Direct Sponsored Content ("dark post") authored by a Company Page, with supported goals
Key request fields for LinkedIn:
•
•
•
•
•
•
Also updated:
•
• Meta-only: new
/v1/ads/create now supports creating LinkedIn standalone ads.This creates a LinkedIn Single Image Ad backed by a Direct Sponsored Content ("dark post") authored by a Company Page, with supported goals
engagement, traffic, awareness (and traffic requires linkUrl).Key request fields for LinkedIn:
•
organizationId (Company Page org ID or urn:li:organization:N; required unless it can be inferred)•
headline (now required for LinkedIn; max 400)•
body (used as LinkedIn post commentary)•
imageUrl (required for LinkedIn)•
linkUrl (required for goal=traffic)•
longHeadline (optional secondary description on LinkedIn traffic ads)Also updated:
•
callToAction enum expanded with REGISTER, JOIN, ATTEND, REQUESTDEMO, VIEWQUOTE, APPLY, SEEMORE, BUYNOW• Meta-only: new
attributionSpec to control conversion attribution windows (CLICKTHROUGH/VIEWTHROUGH/ENGAGEDVIDEOVIEW with windowDays 1/7/28).New Feature
Meta ads (boosted/dark posts) are now included in
Ad rows are flagged so you can route comment fetching to the ads comments thread.
Use
•
•
• omit
New fields on inbox rows:
GET /v1/inbox/comments for users with the Ads add-on (Metronome plans always qualify).Ad rows are flagged so you can route comment fetching to the ads comments thread.
Use
platform:•
metaads - ads only•
facebook/instagram - organic only• omit
platform - bothNew fields on inbox rows:
isAd (boolean), adId (string). For ad rows, fetch the thread via GET /v1/ads/{adId}/comments.GET /v1/inbox/comments/{postId} now returns meta.adComments when a boosted post has no organic comments (includes adId and adCommentsUrl), and may return 400 with code USEADCOMMENTS_ENDPOINT when postId is an ad creative/ad ID.GET /v1/ads adds filters to map Business Manager IDs back to Zernio ads: platformAdId, effectiveObjectStoryId, effectiveInstagramMediaId.Improvement
GET
This makes it easier to identify the boosted IG post and which connected Instagram account was used to read comments (useful for follow-up reply/hide actions via
New
•
•
•
Also clarified: for Instagram-placed ads, if no connected Instagram account on the profile can read the ad’s media, the endpoint returns
/v1/ads/{adId}/comments now returns additional Instagram metadata in meta for Instagram-placed ads.This makes it easier to identify the boosted IG post and which connected Instagram account was used to read comments (useful for follow-up reply/hide actions via
/v1/inbox/comments).New
meta fields (Instagram-only):•
instagramUserId•
instagramPermalink•
instagramAccountIdAlso clarified: for Instagram-placed ads, if no connected Instagram account on the profile can read the ad’s media, the endpoint returns
422 with code adsconnectionrequired.New Feature
Meta Ads now supports managing Tracking Tags (Meta Pixels) via new endpoints.
You can list/create/get/update pixels, share/unshare them across ad accounts, and fetch aggregated event stats.
New endpoints:
•
•
•
•
•
•
Notes: Meta-only (
You can list/create/get/update pixels, share/unshare them across ad accounts, and fetch aggregated event stats.
New endpoints:
•
GET /v1/accounts/{accountId}/tracking-tags (optional adAccountId=act_...)•
POST /v1/accounts/{accountId}/tracking-tags body: adAccountId, name (not idempotent)•
GET /v1/accounts/{accountId}/tracking-tags/{tagId} (includes install code)•
PATCH /v1/accounts/{accountId}/tracking-tags/{tagId} fields: name, enableAutomaticMatching, automaticMatchingFields (em/ph/fn/ln/ge/db/ct/st/zp/country/externalid), firstPartyCookieStatus (empty/firstpartycookiedisabled/firstpartycookieenabled), dataUseSetting (advertisingandanalytics/analyticsonly/empty)•
GET|POST|DELETE /v1/accounts/{accountId}/tracking-tags/{tagId}/shared-accounts (DELETE supports adAccountId query)•
GET /v1/accounts/{accountId}/tracking-tags/{tagId}/stats (optional aggregation, startTime, endTime; aggregation enum includes event, url, devicetype, browsertype, etc.)Notes: Meta-only (
platform=metaads; other platforms return 405). Requires the Meta ads SocialAccount from the Ads add-on connect flow; list view omits code (use get to retrieve it).New Feature
Comment-to-DM automations now support inline DM buttons via
You can attach up to 3 buttons to the auto-DM (URL, postback, or phone on Facebook) to drive clicks or capture postbacks.
Use
•
•
Button schema:
Note: when
buttons.You can attach up to 3 buttons to the auto-DM (URL, postback, or phone on Facebook) to drive clicks or capture postbacks.
Use
buttons in:•
POST /v1/comment-automations•
PATCH /v1/comment-automations/{automationId} (pass buttons: [] to clear)Button schema:
{ type, title, url|payload|phone } where type is url | postback | phone (phone = Facebook-only), and title is max 20 chars.Note: when
buttons is non-empty, dmMessage must be ≤ 640 characters. Responses from list/get/create/update may now include buttons (omitted when none are set).Deprecation
X API pricing/usage reporting now includes a new $0.200 “posts with URL” operation and tier.
This lets you attribute X spend more accurately per action; new clients should rely on per-operation counts rather than tier aggregates.
Key changes:
• New operation in
•
• In
This lets you attribute X spend more accurately per action; new clients should rely on per-operation counts rather than tier aggregates.
Key changes:
• New operation in
GET /v1/billing/x-pricing: operation=contentcreatewithurl with tier=xapi_200 and pricePerCallUsd=0.2•
contentcreate price moved to pricePerCallUsd=0.015 (now tier=xapi_015)• In
GET /v1/usage-stats, usage.xApiCalls is now deprecated and excludes xapi200 (and future tiers); use usage.xApiCallsByOperation as the source of truthImprovement
WhatsApp is now supported on
On WhatsApp, the typing indicator shows typing... for up to 25 seconds. It requires a recent inbound message in the conversation (Meta references the inbound message id) and will also mark that inbound message as read as a side-effect.
Use:
POST /v1/inbox/conversations/{conversationId}/typing.On WhatsApp, the typing indicator shows typing... for up to 25 seconds. It requires a recent inbound message in the conversation (Meta references the inbound message id) and will also mark that inbound message as read as a side-effect.
Use:
{ "accountId": "..." }New Feature
Message webhooks now include expanded Meta ad-click attribution in
This lets you attribute the first inbound message to a specific Facebook/Instagram ad click for CTM/CTD flows.
New fields in
•
•
•
•
•
metadata.referral (in addition to WhatsApp CTWA).This lets you attribute the first inbound message to a specific Facebook/Instagram ad click for CTM/CTD flows.
New fields in
metadata.referral:•
ad_id•
ref•
source•
type•
adscontextdata (object: adtitle, photourl, videourl, postid, productid, flowid)metadata.referral is now nullable and is only populated on the first inbound message after the click.New Feature
LinkedIn Ads now plugs into Zernio's unified Conversions API. Stream offline conversion events to LinkedIn's
New capabilities for
•
•
•
•
•
•
Supports the LinkedIn first-party click ID
Reconnect required for LinkedIn accounts connected before this change — LinkedIn does not silently upgrade existing OAuth grants with the new
/rest/conversionEvents through the same /v1/ads/conversions endpoint you already use for Meta and Google.New capabilities for
linkedinads accounts:•
POST /v1/ads/conversions — stream events (BATCH_CREATE, up to 5,000 per call)•
GET /v1/accounts/{accountId}/conversion-destinations — list rules across every sponsored ad account•
POST /v1/accounts/{accountId}/conversion-destinations — create a new rule (with autoAssociationType=ALL_CAMPAIGNS by default)•
GET|PATCH|DELETE /v1/accounts/{accountId}/conversion-destinations/{destinationId} — single-rule CRUD (DELETE soft-deletes via enabled: false)•
GET|POST|DELETE .../associations — campaign↔conversion mapping•
GET .../metrics?startDate=&endDate=&granularity= — externalWebsiteConversions, conversionValueInLocalCurrency, qualifiedLeads, etc., bucketed by dateSupports the LinkedIn first-party click ID
lifatid (added to user.clickIds) for higher match rates. Standard event names map to LinkedIn rule types (Lead → LEAD, Purchase → PURCHASE, ...); LinkedIn enum values pass through unchanged.Reconnect required for LinkedIn accounts connected before this change — LinkedIn does not silently upgrade existing OAuth grants with the new
rwconversions scope. Affected accounts return 403 linkedinreconnect_required until the user re-authorises.New Feature
Webhooks now support additional message events you can subscribe to when creating/updating webhook settings.
This lets you receive callbacks for outbound message lifecycle changes (send/edit/delete/delivery/read/failure), not just inbound messages.
Use
•
Also added to
This lets you receive callbacks for outbound message lifecycle changes (send/edit/delete/delivery/read/failure), not just inbound messages.
Use
events in POST /v1/webhooks/settings or PUT /v1/webhooks/settings with any of:•
message.sent, message.edited, message.deleted, message.delivered, message.read, message.failedAlso added to
WebhookPayloadMessage.metadata (Instagram only): storyReply and isStoryMention.Improvement
Ads analytics endpoints now support up to a 730-day metrics date range (previously 90 days).
You can query longer historical performance, and requests for ranges older than 90 days may trigger a one-time on-demand fetch from the ad platform, then be served from cache on subsequent requests.
Applies to:
•
•
•
Use
You can query longer historical performance, and requests for ranges older than 90 days may trigger a one-time on-demand fetch from the ad platform, then be served from cache on subsequent requests.
Applies to:
•
GET /v1/ads (max range 730 days)•
GET /v1/ads/tree (max range 730 days)•
GET /v1/ads/{adId}/analytics (max range 730 days)Use
fromDate / toDate; ranges older than 90 days may require scoping to accountId (where noted) to trigger the on-demand fetch.New Feature
POST
You can attach 1–3 buttons rendered in the same message bubble (Meta
New request field:
Button types:
•
•
•
/v1/inbox/comments/{postId}/{commentId}/private-reply now supports inline buttons on the first private reply.You can attach 1–3 buttons rendered in the same message bubble (Meta
button_template). This is recommended for cold reach because quickReplies don’t render in Instagram Message Requests.New request field:
buttons (min 1, max 3), mutually exclusive with quickReplies.Button types:
•
url: { type, title, url }•
postback: { type, title, payload }•
phone (Facebook only): { type, title, phone }New Feature
WhatsApp headless OAuth now supports multi-phone WABA selection via a new two-step flow.
When the OAuth redirect includes
Then bind the chosen number with
•
•
•
•
Note:
When the OAuth redirect includes
step=selectphonenumber, first list available numbers with GET /v1/connect/whatsapp/select-phone-number using profileId + tempToken (optional X-Connect-Token).Then bind the chosen number with
POST /v1/connect/whatsapp/select-phone-number:•
profileId•
tempToken•
wabaId•
phoneNumberIdNote:
GET /v1/connect/pending-data is now documented as only for LinkedIn organizations and Snapchat profiles; WhatsApp selection state is passed via redirect params and pending-data may return 404 for WhatsApp flows.New Feature
New endpoint
For
•
•
•
•
•
•
Also:
GET /v1/billing/x-pricing returns Zernio’s canonical X/Twitter API pricing table (per-operation rates, 0% markup). Use it with GET /v1/usage-stats to compute exact X spend from per-operation call counts.GET /v1/usage-stats now varies by billingSystem: stripe | metronome.For
metronome accounts it includes:•
usage.connectedAccounts•
usage.xApiCalls (tier buckets: xapi005 | xapi010 | xapi015)•
usage.xApiCallsByOperation (keys resolve via GET /v1/billing/x-pricing)•
spend.currentPeriodCents, spend.xSpendCents, spend.xSpendLimitCentsPUT /v1/accounts/{accountId} (X only, usage-based) accepts xCapabilities to opt into background metered operations:•
xCapabilities.analytics•
xCapabilities.inboxAlso:
POST /v1/profiles and GET /v1/connect/{platform} may now return 402 PaymentRequired.New Feature
POST
You can include up to 13 chips to let users pick an intent in-app (Instagram/Messenger apps only).
New optional field:
•
•
•
/v1/inbox/comments/{postId}/{commentId}/private-reply now supports optional quick reply chips on the first private reply.You can include up to 13 chips to let users pick an intent in-app (Instagram/Messenger apps only).
New optional field:
quickReplies (max 13)•
quickReplies[].title (max 20 chars)•
quickReplies[].payload•
quickReplies[].imageUrl (optional)Improvement
Account groups can now include accounts across multiple profiles. For API keys scoped to specific profiles,
Meta ads connect can now be scoped to specific ad accounts via
Webhook
•
•
GET /v1/account-groups only returns groups whose accountIds are all in allowed profiles.GET /v1/account-groups may now include createdBy and legacy profileId (only on older groups).POST /v1/account-groups accepts deprecated profileId (ignored) for backward compatibility.Meta ads connect can now be scoped to specific ad accounts via
GET /v1/connect/{platform}/ads (metaads only): adAccountId (format act_<digits>) or adAccountIds (repeat or comma-separated). Response may include scopedAdAccountIds.Webhook
account.ads.initialsynccompleted now includes richer correlation + error details:•
account.platformAdAccountId, account.platformAdAccountIds•
sync.error, sync.errorCode, sync.errorSubcode, sync.errorCategory = tokeninvalid | permissiondenied | noadaccounts | ratelimited | discoveryfailed | unknownBreaking Change
POST /v1/account-groups no longer requires profileId in the request body.You can now create an account group using only a name and the list of social account IDs.
Request body now requires:
•
name•
accountIdsNew Feature
POST /v1/comment-automations now supports account-wide (any post) comment-to-DM automations.You can omit
platformPostId (and postId) to evaluate comments across every post on an account. Multiple account-wide automations can be stacked and run independently; per-post automations take priority on their post.Key request rules:
• Per-post: set
platformPostId and include postId• Account-wide: omit
platformPostId (and postId)•
matchMode: exact | containsNote:
409 now applies only to per-post automations (duplicate active platformPostId).New Feature
New Meta geo targeting lookup endpoint:
It lets you resolve a human-readable location name into Meta’s opaque location
Call with
•
•
GET /v1/ads/targeting/search.It lets you resolve a human-readable location name into Meta’s opaque location
key for use in ad targeting.Call with
accountId, q, optional type (country/region/city/subcity/neighborhood/zip/metroarea/geomarket, default city), optional countryCode, and optional limit.POST /v1/ads/create now supports Meta-only geo targeting via cities[] and regions[]:•
cities[]: { key, radius?, distanceunit? } where distanceunit is mile or kilometer•
regions[]: { key }countries[] is now documented as ISO-2 codes and defaults to ["US"] when no cities/regions are provided.Improvement
TikTok ads created via
This lets you set a CTA on TikTok using the same field you already use for Meta.
Set
•
POST /v1/ads/create now honor callToAction (it’s passed through to the Spark Ad creative’s calltoaction).This lets you set a CTA on TikTok using the same field you already use for Meta.
Set
callToAction to one of:•
LEARNMORE, SHOPNOW, SIGNUP, BOOKTRAVEL, CONTACTUS, DOWNLOAD, GETOFFER, GETQUOTE, SUBSCRIBE, WATCHMOREBreaking Change
TikTok boost ads now surface clearer requirements and failure modes in
For TikTok boosts, include
The
Also note:
POST /v1/ads/boost.For TikTok boosts, include
targeting.countries (ISO country codes). TikTok requires locations on the ad group; other platforms can omit it.The
422 response can now also indicate the connected TikTok user isn’t authorized as an Identity on the target advertiser (returned with code adsconnectionrequired and an actionable remediation message).Also note:
BusinessCenter.advertiserCount is now nullable (null when the BC asset walk is empty/failed, distinct from 0).Improvement
Analytics endpoints now support filtering by social account via
This lets you scope results to a specific connected account (useful when a
Added optional query param
•
•
•
accountId.This lets you scope results to a specific connected account (useful when a
profileId contains multiple accounts).Added optional query param
accountId on:•
GET /v1/analytics/best-time•
GET /v1/analytics/content-decay•
GET /v1/analytics/posting-frequencyNew Feature
New Ads endpoints and expanded TikTok support.
You can now list TikTok Business Centers.
• List TikTok BCs:
TikTok campaign duplication is now supported via
Ad updates now support more platforms/fields:
•
Ad account listing now supports filtering/limiting:
•
TikTok boost now supports cross-creator Spark Ads:
•
Standalone ad creation “attach” mode now supports TikTok:
•
New response fields:
•
•
You can now list TikTok Business Centers.
• List TikTok BCs:
GET /v1/ads/business-centers with accountId → returns businessCenters[] (bcId, name, advertiserCount)TikTok campaign duplication is now supported via
POST /v1/ads/campaigns/{campaignId}/duplicate by setting platform to tiktok (Meta still supported).Ad updates now support more platforms/fields:
•
PUT /v1/ads/{adId} supports targeting + creative on facebook/instagram/tiktok. Other platforms return 501 (unsupportedplatformoperation) when sending targeting or creative.Ad account listing now supports filtering/limiting:
•
GET /v1/ads/accounts adds optional adAccountId and limitTikTok boost now supports cross-creator Spark Ads:
•
POST /v1/ads/boost adds sparkAuthCodeStandalone ad creation “attach” mode now supports TikTok:
•
POST /v1/ads/create adSetId is now supported for tiktok (ad group ID)New response fields:
•
Ad adds platformAdAccountName and platformCreatedAt•
AdCampaign and AdTreeCampaign add platformAdAccountNameNew Feature
Ads API now supports setting bid strategy on campaigns, ad sets, and boosted posts.
You can update campaign-level defaults via
Set
•
•
•
•
Boosting posts via
Ad read models now include
GBP location details (
You can update campaign-level defaults via
PUT /v1/ads/campaigns/{campaignId} using bidStrategy (optionally alongside budget). Ad sets can now be updated via PUT /v1/ads/ad-sets/{adSetId} with bidStrategy plus bidAmount or roasAverageFloor when required.Set
bidStrategy to:•
LOWESTCOSTWITHOUT_CAP•
LOWESTCOSTWITHBIDCAP (requires bidAmount)•
COST_CAP (requires bidAmount)•
LOWESTCOSTWITHMINROAS (requires roasAverageFloor)Boosting posts via
POST /v1/ads/boost now accepts bidStrategy, bidAmount, and roasAverageFloor. TikTok boosts also add linkUrl and callToAction.Ad read models now include
bidStrategy (normalized for Meta + TikTok) and may include bidAmount / roasAverageFloor on Ad, AdCampaign, and AdTree* responses.GBP location details (
GET /v1/accounts/{accountId}/gmb-location-details) now always includes title + metadata and always populates the derived location summary; passing location in readMask returns 400.New Feature
GET
This provides a compact, public-facing view derived from GBP
New fields under
•
•
•
•
•
Populated when
/v1/accounts/{accountId}/gmb-location-details now returns a new location summary object in the 200 response.This provides a compact, public-facing view derived from GBP
metadata, making it easy to surface “leave a review” and Maps URLs without parsing the raw metadata block.New fields under
location:•
name•
placeId•
reviewUrl•
mapsUri•
isVerifiedPopulated when
readMask includes metadata (default). For unverified/new locations, placeId/reviewUrl/mapsUri may be null.Improvement
GET
Use these fields to align daily budget reset timing and Insights day boundaries with the ad account’s local timezone.
New fields in each item of
•
•
/v1/ads/accounts now returns Meta ad account timezone details: timezoneName and timezoneOffsetHoursUtc.Use these fields to align daily budget reset timing and Insights day boundaries with the ad account’s local timezone.
New fields in each item of
accounts[]:•
timezoneName (IANA timezone, Meta only)•
timezoneOffsetHoursUtc (signed UTC offset in hours, reflects current DST, Meta only)Improvement
Meta ads now support DSA (EU) advertiser disclosures via two new optional fields on ad creation endpoints.
When your targeting intersects EU member states, Meta may require these values (enforced server-side) to comply with DSA Article 26.
Set:
•
•
Available on:
•
•
•
When your targeting intersects EU member states, Meta may require these values (enforced server-side) to comply with DSA Article 26.
Set:
•
dsaBeneficiary — legal entity benefiting from the ad•
dsaPayor — legal entity paying for the ad (Meta spelling: dsa_payor)Available on:
•
POST /v1/ads/boost•
POST /v1/ads/create•
POST /v1/ads/ctwaImprovement
GET
You can page through long conversations using an opaque cursor, and request oldest-first or newest-first results (with platform-specific limitations).
New query params:
•
•
•
New response fields:
•
•
•
Also:
/v1/inbox/conversations/{conversationId}/messages now supports cursor-based pagination and sort order control.You can page through long conversations using an opaque cursor, and request oldest-first or newest-first results (with platform-specific limitations).
New query params:
•
limit (1–100, default 100)•
cursor (pass prior pagination.nextCursor)•
sortOrder: asc | desc (default asc)New response fields:
•
pagination.hasMore•
pagination.nextCursor•
sortOrderApplied: asc | descAlso:
WebhookPayloadMessage.metadata adds referral (WhatsApp only) for Click-to-WhatsApp ad attribution on the first inbound message after a CTWA ad click (e.g. ctwaclid, sourceurl, imageurl/videourl).New Feature
POST
Use this to restrict the audience by gender when creating Meta (facebook/instagram) ads; non-Meta platforms ignore it.
Set
/v1/ads/create now supports Meta-only gender targeting via gender.Use this to restrict the audience by gender when creating Meta (facebook/instagram) ads; non-Meta platforms ignore it.
Set
gender to: • all (default) • male • femaleNew Feature
New endpoints added for Click-to-WhatsApp ads and WhatsApp conversion attribution.
Create CTWA ads on Meta in one call via
Key fields:
Send WhatsApp conversation conversion events to Meta CAPI (business messaging) via
Key fields:
Create CTWA ads on Meta in one call via
POST /v1/ads/ctwa (creates campaign → ad set → creative → ad).Key fields:
accountId, adAccountId, name, headline, body, budgetAmount, budgetType (daily | lifetime), and exactly one of imageUrl or video (url, thumbnailUrl). Optional: objective (OUTCOMEENGAGEMENT | OUTCOMESALES | OUTCOME_LEADS).Send WhatsApp conversation conversion events to Meta CAPI (business messaging) via
POST /v1/whatsapp/conversions.Key fields:
accountId, eventName (LeadSubmitted | Purchase | AddToCart | InitiateCheckout | ViewContent), eventId, optional eventTime, and at least one of conversationId or phoneE164. Note: returns 422 if no captured ctwa_clid (no attribution possible); on 200 check eventsFailed and failures[] for Meta rejections.Improvement
GET
Use it to return only posts published via a specific connected account (useful when multiple accounts exist per platform/profile).
New parameter:
•
/v1/posts now supports filtering by social account via the optional query param accountId.Use it to return only posts published via a specific connected account (useful when multiple accounts exist per platform/profile).
New parameter:
•
accountId (string, 24-char hex ObjectId) — filter posts to those published via a specific social accountNew Feature
Webhooks now support the new event
This fires once per ads-enabled account when the initial discovery + 90-day ads backfill finishes (including partial success or failure), so you can trigger downstream processing when the initial ads dataset is ready.
Subscribe via
•
•
Event:
Payload highlights:
account.ads.initialsynccompleted.This fires once per ads-enabled account when the initial discovery + 90-day ads backfill finishes (including partial success or failure), so you can trigger downstream processing when the initial ads dataset is ready.
Subscribe via
events on:•
POST /v1/webhooks/settings•
PUT /v1/webhooks/settingsEvent:
account.ads.initialsynccompletedPayload highlights:
account.accountId, account.profileId, account.platform, sync.status (success | failure), sync.totalAds, sync.synced, sync.failed, timestamp.Improvement
Comment automation logs now include separate results for the DM and the optional public comment reply in
You can now track whether the DM was sent and whether the public reply was attempted/sent, with separate error messages.
New log fields:
•
•
•
•
GET /v1/comment-automations/{automationId} and GET /v1/comment-automations/{automationId}/logs.You can now track whether the DM was sent and whether the public reply was attempted/sent, with separate error messages.
New log fields:
•
status (sent | failed | skipped) — DM outcome•
error — DM error if status is failed•
commentReplyStatus (sent | failed | skipped) — public reply outcome•
commentReplyError — public-reply error if commentReplyStatus is failedNew Feature
New analytics endpoints are available for YouTube, LinkedIn org pages, TikTok, Facebook Pages, and Instagram follower history. All new endpoints reuse the same response envelope as
YouTube channel totals (no per-video looping):
Key params:
Notes: requires
LinkedIn organization page aggregate analytics:
Key params:
Notes: requires scopes
TikTok account-level insights:
Key params:
Notes: requires
Facebook Page insights (post-Nov-2025 Meta metric names):
Key params:
Notes: deprecated Meta metrics (
Instagram follower history (daily follower count time series):
Key params:
Also added for LinkedIn personal analytics:
•
•
/v1/analytics/instagram/account-insights for consistent client handling.YouTube channel totals (no per-video looping):
GET /v1/analytics/youtube/channel-insightsKey params:
accountId, metrics, since, until, metricType (totalvalue|timeseries)Notes: requires
yt-analytics.readonly (412 if missing) + Analytics add-on; data is delayed 2–3 days (range clamped).LinkedIn organization page aggregate analytics:
GET /v1/analytics/linkedin/org-aggregate-analyticsKey params:
accountId, metrics, since, until, metricType (totalvalue|timeseries)Notes: requires scopes
rorganizationsocial + rorganizationfollowers + rorganizationadmin (412 if missing) + Analytics add-on; page-view metrics are total_value only.TikTok account-level insights:
GET /v1/analytics/tiktok/account-insightsKey params:
accountId, metrics, since, until, metricType (totalvalue|timeseries)Notes: requires
user.info.stats (412 if missing) + Analytics add-on.Facebook Page insights (post-Nov-2025 Meta metric names):
GET /v1/analytics/facebook/page-insightsKey params:
accountId, metrics, since, until, metricType (totalvalue|timeseries)Notes: deprecated Meta metrics (
pageimpressions, pagefans, pagefanadds, pagefanremoves) are rejected; use pagemediaview, pagefollows, etc. followersgained/followers_lost are synthesized.Instagram follower history (daily follower count time series):
GET /v1/analytics/instagram/follower-historyKey params:
accountId, metrics (default followercount,followersgained,followerslost), since, until, metricType (totalvalue|time_series)Also added for LinkedIn personal analytics:
•
GET /v1/accounts/{accountId}/linkedin-aggregate-analytics now supports POSTSAVE and POSTSEND in metrics•
GET /v1/accounts/{accountId}/linkedin-post-analytics now returns saves and sends (personal only; org returns 0)New Feature
LinkedIn post analytics now include
LinkedIn added POSTSAVE and POSTSEND to
•
•
Scope: personal LinkedIn accounts only. LinkedIn did not add these metrics to the organization/company-page analytics endpoint, so organization accounts return
saves and sends.LinkedIn added POSTSAVE and POSTSEND to
memberCreatorPostAnalytics in their 202604 API version. Both fields are now returned on:•
GET /v1/accounts/{accountId}/linkedin-post-analytics•
GET /v1/accounts/{accountId}/linkedin-aggregate-analytics (pass POSTSAVE and/or POSTSEND via the metrics query param, or omit metrics to get them automatically)Scope: personal LinkedIn accounts only. LinkedIn did not add these metrics to the organization/company-page analytics endpoint, so organization accounts return
0 for saves and sends. No OAuth reconnect required, the existing rmemberpostAnalytics scope already covers it.Breaking Change
POST /v1/ads/create now supports 3 mutually-exclusive request shapes (and has updated required fields).You can now (Meta-only) create multiple ads in one call via
creatives[], or attach a new ad to an existing ad set via adSetId.Key request options:
• Legacy single-creative: use top-level
goal, budgetAmount, budgetType, headline, body, imageUrl, linkUrl, callToAction• Multi-creative (Meta only): set
creatives[] (min 1). Each item requires headline, body, imageUrl, linkUrl, callToAction• Attach (Meta only): set
adSetId + a single creative at top-level; goal/budgetAmount/budgetType are inheritedcreatives[] and adSetId are mutually exclusive (400 if both). Non-Meta platforms with creatives[]/adSetId return 400.Response change: 201 may return either
ad (legacy/attach) or ads[] + platformCampaignId + platformAdSetId (multi-creative).Improvement
Ad metrics now include revenue and ROAS fields for Meta insights.
You can read monetary action totals via
•
•
Meta-only; other platforms return
You can read monetary action totals via
actionValues (mirrors actions, values in ad-account currency), plus convenience rollups:•
purchaseValue — summed purchase-type action values•
roas — purchaseValue / spendMeta-only; other platforms return
{} for actionValues and 0 for purchaseValue/roas.New Feature
New Ads API write endpoints are available for managing campaigns and ad sets (budget updates, pause/resume, duplication, deletion). This adds first-class support for CBO vs ABO budget routing and bulk status changes.
Campaign budget updates (CBO):
• Body:
• If the campaign is ABO, returns
Ad set updates (ABO budget and/or status):
• Body:
• If parent campaign is CBO and you try to update budget here, returns
Convenience ad set status toggle:
• Body:
Bulk pause/resume campaigns (up to 50):
• Body:
• Returns per-campaign results so one failure doesn’t fail the whole batch
Duplicate a campaign (Meta async copy + optional discovery):
• Key options:
Delete a campaign (cascades to ad sets/ads):
• Body:
Also added to campaign responses (
•
•
Meta-only for now where noted; other platforms may return
Campaign budget updates (CBO):
PUT /v1/ads/campaigns/{campaignId}• Body:
platform (facebook|instagram), budget.amount, budget.type (daily|lifetime)• If the campaign is ABO, returns
409 (BUDGETLEVELMISMATCH) — update the ad set insteadAd set updates (ABO budget and/or status):
PUT /v1/ads/ad-sets/{adSetId}• Body:
platform, optional budget, optional status (active|paused)• If parent campaign is CBO and you try to update budget here, returns
409 (BUDGETLEVELMISMATCH)Convenience ad set status toggle:
PUT /v1/ads/ad-sets/{adSetId}/status• Body:
platform, status (active|paused)Bulk pause/resume campaigns (up to 50):
POST /v1/ads/campaigns/bulk-status• Body:
status (active|paused), campaigns[] with platformCampaignId + platform• Returns per-campaign results so one failure doesn’t fail the whole batch
Duplicate a campaign (Meta async copy + optional discovery):
POST /v1/ads/campaigns/{campaignId}/duplicate• Key options:
deepCopy (default true), statusOption (ACTIVE|PAUSED|INHERITEDFROMSOURCE), syncAfter (default true)Delete a campaign (cascades to ad sets/ads):
DELETE /v1/ads/campaigns/{campaignId}• Body:
platform (facebook|instagram)Also added to campaign responses (
GET /v1/ads/campaigns and GET /v1/ads/tree):•
reviewStatus (inreview|approved|rejected|withissues)•
budgetLevel (campaign|adset), campaignBudget, and currency to disambiguate CBO vs ABO budgetsMeta-only for now where noted; other platforms may return
501 for unsupported operations.Improvement
Ad metrics now include conversion reporting and raw action breakdowns via new fields on
This lets you read conversion counts/cost directly and also extract any Meta action/conversion type from Insights without relying only on derived metrics.
New fields:
•
•
•
AdMetrics.This lets you read conversion counts/cost directly and also extract any Meta action/conversion type from Insights without relying only on derived metrics.
New fields:
•
conversions (integer) — conversion events matching the campaign’s promoted event type (Meta-only for now; others return 0)•
costPerConversion (number) — spend / conversions (0 when conversions is 0)•
actions (object<string, integer>) — per actiontype counts (e.g. linkclick, offsiteconversion.fbpixel_purchase)New Feature
Announcing Zernio Ads. Run paid social campaigns across 6 platforms directly through the Zernio API.
You can now boost organic posts, create standalone ads with custom creative, manage Campaign / Ad Set / Ad hierarchies, build custom audiences, target by interests and geography, pull analytics, and relay conversion events, all through one unified API.
Supported platforms: Meta (Facebook, Instagram), Google Ads, TikTok, LinkedIn, Pinterest, X/Twitter.
Endpoints:
•
•
•
•
•
•
•
•
•
Targeting supports age (13 to 65), countries, interests, custom audiences, and Meta's Advantage Audience. Goals include
Requires the Ads add-on on your account.
You can now boost organic posts, create standalone ads with custom creative, manage Campaign / Ad Set / Ad hierarchies, build custom audiences, target by interests and geography, pull analytics, and relay conversion events, all through one unified API.
Supported platforms: Meta (Facebook, Instagram), Google Ads, TikTok, LinkedIn, Pinterest, X/Twitter.
Endpoints:
•
POST /v1/ads/boost: turn an organic post into a paid ad•
POST /v1/ads/create: create standalone ads with custom creative•
GET /v1/ads: list ads with spend, impressions, clicks, CTR, CPC, CPM (up to 90 days)•
GET /v1/ads/tree: nested Campaign > Ad Set > Ad view•
GET /v1/ads/campaigns: aggregated campaign view•
GET /v1/ads/audiences, POST /v1/ads/audiences: list and create custom audiences (Meta lookalike, customer list, website pixel)•
POST /v1/ads/sync: pull external ads from platforms into Zernio•
POST /v1/ads/conversions: unified Conversions API relay (Meta, Google Ads)•
GET /v1/connect/{platform}/ads: OAuth connection flow for ad accountsTargeting supports age (13 to 65), countries, interests, custom audiences, and Meta's Advantage Audience. Goals include
engagement, traffic, awareness, videoviews, leadgeneration, conversions, and app_promotion (availability varies by platform).Requires the Ads add-on on your account.
Improvement
Meta ads now support Advantage Audience controls when creating/boosting ads and when updating Meta targeting.
This lets you explicitly enable/disable Meta’s targeting automation during ad set creation, and optionally preserve the current setting on updates.
Use:
•
•
•
This lets you explicitly enable/disable Meta’s targeting automation during ad set creation, and optionally preserve the current setting on updates.
Use:
•
POST /v1/ads/boost: targeting.advantage_audience = 0 | 1•
PUT /v1/ads/{adId}: targeting.advantage_audience = 0 | 1 (omit to keep existing)•
POST /v1/ads/create: advantageAudience = 0 | 1Improvement
PUT /v1/inbox/conversations/{conversationId} can now return 404.This happens when the conversation isn’t found on WhatsApp (other platforms upsert instead of returning not found).
Handle the new response:
404 Conversation not found.New Feature
Reddit posting now supports native video submissions via new fields in
You can upload videos to Reddit’s CDN for an embedded player (with automatic fallback to a link post if video posts aren’t allowed), or submit silent looping clips.
New fields:
•
•
•
RedditPlatformData.You can upload videos to Reddit’s CDN for an embedded player (with automatic fallback to a link post if video posts aren’t allowed), or submit silent looping clips.
New fields:
•
nativeVideo (boolean, default true) - use Reddit native video flow; set false to fall back to a legacy link post•
videogif (boolean) - submit as silent videogif when nativeVideo is active•
videoPosterUrl (uri) - optional thumbnail/poster image for native video postsNew Feature
Webhooks now support review events via
Sending inbox messages now supports WhatsApp rich interactive messages via
Set
Interactive taps/submissions are delivered in the
events: review.new, review.updated (in POST /v1/webhooks/settings and PUT /v1/webhooks/settings).Sending inbox messages now supports WhatsApp rich interactive messages via
interactive in POST /v1/inbox/conversations/{conversationId}/messages.Set
interactive.type to: list, cta_url, or flow (WhatsApp only). When interactive is set, it takes priority over buttons and quickReplies.Interactive taps/submissions are delivered in the
message.received webhook under metadata: interactiveType (buttonreply | listreply | nfm_reply), interactiveId, flowResponseJson, flowResponseData.New Feature
New Google Business Profile (GBP) capabilities are available: manage Services, update Place Action links, and fetch Reviews across multiple locations in one call.
Manage location services:
•
•
Update a Place Action link:
•
•
Batch fetch reviews for multi-location accounts:
•
Manage location services:
•
GET /v1/accounts/{accountId}/gmb-services (optional locationId)•
PUT /v1/accounts/{accountId}/gmb-services replaces the full list via serviceItems (structured or free-form, optional price)Update a Place Action link:
•
PATCH /v1/accounts/{accountId}/gmb-place-actions with name (required), and optionally uri and/or placeActionType•
placeActionType: APPOINTMENT, ONLINEAPPOINTMENT, DININGRESERVATION, FOODORDERING, FOODDELIVERY, FOODTAKEOUT, SHOPONLINEBatch fetch reviews for multi-location accounts:
•
POST /v1/accounts/{accountId}/gmb-reviews/batch with locationNames, optional pageSize (max 50) and pageTokenNew Feature
Google Business Platform posts now support EVENT and OFFER types via
This lets you publish event and offer posts (in addition to standard updates) to Google Search/Maps/GBP.
Set
When
When
topicType in GoogleBusinessPlatformData.This lets you publish event and offer posts (in addition to standard updates) to Google Search/Maps/GBP.
Set
topicType to: STANDARD | EVENT | OFFER (default: STANDARD)When
topicType is EVENT, include event (required): event.title, event.schedule.startDate, event.schedule.endDate (optional: startTime, endTime).When
topicType is OFFER, include offer (recommended): offer.offerType (OFFER | BUYONEGET_ONE), offer.redeemOnlineUrl, offer.termsConditions, offer.couponCode.Breaking Change
Webhook comment.received payloads may now include
Handle missing internal IDs by relying on the platform IDs instead.
Fields that can now be
•
•
Use
null for Zernio internal post IDs when the post was not published through Zernio.Handle missing internal IDs by relying on the platform IDs instead.
Fields that can now be
null:•
comment.postId•
post.idUse
comment.platformPostId / post.platformPostId as the stable identifiers in these cases.New Feature
Discord support has been added: you can now manage Discord account settings, list available guild channels, and post to Discord with Discord-specific options.
Manage Discord connection settings:
•
•
•
Post to Discord via
• Required:
• Optional:
Analytics responses now include
Ads goals have been expanded in
Manage Discord connection settings:
•
GET /v1/accounts/{accountId}/discord-settings (read current channel/guild + webhook identity)•
PATCH /v1/accounts/{accountId}/discord-settings (update webhookUsername/webhookAvatarUrl and/or switch channelId)•
GET /v1/accounts/{accountId}/discord-channels (discover channels: type = 0/5/15)Post to Discord via
POST /v1/posts using platformSpecificData = DiscordPlatformData:• Required:
channelId• Optional:
embeds, poll, crosspost, forumThreadName, forumAppliedTags, threadFromMessage, tts, webhookUsername, webhookAvatarUrlAnalytics responses now include
platformPostId per platform in GET /v1/analytics.Ads goals have been expanded in
POST /v1/ads/boost and POST /v1/ads/create: goal now supports engagement, traffic, awareness, videoviews, leadgeneration, conversions, app_promotion (availability varies by platform).New Feature
New Ads Conversions API endpoints are available:
You can now relay conversion events to Meta or Google Ads; platform is inferred from
Use
Send events via
Response includes
POST /v1/ads/conversions and GET /v1/accounts/{accountId}/conversion-destinations.You can now relay conversion events to Meta or Google Ads; platform is inferred from
accountId. List valid destinations first, then send a batch of events.Use
GET /v1/accounts/{accountId}/conversion-destinations to get destinations[].id (use as destinationId) and destinations[].status (active/inactive). For Google, destinations[].type is present (event type locked to the conversion action).Send events via
POST /v1/ads/conversions with accountId, destinationId, events[] (each requires eventName, eventTime, eventId, user). Optional: testCode (Meta only) and batch-level consent (adUserData/adPersonalization: GRANTED/DENIED, Google only).Response includes
platform (metaads/googleads), eventsReceived, eventsFailed, failures[], and traceId. Requires the Ads add-on.Improvement
Meta ads now return additional campaign/ad set details in ad reporting responses.
You can use these fields to display objective/optimization/bidding context and conversion event setup alongside metrics.
New fields:
• On
• On
• On
You can use these fields to display objective/optimization/bidding context and conversion event setup alongside metrics.
New fields:
• On
Ad: platformObjective, optimizationGoal, bidStrategy, promotedObject• On
AdTreeCampaign and AdCampaign: platformObjective, optimizationGoal (can be string or string[]), bidStrategy, promotedObject• On
AdTreeAdSet: optimizationGoal, bidStrategy, promotedObjectpromotedObject may include customeventtype, pixelid, pageid (and on Ad also applicationid, productset_id). Fields are null when not applicable and are only present for Meta ads.New Feature
Discord is now supported as a platform in several endpoints and response schemas.
You can now use
Updated enums:
•
•
•
• Schemas:
You can now use
platform=discord to connect accounts, validate content, and filter account health.Updated enums:
•
GET /v1/connect/{platform}: {platform} includes discord•
POST /v1/tools/validate/post: platforms[].platform includes discord•
GET /v1/accounts/health: query platform includes discord• Schemas:
SocialAccount.platform, PostLog.platform, ConnectionLog.platform include discordImprovement
GET
Use this to scope results to a specific platform ad account (e.g. Meta
New query param:
The
•
•
•
/v1/ads now supports filtering by platform ad account via adAccountId.Use this to scope results to a specific platform ad account (e.g. Meta
act_123), consistent with /v1/ads/campaigns and /v1/ads/tree.New query param:
adAccountIdThe
Ad schema also adds Meta creative metadata to improve previews for video/moderation states:•
creative.videoId (nullable)•
creative.videoUrl (nullable)•
creative.objectTypeNew Feature
Webhooks and Inbox messages now support message lifecycle updates:
You can subscribe to these via
• Edit tracking:
• Delete tracking:
• Delivery tracking:
message.edited, message.deleted, message.delivered, message.read, message.failed.You can subscribe to these via
Webhook.events and filter webhook logs with GET /v1/webhooks/logs using event values: message.edited, message.deleted, message.delivered, message.read, message.failed.GET /v1/inbox/conversations/{conversationId}/messages now returns additional fields for message state:• Edit tracking:
isEdited, editedAt, editCount, editHistory• Delete tracking:
isDeleted, deletedAt• Delivery tracking:
deliveryStatus (sent/delivered/read/failed/deleted), sentAt, deliveredAt, readAt, deliveryErrorBreaking Change
POST /v1/connect/facebook/select-page now requires userProfile in the request body.If you were previously sending only
profileId, pageId, and tempToken, you must now also include userProfile (decoded from the OAuth callback) to complete the Facebook Page selection flow.Required fields:
profileId, pageId, tempToken, userProfile.New Feature
Webhooks now support message sent events via
Use this to get notified when a message is sent via the API, including message + conversation context.
Subscribe by adding
You can also filter/view deliveries in logs with
Payload schema:
message.sent.Use this to get notified when a message is sent via the API, including message + conversation context.
Subscribe by adding
message.sent to Webhook.events.You can also filter/view deliveries in logs with
GET /v1/webhooks/logs using event=message.sent.Payload schema:
WebhookPayloadMessageSent (includes message, conversation, account, timestamp).Breaking Change
Thread publishing behavior changed for X (Twitter), Threads, and Bluesky when using
If
Key fields:
•
•
•
threadItems.If
threadItems is provided, the top-level content is now used only for display/search and is NOT published. You must include the first post as threadItems[0].Key fields:
•
platformSpecificData.twitter.threadItems•
platformSpecificData.threads.threadItems•
platformSpecificData.bluesky.threadItemsImprovement
Facebook now supports posting an optional first comment for Reels as well as feed posts.
Use
Applies when
Use
platformSpecificData.facebook.firstComment to add a comment immediately after publishing (not supported for contentType=story). Skipped when draft is true.Applies when
contentType is reel (or omitted for feed posts).New Feature
You can now edit a published X (Twitter) post via
This updates the post on X and stores edit history in Zernio. X constraints apply (Premium required, 1-hour edit window, max 5 edits, text-only).
Request body:
•
•
Response includes:
•
•
You can now start/send X DMs via
If a thread already exists, the message is appended. If the recipient doesn’t accept DMs, you’ll get
Key fields:
•
•
•
•
Also added:
• Root-level
•
POST /v1/posts/{postId}/edit.This updates the post on X and stores edit history in Zernio. X constraints apply (Premium required, 1-hour edit window, max 5 edits, text-only).
Request body:
•
platform: twitter•
content: new textResponse includes:
•
id (new tweet ID after edit)•
urlYou can now start/send X DMs via
POST /v1/inbox/conversations.If a thread already exists, the message is appended. If the recipient doesn’t accept DMs, you’ll get
422 with code=DMNOTALLOWED (or bypass the pre-check with skipDmCheck).Key fields:
•
accountId•
participantId or participantUsername•
message (or attachment via multipart/form-data)•
skipDmCheckAlso added:
• Root-level
facebookSettings on POST /v1/posts and PUT /v1/posts/{postId} (e.g. facebookSettings.draft=true to create an unpublished draft in Facebook Publishing Tools)•
TwitterPlatformData.longVideo=true to enable long video uploads (requires X Premium; may require allowlisting).New Feature
New Google Business Profile analytics endpoints are available.
Fetch daily performance metrics (impressions, clicks, calls, directions, etc.) via
Key params:
•
•
•
Fetch monthly search keywords via
Key params:
•
•
Note: keywords below Google’s minimum impression threshold are excluded.
Fetch daily performance metrics (impressions, clicks, calls, directions, etc.) via
GET /v1/analytics/googlebusiness/performance. Requires accountId and the Analytics add-on; data may be delayed 2–3 days; up to 18 months history.Key params:
•
accountId (required)•
metrics (comma-separated; defaults to all): BUSINESSIMPRESSIONSDESKTOPMAPS, BUSINESSIMPRESSIONSDESKTOPSEARCH, BUSINESSIMPRESSIONSMOBILEMAPS, BUSINESSIMPRESSIONSMOBILESEARCH, BUSINESSCONVERSATIONS, BUSINESSDIRECTIONREQUESTS, CALLCLICKS, WEBSITECLICKS, BUSINESSBOOKINGS, BUSINESSFOODORDERS, BUSINESSFOODMENU_CLICKS•
startDate, endDate (YYYY-MM-DD)Fetch monthly search keywords via
GET /v1/analytics/googlebusiness/search-keywords. Requires accountId and the Analytics add-on; up to 18 months history.Key params:
•
accountId (required)•
startMonth, endMonth (YYYY-MM)Note: keywords below Google’s minimum impression threshold are excluded.
New Feature
YouTube analytics now supports audience demographics via
Fetch demographic insights for a YouTube channel broken down by age/gender (percentages) and country (view counts). Requires the Analytics add-on and YouTube Analytics scope.
Key params:
•
•
•
WhatsApp now supports Flows (create/manage/publish and send interactive flow messages).
Manage flows:
•
•
•
•
•
Flow JSON asset:
•
•
Lifecycle:
•
•
Send a flow message:
•
• Optional
GET /v1/analytics/youtube/demographics.Fetch demographic insights for a YouTube channel broken down by age/gender (percentages) and country (view counts). Requires the Analytics add-on and YouTube Analytics scope.
Key params:
•
accountId (required)•
breakdown = age, gender, country (comma-separated; defaults to all)•
startDate, endDateWhatsApp now supports Flows (create/manage/publish and send interactive flow messages).
Manage flows:
•
GET /v1/whatsapp/flows (accountId)•
POST /v1/whatsapp/flows (accountId, name, categories, optional cloneFlowId)•
GET /v1/whatsapp/flows/{flowId} (accountId, optional fields)•
PATCH /v1/whatsapp/flows/{flowId} (accountId, name/categories; DRAFT only)•
DELETE /v1/whatsapp/flows/{flowId} (accountId; DRAFT only)Flow JSON asset:
•
GET /v1/whatsapp/flows/{flowId}/json (accountId)•
PUT /v1/whatsapp/flows/{flowId}/json (accountId, flow_json; DRAFT only)Lifecycle:
•
POST /v1/whatsapp/flows/{flowId}/publish (accountId; irreversible)•
POST /v1/whatsapp/flows/{flowId}/deprecate (accountId; irreversible)Send a flow message:
•
POST /v1/whatsapp/flows/send with accountId, to, flowid, flowcta, body• Optional
flowaction = navigate | dataexchange, flowtoken, flowaction_payload, draftImprovement
YouTube metadata updates now support additional fields on
You can now set COPPA compliance, disclose AI/synthetic media, and add a video to a playlist without re-uploading.
New optional properties:
•
•
•
POST /v1/posts/{postId}/update-metadata.You can now set COPPA compliance, disclose AI/synthetic media, and add a video to a playlist without re-uploading.
New optional properties:
•
madeForKids (boolean)•
containsSyntheticMedia (boolean)•
playlistId (string)New Feature
YouTube metadata updates now support setting a custom thumbnail via
You can update the thumbnail without re-uploading, including for existing videos not published through Zernio (direct video ID mode).
Use
Key params:
POST /v1/posts/{postId}/update-metadata.You can update the thumbnail without re-uploading, including for existing videos not published through Zernio (direct video ID mode).
Use
thumbnailUrl (public image URL) with platform youtube. In direct mode also include videoId + accountId.Key params:
platform (youtube), thumbnailUrl, videoId, accountIdNew Feature
YouTube posts now support adding a video to a playlist via
Use the new playlist endpoints to discover playlist IDs and (optionally) store a client-side default for an account.
New endpoints:
•
•
New field:
•
playlistId in YouTubePlatformData.Use the new playlist endpoints to discover playlist IDs and (optionally) store a client-side default for an account.
New endpoints:
•
GET /v1/accounts/{accountId}/youtube-playlists → returns playlists[] and defaultPlaylistId•
PUT /v1/accounts/{accountId}/youtube-playlists with defaultPlaylistId (optional defaultPlaylistName)New field:
•
platformSpecificData.youtube.playlistId (string) — optional playlist to add the video to after uploadNew Feature
POST /v1/posts/{postId}/update-metadata now supports updating YouTube video metadata even if the video was uploaded outside Zernio.
Use one of two modes:
• Post-based: call with the real
• Direct video ID: set
Key params:
•
•
•
In direct mode, the 200 response may include
Use one of two modes:
• Post-based: call with the real
{postId} and platform• Direct video ID: set
{postId} to _ and include videoId + accountId + platformKey params:
•
platform: youtube•
videoId (direct mode)•
accountId (direct mode)In direct mode, the 200 response may include
videoId.New Feature
New endpoint:
Currently supported for YouTube videos only; the post must be
Request body:
POST /v1/posts/{postId}/update-metadata to update metadata on an already-published post without re-uploading media.Currently supported for YouTube videos only; the post must be
published on the target platform and you must provide at least one updatable field.Request body:
platform (youtube) plus any of title, description, tags, categoryId, privacyStatus (public | private | unlisted).New Feature
Threads posts now support an explicit topic tag via
Use this to categorize posts for discoverability on Threads; when provided, it overrides auto-extraction from content hashtags.
Set
• 1–50 characters
• cannot contain
platformSpecificData.topic_tag.Use this to categorize posts for discoverability on Threads; when provided, it overrides auto-extraction from content hashtags.
Set
topic_tag (string):• 1–50 characters
• cannot contain
. or &Improvement
Analytics endpoints now support filtering by social account via
This lets you scope results to a specific connected account (useful when a
New query parameter:
•
•
accountId.This lets you scope results to a specific connected account (useful when a
profileId contains multiple accounts).New query parameter:
•
GET /v1/analytics: accountId•
GET /v1/analytics/daily-metrics: accountIdNew Feature
You can now send inbox attachments by URL when calling
Provide
Twitter posts now support polls via
Set
POST /v1/inbox/conversations/{conversationId}/messages (JSON body), without using multipart upload.Provide
attachmentUrl (publicly accessible) and optionally attachmentType (image, video, audio, file).Twitter posts now support polls via
TwitterPlatformData.poll.Set
poll.options (2-4 items, max 25 chars each) and poll.duration_minutes (5-10080). Polls are mutually exclusive with media attachments and threads.New Feature
Instagram Analytics now supports account-level insights and audience demographics.
Fetch account performance totals or daily reach via
Key params:
Fetch audience breakdowns via
Key params:
Fetch account performance totals or daily reach via
GET /v1/analytics/instagram/account-insights.Key params:
accountId (required), metrics, since, until, metricType (timeseries | totalvalue), breakdown (totalvalue only). Note: timeseries only works with reach. Max range: 90 days (default last 30). Data may be delayed up to 48 hours. Requires Analytics add-on.Fetch audience breakdowns via
GET /v1/analytics/instagram/demographics.Key params:
accountId (required), metric (followerdemographics | engagedaudiencedemographics), breakdown (age, city, country, gender), timeframe (thisweek | this_month). Requires 100+ followers, returns top 45 per dimension, and may be delayed up to 48 hours. Requires Analytics add-on.New Feature
TikTok video posts now support a custom cover image via
Provide a thumbnail image URL (JPG/PNG/WebP, max 20MB). When set, it overrides
Use:
•
•
videoCoverImageUrl in TikTokPlatformData.Provide a thumbnail image URL (JPG/PNG/WebP, max 20MB). When set, it overrides
videoCoverTimestampMs.Use:
•
videoCoverImageUrl - custom thumbnail image URL•
videoCoverTimestampMs - frame timestamp (ms), ignored if videoCoverImageUrl is providedNew Feature
New endpoint:
You can now fetch TikTok creator details plus the allowed posting options for an account (privacy levels, interaction settings, posting limits, and commercial content disclosures). Useful for validating post settings before publishing.
Key params:
•
•
Also added to
GET /v1/accounts/{accountId}/tiktok/creator-info.You can now fetch TikTok creator details plus the allowed posting options for an account (privacy levels, interaction settings, posting limits, and commercial content disclosures). Useful for validating post settings before publishing.
Key params:
•
accountId (path)•
mediaType (query, default video): video, photoAlso added to
TikTokPlatformData for video posts: videoCoverImageUrl (custom thumbnail URL). When set, it overrides videoCoverTimestampMs.Breaking Change
GET
Single-post lookups can return
Key changes:
• Date defaults/limits:
• Sorting:
• Single post response adds
• Per-platform analytics now include
• List responses add
/v1/analytics now supports richer analytics sync states and more sorting options.Single-post lookups can return
202 when analytics sync is still pending, or 424 when analytics are unavailable because the post failed on all platforms.Key changes:
• Date defaults/limits:
fromDate defaults to 90 days ago, toDate defaults to today, max range 366 days• Sorting:
sortBy now supports date, engagement, impressions, reach, likes, comments, shares, saves, clicks, views• Single post response adds
latePostId, syncStatus (synced/pending/partial/unavailable) and message; status can be published/failed/partial• Per-platform analytics now include
syncStatus (synced/pending/unavailable), platformPostUrl, and errorMessage; analytics may be null• List responses add
posts[].latePostId, posts[].profileId, and overview.dataStalenessNew Feature
Late API now supports X/Twitter engagement actions: retweet, bookmark, and follow (plus undo/remove/unfollow).
Use:
• Retweet:
• Undo retweet:
• Bookmark:
• Remove bookmark:
• Follow:
• Unfollow:
Inbox comment moderation also adds X/Twitter support:
•
•
For X/Twitter, the reply must be in a conversation started by the authenticated user and requires
Use:
• Retweet:
POST /v1/twitter/retweet with accountId, tweetId• Undo retweet:
DELETE /v1/twitter/retweet with accountId, tweetId• Bookmark:
POST /v1/twitter/bookmark with accountId, tweetId• Remove bookmark:
DELETE /v1/twitter/bookmark with accountId, tweetId• Follow:
POST /v1/twitter/follow with accountId, targetUserId (response may include pending_follow for protected accounts)• Unfollow:
DELETE /v1/twitter/follow with accountId, targetUserIdInbox comment moderation also adds X/Twitter support:
•
POST /v1/inbox/comments/{postId}/{commentId}/hide•
DELETE /v1/inbox/comments/{postId}/{commentId}/hideFor X/Twitter, the reply must be in a conversation started by the authenticated user and requires
tweet.moderate.write + X API Basic tier (or higher). Bookmark/follow also require bookmark.write / follows.write scopes, and X API Basic tier (or higher).Improvement
GET /v1/analytics now returns richer media metadata for analytics results.You can render previews and handle carousels consistently using the new fields on both single-post and list responses.
New fields:
•
thumbnailUrl•
mediaType = image | video | gif | document | carousel | text (single-post also supports image | video | carousel | text)•
mediaItems (array of { type: image|video, url, thumbnail }; carousel posts include one entry per slide)Improvement
The
Use
GET /v1/usage-stats response now includes billingAnchorDay.Use
billingAnchorDay (1–31) to determine the day of the month when the billing cycle resets, which can help align usage tracking and reset expectations.Improvement
GBP endpoints now support overriding the target location via optional
This lets you fetch/update reviews, media, attributes, place actions, food menus, and location details for a specific GBP location without changing the account’s selected location.
Use
•
•
•
•
•
•
locationId.This lets you fetch/update reviews, media, attributes, place actions, food menus, and location details for a specific GBP location without changing the account’s selected location.
Use
locationId (query) on:•
GET /v1/accounts/{accountId}/gmb-reviews•
GET/PUT /v1/accounts/{accountId}/gmb-food-menus•
GET/PUT /v1/accounts/{accountId}/gmb-location-details•
GET/POST/DELETE /v1/accounts/{accountId}/gmb-media•
GET/PUT /v1/accounts/{accountId}/gmb-attributes•
GET/POST/DELETE /v1/accounts/{accountId}/gmb-place-actionsNew Feature
Twitter posts now support replying to an existing tweet via
Set
replyToTweetId in TwitterPlatformData.Set
replyToTweetId to publish the first tweet (or the root of a thread) as a reply; subsequent thread items chain normally.replySettings also adds verified (now: following, mentionedUsers, subscribers, verified). Note: replySettings cannot be combined with replyToTweetId.New Feature
New endpoint
Currently supported only for LinkedIn organization/company page accounts (personal profile reaction data is restricted by LinkedIn).
Required params:
Optional:
Response includes
GET /v1/accounts/{accountId}/linkedin-post-reactions returns who reacted to a LinkedIn post, including reactor profile details.Currently supported only for LinkedIn organization/company page accounts (personal profile reaction data is restricted by LinkedIn).
Required params:
accountId (path, LinkedIn org account), urn (query, LinkedIn post URN)Optional:
limit (1–100, default 25), cursorResponse includes
reactions[] with reactionType (LIKE, PRAISE, EMPATHY, INTEREST, APPRECIATION, ENTERTAINMENT), reactedAt, and from (e.g., name, headline, profileUrl, profilePicture), plus pagination.Improvement
GET /v1/connect/{platform} redirect behavior now includes accountId in standard mode.After a successful connect, Zernio’s standard redirect to
redirect_url now appends ?connected={platform}&profileId=X&accountId=Y&username=Z (previously no accountId).In headless mode (
headless=true), the redirect includes raw OAuth data only when a post-OAuth selection step is required (e.g. LinkedIn orgs, Facebook pages). If no selection is needed, the account is created directly and the redirect includes accountId.New Feature
Posts can now be recycled (auto-reposted on a schedule) via
After the original post is published, Zernio will create new scheduled copies at a weekly/monthly interval until an expiration condition is met.
Configure with
•
•
•
•
•
•
Webhooks: a new event
Also:
recycling on POST /v1/posts and PUT /v1/posts/{postId}.After the original post is published, Zernio will create new scheduled copies at a weekly/monthly interval until an expiration condition is met.
Configure with
recycling:•
gap (int)•
gapFreq: week | month•
startDate (date-time, optional)•
expireCount (int, optional)•
expireDate (date-time, optional)•
contentVariations (string[], optional)Webhooks: a new event
post.recycled is available in POST/PUT /v1/webhooks/settings and filterable in GET /v1/webhooks/logs.Also:
GET /v1/tools/youtube/transcript may now return 503, and DELETE /v1/inbox/comments/{postId} may now return 400 when the platform rejects the delete.New Feature
New endpoint
Query params:
•
•
•
Response includes
GET /v1/analytics/post-timeline returns a daily analytics timeline for a specific post (per platform for multi-platform Zernio posts), showing how metrics evolve day-by-day since publishing. Requires the Analytics add-on.Query params:
•
postId (required) — accepts ExternalPost ID, platformPostId, or Zernio Post ID•
fromDate (optional, ISO 8601; default 90 days ago)•
toDate (optional, ISO 8601; default now)Response includes
timeline[] rows with date, platform, platformPostId, and metrics like impressions, reach, likes, comments, shares, saves, clicks, views.New Feature
New validation endpoints are available under
Validate weighted character counts per platform with
Dry-run the full post validation pipeline (same body as
Validate a public media URL and compare against per-platform size limits with
Check subreddit existence/info with
/v1/tools/validate to preflight-check content before publishing.Validate weighted character counts per platform with
POST /v1/tools/validate/post-length (body: { text }) and get per-platform { count, limit, valid }.Dry-run the full post validation pipeline (same body as
POST /v1/posts) with POST /v1/tools/validate/post. Target platforms via platforms[].platform: twitter, instagram, tiktok, youtube, facebook, linkedin, bluesky, threads, reddit, pinterest, telegram, snapchat, googlebusiness. Returns valid plus errors[] and/or warnings[].Validate a public media URL and compare against per-platform size limits with
POST /v1/tools/validate/media (body: { url }). Returns contentType, size, type (image/video/unknown), and platformLimits when available.Check subreddit existence/info with
GET /v1/tools/validate/subreddit using query name.New Feature
New Analytics endpoints are available for aggregated reporting and scheduling optimization (requires the Analytics add-on).
Daily aggregated metrics:
• Filters:
• Returns:
Best times to post:
• Filters:
• Returns:
Content performance decay:
• Filters:
• Returns:
Posting frequency vs engagement:
• Filters:
• Returns:
Daily aggregated metrics:
GET /v1/analytics/daily-metrics• Filters:
platform, profileId, fromDate, toDate• Returns:
dailyData + platformBreakdown with summed metrics (impressions, reach, likes, comments, shares, saves, clicks, views)Best times to post:
GET /v1/analytics/best-time• Filters:
platform, profileId• Returns:
slots grouped by dayofweek (0=Monday..6=Sunday) and hour (UTC 0-23) with avg_engagementContent performance decay:
GET /v1/analytics/content-decay• Filters:
platform, profileId• Returns:
buckets with bucketlabel and avgpctoffinalPosting frequency vs engagement:
GET /v1/analytics/posting-frequency• Filters:
platform, profileId• Returns:
frequency rows with postsperweek, avgengagementrate, weeks_countImprovement
LinkedIn document (PDF/carousel) posts now let you control the displayed document title via
LinkedIn requires a title for document posts; if omitted, Zernio will fall back to the media item title, then the filename.
Use:
•
•
platformSpecificData.linkedin.documentTitle.LinkedIn requires a title for document posts; if omitted, Zernio will fall back to the media item title, then the filename.
Use:
•
platformSpecificData.linkedin.documentTitle•
media[].title (used as the LinkedIn document title if documentTitle is not set; otherwise falls back to filename)New Feature
API keys can now be created with restricted access using
This lets you issue keys limited to specific profiles and/or make keys read-only for safer analytics/integration use.
New request fields:
•
•
•
Key responses now include
scope, profileIds, and permission on POST /v1/api-keys.This lets you issue keys limited to specific profiles and/or make keys read-only for safer analytics/integration use.
New request fields:
•
scope: full | profiles (default full)•
profileIds: required when scope=profiles•
permission: read-write | read (default read-write)Key responses now include
scope, profileIds (populated profile objects when scoped), and permission (e.g., in GET /v1/api-keys and create responses).New Feature
Facebook publishing now supports Reels via
Set
New field for reels:
•
Note:
FacebookPlatformData.contentType.Set
contentType to story or reel (omit for a feed post). Reels require a single vertical video (9:16, 3–60s).New field for reels:
•
title — Reel title (only when contentType=reel)Note:
firstComment applies to feed posts only (not stories or reels).New Feature
New endpoint:
You can now delete a previously published post from a specific social platform while keeping the Zernio post record (platform status becomes
Request body:
Notes: Instagram/TikTok/Snapchat deletion isn’t supported. Telegram deletions may fail for messages older than 48h. YouTube deletion permanently removes the video.
POST /v1/posts/{postId}/unpublish.You can now delete a previously published post from a specific social platform while keeping the Zernio post record (platform status becomes
cancelled).Request body:
platform = threads | facebook | twitter | linkedin | youtube | pinterest | reddit | bluesky | googlebusiness | telegram.Notes: Instagram/TikTok/Snapchat deletion isn’t supported. Telegram deletions may fail for messages older than 48h. YouTube deletion permanently removes the video.
Improvement
Instagram photo tagging now supports tagging specific slides in carousel posts via
This lets you place tags on any image within a carousel. Tags without
Use:
•
Notes:
• Not supported for stories or videos; tags targeting video items (or out-of-range indices) are ignored.
mediaIndex in platformSpecificData.userTags.This lets you place tags on any image within a carousel. Tags without
mediaIndex still default to the first image (index 0).Use:
•
userTags[].mediaIndex (integer, 0-based)Notes:
• Not supported for stories or videos; tags targeting video items (or out-of-range indices) are ignored.
New Feature
POST
You can send a private reply to a Facebook post comment, which opens a Messenger conversation with the commenter (text-only; still limited to 1 private reply per comment within 7 days).
Use
/v1/inbox/comments/{postId}/{commentId}/private-reply now supports Facebook in addition to Instagram.You can send a private reply to a Facebook post comment, which opens a Messenger conversation with the commenter (text-only; still limited to 1 private reply per comment within 7 days).
Use
accountId (Instagram or Facebook) and message in the JSON body. The response platform is now instagram or facebook.Improvement
Google Business Profile posts now support explicitly setting the content language via
If omitted, the language is auto-detected from the post text; set it when auto-detection may be unreliable (short posts, mixed languages, transliteration).
New field:
languageCode in GoogleBusinessPlatformData.If omitted, the language is auto-detected from the post text; set it when auto-detection may be unreliable (short posts, mixed languages, transliteration).
New field:
languageCode (BCP 47, e.g. en, de, es, fr).New Feature
Reddit posting now supports post flairs via
Some subreddits require a flair; you can now fetch available flairs and set the one you want on the post.
New endpoint:
Required query:
New field:
If omitted, the API will attempt to use the first available flair as a fallback.
flairId in platformSpecificData.Some subreddits require a flair; you can now fetch available flairs and set the one you want on the post.
New endpoint:
GET /v1/accounts/{accountId}/reddit-flairsRequired query:
subreddit (without r/)New field:
platformSpecificData.reddit.flairId (string)If omitted, the API will attempt to use the first available flair as a fallback.
Improvement
Instagram conversations now include optional participant profile context via
This lets you show follower/verification info (and follower count) alongside DMs, and use it in routing/automation.
Added to:
•
•
Fields:
•
Webhook update:
•
instagramProfile.This lets you show follower/verification info (and follower count) alongside DMs, and use it in routing/automation.
Added to:
•
GET /v1/inbox/conversations → data[].instagramProfile•
GET /v1/inbox/conversations/{conversationId} → data.instagramProfileFields:
•
isFollower, isFollowing, followerCount, isVerified, fetchedAtWebhook update:
•
WebhookPayloadMessage.message.sender.instagramProfile (Instagram only) with isFollower, isFollowing, followerCount, isVerified.New Feature
Interactive messaging support for the Inbox API.
• Quick replies (
• Buttons (
• Carousels (
• Telegram keyboards (
• Message tags (
• Reply to (
Incoming messages now include
Also added:
POST /v1/inbox/conversations/{conversationId}/messages now supports:• Quick replies (
quickReplies) - Up to 13 quick reply buttons (Instagram, Facebook, Telegram)• Buttons (
buttons) - Up to 3 action buttons: URL, postback, or phone (Instagram, Facebook, Telegram)• Carousels (
template) - Generic template with up to 10 elements (Instagram, Facebook)• Telegram keyboards (
replyMarkup) - Native inline or reply keyboards• Message tags (
messageTag) - Send outside the 24h window: HUMANAGENT (Instagram), CONFIRMEDEVENTUPDATE, POSTPURCHASEUPDATE, ACCOUNTUPDATE, HUMAN_AGENT (Facebook)• Reply to (
replyTo) - Reply to a specific message (Telegram)Incoming messages now include
metadata with quickReplyPayload, postbackPayload, postbackTitle, and callbackData to identify interactive message taps.Also added:
PATCH /v1/inbox/conversations/{conversationId}/messages/{messageId} to edit sent Telegram messages (text and inline keyboard).New Feature
New Account Settings endpoints for managing platform-specific messaging features.
Facebook Persistent Menu (
GET/PUT/DELETE to manage the persistent menu shown in Facebook Messenger conversations. Max 3 top-level items, max 5 nested items.
Instagram Ice Breakers (
GET/PUT/DELETE to manage ice breaker prompts shown when users start a new Instagram DM. Max 4 ice breakers, question max 80 chars.
Telegram Bot Commands (
GET/PUT/DELETE to manage the bot command menu shown in Telegram chats.
Facebook Persistent Menu (
/v1/accounts/{accountId}/messenger-menu)GET/PUT/DELETE to manage the persistent menu shown in Facebook Messenger conversations. Max 3 top-level items, max 5 nested items.
Instagram Ice Breakers (
/v1/accounts/{accountId}/instagram-ice-breakers)GET/PUT/DELETE to manage ice breaker prompts shown when users start a new Instagram DM. Max 4 ice breakers, question max 80 chars.
Telegram Bot Commands (
/v1/accounts/{accountId}/telegram-commands)GET/PUT/DELETE to manage the bot command menu shown in Telegram chats.
Minor
Added 2 new endpoints:
GET /v1/posts/logs - Get publishing logsGET /v1/connections/logs - Get connection logsNew Feature
New Google Business Profile endpoints are now available for managing your GMB listings programmatically.
Location Details (
GET/PUT to read and update business hours, special hours, description, phone numbers, and website. Use
Media (
GET/POST/DELETE to manage photos. Upload via public URL with categories:
Attributes (
GET/PUT to manage amenities and services like
Place Actions (
GET/POST/DELETE to manage booking and ordering buttons. Types include:
Location Details (
/v1/accounts/{accountId}/gmb-location-details)GET/PUT to read and update business hours, special hours, description, phone numbers, and website. Use
readMask and updateMask to specify fields.Media (
/v1/accounts/{accountId}/gmb-media)GET/POST/DELETE to manage photos. Upload via public URL with categories:
COVER, PROFILE, LOGO, EXTERIOR, INTERIOR, FOODANDDRINK, MENU, PRODUCT, TEAMS, ADDITIONALAttributes (
/v1/accounts/{accountId}/gmb-attributes)GET/PUT to manage amenities and services like
hasdelivery, hastakeout, hasoutdoorseating, has_wifi, payment types, etc.Place Actions (
/v1/accounts/{accountId}/gmb-place-actions)GET/POST/DELETE to manage booking and ordering buttons. Types include:
APPOINTMENT, DININGRESERVATION, FOODORDERING, FOODDELIVERY, FOODTAKEOUT, SHOP_ONLINENew Feature
New endpoints for Google Business Profile food menus:
Fetch the full menu structure for a connected GBP location.
Update food menus with sections, items, pricing, dietary info, and allergens.
Menu items support:
•
•
•
•
Only available for GBP locations with food menu support (restaurants, cafes, etc.).
Also:
GET /v1/accounts/{accountId}/gmb-food-menusFetch the full menu structure for a connected GBP location.
PUT /v1/accounts/{accountId}/gmb-food-menusUpdate food menus with sections, items, pricing, dietary info, and allergens.
Menu items support:
•
price with currency code•
dietaryRestriction - VEGETARIAN, VEGAN, GLUTEN_FREE, etc.•
allergen - DAIRY, GLUTEN, SHELLFISH, etc.•
spiciness, servesNumPeople, preparationMethodsOnly available for GBP locations with food menu support (restaurants, cafes, etc.).
Also:
PostAnalytics now includes saves for tracking bookmarks on Instagram and Pinterest.New Feature
The Inbox API now supports Twitter/X for conversations, comments, and comment interactions.
Conversations (
Filter by
Comments (
Twitter/X posts with replies are now included. Also added Threads support for comments listing.
Like/Unlike comments:
Conversations (
GET /v1/inbox/conversations):Filter by
platform=twitter to fetch Twitter/X DMs.Comments (
GET /v1/inbox/comments):Twitter/X posts with replies are now included. Also added Threads support for comments listing.
Like/Unlike comments:
POST and DELETE /v1/inbox/comments/{postId}/{commentId}/like now work with Twitter/X replies.New Feature
The
Values:
•
•
•
This makes it easier to separate analytics for posts you scheduled through Zernio from posts that were published directly on the platform.
GET /v1/analytics endpoint now supports a source query parameter to filter posts by origin.Values:
•
late - only posts scheduled/published via Zernio API•
external - only posts synced from the platform (not posted via Late)•
all - all posts (default)This makes it easier to separate analytics for posts you scheduled through Zernio from posts that were published directly on the platform.
New Feature
The
Three new fields are available:
•
•
•
•
•
•
•
•
•
•
•
These fields are populated when a platform's
PlatformTarget schema now includes detailed error information when posts fail to publish.Three new fields are available:
errorMessage - Human-readable explanation of why the publish failederrorCategory - Programmatic category for handling:•
auth_expired - token expired, reconnect needed•
user_content - content doesn't meet platform requirements•
user_abuse - rate limits or spam detection•
account_issue - account configuration problems•
platform_rejected - policy violation•
platform_error - platform-side issues•
system_error - Zernio infrastructure issues•
unknown - unclassifiederrorSource - Who caused the error:•
user - user action required•
platform - platform-side issue•
system - Zernio system issueThese fields are populated when a platform's
status is failed. Posts with mixed results now return status: "partial" at the post level.New Feature
YouTube uploads now support video categories via the new
You can now specify which category your video belongs to. Defaults to
Common category IDs:
•
•
•
•
•
•
categoryId field in platformSpecificData.You can now specify which category your video belongs to. Defaults to
22 (People & Blogs) if not set.Common category IDs:
•
1 - Film & Animation•
10 - Music•
20 - Gaming•
24 - Entertainment•
27 - Education•
28 - Science & TechnologyNew Feature
Multi-page/multi-location posting is now supported for Facebook, LinkedIn, and Google Business.
You can now post to multiple pages, organizations, or locations from a single account connection by using the same
• Facebook:
• LinkedIn:
• Google Business:
List available targets via:
•
•
•
Also:
You can now post to multiple pages, organizations, or locations from a single account connection by using the same
accountId multiple times with different targets in platformSpecificData:• Facebook:
pageId - post to multiple Pages• LinkedIn:
organizationUrn - post to multiple organizations• Google Business:
locationId - post to multiple locationsList available targets via:
•
GET /v1/accounts/{id}/facebook-page•
GET /v1/accounts/{id}/linkedin-organizations•
GET /v1/accounts/{id}/gmb-locationsAlso:
content is now optional when media is attached or all platforms have customContent set.New Feature
New endpoint:
Send a private DM to the author of a comment on your Instagram post. Useful for handling customer inquiries or sensitive matters privately.
Required body:
•
•
Limitations:
• Instagram only (not available on other platforms)
• One private reply per comment
• Must be sent within 7 days of the comment
• Only works for comments on posts you own
• Requires the Inbox addon
POST /v1/inbox/comments/{postId}/{commentId}/private-replySend a private DM to the author of a comment on your Instagram post. Useful for handling customer inquiries or sensitive matters privately.
Required body:
•
accountId - Instagram social account ID•
message - text to send as DMLimitations:
• Instagram only (not available on other platforms)
• One private reply per comment
• Must be sent within 7 days of the comment
• Only works for comments on posts you own
• Requires the Inbox addon
New Feature
New endpoints for managing Facebook Pages and Google Business Profile locations.
List all Facebook pages the connected account can access, see which one is currently selected.
List all Google Business Profile locations available to the account.
Change the selected GBP location by passing
Useful if you manage multiple pages/locations and need to switch between them programmatically.
GET /v1/accounts/{accountId}/facebook-pageList all Facebook pages the connected account can access, see which one is currently selected.
GET /v1/accounts/{accountId}/gmb-locationsList all Google Business Profile locations available to the account.
PUT /v1/accounts/{accountId}/gmb-locationsChange the selected GBP location by passing
selectedLocationId in the request body.Useful if you manage multiple pages/locations and need to switch between them programmatically.
Improvement
POST /v1/posts now returns
If you post identical content to the same account within 24 hours, you'll get:
Additionally,
• Velocity limit: 15 posts/hour per account
• Account cooldown: Escalating delays (10min → 24h) after repeated errors
• Daily post limits: Platform-specific (X: 20, Pinterest: 25, Instagram/Facebook: 100, Threads: 250, others: 50)
New headers on 429:
These changes also apply to
409 Conflict when duplicate content is detected.If you post identical content to the same account within 24 hours, you'll get:
{
"error": "This exact content was already posted...",
"details": {
"accountId": "...",
"platform": "...",
"existingPostId": "..."
}
}Additionally,
429 responses now include detailed context about which limit was hit:• Velocity limit: 15 posts/hour per account
• Account cooldown: Escalating delays (10min → 24h) after repeated errors
• Daily post limits: Platform-specific (X: 20, Pinterest: 25, Instagram/Facebook: 100, Threads: 250, others: 50)
New headers on 429:
Retry-After, X-RateLimit-Limit, X-RateLimit-RemainingThese changes also apply to
POST /v1/posts/bulk-upload and POST /v1/posts/{postId}/retry.New Feature
YouTube uploads now support
This COPPA compliance field declares whether your video is child-directed content. Defaults to
Set to
Important: YouTube requires this to be explicitly set. If omitted, the video defaults to not made for kids, but may be blocked from views until configured in YouTube Studio.
madeForKids in platformSpecificData.youtube.This COPPA compliance field declares whether your video is child-directed content. Defaults to
false.Set to
true if your video is made for kids. Note: kid-directed videos have restricted features (no comments, no notifications, limited ad targeting).Important: YouTube requires this to be explicitly set. If omitted, the video defaults to not made for kids, but may be blocked from views until configured in YouTube Studio.
Improvement
Analytics responses now include
When an external post originated from a Late-scheduled post, the response now includes the original Zernio Post ID directly. This makes it easier to correlate analytics data with posts created via
The field appears in both single post queries and paginated list responses. It's
latePostId field.When an external post originated from a Late-scheduled post, the response now includes the original Zernio Post ID directly. This makes it easier to correlate analytics data with posts created via
POST /v1/posts.The field appears in both single post queries and paginated list responses. It's
null for posts that weren't scheduled through Zernio.Breaking Change
LinkedIn headless OAuth flow updated
LinkedIn headless mode now uses a token-based approach to retrieve OAuth data, preventing URITOOLONG errors for users with many organizations.
What changed:
After OAuth redirect, you now receive
Call the new endpoint to fetch the data:
Returns:
Important:
• One-time use: data is deleted after fetching
• Data expires after 10 minutes if not fetched
• No authentication required, just the token
Other platforms (Facebook, Pinterest, Google Business, Snapchat) are unchanged.
LinkedIn headless mode now uses a token-based approach to retrieve OAuth data, preventing URITOOLONG errors for users with many organizations.
What changed:
After OAuth redirect, you now receive
pendingDataToken instead of tempToken, userProfile, and organizations in the URL.Call the new endpoint to fetch the data:
GET /v1/connect/pending-data?token=PENDINGDATATOKENReturns:
tempToken, refreshToken, expiresIn, userProfile, organizations, selectionTypeImportant:
• One-time use: data is deleted after fetching
• Data expires after 10 minutes if not fetched
• No authentication required, just the token
Other platforms (Facebook, Pinterest, Google Business, Snapchat) are unchanged.
New Feature
Instagram Reels now support thumbnail offset selection via
Specify a millisecond offset from the start of the video to use as the Reel cover image. Defaults to 0 (first frame).
If you provide a custom thumbnail URL via
thumbOffset in platformSpecificData.Specify a millisecond offset from the start of the video to use as the Reel cover image. Defaults to 0 (first frame).
thumbOffset: integer, minimum 0 (milliseconds)If you provide a custom thumbnail URL via
instagramThumbnail in mediaItems, it takes priority and this offset is ignored.New Feature
New webhook event:
You can now subscribe to notifications when a social account is connected to a profile. The webhook payload includes
Add it to your
account.connectedYou can now subscribe to notifications when a social account is connected to a profile. The webhook payload includes
accountId, profileId, platform, username, and displayName.Add it to your
events array when creating or updating webhooks.New Feature
YouTube daily views breakdown is now available via
GET /v1/analytics/youtube/daily-views. This endpoint returns historical daily view counts for a specific YouTube video, including views, watch time, and subscriber changes. Required parameters include videoId and accountId, with optional date range parameters startDate and endDate.Improvement
Zernio now automatically compresses media that exceeds platform limits during publishing, simplifying the upload process.
Image compression thresholds are set for platforms like
Video compression thresholds include
Image compression thresholds are set for platforms like
Twitter/X: >5 MB, Instagram: >8 MB, Facebook: >10 MB, and more. Video compression thresholds include
Twitter/X: >512 MB, Instagram Stories: >100 MB, Facebook: >4 GB, among others.Breaking Change
The endpoint
POST /v1/profiles/{profileId}/clone-connection has been removed. This endpoint was used to clone an existing connection to a profile, allowing users to manage multiple brands with the same underlying social media account.Improvement
YouTube now supports AI-generated content disclosure via
Set
containsSyntheticMedia in YouTubePlatformData.Set
containsSyntheticMedia to true if your video contains AI-generated or synthetic content that could be mistaken for real people, places, or events. YouTube may add a label to videos when this is set.Improvement
Instagram now supports custom audio names for Reels via
You can set a custom name for the original audio, replacing the default "Original Audio" label. This can only be set once during creation or later from the Instagram audio page in the app.
audioName in platformSpecificData.You can set a custom name for the original audio, replacing the default "Original Audio" label. This can only be set once during creation or later from the Instagram audio page in the app.
Improvement
InstagramPlatformData Schema Update
The
Here are the details of the new
-
-
- Possible values:
- This field determines how a trial reel transitions to a regular reel:
-
-
The rest of the schema remains unchanged, including the parameters
This update enhances your ability to manage Reels effectively, allowing for strategic sharing and performance-based visibility adjustments. Keep this in mind as you plan your content strategy.
The
InstagramPlatformData schema has been updated to include a new parameter: trialParams. This parameter allows for configuration of trial Reels, which are initially shared only with non-followers. Here are the details of the new
trialParams field:-
trialParams: object (optional)-
graduationStrategy: string (required)- Possible values:
MANUAL, SS_PERFORMANCE- This field determines how a trial reel transitions to a regular reel:
-
MANUAL: Requires manual graduation via the Instagram app.-
SS_PERFORMANCE: Automatically graduates based on performance metrics with non-followers.The rest of the schema remains unchanged, including the parameters
contentType, shareToFeed, collaborators, firstComment, and userTags. This update enhances your ability to manage Reels effectively, allowing for strategic sharing and performance-based visibility adjustments. Keep this in mind as you plan your content strategy.
New Feature
New Endpoints Available:
1.
- This endpoint allows you to retrieve publishing logs for all posts. You can filter logs by status (
-
-
-
-
-
-
- The response includes an array of logs and pagination details.
2.
- Use this endpoint to retrieve detailed information about a specific log entry, including full request and response bodies for debugging purposes.
-
- The response contains the log entry details, including the full context of the publishing attempt.
3.
- This endpoint retrieves all publishing logs for a specific post, showing the complete history of publishing attempts across all platforms.
-
-
- The response includes an array of logs specific to the post and the count of logs returned.
1.
GET /v1/logs- This endpoint allows you to retrieve publishing logs for all posts. You can filter logs by status (
status), platform (platform), and action (action). The logs include detailed information about each publishing attempt, such as API requests, responses, and timing data. -
status (string, optional): Filter by log status (options: success, failed, pending, skipped, all).-
platform (string, optional): Filter by platform (options include tiktok, instagram, facebook, etc.).-
action (string, optional): Filter by action type (options: publish, retry, media_upload, etc.).-
days (integer, optional): Number of days to look back (max 7, default 7).-
limit (integer, optional): Maximum number of logs to return (max 100, default 50).-
skip (integer, optional): Number of logs to skip for pagination (default 0).- The response includes an array of logs and pagination details.
2.
GET /v1/logs/{logId}- Use this endpoint to retrieve detailed information about a specific log entry, including full request and response bodies for debugging purposes.
-
logId (string, required): The ID of the log entry you want to retrieve.- The response contains the log entry details, including the full context of the publishing attempt.
3.
GET /v1/posts/{postId}/logs- This endpoint retrieves all publishing logs for a specific post, showing the complete history of publishing attempts across all platforms.
-
postId (string, required): The ID of the post for which you want to retrieve logs.-
limit (integer, optional): Maximum number of logs to return (max 100, default 50).- The response includes an array of logs specific to the post and the count of logs returned.
New Feature
New Endpoints for Pinterest Integration
We have added two new endpoints to enhance your integration with Pinterest in headless mode:
1.
- Summary: List Pinterest Boards after OAuth (Headless Mode)
- Description: This endpoint retrieves a list of Pinterest boards available for selection after initiating OAuth via
- Parameters:
-
-
-
- Responses:
-
-
-
-
-
2.
- Summary: Select a Pinterest Board to complete the connection (Headless Mode)
- Description: Use this endpoint to save the selected board and complete the Pinterest account connection after OAuth.
- Request Body: Must include the following fields:
-
-
-
-
-
-
-
-
- Responses:
-
-
-
-
-
We have added two new endpoints to enhance your integration with Pinterest in headless mode:
1.
GET /v1/connect/pinterest/select-board- Summary: List Pinterest Boards after OAuth (Headless Mode)
- Description: This endpoint retrieves a list of Pinterest boards available for selection after initiating OAuth via
/v1/connect/pinterest with headless=true. It allows you to build your own fully-branded board selector instead of using Zernio's hosted UI.- Parameters:
-
X-Connect-Token (header, required): Short-lived connect token from the OAuth redirect.-
profileId (query, required): Your Zernio profile ID.-
tempToken (query, required): Temporary Pinterest access token from the OAuth callback redirect.- Responses:
-
200: Returns a list of Pinterest boards available for connection, including each board's ID, name, description, and privacy setting.-
400: Missing required parameters.-
401: Unauthorized access.-
403: No access to profile.-
500: Failed to fetch boards.2.
POST /v1/connect/pinterest/select-board- Summary: Select a Pinterest Board to complete the connection (Headless Mode)
- Description: Use this endpoint to save the selected board and complete the Pinterest account connection after OAuth.
- Request Body: Must include the following fields:
-
profileId (string, required): Your Zernio profile ID.-
boardId (string, required): The Pinterest Board ID selected by the user.-
boardName (string, optional): The board name for display purposes.-
tempToken (string, required): Temporary Pinterest access token from OAuth.-
userProfile (object, optional): User profile data from OAuth redirect.-
refreshToken (string, optional): Pinterest refresh token if available.-
expiresIn (integer, optional): Token expiration time in seconds.-
redirect_url (string, optional): Custom redirect URL after connection completes.- Responses:
-
200: Pinterest Board connected successfully, returns a message and account details.-
400: Missing required fields.-
401: Unauthorized access.-
403: No access to profile or profile limit exceeded.-
500: Failed to save Pinterest connection.Improvement
GET /v1/accounts/{accountId}/linkedin-mentions
This endpoint resolves a LinkedIn profile URL (for a person) or a company page URL (for an organization) to a URN that can be used to @mention them in posts.
Parameters:
-
-
- Person:
- Organization:
-
Response:
On success (HTTP 200), the response contains:
-
-
-
-
-
-
This change allows developers to mention organizations without needing to be an admin of a LinkedIn organization, which simplifies the process of tagging companies in posts. Person mentions still require admin access for the organization but now support a broader range of input formats.
This endpoint resolves a LinkedIn profile URL (for a person) or a company page URL (for an organization) to a URN that can be used to @mention them in posts.
Parameters:
-
accountId (path, required): The LinkedIn account ID.-
url (query, required): LinkedIn profile URL, company URL, or vanity name. It supports:- Person:
miquelpalet, linkedin.com/in/miquelpalet- Organization:
company/microsoft, linkedin.com/company/microsoft-
displayName (query, optional): The exact display name as shown on LinkedIn. This is required for clickable person mentions. If not provided, the name is derived from the vanity URL, which may not match exactly.Response:
On success (HTTP 200), the response contains:
-
urn: The LinkedIn URN (either for a person or organization).-
type: The type of entity, which can be either person or organization.-
displayName: The display name (provided, from API, or derived from vanity URL).-
mentionFormat: Ready-to-use mention format for post content.-
vanityName: The vanity name/slug (only for organization mentions).-
warning: A warning about clickable mentions (only present for person mentions if displayName was not provided).This change allows developers to mention organizations without needing to be an admin of a LinkedIn organization, which simplifies the process of tagging companies in posts. Person mentions still require admin access for the organization but now support a broader range of input formats.
Improvement
GET /v1/profiles
This endpoint lists profiles visible to the authenticated user. It now includes a new optional query parameter
Response contains:
-
-
-
-
-
-
-
GET /v1/accounts
This endpoint lists connected social accounts. It now also includes the
Response contains:
-
-
-
-
-
-
-
-
-
This endpoint lists profiles visible to the authenticated user. It now includes a new optional query parameter
includeOverLimit (type: boolean, default: false). When set to true, it includes profiles that exceed the user's plan limit, which will have isOverLimit: true in the response. This is useful for managing or deleting profiles after a plan downgrade. The profiles are sorted by creation date (oldest first).Response contains:
-
profiles: an array of profile objects, each containing:-
id: string-
name: string-
color: string-
isDefault: boolean-
isOverLimit: boolean (only present when includeOverLimit=true)-
createdAt: string (date-time)GET /v1/accounts
This endpoint lists connected social accounts. It now also includes the
includeOverLimit parameter (type: boolean, default: false). When true, it includes accounts from profiles that exceed the user's plan limit, allowing users to disconnect accounts from over-limit profiles for deletion.Response contains:
-
accounts: an array of account objects, each containing:-
id: string-
platform: string-
profileId: object containing profile details-
username: string-
displayName: string-
profileUrl: string-
isActive: boolean-
hasAnalyticsAccess: boolean (indicates if the user has analytics add-on access)Improvement
The
-
-
-
These fields help you understand why an account was disconnected and quickly look up related data.
account.disconnected webhook payload now includes additional fields for better context:-
accountId - The account's unique identifier (same as used in /v1/accounts/{accountId})-
profileId - The profile's unique identifier this account belongs to-
disconnectionType - Either intentional (user disconnected manually) or unintentional (token expired or was revoked)These fields help you understand why an account was disconnected and quickly look up related data.
Was this page helpful?