Zernio
Zernio
QuickstartBuild a PlatformSDKsCLIMCPWebhooksWorkflowsGuidesSecurityGlossaryPricingBillingChangelogRefer & earn
Dashboard
llms.txtOpenAPI
OverviewPlatformsAPI ReferenceResources

Changelog

Every change to the Zernio API, newest first.


Significant changes are announced here, on the Telegram channel and on X. Every entry is tagged with the platforms and areas it touches: filter below by platform and type, and share the URL (for example /changelog?platform=whatsapp&type=breaking_change). The same filters work on the RSS feed (/changelog/feed.xml?platform=whatsapp) and on /changelog.json. To automate on changes, subscribe a webhook to api.changelog.published: each entry arrives as it is published, with its tags and the diff of the OpenAPI spec behind it as data. List API changelog entries returns the same entries, with a platform filter, for back-filling.

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.

RSS for this viewJSONWebhook

818 entries

October 6, 2026New feature
Video media items now support subtitle/closed-caption tracks via MediaItem.subtitles.

Provide up to 20 tracks (SRT/WebVTT). Zernio fetches and validates each file when the post is created/updated, converts as needed per platform, and skips unsupported tracks with response warnings.

Use:
• subtitles (array, max 20)
• subtitles[].url (public direct http(s) URL; redirects refused)
• subtitles[].language (BCP-47, e.g. en, pt-BR)

Uploads via POST /v1/media/presign now accept contentType values: application/x-subrip, text/vtt.

Validation: unreachable/redirecting files, >1 MB, invalid SRT/VTT, or platform-limit violations are rejected with 400 (INVALID_SUBTITLES).
October 6, 2026Improvement
TikTok Commercial Music: when attaching CML audio, prefer the track’s clip.id as tiktokSettings.musicSoundInfo.musicSoundId (fall back to id only if clip is missing).

Both IDs can publish, but using the full-track id has been observed to show viewers “This song is not available in your country” on the sound page (e.g., Germany), while clip.id opens a working sound page for the same track.

Use:
• GET /v1/accounts/{accountId}/tiktok/commercial-music → tracks[].clip.id
• Post creation → tiktokSettings.musicSoundInfo.musicSoundId
October 6, 2026Improvement
LinkedIn boosts/ads created with adStatus: PAUSED now create the LinkedIn creative as DRAFT (LinkedIn does not allow PAUSED until review is APPROVED).

A DRAFT creative is not submitted for review and cannot serve; it will read back as paused. To start review and delivery, resume the ad via PUT /v1/ads/{adId}/status with active.

Applies to:
• POST /v1/ads/boost with adStatus
• POST /v1/ads/create (attach shape via adSetId) with adStatus

Key field: adStatus (ACTIVE | PAUSED)
October 6, 2026Breaking change
DELETE /v1/ads/{adId} can now also clean up empty parent objects on Meta.

When cancelling an ad, Zernio may delete the now-empty ad set and then the campaign too, but only if they were created by Zernio (via POST /v1/ads/create or POST /v1/ads/boost) and Meta reports no other ads/ad sets (archived ads count). Parents created outside Zernio (or from imported ads) are always kept.

If you want to delete a whole campaign explicitly, use DELETE /v1/ads/campaigns/{campaignId}.

Also, POST /v1/ads/boost now supports per-level publish control:
• campaignStatus: ACTIVE | PAUSED
• adSetStatus: ACTIVE | PAUSED
• adStatus: ACTIVE | PAUSED

These override status for their respective level (and are rejected with adSetId/existingCampaignId where that level isn’t created).
October 6, 2026New feature
Messaging ads now support carousel creatives on POST /v1/ads/messaging via carouselCards.

You can create a 2–10 card image carousel where every card opens the same conversation (WhatsApp/Messenger/Instagram Direct), instead of using imageUrl or video.

Use carouselCards (2–10 items) with:
• imageUrl (required)
• headline (optional)
• description (optional)

Notes:
• carouselCards replaces imageUrl/video (sending either is a 400)
• Requires body
• Not compatible with placementAssets or existing-post fields (platformPostId/objectStoryId/existingPostId)
• validateOnly: true now supports carousel validation
October 6, 2026Improvement
POST /v1/sms/messages can now return 403 when the API key is profile-scoped and the from sender belongs to a profile outside the key’s scope (including alphanumeric sender IDs).

This helps surface sender-scope issues explicitly instead of failing in other ways.

Handle the new response:
• 403 - profile-scoped key cannot use the provided from
October 5, 2026Improvement
comment.received webhook payload now includes Instagram ad context comment.ad.originalMediaId.

This lets you correlate comments on paid Instagram content to the original media Meta reports for the ad (when Meta includes it).

New field:
• comment.ad.originalMediaId (Instagram only, optional)

Event: comment.received
October 5, 2026Improvement
Google Ads keyword endpoints now return more specific 429 rate limit details for Zernio burst limits.

When you hit a Zernio-side burst limit (before the call reaches Google), the error details can include budgetScope so you can throttle correctly per scope.

Look for details.budgetScope:
• account - 15 requests/min per connected Google Ads account
• user - 120 requests/min per Zernio user across all their Google Ads accounts

Applies to POST /v1/ads/keywords/ideas and POST /v1/ads/keywords/historical-metrics (429 responses).
October 5, 2026Breaking change
GET /v1/analytics/post-timeline now returns follows as nullable (integer | null).

This reflects platform limitations: for some Meta media types the follows metric isn’t available, so it will be null instead of 0.

Update your parsing/aggregation logic to handle:
• follows: null (not reported)
• follows: 0 (reported, but zero)

Key field: timeline[].follows
October 5, 2026New feature
New endpoint GET /v1/ads/accounts/live reads a Meta ad account’s campaigns and ad sets live from Meta (no sync delay) in one request, and stores nothing.

Use it when you need current daily_budget/lifetime_budget, bid settings, targeting, etc. before making writes (e.g. spend gates).

Required params:
• accountId (Zernio SocialAccount id)
• adAccountId (Meta act_<n>)

Optional params:
• status (comma-separated Meta effective_status; defaults to all except DELETED, ARCHIVED; ad sets also support CAMPAIGN_PAUSED)
• limit (1–500, default 200)
• level = campaign | adSet (required with after)
• after (cursor from paging.campaigns.after / paging.adSets.after)

Notes:
• Budgets/bids are returned in whole units of currency (Meta minor units converted).
• Other platforms return 501 (Meta only today).
October 5, 2026New feature
Meta ad video uploads now support async processing, with a new status endpoint and webhook.

Use POST /v1/ads/videos with async: true to return immediately (202) after Meta accepts the bytes, then wait for processing to finish before using video.id on create endpoints.

New endpoint: GET /v1/ads/videos/{videoId}
Required: videoId (path), accountId (query), adAccountId (query)
Returns video.status: processing | ready | error plus platformStatus, processingProgress (0-100), error, thumbnailUrl.

Upload responses:
• 201 (default): video.status: ready
• 202 (async: true): video.status: processing

Webhook: subscribe to ad.video.processed (fires for async: true uploads) with video.status: ready | error.
October 5, 2026New feature
You can now filter GET /v1/ads/campaigns by a specific platform campaign ID using campaignId.

This lets you fetch exactly one campaign (or an empty list if it isn’t visible to the caller), matching the same filter behavior as /v1/ads and /v1/ads/tree.

Use: campaignId=<platformCampaignId>

Ad set duplication now supports TikTok via POST /v1/ads/ad-sets/{adSetId}/duplicate.

Set platform to tiktok (in addition to facebook, instagram). Note: TikTok ignores startTime, endTime, and renameStrategy; renamePrefix/renameSuffix still apply, and statusOption must be PAUSED (or omitted).
October 5, 2026Improvement
POST /v1/ads/create now supports per-level status controls via campaignStatus, adSetStatus, and adStatus (each: ACTIVE | PAUSED).

This lets you choose exactly which level is created paused (campaign vs ad set vs ad), instead of relying only on the top-level status behavior.

Use:
• campaignStatus to pause/activate only the new campaign (ignored when using existingCampaignId or adSetId)
• adSetStatus to pause/activate only the new ad set (400 with adSetId)
• adStatus to pause/activate only the new ad (works with adSetId; X returns 400)

Precedence: level statuses win for their level; status: PAUSED only adds a “hold” when none of the level statuses are PAUSED. To create everything paused, set all three to PAUSED.
October 5, 2026Breaking change
GET /v1/accounts/{accountId}/facebook-page now returns the Instagram professional account linked to each Facebook Page via pages[].instagramAccount.

This lets you discover the IG account tied to a Page (and use its id when creating ads). The endpoint also now works for both facebook and metaads accounts.

Key fields:
• pages[].instagramAccount (object|null) with id and username
• selectedPageId is now string|null (null for classic metaads connections)
• If your cached list predates this field, call with refresh=true to populate it

PUT /v1/accounts/{accountId}/facebook-page now clarifies behavior for metaads: classic metaads connections return 400 (no default Page); pass pageId per ad on POST /v1/ads/create instead. It can also return 409 with reconnect_required when the Page has no stored access token.
October 5, 2026Improvement
Rate limiting is now standardized across Meta ads endpoints, and GET /v1/ads/insights documents consistent 429 behavior.

For Meta throttles (codes 4, 17, 32, 613, 80000-80014), Zernio returns 429 rate_limited with a Retry-After header (even when Meta responds with HTTP 400).

Handle 429 by reading:
• Retry-After (seconds)
• JSON error body: ErrorResponse

Applies to: GET /v1/ads/insights, POST /v1/ads/insights/reports, GET /v1/ads/insights/reports/{reportRunId}, POST /v1/ads/preview, GET /v1/ads/{adId}/preview.
October 5, 2026New feature
New endpoint: GET /v1/accounts/{accountId}/instagram/business-discovery.

You can now look up any public Instagram Business or Creator account by username and retrieve its public profile plus most recent media (useful for competitor/market research).

Call with:
• accountId (path) - a connected Instagram account via Facebook Login
• username (query) - handle with/without @
• limit (query, default 12, max 25)

Response includes profile and media (most recent first). Media fields include mediaType (IMAGE/VIDEO/CAROUSEL_ALBUM) and mediaProductType (FEED/REELS); likeCount is null when hidden.

Note: requires Facebook Login; classic Instagram Login will return 400 instagram_business_discovery_requires_facebook_login.
October 4, 2026New feature
New endpoint: GET /v1/analytics/facebook/demographics returns Facebook Page follower demographics (country/city) from Meta’s latest daily snapshot.

Use accountId (required) and optional breakdown (country, city; defaults to both). Response includes snapshotDate (may be null) and demographics.country/demographics.city arrays.

Requires Analytics access; missing access returns 402 (analytics_addon_required).

Also updated: DELETE /v1/ads/audiences/{audienceId} now documents platform-specific removal behavior and adds 422 when a platform can’t remove an audience via API (e.g., Google lookalike lists) or refuses removal; on refusal, the Zernio record is kept so retries are safe.
October 4, 2026New feature
Custom audiences now support new engagement audience types: tiktok_engagement and pinterest_engagement.

You can create and list these via POST /v1/ads/audiences and GET /v1/ads/audiences using type = tiktok_engagement or pinterest_engagement.

Key request fields:
• TikTok engagement: type=tiktok_engagement, retentionDays, source (ads | organic_video | live_video | business_account), event, plus sourceIds/identityId/identityType as required
• Pinterest engagement: type=pinterest_engagement, optional engagerType, engagementType (click | save | closeup | comment | like), and at least one of engagementDomains/campaignIds/adIds/pinIds

Also updated: website and lookalike audience creation now supports additional platforms with platform-specific validation (e.g. Google website rejects pixelId; TikTok/Google lookalike uses size (narrow | balanced | broad) instead of ratio).
October 4, 2026Improvement
GET /v1/analytics/post-timeline now returns Facebook Reels retention metrics via two new fields: replays and retentionCurve.

This lets you track how many plays were replays and how viewer retention changes over time (per day, per platform row). These fields are populated for Facebook Reels and return 0/{} elsewhere.

New fields:
• replays - Facebook Reels replay count
• retentionCurve - map of "second" → fraction (0..1) still watching at that second
October 3, 2026Improvement
igReelsAvgWatchTime and igReelsVideoViewTotalTime analytics now cover more than Instagram Reels.

These watch-time metrics are now reported for Instagram Reels, Facebook Reels, and TikTok videos (business accounts), instead of Instagram Reels only.

Key fields:
• PostAnalytics.igReelsAvgWatchTime (ms per play)
• PostAnalytics.igReelsVideoViewTotalTime (ms total, incl. replays)
• AnalyticsDeltaEntry.metrics.igReelsAvgWatchTime
• AnalyticsDeltaEntry.metrics.igReelsVideoViewTotalTime
October 3, 2026New feature
Meta attach mode (adSetId) now supports setting a call number via phoneNumber.

Use this when adding an ad to a Meta "website and phone call" ad set (destination_type WEBSITE_AND_PHONE_CALL). If omitted, Zernio reuses the number from existing ads in the ad set; if none exists, the request returns 400.

Set phoneNumber as E.164:
• +4712345678 (pattern ^\+[1-9]\d{6,14}$)

Constraints:
• Only allowed with adSetId (Meta attach shape)
• Rejected with existingCreativeId
• Rejected on non-call ad sets and on non-Meta platforms
October 3, 2026New feature
X ads created via POST /v1/ads/create now support website cards.

You can create an X website card by sending imageUrl + headline + linkUrl together (the URL is not added to the post text).

X rules:
• Website card requires imageUrl, headline, and linkUrl (sending only some of them returns 400)
• description, video, and callToAction are rejected with 400 on X
October 3, 2026New feature
Google ads targeting now supports interests via targeting.interests.

On Google, interests are applied as ad-group interest criteria (affinity + in-market) for newly created Search/Display ad groups.

Use targeting.interests = [{ id, name? }].

Constraints on Google:
• Not supported on Performance Max (400)
• Not supported on Demand Gen (use Demand Gen audience userInterests)
• Not allowed when attaching to an existing ad group via adSetId (400)
• interests are create-only on Google
October 3, 2026New feature
TikTok now supports Smart Targeting on ad groups via smartTargeting.

This lets TikTok expand delivery beyond your selected audiences/interests using TikTok’s smart targeting flags.

Available on:
• PUT /v1/ads/ad-sets/{adSetId} (TikTok only; 400 on other platforms)
• POST /v1/ads/boost (TikTok only; not allowed with smartPlus: true or when attaching via adSetId)
• POST /v1/ads/create (TikTok only; not allowed with smartPlus: true or when attaching via adSetId)

Set:
• smartTargeting.audience (boolean)
• smartTargeting.interestsBehaviors (boolean)

Only the flags you send are written; send false explicitly if you need to verify it’s off (unsent flags may read back as null in native settings).
October 3, 2026Improvement
GET /v1/ads/targeting/search now supports TikTok advertiser scoping via a new optional adAccountId query parameter.

If your TikTok connection contains multiple advertisers, you can now choose which advertiser’s targetable markets/catalogs are used for geo/targeting searches (instead of defaulting to the first advertiser).

Use:
• adAccountId - TikTok advertiser id to search as (optional; invalid/unowned advertiser returns 400)
October 3, 2026Improvement
GET /v1/ads/targeting/search now returns platformId on country results.

This lets you keep using id as the ISO 3166-1 alpha-2 code for targeting.countries, while also having the platform’s native country identifier available for reconciliation with platform reads.

Key fields on a country result:
• id = ISO country code (e.g. GB)
• platformId = platform-native country id (e.g. TikTok location_id/GeoNames id, Google geo target constant id, LinkedIn geo URN)

Only present when type is country (dimension geo).
October 3, 2026Improvement
GET /v1/ads/ad-sets now returns TikTok live configuration details when you pass live=true.

For TikTok ad sets read live, you can now inspect the platform-applied settings verbatim (not stored) to verify what TikTok actually applied (budget, schedule, placements, targeting, exclusions, etc.).

New fields (TikTok only, only with live=true and only on rows read live):
• nativeSettings - TikTok adgroup/get record verbatim (+ advertiser_currency, advertiser_timezone)
• configReadAt - timestamp when nativeSettings was read (null if not read now)

Also added to the AdCampaign schema for TikTok live reads:
• nativeSettings
• configReadAt
October 2, 2026New feature
New endpoint: GET /v1/ads/{adId}/review returns the platform’s review verdict for an ad.

This lets you read TikTok’s audit outcome (including partial delivery restrictions and detailed rejection reasons) without re-enabling a paused ad.

Key params/fields:
• Path: adId (Zernio ad id or platform ad id)
• Response: review.approved, review.reviewStatus (ALL_AVAILABLE | PART_AVAILABLE | UNAVAILABLE)
• Restrictions: review.forbiddenPlacements, review.forbiddenAges, review.forbiddenLocations, review.forbiddenOperatingSystems
• Rejections: review.rejections[] with reasons[], suggestion, and reviewed content

TikTok-only: other platforms return 501. 404 if the ad isn’t found, has no TikTok ad id yet, or TikTok has no review record.
October 2, 2026Improvement
GET /v1/ads/ad-sets now returns TikTok’s applied optimization goal and billing event when using live=true.

This lets you verify what TikTok actually applied (from adgroup/get), not just what was requested.

With platform=tiktok and live=true (requires campaignId or adSetId), rows read live may include:
• optimizationGoal (e.g. ENGAGED_VIEW, ENGAGED_VIEW_FIFTEEN, CLICK, CONVERT)
• billingEvent (e.g. CPV, CPC, OCPM)

Only present for TikTok and only on rows that were read live (others will be absent/null).
October 2, 2026Breaking change
Google Business Profile, Discord, and Slack connect endpoints now support batch connecting multiple destinations in one call.

You can connect several GBP locations / Discord channels / Slack channels at once (up to 25). When you send 2+ distinct ids, the response returns accounts and failed (instead of account), and may return 422 if none could be connected.

Google Business Profile: POST /v1/connect/googlebusiness/select-location
• Request now requires only profileId + pendingDataToken
• Send locationId or locations (not both)
• locations[]: { locationId, accountId? }
• New response fields for batch: accounts, failed; new 422 when none connect

Discord: POST /v1/connect/discord
• channelId is no longer required (breaking)
• Send channelId or channelIds (not both)
• New redirect_url (only for channelIds) and new 422 when none connect

Slack: POST /v1/connect/slack
• channelId is no longer required (breaking)
• Send channelId or channelIds (not both)
• New redirect_url (only for channelIds) and new 422 when none connect
October 2, 2026Improvement
Ordering iMessage senders via POST /v1/imessage/senders/order may now charge the first month before the number is bought when your account spend threshold is below the sender price.

This also clarifies failure/retry behavior so clients can handle billing and idempotent retries correctly.

Key updates:
• 402 now also covers first-month card declines (code: payment_required); nothing is ordered — update Billing and order again
• 409 can be code: invalid_resource_state when first-month payment isn’t confirmed in time; retry within 1 hour using the same purchaseIntentId (details.purchase_intent_id) to avoid double-charging
• 502: provider rejection; nothing charged, or any first month already billed is returned as account credit

Use purchaseIntentId as the idempotency key for safe retries.
October 2, 2026Improvement
GET /v1/accounts now supports server-side search, category filtering, and sorting for easier account discovery (especially when combined with pagination).

New query params:
• search - case-insensitive match on username/display name/platform user id, or exact account id
• category - social | ads | communication | blogs
• sort - account | platform | profile | status | connected
• order - asc | desc (default asc)
October 2, 2026Improvement
Error responses now may include a documentation URL via docUrl in ErrorResponse.

When present, docUrl points to a page describing how to resolve the specific error.

New field:
• docUrl (string)
October 2, 2026New feature
New endpoint: POST /v1/phone-numbers/{id}/whatsapp/request-code to request (or restart) WhatsApp verification for a Zernio-hosted number.

This triggers Meta to send a verification code (captured on the number) and can also activate the number immediately if Meta already reports it as verified.

Optional body: method = SMS | VOICE (omit to let Zernio choose).

Success response may include: alreadyVerified, replaced, newPhoneNumber (when a never-live number is replaced).

Possible conflicts: 409 with code such as number_not_whatsapp_eligible or whatsapp_number_in_use; 429 may include retryAt.
October 2, 2026Improvement
Google ads now return YouTube video IDs on video-based ads via creative.youtubeVideoIds.

This lets you identify the underlying YouTube assets for Google Video campaign ads (video/responsive video) and Demand Gen video ads; when an ad has no image, thumbnailUrl is derived from the first video’s YouTube thumbnail.

New field:
• creative.youtubeVideoIds (array of strings) — absent on ads without a video
October 2, 2026Breaking change
CampaignAnalyticsResponse.campaign.status now returns the platform’s own campaign status (same as platformCampaignStatus on /v1/ads/campaigns and /v1/ads/tree), instead of an “effective” derived status.

This means values are now platform-specific (e.g. Google ENABLED/PAUSED/REMOVED, Meta ACTIVE/PAUSED, ...). For older synced campaigns, it falls back to a child ad’s status as described.

If you were checking for ACTIVE (or other normalized values), update your logic to handle platform-specific statuses from campaign.platform + campaign.status.
October 1, 2026Breaking change
Profiles can now hold multiple accounts per platform in the connect flow.

Connecting a different identity now adds a new account, and reconnecting the same identity refreshes it (ads connections remain one-per-profile). Some selection/assignment endpoints were updated to match this behavior.

Key changes:
• GET /v1/connect/{platform}: connecting a different account adds it; legacy plans may return 403 platform_account_limit once the per-platform cap is reached
• POST /v1/connect/instagram/select-account: no longer replaces the existing Instagram account on the profile; it adds/refreshes instead
• POST /v1/accounts/{accountId}/gmb-locations/assign: target profileId may already have other Google Business locations; assigning an existing one refreshes it (previously 409 if any GBP connection existed)
• POST /v1/imessage/senders: a profile can now register several iMessage senders; re-registering the same sender refreshes
• POST /v1/rcs/agents: a profile can now hold several RCS agents

Note: WhatsApp number selection conflicts were tightened in POST /v1/connect/whatsapp/select-phone-number (the one_whatsapp_per_profile conflict is no longer listed there).
October 1, 2026New feature
GET /v1/accounts now supports a per-profile preview mode via profileIds and perProfile.

This lets you request a compact “preview” set of accounts per profile (useful for dashboards/lists) and also returns per-profile totals.

New query params:
• profileIds — comma-separated profile IDs (up to 50)
• perProfile — integer (1–24); requires profileIds; cannot be combined with page/limit

New response field (only with profileIds + perProfile):
• profileTotals — map of { profileId: count } for accounts matching the filters

Schema addition:
• Profile.accountCount — connected account count shown in the profile list
October 1, 2026Improvement
Billing for branded calling identities created via POST /v1/branded-calling/identities has changed.

You are now charged $100/identity/month only while the identity is verified. Nothing is charged while the identity is in Zernio review or carrier vetting, and rejected identities are not charged. If you edit a verified identity and it goes back to vetting, the monthly fee is paused until it is verified again.

Applies to identities created with POST /v1/branded-calling/identities (status starts as requested).
October 1, 2026Improvement
GET /v1/changelog no longer requires an API key and is now rate-limited (120 requests/min per address).

If you exceed the limit, the endpoint can return 429 (RateLimited), so make sure your client handles 429 responses (e.g., backoff/retry).
October 1, 2026New feature
API changelog is now available via GET /v1/changelog.

You can page through changelog entries (newest first) and filter by type and platform. Each entry includes the announcement (message) plus a deterministic OpenAPI diff in changes for automation.

Query params:
• type: new_feature | breaking_change | improvement | deprecation | minor
• platform: slug (e.g. whatsapp)
• before: cursor (date-time)
• limit: 1–100

Webhooks can now subscribe to api.changelog.published by including it in events on POST /v1/webhooks/settings or PUT /v1/webhooks/settings.
October 1, 2026Breaking change
POST /v1/connect/linkedin/select-organization now supports connecting multiple LinkedIn accounts (personal and/or organizations) from a single sign-in via selections.

Breaking change: accountType is no longer required at the top level (required is now profileId, tempToken, userProfile). Send either accountType (+ selectedOrganization when needed) or selections, not both.

Use selections (1..25 items), each with:
• accountType = personal | organization
• selectedOrganization (for organization)

When using selections, the 200 response returns accounts and failed (instead of a single account), and a new 422 is returned if none of the selections could be connected (with per-item reasons in details.failed).
October 1, 2026Improvement
Facebook and Instagram connect flows now support connecting multiple Pages/accounts in one request via pageIds.

Send pageIds (array, 1–25) instead of pageId to connect several accounts from a single sign-in. With 2+ distinct IDs, the success response returns accounts and failed (instead of account).

Applies to:
• POST /v1/connect/facebook/select-page: pageIds added; pageId is now optional (send pageId or pageIds, not both)
• POST /v1/connect/instagram/select-account: pageIds added; pageId is now optional (send pageId or pageIds, not both)

New error case for pageIds: 422 when none of the requested Pages/accounts could be connected (see details.failed for per-ID reasons).
October 1, 2026Breaking change
X ads creation via POST /v1/ads/create is now more strict: X no longer accepts creative fields like imageUrl (and also headline, description, video, callToAction) and will return 400.

For X, an ad is treated as a promoted post built from body + linkUrl. If you need an image/video, create/choose a post that contains the media and promote it via POST /v1/ads/boost instead.
October 1, 2026New feature
New endpoint GET /v1/analytics/dashboard returns an analytics dashboard in a single call (totals, follower growth, per-day series, top posts, recent posts), with optional previous-period comparison.

Query params:
• fromDate (required, YYYY-MM-DD), toDate (required, YYYY-MM-DD, max 366 days)
• profileId (default all), platform (default all)
• compare: previous_period
• topPosts (0–25, default 5), recentPosts (0–25, default 10)

Response includes totals, followers, daily, topPosts, recentPosts, and dataAsOf (plus previousTotals/previousFollowers when compare=previous_period). Requires the Analytics add-on (HTTP 402 if not enabled).
October 1, 2026Improvement
Google ads targeting now supports age and gender on Google Search/Display.

You can now include ageMin/ageMax and gender in TargetingSpec for Search/Display; Google will exclude ranges/genders outside your request.

Use:
• ageMin, ageMax
• gender: all, male, female

Note: ageMin/gender are still rejected (400) on Google Performance Max and Demand Gen.
October 1, 2026Improvement
Pinterest ads now support language targeting via targeting.languages.

You can restrict the audience by language on Pinterest (previously this field was rejected with 400).

Use targeting.languages as an array of language codes (e.g. ["en"]).
October 1, 2026Improvement
Ad targeting now supports age, gender, and languages on more platforms via TargetingSpec (used in POST /v1/ads/create and POST /v1/ads/targeting/reach-estimate).

You can now set:
• ageMin / ageMax on LinkedIn and X (in addition to Meta/TikTok/Pinterest)
• gender on LinkedIn and X (in addition to Meta/TikTok/Pinterest); values: all, male, female
• languages on TikTok, LinkedIn, and X (in addition to Meta/Google)

Note: ageMin/ageMax and gender are still rejected on Google; languages is still rejected on Pinterest. Unsupported values return 400 with details.
October 1, 2026Breaking change
GET /v1/connect/{platform}/ads now uses accountId to select the posting account for same-token platforms when a profile has multiple active accounts.

If your profile has several active accounts for platform (facebook, instagram, linkedin, pinterest), pass accountId to choose which one the ads connection should use; with only one active account it can be omitted.

New failure case: 409 when the selection is ambiguous (error_reason=ambiguous_account).
October 1, 2026Breaking change
Ad targeting fields that a platform can’t honor are now rejected (400 INVALID_FIELD_VALUE) on create, boost, and targeting update (previously some were documented as ignored).

This makes cross-platform targeting stricter: if you send unsupported fields for a given platform, the request will fail instead of being dropped/ignored.

Notable platform restrictions called out in TargetingSpec:
• ageMin / gender: rejected on Google, LinkedIn, X, OpenAI
• languages: only applied on Meta + Google; rejected on TikTok, LinkedIn, Pinterest, X, OpenAI
• interests: rejected on Google and OpenAI

Also clarified: userOs and userDevice are Meta only.
October 1, 2026New feature
Meta ads now support a new goal: page_visits.

Use it to create Meta "Page visits and followers" campaigns/ads (Traffic objective with Facebook Page destination).

Set goal to page_visits on:
• POST /v1/ads/campaigns
• POST /v1/ads/create

Enum update: goal now includes page_visits (in addition to existing values).
October 1, 2026Improvement
GET /v1/ads/accounts now returns TikTok advertiser balance details in each item.

This lets you display available spend (cash + grant) for TikTok ad accounts; cashBalance and grantBalance are only present when the connected token has a finance role in the advertiser’s Business Center.

New response fields (TikTok only):
• balance
• cashBalance
• grantBalance
September 30, 2026New feature
LinkedIn now supports behavior targeting via TargetingSpec.behaviors.

You can search LinkedIn member behaviors (e.g. Frequent Travelers, Job Seekers) and then pass the returned IDs in your targeting when creating ads.

Use:
• GET /v1/ads/targeting/search with dimension=behavior
• then set targeting.behaviors (or top-level behaviors on POST /v1/ads/create) to [{ id }] (IDs look like urn:li:memberBehavior:9)
September 30, 2026Improvement
Account health responses now include automation DM retry timing when Meta temporarily refuses messaging.

When messagingRestriction is present, you may also receive pausedUntil (date-time) to indicate when held automation DMs will be retried next.

New field:
• messagingRestriction.pausedUntil - next retry time (or null when nothing is held)

Available on:
• GET /v1/accounts/health
• GET /v1/accounts/{accountId}/health
September 30, 2026New feature
Comment-to-DM automations now support tap-to-unlock gating for Instagram via audience.tapToUnlock.

When enabled, Zernio sends the followGate.message + button to every commenter and only delivers the real DM after they tap, with no follow check.

Set:
• audience.tapToUnlock: true
• followGate.message (optional; used for whenUnknown=verify and tapToUnlock=true)
• followGate.buttonLabel (optional; defaults to “Unlock” for tap-to-unlock)

Notes:
• whenUnknown is ignored when tapToUnlock is true
• Cannot be combined with followerStatus != any or with minFollowerCount (400)
• Sending audience in a PATCH without tapToUnlock clears it
September 30, 2026New feature
TikTok now supports behavior targeting via behaviors in POST /v1/ads/create (and in TargetingSpec / saved targeting).

You can target TikTok audiences based on video/creator interaction categories (e.g. watched/liked/commented/shared in the last 15 days, or creator interactions). Use GET /v1/ads/targeting/search with dimension=behavior to discover ids.

Set:
• behaviors: [{ id: "video:..." }] or behaviors: [{ id: "creator:..." }]

Note: TikTok behavior targeting uses TikTok placement only.
September 30, 2026New feature
Webhooks now support voice message playback status via the new event message.played.

This lets you track when an outgoing WhatsApp voice message is played, similar to existing delivery/read status updates.

Subscribe by adding message.played to events when calling POST /v1/webhooks/settings (or update via PUT /v1/webhooks/settings).

The shared delivery-status webhook payload WebhookPayloadMessageDeliveryStatus.event now includes: message.delivered, message.read, message.played, message.failed.

In GET /v1/inbox/conversations/{conversationId}/messages, deliveryStatus can now be played (in addition to sent, delivered, read, failed, deleted).
September 30, 2026Improvement
WhatsApp message.received webhooks now include the unsupported content kind in metadata.unsupported.type.

This helps you distinguish what WhatsApp couldn’t deliver (e.g. view-once, polls, edits) instead of only seeing the generic [Unsupported message] placeholder.

When metadata.unsupported is present, you can now read:
• metadata.unsupported.code
• metadata.unsupported.title
• metadata.unsupported.details
• metadata.unsupported.type (e.g. view_once, poll_creation, group_invite, edit)
September 30, 2026Improvement
GET /v1/inbox/conversations/search now returns additional WhatsApp participant identifiers in each conversation.

This helps you anchor identity more reliably across conversations (stable ID) and optionally display the user’s current WhatsApp username.

New fields (WhatsApp only):
• businessScopedUserId — Meta BSUID (stable identity anchor; present when provided by Meta)
• whatsappUsername — participant username (not stable; captured from inbound messages, may be null until next inbound)
September 30, 2026Improvement
WhatsApp inbound message.received webhooks now include forwarded indicators and CTWA welcome message context.

This lets you detect forwarded content for moderation/routing, and capture the greeting text Meta showed on Click-to-WhatsApp ads for attribution.

New fields in WebhookPayloadMessage.metadata:
• forwarded (boolean)
• frequentlyForwarded (boolean)
• referral.welcome_message.text (string)

WhatsApp audio attachments now include voice-note metadata:
• message.attachments[].payload.voice (boolean) — true for WhatsApp voice notes, false for regular audio files
September 30, 2026Improvement
WhatsApp conversations now include participant identity/display fields in the Inbox conversation APIs.

This helps you anchor WhatsApp users to Meta’s stable BSUID (and optionally show their WhatsApp username) when listing or fetching a conversation.

New fields:
• GET /v1/inbox/conversations → businessScopedUserId, whatsappUsername
• GET /v1/inbox/conversations/{conversationId} → businessScopedUserId, whatsappUsername

Notes:
• businessScopedUserId is the recommended stable identity anchor (WhatsApp only)
• whatsappUsername (e.g. jane.shop) is for display and can change
September 30, 2026Improvement
OAuth connect now supports safe reconnect via reconnectAccountId on GET /v1/connect/{platform}.

Use it to refresh a specific existing account and prevent a login from accidentally writing to a different account on the same profile; mismatches are rejected with reconnect_account_mismatch.

New parameter:
• reconnectAccountId — Zernio account id to refresh (must be same platform and on the same profileId, otherwise 400)

Notes:
• In headless=true flows, keep the returned redirect_url unchanged when calling the selection endpoints so the marker is preserved.
• On X (twitter), reconnectAccountId counts toward the 500-char OAuth state limit.
September 30, 2026New feature
New endpoint GET /v1/whatsapp/pricing-analytics returns WhatsApp message volume and approximate cost for a connected number, read live from Meta’s pricing_analytics (figures can lag and are approximate).

Query params:
• accountId (required)
• start, end (required, ISO 8601)
• granularity (required): HALF_HOUR | DAILY | MONTHLY
• Optional breakdowns via dimensions: COUNTRY, PHONE, PRICING_CATEGORY, PRICING_TYPE, TIER
• Optional filters: metricTypes (COST, VOLUME), pricingTypes (REGULAR, FREE_CUSTOMER_SERVICE, FREE_ENTRY_POINT), pricingCategories, countryCodes

Response includes dataPoints with start/end, optional dimension fields, plus volume and cost.
September 30, 2026Improvement
messagingRestriction in account health responses has been expanded to cover WhatsApp and now returns both Meta subcodes and WhatsApp Cloud API error codes.

This helps you detect messaging blocks/restrictions across Facebook/Instagram and WhatsApp from observed send/delivery failures (not a live probe).

In GET /v1/accounts/health and GET /v1/accounts/{accountId}/health, messagingRestriction now includes:
• code (WhatsApp Cloud API; nullable)
• subcode (Meta FB/IG; nullable)
• message (nullable)
• firstSeenAt, lastSeenAt

WhatsApp codes tracked: 131042, 131031, 368. FB/IG subcodes tracked: 2534122, 1893063, 2534029.
September 30, 2026New feature
Webhooks now support new WhatsApp account-level events: whatsapp.account.quality_updated, whatsapp.account.status_updated, whatsapp.account.alert_received.

Use these to get notified when Meta changes a connected number’s quality rating / messaging tier, when a WhatsApp Business Account is restricted/reinstated, and when Meta sends account alerts.

Subscribe by including the events in events when calling POST /v1/webhooks/settings or PUT /v1/webhooks/settings:
• whatsapp.account.quality_updated
• whatsapp.account.status_updated
• whatsapp.account.alert_received
September 30, 2026New feature
Webhooks now support a new WhatsApp event: whatsapp.contact.identity_changed.

This fires when Meta reports a WhatsApp user’s identifier changed (phone number and/or BSUID). Zernio re-keys the inbox conversation/contact channel before emitting the event.

To subscribe, include whatsapp.contact.identity_changed in events when calling POST /v1/webhooks/settings (or update via PUT /v1/webhooks/settings).

Payload key fields:
• reason: user_changed_number | user_changed_user_id | user_identity_changed | user_id_update
• previous, current: phoneNumber, businessScopedUserId, parentBusinessScopedUserId, whatsappUsername
• contactId, conversationId
• changedAt, timestamp
September 30, 2026New feature
WhatsApp webhooks now include Meta billing details via pricing and billingConversation on message.sent, message.delivered, message.read, and message.failed.

This lets you attribute message costs/categories and detect free entry point billing windows directly from webhook events (fields are absent on non-WhatsApp platforms and may be null on some statuses).

pricing:
• billable
• pricingModel (PMP | CBP)
• category (e.g. marketing, utility, authentication, service)
• type (regular | free_customer_service | free_entry_point)

billingConversation:
• id
• expiresAt
• originType (e.g. utility)
September 30, 2026Improvement
Switching destinations can now return 409 Conflict when the target is already connected by another account on the same profile.

Applies to:
• PUT /v1/accounts/{accountId}/facebook-page
• PUT /v1/accounts/{accountId}/linkedin-organization
• PUT /v1/accounts/{accountId}/gmb-locations

Handle 409 with ErrorResponse when you hit profile_platform_conflict.
September 30, 2026New feature
GET /v1/ads/tree now supports incremental syncing and bulk campaign filtering.

You can fetch only campaigns that changed since a timestamp (new ad, or changes to ad status/review status/name/budget/creative), which is useful for polling without reloading the full tree.

New/updated query params:
• updatedSince (ISO 8601 date-time, e.g. 2026-09-30T10:00:00Z)
• campaignId now accepts multiple platform campaign IDs (comma-separated, up to 100): ?campaignId=123,456
September 30, 2026Breaking change
Multi-account posts now require disambiguation via accountId on some write actions.

If a post was published to several accounts on the same platform, you must specify which account’s copy to target; otherwise the request can fail with 409 (ambiguous_account).

Changes:
• POST /v1/posts/{postId}/edit: accountId is required when the post has multiple accounts on that platform; otherwise returns 409
• POST /v1/posts/{postId}/update-metadata (YouTube): accountId is required in direct mode, and also required in post-based mode when the post was published to multiple YouTube accounts; otherwise returns 409
• POST /v1/posts/{postId}/unpublish: new optional accountId to delete a specific account’s copy; returns 409 if ambiguous

Platforms:
• /edit platform enum: twitter, discord, facebook, reddit, linkedin, telegram, pinterest, googlebusiness, youtube, slack
• /unpublish platform enum: threads, facebook, twitter, linkedin, youtube, pinterest, reddit, bluesky, googlebusiness, telegram
September 30, 2026Improvement
Google Ads keyword data now refreshes daily instead of weekly on GET /v1/ads/keywords.

New/updated keywords added in Google should typically appear within ~1 day (first-time sync still populates on the next discovery pass; manual sync/connecting refreshes immediately).

Also updated 429 rate-limit semantics for Google keyword planner + insights endpoints:
• POST /v1/ads/keywords/ideas and POST /v1/ads/keywords/historical-metrics: 429 may indicate a per-user burst limit (15 requests/min) or a Google rate limit
• GET /v1/ads/insights: 429 now calls out Google per-user burst limit vs Google rate limit

Error details for Zernio Google Ads burst limits are now represented as details.budgetScope = user (previously could be user or platform).
September 30, 2026Improvement
WhatsApp OTP verifications now return delivery telemetry via deliveryStatus and deliveryErrorCode on the Verification object.

This lets you see what Meta reported for the latest send (e.g., whether the message was delivered/read/failed) and, when failed, the Meta error code.

New fields:
• deliveryStatus: delivered | read | failed | null
• deliveryErrorCode: integer | null

Also, OwnedPhoneNumber now includes metaPoolAddRejectedAt (date-time) when WhatsApp reports the number is registered to another WhatsApp account during connect.
September 30, 2026Breaking change
MetaCustomerLifecycle validation rules changed for Meta Sales ad sets (ad_set_goal).

Audience ID fields are now required/forbidden depending on strategy, matching Meta’s 400 behavior.

Set strategy to one of: all_customers, new_customers, new_customers_excluding_engaged

Rules:
• all_customers: existingCustomerAudienceIds not allowed
• new_customers: existingCustomerAudienceIds required
• new_customers_excluding_engaged: existingCustomerAudienceIds required and engagedAudienceIds required (and engagedAudienceIds not allowed for the other strategies)
September 30, 2026New feature
Meta ad set updates now support Customer Lifecycle Strategy via platformSpecificData.customerLifecycle on PUT /v1/ads/ad-sets/{adSetId}.

This lets you set Meta’s Sales ad set goal to optimize for all customers vs acquiring new customers (optionally excluding engaged audiences).

Set platformSpecificData.customerLifecycle.strategy to:
• all_customers
• new_customers
• new_customers_excluding_engaged

Optional (depending on strategy):
• existingCustomerAudienceIds
• engagedAudienceIds
September 29, 2026Improvement
Meta Ads connect flow changed for GET /v1/connect/{platform}/ads: you can now switch a Business Login connection back to the standard login without first completing a Business Login reconnect.

If you receive 409 (Business Login connection needs reconnecting), you can either:
• Reconnect with loginMode=business
• Or switch to standard login by passing force=true (or disconnecting the business connection first)

Key params: loginMode (classic | business), force (true | false)
September 29, 2026Breaking change
Profiles now support a default timezone via timezone.

When creating posts with POST /v1/posts, if you omit timezone and your scheduledFor has no Z/+offset, Zernio will now interpret it using the profile’s timezone (instead of always defaulting to UTC).

Set the profile default timezone with:
• POST /v1/profiles → timezone
• PUT /v1/profiles/{profileId} → timezone (set null to go back to UTC)

Bulk CSV uploads also now default schedule_time to the profile timezone when tz is omitted (else UTC).
September 29, 2026New feature
OAuth connect now supports requesting a reduced permission set via scopes on GET /v1/connect/{platform}.

This lets you ask users only for the permission areas you need; anything not requested won’t be granted and will be missing from the account’s permissions (e.g. a posting-only account can’t read analytics/inbox until reconnected with more areas).

Use scopes as a comma-separated list:
• posting, analytics, comments, messaging, ads

Notes:
• Omit scopes to request the full permission set (current default behavior).
• Rejected with 400 INVALID_FIELD_VALUE on platforms that can’t be reduced: bluesky, telegram, discord, snapchat, whatsapp.

Slack start-OAuth also supports this in GET /v1/connect/slack (start-OAuth mode only) via scopes (on Slack, analytics/comments/ads add nothing; messaging adds inbox/history-related scopes).
September 29, 2026Improvement
LinkedIn connect now returns 409 when a tempToken was already used or has expired.

This lets you detect a consumed/expired OAuth sign-in and restart the sign-in flow (or use refreshToken if applicable) instead of treating it as a generic failure.

Applies to POST /v1/connect/linkedin/select-organization with error code oauth_sign_in_consumed.

Phone number availability responses now include more detailed area metadata.

In GET /v1/phone-numbers/availability (and deprecated alias GET /v1/whatsapp/phone-numbers/availability), areaAvailability.* items may now include:
• ndcs (array of all area codes for the city)
• aliases (array of alternate area names)
September 29, 2026Improvement
GET /v1/phone-numbers/availability (and the deprecated alias GET /v1/whatsapp/phone-numbers/availability) now returns detailed per-area stock state via areaAvailability.

This lets you build an area picker that distinguishes deliverable stock vs pre-orderable areas vs true out-of-stock, using the same 6-hour refreshed data as the dashboard.

New response object: areaAvailability
• inStock (items: ndc, name, count)
• preOrder (items: ndc, name)
• outOfStock (items: ndc, name, listed)

Notes:
• areaOptions is now documented as equal to areaAvailability.inStock
• soldOutAreas is now described as areaAvailability.preOrder + areaAvailability.outOfStock (kept for older clients)

POST /v1/phone-numbers/stock-watches now includes preOrderable in the 200/201 response, so you can tell when the watched area can be bought immediately as a pre-order (submit KYC with areaCode + preOrder: true) while still arming the watch.
September 29, 2026Improvement
CTWA ad creation now supports a fuller campaign/ad set + targeting surface in CtwaAdRequestBody, aligned with POST /v1/ads/create.

You can now schedule delivery, choose where budget lives, add ads under an existing campaign, and use advanced targeting options.

New/expanded fields include:
• Creative: description (link description; not allowed with existing post creatives)
• Campaign/ad set: startDate, budgetLevel (adset | campaign), existingCampaignId
• Targeting: gender (all | male | female), languages, excludedLocations, behaviors, workPositions, workEmployers, workIndustries, incomeTier (top_5 | top_10 | top_10_25 | top_25_50), userOs, userDevice, audienceInclude, audienceExclude, savedTargetingId, targeting, rawTargeting, specialAdCategories, specialAdCategoryCountry

Note: when using adSetId (attach mode) or existingCampaignId, more fields are now explicitly rejected with a 400 per the updated validation rules.
September 29, 2026Breaking change
TikTok conversion event deletion is now reported as unsupported on DELETE /v1/accounts/{accountId}/tracking-tags/{tagId}/events/{eventId}.

TikTok Ads now returns 501 (platform_not_supported) because the upstream delete endpoint responds OK but does not actually remove the event; delete it in TikTok Events Manager instead.

Handle 501 for TikTok when calling DELETE /v1/accounts/{accountId}/tracking-tags/{tagId}/events/{eventId}; successful responses still return state = deleted | archived | disabled.
September 29, 2026Improvement
platformPostId in CtwaAdRequestBody now supports Instagram media without requiring an Instagram connection (it’s resolved from the media via a Meta ads business-login connection).

This makes it easier to reuse existing Instagram posts/reels for CTWA/messaging ads, and you can still customize the chat greeting.

Use:
• platformPostId (IG or FB post/reel ID)
• Optional welcomeMessage (allowed with existing post references)

Reminder: existing post references are still mutually exclusive with fresh creative fields (headline, body, imageUrl, video) and with each other (platformPostId vs objectStoryId).
September 29, 2026Improvement
KYC submission can now return 409 with code=area_pre_order_available when the country requires the number to match the registered address area, but that area has no stock.

In this case, nothing is created; you can show the offer to the customer and, if they accept, resend the same request with preOrder=true to place a carrier-sourced pre-order for that area.

Handle 409 code=area_pre_order_available and read details: { areaCode, areaName, estimatedWeeks: "2-4", billedWhenActive: true }. Then retry with preOrder (optionally set areaCode to details.areaCode).

Applies to POST /v1/phone-numbers/kyc and the deprecated alias POST /v1/whatsapp/phone-numbers/kyc.
September 29, 2026Improvement
Commerce endpoints now document required store capabilities and platform support, and CommerceCapability adds new values: collections.metafields, discounts.codes.

This clarifies which operations may return 400 platform_not_supported vs 403 insufficient_permissions, and which features are Shopify-only vs available on WooCommerce.

Key notes:
• Collection metafields endpoints (GET/PUT/DELETE /v1/commerce/collections/{collectionId}/metafields) require collections.metafields; WooCommerce returns 400 platform_not_supported
• Discount code management (POST /v1/commerce/discounts/{discountId}/codes) requires discounts.codes; WooCommerce does not support it
• Pages endpoints require pages.read/pages.write
• Navigation endpoints (/v1/commerce/menus, /v1/commerce/redirects) are Shopify-only and require navigation.read/navigation.write
September 29, 2026Improvement
KYC submission now returns clearer stock/pre-order behavior and more specific 409 failure cases on POST /v1/phone-numbers/kyc (and the deprecated alias POST /v1/whatsapp/phone-numbers/kyc).

Pre-orders can now happen automatically even without sending preOrder when the country/type has no deliverable stock, or when a geographic-match country requires an address-covered area that’s out of stock. The response preOrder indicates when this happened.

Handle new/expanded 409 error codes:
• area_code_unavailable - requested areaCode (or address-covered area) has no deliverable inventory and can’t be pre-ordered
• country_out_of_stock - the whole country/type pool has nothing deliverable and can’t be pre-ordered

When inventory is temporarily held due to recent WhatsApp registration failures, details.undeliverableUntil is returned as an ISO timestamp in the error details.
September 29, 2026Improvement
POST /v1/ads/boost and POST /v1/ads/create now have clarified, consistent semantics for status when creating ads.

When you send status: PAUSED, Zernio pauses only the top-most object created by the request and leaves everything below it switched on, so a single resume of that object brings the new tree live (and existing parents are never touched).

Key behavior:
• New campaign created: campaign is paused; ad set + ad are on
• With existingCampaignId: new ad set is paused; its ad is on
• With adSetId: the new ad itself is paused

Values: status = ACTIVE | PAUSED

Also note: Ad.configuredStatus is the platform-level on/off toggle for the ad itself; on a paused create it may still read ACTIVE while delivery status reads paused (because the pause is held at the campaign/ad set).
September 29, 2026New feature
New iMessage sandbox contacts endpoints let you test iMessage inbox + webhooks without ordering a sender by adding your own phone/email as a sandbox contact.

Endpoints:
• GET /v1/imessage/sandbox/contacts — returns the sandbox line (sandbox.accountId, sandbox.handle, sandbox.contactLimit) and your contacts
• POST /v1/imessage/sandbox/contacts — add a contact with handle (E.164 phone like +15551234567 or Apple ID email)
• DELETE /v1/imessage/sandbox/contacts/{contactId} — remove a sandbox contact

Contact fields:
• status: pending | active
• Activate by sending joinText from the contact to the sandbox line (or use joinLink)
• Reply window is based on lastInboundAt (24 hours after each inbound message)
September 29, 2026Breaking change
New Commerce API is available: you can now manage store catalogs (products, collections, inventory, discounts, pages, navigation, metaobjects, markets) and sync products into Meta catalogs.

Start by fetching store capabilities so you can detect required permissions up front.

Key endpoints:
• Store + capabilities: GET /v1/commerce/store (accountId) → capabilities, missingCapabilities, grantPermissionsUrl
• Products: GET /v1/commerce/products (accountId, limit, cursor, status, query, collectionId), POST /v1/commerce/products, PATCH /v1/commerce/products/{productId}, POST /v1/commerce/products/state (action: activate/deactivate/archive/delete), POST /v1/commerce/products/{productId}/price
• Collections: GET/POST /v1/commerce/collections, PATCH/DELETE /v1/commerce/collections/{collectionId}, POST /v1/commerce/collections/{collectionId}/products, POST /v1/commerce/collections/{collectionId}/reorder
• Inventory: GET /v1/commerce/inventory (accountId, productId), POST /v1/commerce/products/{productId}/inventory (mode: set/adjust)
• Discounts: GET/POST /v1/commerce/discounts, PATCH/DELETE /v1/commerce/discounts/{discountId}, POST /v1/commerce/discounts/{discountId}/state, POST /v1/commerce/discounts/{discountId}/codes
• Meta catalog sync: POST /v1/commerce/catalog-syncs (accountId, catalogAccountId, catalogId), POST /v1/commerce/catalog-syncs/{syncId}/run

Webhooks: you can now subscribe to product lifecycle events via POST /v1/webhooks/settings / PUT /v1/webhooks/settings with:
• commerce.product.created
• commerce.product.updated
• commerce.product.deleted

Breaking change (Ads): campaign/ad set status toggles now write only the campaign/ad set switch and no longer cascade to child ad sets/ads.
• Campaign: PUT /v1/ads/campaigns/{campaignId}/status
• Bulk: POST /v1/ads/campaigns/bulk-status
• Ad set: PUT /v1/ads/ad-sets/{adSetId}/status and PUT /v1/ads/ad-sets/{adSetId}
If you relied on cascade resume/pause, also toggle children explicitly (e.g. PUT /v1/ads/ad-sets/{adSetId}/status, PUT /v1/ads/{adId}/status).
September 29, 2026Improvement
POST /v1/whatsapp/templates can now return 502 when Meta rejects the request or is unreachable.

This makes Meta-side failures explicit so you can distinguish them from validation errors and apply retries/backoff.

Handle:
• 502 - Meta rejected the request or was unreachable (Meta 4xx statuses are forwarded as-is)
September 29, 2026Breaking change
TikTok no longer supports behavior targeting.

Requests that include TargetingSpec.behaviors for TikTok ads will now be rejected with 400.

Impacted surfaces:
• POST /v1/ads/create (via targeting.behaviors or top-level behaviors)
• GET /v1/ads/targeting/search with dimension=behavior (now Meta-only)

If you need TikTok audience refinement, use interests, geo (countries/regions/cities/zips/metros), ageMin/ageMax, gender (all/male/female), and incomeTier (top_5/top_10/top_10_25/top_25_50) where supported.
September 29, 2026New feature
Facebook Messenger accounts now support managing the Get Started button via new endpoints.

This lets you read/set/remove the page’s Get Started postback payload (required by Meta before a persistent menu can be shown).

New endpoints:
• GET /v1/accounts/{accountId}/messenger-get-started → returns data (or null) with payload
• PUT /v1/accounts/{accountId}/messenger-get-started with payload (1–1000 chars), e.g. GET_STARTED or zernio:workflow:<workflowId>
• DELETE /v1/accounts/{accountId}/messenger-get-started (returns 409 if a persistent menu is still set)

Also updated: PUT /v1/accounts/{accountId}/messenger-menu now returns 409 if the page has no Get Started button set.
September 29, 2026Improvement
OpenAI Ads tracking tags now return more complete conversion visibility.

GET /v1/accounts/{accountId}/tracking-tags/{tagId}/stats now includes both on-site Pixel and Conversions API events in the recent-events stream. Check api_channel: pixel_sdk (includes openai::sdk_init) and server_to_server (Conversions API).

GET /v1/accounts/{accountId}/tracking-tags/{tagId}/events clarifies OpenAI Ads attribution windows: clickWindowDays and viewWindowDays (0 = off).
September 29, 2026Improvement
Campaign/ad set/ad status endpoints now support optional live status reads to reduce stale switch/status data.

Use live=true to read switches from the platform now, store them, and return when the read happened (or null if the live read failed and stored values were returned).

New/updated parameters:
• GET /v1/ads/campaigns: live=true (requires limit<=20)
• GET /v1/ads/ad-sets: live=true (requires campaignId or adSetId; reads live for first 20 rows)
• GET /v1/ads/{adId}: live=true

New response fields:
• Campaigns: statusReadAt (only when live=true)
• Ad sets list: statusReadAt per row (only when live=true)
• Ad details: statusReadAt (only when live=true)

Status write behavior is now more immediately consistent:
• PUT /v1/ads/campaigns/{campaignId}/status, POST /v1/ads/campaigns/bulk-status, and PUT /v1/ads/ad-sets/{adSetId}/status now read child ad switches live (up to 20), then re-read and store switches/statuses after the write so a subsequent GET reflects what the platform reports right away.
September 28, 2026Improvement
Date/time handling is now explicitly defined for ad scheduling and duplication, including how values without a timezone offset are interpreted.

This helps avoid accidental off-by-timezone starts/ends when using startTime/endTime (duplicate) and startDate/endDate (create/boost).

Key rules:
• POST /v1/ads/campaigns/{campaignId}/duplicate: startTime/endTime are read in the ad account timezone on Meta/TikTok; LinkedIn reads no-offset values as UTC. Date-only endTime runs to 23:59:59 local.
• POST /v1/ads/ad-sets/{adSetId}/duplicate (Meta): startTime/endTime no-offset values are read in the ad account timezone; date-only endTime runs to 23:59:59 local.
• POST /v1/ads/boost: startDate/endDate no-offset values are read in the ad account timezone on Meta/TikTok/X/Pinterest; date-only endDate runs to 23:59:59 local.
• POST /v1/ads/create: startDate/endDate parsing is now specified per platform (account timezone on Meta/TikTok/X/Pinterest/OpenAI; Google uses account-local calendar days; LinkedIn reads no-offset values as UTC).
September 28, 2026Improvement
startDate / endDate handling was updated for ads scheduling.

For Meta (and now also TikTok on boosts), a timestamp without a timezone offset (e.g. YYYY-MM-DD, YYYY-MM-DD HH:MM:SS, YYYY-MM-DDTHH:MM:SS) is interpreted in the ad account timezone instead of UTC.

Applies to:
• PUT /v1/ads/ad-sets/{adSetId} via platformSpecificData.startDate / platformSpecificData.endDate (Meta)
• POST /v1/ads/boost via startDate / endDate (Meta + TikTok)
• POST /v1/ads/create via startDate / endDate (Meta + TikTok)

If you need an exact instant, always send ISO 8601 with an offset (e.g. 2026-06-10T09:00:00Z or 2026-06-10T09:00:00-05:00).
September 28, 2026Breaking change
PUT /v1/ads/ad-sets/{adSetId}/status now writes the ad set’s own on/off switch on every supported platform (instead of emulating via child ads on some platforms).

The response no longer returns message when nothing was written; callers should rely on status, updated, skipped, and skippedReasons.

Set status to active or paused (with required platform).

PUT /v1/ads/{adId}/status now returns the post-write platform read-back:
• status (delivery status)
• configuredStatus (the ad’s own switch; may be null where unsupported)

Skips are now based on the ad’s own switch already matching the target (not the rolled-up delivery status).
September 28, 2026Improvement
TikTok Ads now exposes the platform’s own on/off switches separately from delivery status.

You can distinguish ancestor-cascaded delivery status from the entity’s own toggle:
• Ad: configuredStatus now maps TikTok operation_status (ENABLE → ACTIVE, DISABLE → PAUSED)
• Campaign rollups: platformCampaignStatus now includes TikTok campaign operation_status (ENABLE/DISABLE)
• Ad sets list: platformAdSetStatus now documents TikTok ad group operation_status (ENABLE/DISABLE)

Also clarified scheduling behavior: startDate/endDate are stored/read as UTC instants, and TikTok inputs without an offset are interpreted in the ad account timezone.
September 28, 2026New feature
Tracking tags + conversion events now support X Ads (platform: xads) on the existing tracking-tag endpoints.

You can now list/read/create X Pixels and manage X web event tags via the same /v1/accounts/{accountId}/tracking-tags API surface.

Key behavior for X Ads:
• List pixels: GET /v1/accounts/{accountId}/tracking-tags (optional adAccountId to scope). TrackingTag.id is the X ad account id (base36, e.g. 18ce54d4x5t); siteTagId is the pixel id embedded on the site.
• Create pixel: POST /v1/accounts/{accountId}/tracking-tags with adAccountId (X ad account id). One pixel per ad account; if it already exists, X returns 409.
• Read pixel: GET /v1/accounts/{accountId}/tracking-tags/{tagId} where tagId is the X ad account id; returns code, siteTagId, and events.
• Update pixel: PATCH /v1/accounts/{accountId}/tracking-tags/{tagId} supports firstPartyCookieStatus = first_party_cookie_enabled | first_party_cookie_disabled.
• Events: POST/PATCH/DELETE /v1/accounts/{accountId}/tracking-tags/{tagId}/events now work for X web event tags. Create supports type (X enum: ADDED_PAYMENT_INFO, ADD_TO_CART, ADD_TO_WISHLIST, CHECKOUT_INITIATED, CONTENT_VIEW, CUSTOM, DOWNLOAD, INSTALL, LANDING_PAGE_VIEW, LOGIN, PRODUCT_CUSTOMIZATION, PURCHASE, SEARCH, SESSION, SIGN_UP, SITE_VISIT, START_TRIAL, SUBSCRIBE) plus clickWindowDays and viewWindowDays.
• Stats: GET /v1/accounts/{accountId}/tracking-tags/{tagId}/stats returns X web event tag health rows (no per-event counts).
September 28, 2026New feature
Tracking tags now support TikTok Ads (platform=tiktokads) across the tracking-tags API (pixels, sharing, events, and stats).

You can now manage TikTok Pixels via:
• GET /v1/accounts/{accountId}/tracking-tags (optionally adAccountId=<numeric advertiser_id>)
• GET /v1/accounts/{accountId}/tracking-tags/{tagId} (searches advertisers if adAccountId omitted; TikTok has no lastFiredTime)
• POST /v1/accounts/{accountId}/tracking-tags (adAccountId=numeric advertiser_id; name must be unique, max 40 chars; no delete API)

Sharing is supported via Business Center:
• GET|POST|DELETE /v1/accounts/{accountId}/tracking-tags/{tagId}/shared-accounts (TikTok pixels must be Business Center assets; otherwise 400)

Conversion events and stats:
• GET|POST /v1/accounts/{accountId}/tracking-tags/{tagId}/events (TikTok event list may lag 2–4 hours)
• PATCH|DELETE /v1/accounts/{accountId}/tracking-tags/{tagId}/events/{eventId} (update supports name, defaultValue, currency; delete is hard delete but fails if bound to an ad group)
• GET /v1/accounts/{accountId}/tracking-tags/{tagId}/stats (TikTok per-event daily counts; defaults last 7 days; max 30 days; aggregation not accepted)

Note: TikTok connections authorized before the Pixel Management permission (about 2026-05-28) may return 403 until reconnected.
September 28, 2026New feature
Pinterest Ads is now supported for Conversions API + event quality.

You can now send server-side conversion events to Pinterest, list Pinterest conversion destinations (ad accounts), and read Pinterest event quality metrics.

Use POST /v1/ads/conversions with:
• accountId = a pinterestads SocialAccount
• destinationId = numeric Pinterest ad account id (e.g. 549755885175)
• Pinterest requirements: each event needs user.email OR (user.ipAddress + user.userAgent)
• Pinterest click id: user.clickIds.epik (sent as click_id)
• testCode on Pinterest sends with test=true
• consent.adUserData: DENIED sets Pinterest opt_out

Event quality now supports Pinterest via GET /v1/ads/conversions/quality:
• accountId = metaads or pinterestads
• destinationId = Meta pixel/dataset id OR Pinterest numeric ad account id

Conversion destinations now include Pinterest via GET /v1/accounts/{accountId}/conversion-destinations (platform enum now includes pinterestads).
Was this page helpful?

Payment method required

What a 402 payment_required response means, which requests trigger it, and how the user adds a card without leaving the dashboard.

Refer & earn

Earn 20% recurring commission for 12 months when you refer new customers to Zernio.