{"filter":{"platforms":[],"type":null},"total":943,"platforms":[{"slug":"ads","label":"Advertising","count":413},{"slug":"meta-ads","label":"Meta Ads","count":206},{"slug":"whatsapp","label":"WhatsApp","count":165},{"slug":"inbox","label":"Inbox","count":158},{"slug":"analytics","label":"Analytics","count":149},{"slug":"instagram","label":"Instagram","count":137},{"slug":"telephony","label":"Telephony","count":112},{"slug":"webhooks","label":"Webhooks","count":112},{"slug":"publishing","label":"Publishing","count":99},{"slug":"tiktok","label":"TikTok","count":97},{"slug":"google-ads","label":"Google Ads","count":87},{"slug":"linkedin","label":"LinkedIn","count":87},{"slug":"tiktok-ads","label":"TikTok Ads","count":80},{"slug":"facebook","label":"Facebook","count":72},{"slug":"linkedin-ads","label":"LinkedIn Ads","count":50},{"slug":"x","label":"X","count":46},{"slug":"general","label":"General API","count":43},{"slug":"youtube","label":"YouTube","count":37},{"slug":"google-business","label":"Google Business Profile","count":36},{"slug":"pinterest","label":"Pinterest","count":33},{"slug":"reddit","label":"Reddit","count":25},{"slug":"pinterest-ads","label":"Pinterest Ads","count":22},{"slug":"commerce","label":"Commerce","count":21},{"slug":"threads","label":"Threads","count":20},{"slug":"bluesky","label":"Bluesky","count":19},{"slug":"x-ads","label":"X Ads","count":19},{"slug":"telegram","label":"Telegram","count":17},{"slug":"slack","label":"Slack","count":12},{"slug":"imessage","label":"iMessage","count":10},{"slug":"discord","label":"Discord","count":8},{"slug":"snapchat","label":"Snapchat","count":8}],"webhook":"api.changelog.published","api":"https://zernio.com/api/v1/changelog","feed":"https://docs.zernio.com/changelog/feed.xml","entries":[{"id":"1792817944246","date":"2026-10-24T04:59:04.246Z","type":"new_feature","message":"Inbox mentions now include **Facebook**.\n\nYou can retrieve Facebook Page mentions (post + comment mentions) alongside LinkedIn and Instagram via `GET /v1/inbox/mentions`.\n\nNew values:\n• `platform`: `facebook`\n• `type`: `post` (Facebook post mention)\n\nNew field:\n• `postId` (Facebook only)\n\nNote: Facebook mentions can’t be replied to via `POST /v1/inbox/mentions/reply`.\n\n\n```bash\ncurl -sS -H \"Authorization: Bearer $TOKEN\" \\\n  \"$BASE_URL/v1/inbox/mentions?limit=25\"\n```\n","platforms":["whatsapp","inbox","telephony"],"url":"https://docs.zernio.com/changelog#1792817944246"},{"id":"1792810179715","date":"2026-10-24T02:49:39.715Z","type":"new_feature","message":"KYC submissions can now request an **exact phone number** to order via `phoneNumber`.\n\nSend `phoneNumber` (E.164) from the available-number list to order that specific number. If it’s no longer available (or can’t be used), the request fails with `409` (`phone_number_unavailable`) and nothing is ordered—no substitution and no pre-order.\n\nKey rules:\n• `phoneNumber` requires `quantity` = `1` (or omitted)\n• Source it from `GET /v1/phone-numbers/available` (or the WhatsApp alias)\n\n\n```bash\ncurl -X POST \"$BASE_URL/v1/phone-numbers/kyc\" \\\n  -H \"Authorization: Bearer $TOKEN\" -H \"Content-Type: application/json\" \\\n  -d '{\"profileId\":\"prof_123\",\"country\":\"BR\",\"phoneNumber\":\"+5511999999999\"}'\n```\n","platforms":["whatsapp","telephony"],"url":"https://docs.zernio.com/changelog#1792810179715"},{"id":"1792806604058","date":"2026-10-24T01:50:04.058Z","type":"improvement","message":"LinkedIn @mentions can now be written directly in post text and resolved automatically when creating or updating posts.\n\nPut a LinkedIn profile/company URL inside `@[... ]` in `content` (or LinkedIn `customContent`), e.g. `@[https://www.linkedin.com/in/jane-doe]` or `@[Jane](https://www.linkedin.com/company/acme)`. Zernio resolves it to LinkedIn’s real name + link at publish/schedule time.\n\nNew error handling for mention resolution:\n• `400` with `code` `linkedin_mentions_invalid` and `details.mentions[]` (`LinkedInMentionIssue`)\n• `429` with `code` `rate_limited`\n• `401` with `code` `reconnect_required`\n• Retryable `503` with `code` `temporarily_unavailable`\n\nNote: `GET /v1/accounts/{accountId}/linkedin-mentions` still works for previewing/producing `@[Name](urn:...)`, but `displayName` is now deprecated.\n\n\n```bash\ncurl -X POST \"$ZERNIO_API/v1/posts\" \\\n  -H \"Authorization: Bearer $API_KEY\" \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\"content\":\"Thanks @[https://www.linkedin.com/in/jane-doe]\",\"platforms\":[{\"platform\":\"linkedin\",\"accountId\":\"ACCOUNT_ID\"}],\"publishNow\":true}'\n```\n","platforms":["linkedin","publishing"],"url":"https://docs.zernio.com/changelog#1792806604058"},{"id":"1792800430434","date":"2026-10-24T00:07:10.434Z","type":"new_feature","message":"Facebook Pages can now list **posts they’re tagged in** via `GET /v1/accounts/{accountId}/facebook-tagged-posts`.\n\nThis returns public posts where other people or Pages tagged the connected Page (cursor-paginated).\n\nUse:\n• `limit` (1–50, default 25)\n• `cursor` (pass the previous response’s `nextCursor`)\n\nRequires a Facebook account with a Page selected; Meta may return `platform_api_error` if the connection lacks `pages_read_user_content`.\n\n\n```bash\ncurl -sS -H \"Authorization: Bearer $ZERNI0_API_KEY\" \\\n  \"https://api.zernio.com/v1/accounts/$ACCOUNT_ID/facebook-tagged-posts?limit=25\"\n```\n","platforms":["facebook","analytics"],"url":"https://docs.zernio.com/changelog#1792800430434"},{"id":"1792795249021","date":"2026-10-23T22:40:49.021Z","type":"improvement","message":"LinkedIn mention resolution now returns standardized error fields and can respond with `429` when rate limited.\n\nError responses for this endpoint now use `error` plus structured fields like `type`, `code`, `param`, `docUrl`, and `platform` (when applicable), making it easier to branch on specific failure cases.\n\nExample error codes you may see include `missing_required_field`, `linkedin_mention_no_organization`, `reconnect_required`, `linkedin_mention_lookup_forbidden`, `linkedin_person_not_resolvable`, `linkedin_organization_not_found`, `account_not_found`.\n\n\n```bash\ncurl -G \"https://api.zernio.com/v1/accounts/{accountId}/linkedin-mentions\" \\\n  -H \"Authorization: Bearer $ZERNI0_API_KEY\" \\\n  --data-urlencode \"url=https://www.linkedin.com/company/microsoft\"\n```\n","platforms":["linkedin"],"url":"https://docs.zernio.com/changelog#1792795249021"},{"id":"1792788318961","date":"2026-10-23T20:45:18.961Z","type":"improvement","message":"TikTok Smart+ materials reporting now returns `missingRows` on successful responses.\n\nWhen TikTok declares more rows than can be retrieved (even after a retry), the API may still return `200` with `missingRows` set (within a small tolerance). Use `missingRows` to detect partial results and decide whether to retry or proceed.\n\nNew field:\n• `missingRows` - number of declared rows that could not be returned (0 when complete)\n\n\n```bash\ncurl -G \"https://api.zernio.com/v1/ads/tiktok-smart-plus-materials\" \\\n  --data-urlencode \"accountId=...\" \\\n  --data-urlencode \"adAccountId=...\" \\\n  --data-urlencode \"startDate=2026-10-01\" \\\n  --data-urlencode \"endDate=2026-10-07\"\n```\n","platforms":["tiktok-ads","ads"],"url":"https://docs.zernio.com/changelog#1792788318961"},{"id":"1792788120475","date":"2026-10-23T20:42:00.475Z","type":"improvement","message":"TikTok Smart+ materials reporting now returns `missingRows` on successful responses from `GET /v1/ads/tiktok-smart-plus-materials`.\n\nWhen TikTok declares more rows than could be retrieved (after an internal retry), Zernio may still return `200` with a small shortfall and set `missingRows` to the number of rows that could not be returned (otherwise `missingRows=0`).\n\nUse `missingRows` to detect partial results and decide whether to retry or accept the data.\n\n\n```bash\ncurl -G \"https://api.zernio.com/v1/ads/tiktok-smart-plus-materials\" \\\n  --data-urlencode \"accountId=...\" \\\n  --data-urlencode \"adAccountId=...\" \\\n  --data-urlencode \"startDate=2026-10-01\" \\\n  --data-urlencode \"endDate=2026-10-07\"\n```\n","platforms":["tiktok-ads","ads"],"url":"https://docs.zernio.com/changelog#1792788120475"},{"id":"1792787447339","date":"2026-10-23T20:30:47.339Z","type":"breaking_change","message":"`ad_rule.triggered` has been removed from webhook events.\n\nIf your integration subscribes to or tests this event, you must remove it from `events` (create/update) and from `event` in test fires. Existing webhook configs that still include it will no longer validate against the API spec.\n\nRemoved:\n• `ad_rule.triggered` (webhook event)\n• `WebhookPayloadAdRuleTriggered` (payload schema)\n\n\n```bash\ncurl -X PUT \"$ZERNI0_BASE_URL/v1/webhooks/settings\" \\\n  -H \"Authorization: Bearer $TOKEN\" \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\"webhookId\":\"507f1f77bcf86cd799439011\",\"events\":[\"ad.status_changed\"]}'\n```\n","platforms":["webhooks"],"url":"https://docs.zernio.com/changelog#1792787447339"},{"id":"1792786088733","date":"2026-10-23T20:08:08.733Z","type":"new_feature","message":"Webhooks now support the `ad_rule.triggered` event.\n\nThis event fires when a scheduled ad rule run finds campaigns or ad sets that meet all rule conditions (ad rules are currently in closed beta and only fire for pilot accounts).\n\nUse `events` to subscribe to `ad_rule.triggered`, and use `POST /v1/webhooks/test` with `event`=`ad_rule.triggered` to receive a sample payload.\n\n\n```bash\ncurl -X POST \"$ZERNIO_API/v1/webhooks/test\" \\\n  -H \"Authorization: Bearer $TOKEN\" \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\"webhookId\":\"YOUR_WEBHOOK_ID\",\"event\":\"ad_rule.triggered\"}'\n```\n","platforms":["webhooks"],"url":"https://docs.zernio.com/changelog#1792786088733"},{"id":"1792781408871","date":"2026-10-23T18:50:08.871Z","type":"improvement","message":"Invite tokens can now include `disabledResourceGroups` to restrict what resource groups a new team member can access.\n\nSet `disabledResourceGroups` on `POST /v1/invite/tokens` to disable specific groups (same set as restricted API keys). Omitted or empty means full access; groups the caller lacks are always added.\n\nAllowed values include: `publishing`, `engagement`, `messages`, `contacts`, `analytics`, `ads`, `telephony`, `accounts`, `billing`, `webhooks`, `commerce`.\n\nThe created invite now returns `disabledResourceGroups`, and user objects may expose `disabledResourceGroups` (e.g. in post user data).\n\n\n```bash\ncurl -X POST \"$ZERNI0/api/v1/invite/tokens\" \\\n  -H \"Authorization: Bearer $ZERNIO_KEY\" \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\"scope\":\"all\",\"role\":\"member\",\"disabledResourceGroups\":[\"billing\",\"ads\"]}'\n```\n","platforms":["ads","webhooks"],"url":"https://docs.zernio.com/changelog#1792781408871"},{"id":"1792781344560","date":"2026-10-23T18:49:04.560Z","type":"new_feature","message":"Connect now supports **phone handoff** to finish OAuth on a user’s phone via QR code.\n\nStart a connect with `handoff=true` on `GET /v1/connect/{platform}` to receive a `handoff` object (`id`, `url`, `expiresAt`). Show `handoff.url` as a QR code, then poll `GET /v1/connect/handoffs/{handoffId}` until `status` is `connected`, `failed`, or `expired`.\n\nHandoff `status`: `waiting`, `opened`, `connected`, `failed`, `expired`.\n\n\n```bash\ncurl -G \"https://api.zernio.com/v1/connect/instagram\" \\\n  -H \"Authorization: Bearer $TOKEN\" \\\n  --data-urlencode \"profileId=$PROFILE_ID\" \\\n  --data-urlencode \"handoff=true\"\n```\n","platforms":["instagram"],"url":"https://docs.zernio.com/changelog#1792781344560"},{"id":"1792779558708","date":"2026-10-23T18:19:18.708Z","type":"improvement","message":"Account listing and follower stats now support **cursor-based pagination**.\n\nUse `limit` to fetch the first page, then pass `pagination.nextCursor` back as `cursor` until it becomes `null`. This avoids page shifting when accounts are added/removed between requests.\n\nKey details:\n• `GET /v1/accounts`: cursor paging uses `limit` and `cursor`; cannot be combined with `page`, `sort`, or `perProfile`. Response `pagination` is now one of: offset (`{ page, limit, total, pages }`) or cursor (`{ limit, nextCursor }`).\n• `GET /v1/accounts/follower-stats`: new `limit` (max 25) and `cursor`; response may include `pagination`. Invalid cursors now return `400`.\n\n\n```bash\ncurl -G \"https://api.zernio.com/v1/accounts\" \\\n  -H \"Authorization: Bearer $TOKEN\" \\\n  --data-urlencode \"limit=100\" \\\n  --data-urlencode \"cursor=$NEXT_CURSOR\"\n```\n","platforms":["analytics"],"url":"https://docs.zernio.com/changelog#1792779558708"},{"id":"1792754308244","date":"2026-10-23T11:18:28.244Z","type":"new_feature","message":"You can now **remove users from a Meta customer list audience** with `DELETE /v1/ads/audiences/{audienceId}/users`.\n\nSend the same `users` payload as uploads (plaintext `email`/`phone` rows); Zernio normalizes and SHA256-hashes server-side, and each row removes the person it previously added. Meta applies removals asynchronously.\n\nKey details:\n• Meta only, `customer_list` only (others return `422` with `feature_not_available`)\n• Max `10000` users per request\n• Response includes `numReceived`, `numInvalid`, and `invalidEntrySamples`\n\n\n```bash\ncurl -X DELETE \"$BASE_URL/v1/ads/audiences/$AUDIENCE_ID/users\" \\\n  -H \"Authorization: Bearer $TOKEN\" -H \"Content-Type: application/json\" \\\n  -d '{\"users\":[{\"email\":\"a@example.com\"},{\"phone\":\"+14155550100\"}]}'\n```\n","platforms":["meta-ads","ads"],"url":"https://docs.zernio.com/changelog#1792754308244"},{"id":"1792749435151","date":"2026-10-23T09:57:15.151Z","type":"improvement","message":"Review reply create/delete now return clearer failure modes with new `404` and `502` responses.\n\nUse `404` to treat the review/reply as permanently unavailable (deleted or not addressable by the account) and avoid retries. Use `502` to detect upstream platform failures and retry safely with backoff.\n\nApplies to:\n• `POST` reply to review\n• `DELETE` review reply\n\n\n```bash\ncurl -X POST \"$BASE/v1/inbox/reviews/$REVIEW_ID/reply\" \\\n  -H \"Authorization: Bearer $TOKEN\" \\\n  -H \"Idempotency-Key: $KEY\" \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\"accountId\":\"acc_123\",\"message\":\"Thanks!\"}'\n```\n","platforms":["inbox"],"url":"https://docs.zernio.com/changelog#1792749435151"},{"id":"1792740260289","date":"2026-10-23T07:24:20.289Z","type":"new_feature","message":"WhatsApp conversions now accept more Meta business messaging event types via `eventName`.\n\nYou can now send:\n• `QualifiedLead`\n• `OrderCreated`\n• `OrderCanceled`\n• `OrderReturned`\n\nUse these in `POST /v1/whatsapp/conversions` (with `accountId`, `eventId`, and either `conversationId` or `phoneE164`). Recent events returned by `GET /v1/whatsapp/conversions` will also show these names.\n\n\n```bash\ncurl -X POST \"$BASE_URL/v1/whatsapp/conversions\" \\\n  -H \"Authorization: Bearer $TOKEN\" -H \"Content-Type: application/json\" \\\n  -d '{\"accountId\":\"wa_123\",\"eventName\":\"QualifiedLead\",\"eventId\":\"lead_001\",\"conversationId\":\"conv_abc\"}'\n```\n","platforms":["whatsapp"],"url":"https://docs.zernio.com/changelog#1792740260289"},{"id":"1792728780450","date":"2026-10-23T04:13:00.450Z","type":"improvement","message":"Ads timeline rows now include `currency` in `GET /v1/ads/timeline` responses.\n\nThis lets you correctly chart money metrics when an account’s underlying ad accounts bill in different currencies (you may now receive one row per `date` and `currency`, never a sum across currencies).\n\nUse `rows[].currency` (ISO 4217, or `null` if unknown) to interpret `spend`, `cpc`, `cpm`, `costPerConversion`, `purchaseValue`, and `actionValues`.\n\n\n```bash\ncurl -G \"https://api.zernio.com/v1/ads/timeline\" \\\n  -H \"Authorization: Bearer $TOKEN\" \\\n  --data-urlencode \"accountId=acc_123\" \\\n  --data-urlencode \"fromDate=2026-10-01\" \\\n  --data-urlencode \"toDate=2026-10-23\"\n```\n","platforms":["ads"],"url":"https://docs.zernio.com/changelog#1792728780450"},{"id":"1792719323852","date":"2026-10-23T01:35:23.852Z","type":"improvement","message":"Inbox conversations now include **enriched Meta ad attribution** via `adAttribution`.\n\nThis lets you tie a conversation (and returning click referrals on inbound messages) back to the ad, and—when the ad is in your synced ads—also get `campaign*`, `adSet*`, and `adAccount*` fields.\n\nNew fields:\n• `GET /v1/inbox/conversations` and `GET /v1/inbox/conversations/{conversationId}`: `adAttribution`\n• `message.received` webhook: `metadata.ad` (enriched attribution for that message’s `metadata.referral`)\n\nWhatsApp CTWA metadata now also includes creative details in `metadata`: `ctwa_body`, `ctwa_media_type`, `ctwa_image_url`, `ctwa_video_url`, `ctwa_thumbnail_url`.\n\n\n```bash\ncurl -sS -H \"Authorization: Bearer $ZERNIO_API_KEY\" \\\n  \"$ZERNIO_API_BASE/v1/inbox/conversations?platform=whatsapp&limit=10\"\n```\n","platforms":["inbox"],"url":"https://docs.zernio.com/changelog#1792719323852"},{"id":"1792713116080","date":"2026-10-22T23:51:56.080Z","type":"new_feature","message":"Webhooks now support 3 new ads events: `ad.delivery_issue`, `campaign.budget.depleted`, and `ad_account.status_changed`.\n\nUse them to get notified when tracked ads enter an error state, when a TikTok campaign stops delivering due to budget exhaustion, and when an ad account moves between `active`/`restricted`/`disabled`/`closed`.\n\nSubscribe via `events` in `POST /v1/webhooks/settings` or `PUT /v1/webhooks/settings`, and you can generate sample payloads via `POST /v1/webhooks/test` using `event`.\n\n\n```bash\ncurl -X POST \"$BASE_URL/v1/webhooks/test\" \\\n  -H \"Authorization: Bearer $TOKEN\" \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\"webhookId\":\"YOUR_WEBHOOK_ID\",\"event\":\"ad.delivery_issue\"}'\n```\n","platforms":["webhooks"],"url":"https://docs.zernio.com/changelog#1792713116080"},{"id":"1792704828067","date":"2026-10-22T21:33:48.067Z","type":"improvement","message":"Google Ads search terms reporting now includes `matchSource` on each row returned by `GET /v1/ads/search-terms`.\n\nUse `matchSource` to tell what matched the query: `ADVERTISER_PROVIDED_KEYWORD`, `AI_MAX_BROAD_MATCH`, or `AI_MAX_KEYWORDLESS`.\n\n\n```bash\ncurl -G \"https://api.zernio.com/v1/ads/search-terms\" \\\n  --data-urlencode \"accountId=acc_123\" \\\n  --data-urlencode \"fromDate=2026-09-01\" \\\n  --data-urlencode \"toDate=2026-09-30\"\n```\n","platforms":["google-ads","ads"],"url":"https://docs.zernio.com/changelog#1792704828067"},{"id":"1792692911288","date":"2026-10-22T18:15:11.288Z","type":"new_feature","message":"Webhooks now emit ad review outcome events: `ad.review.approved` and `ad.review.rejected`.\n\nUse these to track when an already-known ad’s platform review outcome changes (approved/rejected) across supported ad networks.\n\nSubscribe by adding to `events` on webhook create/update, and you can generate sample payloads via `POST /v1/webhooks/test` using `event`.\n\nNew payload schema: `WebhookPayloadAdReview`.\n\n\n```bash\ncurl -X POST \"$ZERNIO_API/v1/webhooks/test\" \\\n  -H \"Authorization: Bearer $TOKEN\" \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\"webhookId\":\"507f1f77bcf86cd799439011\",\"event\":\"ad.review.rejected\"}'\n```\n","platforms":["webhooks"],"url":"https://docs.zernio.com/changelog#1792692911288"},{"id":"1792691019100","date":"2026-10-22T17:43:39.100Z","type":"improvement","message":"Commerce orders now include **attribution** data in `orders[].attribution` on order reads.\n\nThis lets you see where an order came from (e.g. source/referrer/UTM) and, on Shopify, the first and last visit before purchase.\n\nKey fields:\n• `attribution.ready` (Shopify may be `false` right after creation; re-read later)\n• `attribution.firstVisit` (Shopify only) and `attribution.lastVisit`\n• `visit.landingUrl`, `visit.referrerUrl`, `visit.source`, `visit.sourceType`, `visit.utm`\n• `attribution.customAttributes` (Shopify cart/note attributes or WooCommerce public order meta)","platforms":["commerce","analytics"],"url":"https://docs.zernio.com/changelog#1792691019100"},{"id":"1792690778950","date":"2026-10-22T17:39:38.950Z","type":"new_feature","message":"TikTok Smart+ ad reviews can now be fetched **in bulk per advertiser** with `GET /v1/ads/tiktok-smart-plus-reviews`.\n\nThis returns the review verdict for every Smart+ ad (and each creative) in a single paginated read, so you can refresh review status with one sweep per advertiser instead of calling the single-ad review endpoint per ad.\n\nUse:\n• `accountId` (TikTok / TikTok Ads connection)\n• `adAccountId` (TikTok advertiser ID)\n• Optional `includeDeleted`, `page`, `pageSize`\n\nQuota handling: when TikTok’s Smart+ review quota is hit, the response stays `200` with `incomplete=true`, plus `pendingSmartPlusAdIds` and `retryAfterSeconds` (also in `Retry-After`). Re-read the same page after waiting to complete it.\n\n\n```bash\ncurl -G \"https://api.zernio.com/v1/ads/tiktok-smart-plus-reviews\" \\\n  -H \"Authorization: Bearer $TOKEN\" \\\n  --data-urlencode \"accountId=acct_123\" \\\n  --data-urlencode \"adAccountId=789\"\n```\n","platforms":["tiktok-ads","ads"],"url":"https://docs.zernio.com/changelog#1792690778950"},{"id":"1792665421464","date":"2026-10-22T10:37:01.464Z","type":"new_feature","message":"Inbox mentions now include **Instagram** in `GET /v1/inbox/mentions` (in addition to LinkedIn).\n\nInstagram mentions can be `type` `caption` or `comment`, and include `mediaId` plus `commentId` (comment mentions only) so you can reply via `POST /v1/inbox/mentions/reply`.\n\nLook for:\n• `platform`: `linkedin` | `instagram`\n• `type`: `caption` | `comment` | `null`\n• `mediaId`, `commentId`\n\n\n```bash\ncurl -sS -H \"Authorization: Bearer $TOKEN\" \\\n  \"https://api.zernio.com/v1/inbox/mentions?limit=25&sortOrder=desc\"\n```\n","platforms":["inbox"],"url":"https://docs.zernio.com/changelog#1792665421464"},{"id":"1792655586623","date":"2026-10-22T07:53:06.623Z","type":"new_feature","message":"Instagram now supports fetching **@mention details by ID** for both post captions and comments.\n\nUse:\n• `GET /v1/accounts/{accountId}/instagram/mentions/media/{mediaId}` to fetch a post whose caption mentions the connected account\n• `GET /v1/accounts/{accountId}/instagram/mentions/comments/{commentId}` to fetch a comment mention (includes the parent `media.id`)\n\nKey params:\n• `accountId` (Instagram account connected via **Facebook Login**)\n• `mediaId` / `commentId` (digits only)\n\nNote: accounts connected with classic Instagram Login will get `instagram_mentions_require_facebook_login` and must be reconnected using Facebook Login.\n\n\n```bash\ncurl -sS -H \"Authorization: Bearer $ZERNIOTOKEN\" \\\n  \"https://api.zernio.com/v1/accounts/$ACCOUNT_ID/instagram/mentions/comments/17894227972186120\"\n```\n","platforms":["instagram"],"url":"https://docs.zernio.com/changelog#1792655586623"},{"id":"1792637930649","date":"2026-10-22T02:58:50.649Z","type":"new_feature","message":"Added **media preparation** via `POST /v1/media/prepare` to pre-process an image/video into the exact format a target platform accepts (the same processing Zernio applies before publishing).\n\nSend `url` (public http(s) media URL) and `target` (`instagram`, `instagram_story`, `facebook`, `facebook_reel`, `facebook_story`, `threads`, `tiktok`, `linkedin`, `twitter`, `bluesky`, `pinterest`, `reddit`, `snapchat`, `telegram`, `googlebusiness`, `youtube_thumbnail`). Response includes `changed`, `mediaType`, `originalSize`, `outputSize`, and a publishable `url`.\n\nNotes: synchronous processing; rate-limited to 60 files/hour per team (429 with `retryAfterSeconds`); videos over 200MB return 413.\n\n\n```bash\ncurl -X POST \"$ZERNIO_API/v1/media/prepare\" \\\n  -H \"Authorization: Bearer $TOKEN\" -H \"Content-Type: application/json\" \\\n  -d '{\"url\":\"https://example.com/clip.mov\",\"target\":\"facebook_story\"}'\n```\n","platforms":["publishing"],"url":"https://docs.zernio.com/changelog#1792637930649"},{"id":"1792630518472","date":"2026-10-22T00:55:18.472Z","type":"new_feature","message":"Posts now support an **approval workflow**.\n\nCreate or update a post with `requiresApproval: true` to save it as `status=pending_approval` instead of scheduling/publishing immediately. The post will only publish after it’s reviewed.\n\nReview a pending post via `POST /v1/posts/{postId}/review` with `decision` set to `approve` or `reject`.\n• `approve` schedules the post for its `scheduledFor` (and fires the `post.scheduled` webhook)\n• `reject` returns the post to `draft`\n\nYou can also filter/list these posts via `GET /v1/posts` using `status=pending_approval`.\n\n\n```bash\ncurl -X POST \"$BASE_URL/v1/posts/$POST_ID/review\" \\\n  -H \"Authorization: Bearer $TOKEN\" \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\"decision\":\"approve\"}'\n```\n","platforms":["publishing"],"url":"https://docs.zernio.com/changelog#1792630518472"},{"id":"1792627767163","date":"2026-10-22T00:09:27.163Z","type":"new_feature","message":"Webflow is now supported for tracking tag installs via the existing install endpoints.\n\nYou can connect a Webflow site using `GET /v1/connect/webflow` (returns `authUrl` + `state`), then install/remove tags by passing a Webflow site as `storeAccountId`.\n\nUpdates:\n• `StorePixelInstall.platform` now includes `webflow` (in GET/POST/DELETE install responses)\n• `StorePixelInstall.publishRequired` added (Webflow only; always `true` — changes go live on the site’s next publish)\n\nConnection params for `GET /v1/connect/webflow`:\n• `profileId` (required)\n• `redirect_url` (optional)","platforms":["ads"],"url":"https://docs.zernio.com/changelog#1792627767163"},{"id":"1792627220509","date":"2026-10-22T00:00:20.509Z","type":"new_feature","message":"You can now **update a lead’s status** (and optionally deal value) via `PATCH /v1/ads/leads/{id}`.\n\nThis lets you track Meta leads from `new` → `converted` (or other outcomes) and store attribution value.\n\nRequest body:\n• `status`: `new` | `contacted` | `unreachable` | `qualified` | `converted` | `disqualified`\n• `value` (number, optional; requires `currency`)\n• `currency` (ISO 4217, optional)\n• `accountId` (required when `{id}` is a Meta `leadgenId`)\n\nLead responses now include tracking fields on `GET /v1/ads/leads`: `status`, `statusChangedAt`, `value`, `currency`, `contactId`, `firstContactedAt`.\n\n`GET /v1/ads/lead-forms/{formId}/leads` now also returns `leadId` (Zernio lead id for the PATCH) and `status` when the lead is persisted.\n\nWhen creating Meta lead forms, `POST /v1/ads/lead-forms` now supports `platformSpecificData.customDisclaimer` (consent checkboxes).","platforms":["meta-ads","linkedin-ads","ads"],"url":"https://docs.zernio.com/changelog#1792627220509"},{"id":"1792625528067","date":"2026-10-21T23:32:08.067Z","type":"breaking_change","message":"`install.siteUrl` in tracking tag install/status/remove responses can now be `null`. Update your integration to handle a missing site URL.\n\nThese endpoints now also support Wix sites for install management.\n\nAffected endpoints:\n• `GET /v1/accounts/{accountId}/tracking-tags/{tagId}/install`\n• `POST /v1/accounts/{accountId}/tracking-tags/{tagId}/install`\n• `DELETE /v1/accounts/{accountId}/tracking-tags/{tagId}/install`\n\nChanges:\n• `install.platform` enum now includes `wix`\n• `install.siteUrl`: `string | null` (Wix: null when the site was never published)\n• `storeAccountId` now accepts connected Wix account ids (in query/body, depending on endpoint)","platforms":["ads"],"url":"https://docs.zernio.com/changelog#1792625528067"},{"id":"1792616711189","date":"2026-10-21T21:05:11.189Z","type":"new_feature","message":"Webflow is now supported for **Blogs**, including connecting a site via a site API token.\n\nConnect a Webflow site with `POST /v1/connect/webflow/token` using `profileId` and `siteToken`.\n\nOnce connected, Webflow works in the Blogs API (`platform=webflow`):\n• `GET /v1/accounts/{accountId}/blogs` returns each CMS collection that has a rich text field (collection id = `blogId`)\n• Articles are supported via `/articles` (list/get/create/update/delete)\n\nWebflow-specific behavior:\n• No native scheduling: a future `publishDate` returns 400 (unless `isPublished=false` keeps a draft)\n• `seo` is rejected on Webflow\n• Create/update can include `fieldData` (Webflow only) to supply collection-specific fields by slug; unknown/unmappable required fields return 400\n\nAlso added `webflow` to the `platform` filter on `GET /v1/accounts/health`.","platforms":["publishing"],"url":"https://docs.zernio.com/changelog#1792616711189"},{"id":"1792584963214","date":"2026-10-21T12:16:03.214Z","type":"new_feature","message":"New endpoint `GET /v1/ads/{adId}/tiktok-smart-plus-review` returns TikTok **Smart+ ad** review status per creative (Spark post/video/carousel), including rejection reasons.\n\nUse `adId` (Zernio ad id or TikTok ad id). Response includes `review` (ad-level) and `materials` (per-creative), plus `readAt`.\n\n`reviewStatus` is normalized to: `approved`, `partially_approved`, `rejected`, `unknown` (see `rawReviewStatus`). Each entry can include `rejectReasons`, `passedLocations`, and detailed `rejections` (with `suggestion` and rejected `content`).","platforms":["tiktok-ads","ads"],"url":"https://docs.zernio.com/changelog#1792584963214"},{"id":"1792572287603","date":"2026-10-21T08:44:47.603Z","type":"new_feature","message":"POST `/v1/inbox/conversations/{conversationId}/messages` now supports Instagram-only sends for **sharing a published post** and **sending a sticker**.\n\nUse `sharedMediaId` to share one of the account’s own published Instagram posts by media id, or `sticker` to send a sticker instead of text.\n\nNew fields:\n• `sharedMediaId` (Instagram only, JSON body only; numeric string like `17895695668004550`)\n• `sticker` (Instagram only, JSON body only; `like_heart`)\n\nBoth are sent on their own (can’t be combined with `message`, attachments, `quickReplies`, `buttons`, `template`, or each other).","platforms":["inbox"],"url":"https://docs.zernio.com/changelog#1792572287603"},{"id":"1792566684373","date":"2026-10-21T07:11:24.373Z","type":"new_feature","message":"Instagram now supports listing the posts an account is tagged in via `GET /v1/accounts/{accountId}/instagram/tags`.\n\nReturns tagged media newest-first and is cursor-paginated.\n\nKey params:\n• `accountId` (Instagram account connected via **Facebook Login**)\n• `limit` (1-50, default 25)\n• `cursor` (use the prior response’s `nextCursor`)\n\nResponse includes:\n• `data`\n• `nextCursor` (null when no more pages; the final page may be empty)\n\nNote: accounts connected with classic Instagram Login will get `400 instagram_tagged_media_requires_facebook_login` and must be reconnected using the Facebook option.","platforms":["instagram","ads"],"url":"https://docs.zernio.com/changelog#1792566684373"},{"id":"1792559848502","date":"2026-10-21T05:17:28.502Z","type":"new_feature","message":"Instagram now supports listing **live broadcasts in progress** for a connected account via `GET /v1/accounts/{accountId}/instagram/live-media`.\n\nThis returns the broadcasts that are live at request time (returns `200` with an empty `data` array when nothing is live), so you can pair the returned `id` with live-comment workflows.\n\nKey details:\n• Path param: `accountId` (Instagram account connected via **Facebook Login**)\n• Response: `{ data: InstagramLiveMedia[] }` where each item includes `id`, `mediaType`, `mediaProductType`, `username`, `timestamp`\n• If the account was connected with classic Instagram Login, the endpoint returns `400` with code `instagram_live_media_requires_facebook_login` (reconnect using Facebook Login)\n\nAlso added: richer Meta error details in conversion failures via `platformError` in `POST /v1/ads/conversions` and `POST /v1/whatsapp/conversions` responses.","platforms":["instagram","whatsapp","ads"],"url":"https://docs.zernio.com/changelog#1792559848502"},{"id":"1792557117993","date":"2026-10-21T04:31:57.993Z","type":"new_feature","message":"Instagram now supports listing **live broadcasts in progress** for a connected account via `GET /v1/accounts/{accountId}/instagram/live-media`.\n\nThis returns the live videos the account is broadcasting right now; when nothing is live, you’ll get `200` with an empty `data` array (not an error).\n\nKey details:\n• Path param: `accountId` (Instagram account connected via **Facebook Login**)\n• Response: `{ data: InstagramLiveMedia[] }` with fields like `id`, `mediaType`, `mediaProductType`, `username`, `timestamp`\n• Accounts connected with classic Instagram Login will get `400` `instagram_live_media_requires_facebook_login` and must be reconnected using Facebook Login.","platforms":["instagram","ads"],"url":"https://docs.zernio.com/changelog#1792557117993"},{"id":"1792541657077","date":"2026-10-21T00:14:17.077Z","type":"new_feature","message":"Connect now supports a **workspace-saved welcome screen** you can reuse across OAuth connect flows.\n\nYou can save branding once for the whole workspace, and (optionally) have it show by default on `GET /v1/connect/{platform}` calls that don’t explicitly pass `welcome`.\n\nNew endpoints:\n• `GET /v1/connect/welcome-screen` – fetch saved settings\n• `PUT /v1/connect/welcome-screen` – replace saved settings (omitted fields are cleared)\n\nSaved settings include:\n• `showByDefault`\n• `brandName`\n• `logoUrl`\n• `theme` (same keys as `welcomeTheme`)\n\nConnect behavior update:\n• `GET /v1/connect/{platform}` `welcome` can now be omitted and still show the welcome screen if `showByDefault=true`\n• Pass `welcome=false` to explicitly skip it (also skipped when `headless=true` or `signup=hosted`)","platforms":["general"],"url":"https://docs.zernio.com/changelog#1792541657077"},{"id":"1792541416786","date":"2026-10-21T00:10:16.786Z","type":"new_feature","message":"Workflows now support a new node type: `hubspot_upsert_contact` in `body.nodes[].type`.\n\nThis node lets you create or update a contact in the connected HubSpot portal during a workflow run, and save the result into workflow variables.\n\nUse `type` = `hubspot_upsert_contact` with `config`:\n• `email`, `phone`, `firstName`, `lastName`\n• `properties` (map of HubSpot internal property names → values, max 50)\n• `saveAs` (defaults to `hubspotContact`)\n\nEdges via `sourceHandle`:\n• `success`\n• `error`","platforms":["general"],"url":"https://docs.zernio.com/changelog#1792541416786"},{"id":"1792539897856","date":"2026-10-20T23:44:57.856Z","type":"new_feature","message":"Google Ads ad accounts now support managing **user access invitations** (list, invite by email, and revoke).\n\nList pending/declined/expired invites with `GET /v1/ads/accounts/user-invitations` using `accountId` (required) and optional `adAccountId`.\n\nSend an invite with `POST /v1/ads/accounts/user-invitations`:\n• `accountId`, `adAccountId`, `emailAddress`, `accessRole`, optional `validateOnly`\n• `accessRole`: `ADMIN` | `STANDARD` | `READ_ONLY` | `EMAIL_ONLY`\n• Response `status`: `PENDING` | `PENDING_APPROVAL` (multi-party approval)\n\nRevoke an unaccepted invite with `DELETE /v1/ads/accounts/user-invitations` using `accountId`, `adAccountId`, `invitationId`.","platforms":["google-ads","ads"],"url":"https://docs.zernio.com/changelog#1792539897856"},{"id":"1792536624856","date":"2026-10-20T22:50:24.856Z","type":"improvement","message":"Meta conversions now support **Business Messaging** events via `actionSource` = `business_messaging` and a new `messaging` object on `POST /v1/ads/conversions` events.\n\nUse this to attribute conversions that happen in WhatsApp, Messenger, or Instagram Direct conversations.\n\nSet `events[].actionSource` to `business_messaging` and include `events[].messaging`:\n• `channel`: `whatsapp` | `messenger` | `instagram_direct`\n• WhatsApp: `wabaId`, `ctwaClid`\n• Messenger/Instagram Direct: `pageId`\n• Messenger: `pageScopedUserId`\n• Instagram Direct: `instagramScopedUserId`","platforms":["instagram","facebook","whatsapp","meta-ads","ads","analytics"],"url":"https://docs.zernio.com/changelog#1792536624856"},{"id":"1792536102849","date":"2026-10-20T22:41:42.849Z","type":"new_feature","message":"Instagram posts now support fetching **collaborators** via `GET /v1/accounts/{accountId}/instagram/media/{mediaId}/collaborators`.\n\nUse it to list invited collaborator accounts on a published post and see whether each invite was accepted or is still pending.\n\nParams:\n• `accountId` (Instagram account connected via Facebook Login)\n• `mediaId` (digits-only Instagram media ID)\n\nResponse items include `id`, `username`, `inviteStatus` (`accepted` | `pending`, other Meta values passed through lowercase). Meta returns up to 5 collaborators; no collaborators returns an empty list.\n\nNote: accounts connected with classic Instagram Login will get `instagram_collaborators_require_facebook_login` and must be reconnected using Facebook Login.","platforms":["instagram"],"url":"https://docs.zernio.com/changelog#1792536102849"},{"id":"1792533668190","date":"2026-10-20T22:01:08.190Z","type":"new_feature","message":"HubSpot is now supported as a **CRM connection**, including OAuth connect and per-portal sync settings.\n\nStart the connect flow with `GET /v1/connect/hubspot` using `profileId` (optional `redirect_url`). The response returns `authUrl` and `state`.\n\nRead/update what Zernio syncs to HubSpot via:\n• `GET /v1/accounts/{accountId}/hubspot-settings`\n• `PATCH /v1/accounts/{accountId}/hubspot-settings` with `syncContacts` and/or `logMessages`\n\nHubSpot is also available in account listing/health filters:\n• `GET /v1/accounts` `category=crm`\n• `GET /v1/accounts/health` `platform=hubspot`\n• `platform` enum now includes `hubspot` in relevant responses.","platforms":["general"],"url":"https://docs.zernio.com/changelog#1792533668190"},{"id":"1792524148606","date":"2026-10-20T19:22:28.606Z","type":"improvement","message":"You can now filter `GET /v1/inbox/conversations` by SMS conversations using `platform=sms`.\n\nThis lets you include or isolate SMS threads when listing aggregated inbox conversations.\n\nSet `platform` to one of: `facebook`, `instagram`, `twitter`, `bluesky`, `reddit`, `telegram`, `whatsapp`, `imessage`, `sms`.","platforms":["inbox"],"url":"https://docs.zernio.com/changelog#1792524148606"},{"id":"1792520407818","date":"2026-10-20T18:20:07.818Z","type":"improvement","message":"Google Business Profile search keywords now support pagination and thresholded impression reporting in `GET /v1/analytics/googlebusiness/search-keywords`.\n\nYou can fetch up to 100 keywords per page and continue with `pageToken` when `nextPageToken` is returned.\n\nNew:\n• Request param: `pageToken`\n• Response field: `nextPageToken`\n• Response field: `keywords[].impressionsThreshold` (when Google only reports “below threshold”, `impressions` will be `0` and `impressionsThreshold` will be set; otherwise it’s `null`)","platforms":["google-business","analytics"],"url":"https://docs.zernio.com/changelog#1792520407818"},{"id":"1792519740970","date":"2026-10-20T18:09:00.970Z","type":"new_feature","message":"X posts now support tagging users on attached photos via `mediaTaggedUsers` in `platformSpecificData`.\n\nProvide up to 10 X usernames (with or without `@`) to tag on photos (not video/GIF). Tags are applied to the media of every tweet in a thread; an unknown username will fail the post.\n\nUse `platformSpecificData.mediaTaggedUsers` (max 10, pattern `^@?\\w{1,15}$`).","platforms":["x","publishing"],"url":"https://docs.zernio.com/changelog#1792519740970"},{"id":"1792519004589","date":"2026-10-20T17:56:44.589Z","type":"improvement","message":"`GET /v1/posts` now supports cursor pagination and field selection.\n\nUse `cursor` to page through long histories efficiently (constant cost per page vs deep `page`/`offset`), and use `fields` to return only the post fields you need to reduce response size.\n\nNew options:\n• `cursor` — pass `pagination.nextCursor` from the previous response (takes precedence over `page`/`offset`; must use same filters + `sortBy`; not available with `sortBy=status` or `sortBy=platform`, or with `source=external`)\n• `fields` — comma-separated fields to return (always includes `_id`; `fields=_id` returns ids only; unknown field returns 400; not available with `source=external`)\n\nResponse addition:\n• `pagination.nextCursor` — string or null (null on last page; always null for `sortBy=status`/`sortBy=platform`; absent for `source=external`)","platforms":["publishing"],"url":"https://docs.zernio.com/changelog#1792519004589"},{"id":"1792475698287","date":"2026-10-20T05:54:58.287Z","type":"improvement","message":"GET `/v1/ads` now returns Google Display image asset resource names in `ads[].creative.assets`.\n\nThis lets you see the native Google asset _resourceName_ for each image role on a responsive display ad (useful for auditing what’s currently attached after creative updates).\n\nNew fields:\n• `ads[].creative.assets.marketingImages[].resourceName` (landscape; first item matches `creative.assetResourceName`)\n• `ads[].creative.assets.squareMarketingImages[].resourceName` (square)\n\nOnly applies to Google Display ads; absent for other platforms and for ads synced from Google.","platforms":["ads"],"url":"https://docs.zernio.com/changelog#1792475698287"},{"id":"1792462350598","date":"2026-10-20T02:12:30.598Z","type":"improvement","message":"SMS carrier registration endpoints may now return `403` when the caller is a team member limited to specific profiles (registrations are account-wide).\n\nIf you use restricted team-member tokens, make sure your integration handles `403` on:\n• `GET /v1/sms/registrations`\n• `POST /v1/sms/registrations`\n• `GET /v1/sms/registrations/{id}`\n• `DELETE /v1/sms/registrations/{id}`\n• `POST /v1/sms/registrations/{id}/verify-otp`\n• `POST /v1/sms/registrations/{id}/resend-otp`\n• `POST /v1/sms/registrations/{id}/appeal`\n• `POST /v1/sms/registrations/{id}/respond`","platforms":["telephony"],"url":"https://docs.zernio.com/changelog#1792462350598"},{"id":"1792461755908","date":"2026-10-20T02:02:35.908Z","type":"improvement","message":"Organic Reddit endpoints may now return `503` with `PLATFORM_DISABLED`.\n\nReddit has blocked third-party apps from its API, so these endpoints are temporarily unavailable and will return `503` until Reddit restores access (Reddit Ads is not affected).\n\nHandle `503` (`PLATFORM_DISABLED`) for:\n• `GET /v1/tools/validate/subreddit`\n• `GET /v1/reddit/search`\n• `GET /v1/reddit/feed`\n• `GET /v1/reddit/comments/{postId}`\n• `GET /v1/accounts/{accountId}/reddit-subreddits`\n• `PUT /v1/accounts/{accountId}/reddit-subreddits`\n• `GET /v1/accounts/{accountId}/reddit-subreddits/{subreddit}/rules`\n• `POST /v1/accounts/{accountId}/reddit-vote`\n• `GET /v1/accounts/{accountId}/reddit-flairs`\n• `POST /v1/accounts/{accountId}/reddit-flairs`","platforms":["reddit"],"url":"https://docs.zernio.com/changelog#1792461755908"},{"id":"1792461044942","date":"2026-10-20T01:50:44.942Z","type":"improvement","message":"GET `/v1/ads/accounts` now returns `accounts[].selected`.\n\n`selected` tells you whether each ad account is in the connection’s ad-account selection (the ones Zernio syncs). If a connection has no selection, every account returns `selected: true`.\n\nUse `accounts[].selected` to decide which ad accounts are eligible for writes; writes to an ad account with `selected: false` will be restricted and return `403` `ad_account_not_selected` until it’s added to the selection.","platforms":["ads"],"url":"https://docs.zernio.com/changelog#1792461044942"},{"id":"1792458481376","date":"2026-10-20T01:08:01.376Z","type":"new_feature","message":"Wix is now supported as a **blog platform** in the Blogs API, and you can start the Wix install/connect flow via a new endpoint.\n\nUse `GET /v1/connect/wix` to get the Wix installer URL.\nRequired: `profileId`\nOptional: `redirect_url`\nReturns: `authUrl`, `state`\n\nAfter connecting, Wix accounts work with:\n• `GET /v1/accounts/{accountId}/blogs` (now supports `platform=wix`)\n• `/v1/accounts/{accountId}/blogs/{blogId}/articles` CRUD (now supports `platform=wix`)\n\nNotes for Wix:\n• Wix is connect-only for Blogs (no social posts/inbox/analytics/ads)\n• Wix list-articles returns `bodyHtml: null`; fetch the article to get the body\n\nAlso added `wix` to platform enums in places like `GET /v1/accounts/health` (query `platform`) and account objects (`platform`).","platforms":["general"],"url":"https://docs.zernio.com/changelog#1792458481376"},{"id":"1792453804752","date":"2026-10-19T23:50:04.752Z","type":"new_feature","message":"Commerce now supports **Orders**: you can list and fetch store orders, and subscribe to order webhooks.\n\nNew endpoints (requires `orders.read`; store capability `orders.read`):\n• `GET /v1/commerce/orders` with `accountId`, optional `limit`, `cursor`, `createdAfter`, `createdBefore`, `updatedAfter`\n• `GET /v1/commerce/orders/{orderId}` with `accountId`\n\nNew webhook events (available in `POST/PUT /v1/webhooks/settings` and `POST /v1/webhooks/test`): `commerce.order.created`, `commerce.order.updated`, `commerce.order.refunded`.\n\nOrder webhook payloads use `WebhookPayloadCommerceOrder` and include identifiers only; read full order details via `GET /v1/commerce/orders/{orderId}`.","platforms":["commerce","webhooks"],"url":"https://docs.zernio.com/changelog#1792453804752"},{"id":"1792441040336","date":"2026-10-19T20:17:20.336Z","type":"new_feature","message":"Added `GET /v1/analytics/linkedin/demographics` to fetch LinkedIn organization follower demographics (lifetime breakdown by job function, seniority, industry, and company size).\n\nQuery params:\n• `accountId` (required) - LinkedIn organization SocialAccount ID\n• `breakdown` - comma-separated: `function`, `seniority`, `industry`, `company_size` (defaults to all)\n\nResponse includes `metric`=`follower_demographics` and a `demographics` object with arrays of `{dimension, id, value}` (sorted by `value` desc).\n\nNotes: organization accounts only (personal returns 400 `personal_account_not_supported`). Requires LinkedIn scopes `r_organization_social`, `r_organization_followers`, `r_organization_admin` (412 `linkedin_org_analytics_scope_missing` otherwise) and the Analytics add-on (402 `analytics_addon_required`).","platforms":["linkedin","analytics"],"url":"https://docs.zernio.com/changelog#1792441040336"},{"id":"1792433920223","date":"2026-10-19T18:18:40.223Z","type":"improvement","message":"WhatsApp business username endpoints now return `403` when Meta hasn’t granted username management permissions for the WhatsApp Business Account.\n\nThis makes permission issues explicit via the standard error envelope with code `insufficient_permissions`.\n\nApplies to:\n• `GET /v1/whatsapp/business-profile/username`\n• `POST /v1/whatsapp/business-profile/username`\n• `DELETE /v1/whatsapp/business-profile/username`\n\nHandle `403` + `insufficient_permissions` separately from `401` (auth) and `404` (account not found).","platforms":["whatsapp"],"url":"https://docs.zernio.com/changelog#1792433920223"},{"id":"1792395470894","date":"2026-10-19T07:37:50.894Z","type":"new_feature","message":"PUT `/v1/ads/ad-sets/{adSetId}` now supports updating Meta ad set **placements** and **targeting**.\n\nUse `placements` to replace the full manual placement set (or send `null` to return to automatic placements). Use `targeting` (Meta `TargetingSpec`) to update specific targeting keys; Zernio reads live targeting, merges only the keys you send, and writes the full spec back.\n\nNew request fields (Meta only; other platforms return `400`):\n• `placements` (object | null)\n• `targeting` (object)\n\nNew response behavior:\n• `200.targeting` is returned when `placements` or `targeting` was sent (full Meta targeting as written, snake_case)\n• `502` is returned if Meta live targeting can’t be read, and the placements/targeting update is not written","platforms":["ads"],"url":"https://docs.zernio.com/changelog#1792395470894"},{"id":"1792390357365","date":"2026-10-19T06:12:37.365Z","type":"improvement","message":"GET `/v1/ads/targeting/search` now supports `dimension=interestSuggestion` (Meta only).\n\nThis lets you fetch Meta interest suggestions related to the interest names you provide in `q`, returning interest ids you can use in `TargetingSpec.interests`.\n\nUse:\n• `dimension`: `interestSuggestion`\n• `q`: comma-separated interest names (e.g. `NBA,Michael Jordan`) or a JSON array of strings when a name contains a comma (e.g. `[\"Earth, Wind & Fire\",\"Yoga\"]`, URL-encoded)","platforms":["ads"],"url":"https://docs.zernio.com/changelog#1792390357365"},{"id":"1792371589918","date":"2026-10-19T00:59:49.918Z","type":"improvement","message":"`rateLimitedUntil` is now returned on accounts in `GET /v1/analytics` and `GET /v1/accounts/follower-stats`.\n\nThis exposes the end time of an account’s publishing cooldown so you can avoid scheduling/retrying publishes that would be rejected during the cooldown window.\n\nNew field: `accounts[].rateLimitedUntil` (`date-time` | `null`)","platforms":["publishing","analytics"],"url":"https://docs.zernio.com/changelog#1792371589918"},{"id":"1792296721305","date":"2026-10-18T04:12:01.305Z","type":"new_feature","message":"Workflows now support **TikTok** as a platform.\n\nYou can create or move a workflow onto TikTok by setting `platform` (create) or using `accountId` reassignment (update) for a TikTok social account.\n\nUpdated `platform` enum includes: `whatsapp`, `instagram`, `facebook`, `telegram`, `twitter`, `bluesky`, `reddit`, `tiktok`.","platforms":["inbox"],"url":"https://docs.zernio.com/changelog#1792296721305"},{"id":"1792215858157","date":"2026-10-17T05:44:18.157Z","type":"improvement","message":"GET `/v1/ads` now returns `ads[].creative.assetResourceName` for Google Display ads.\n\nThis exposes the Google resource name of the landscape marketing image asset behind `creative.imageUrl`, making it easier to track which asset an ad is currently pointing at after creative updates.\n\nNew field:\n• `creative.assetResourceName` (Google Display only)","platforms":["ads"],"url":"https://docs.zernio.com/changelog#1792215858157"},{"id":"1792208688134","date":"2026-10-17T03:44:48.134Z","type":"improvement","message":"POST `/v1/ads/videos` now lets you set the video’s name in Meta’s Ads Manager media library.\n\nUse `name` (takes precedence) or `filename` (now `maxLength: 255`) to control how the uploaded video is titled/searchable.\n\nNew optional fields:\n• `name`\n• `filename` (max 255 chars)","platforms":["meta-ads","ads"],"url":"https://docs.zernio.com/changelog#1792208688134"},{"id":"1792191355860","date":"2026-10-16T22:55:55.860Z","type":"new_feature","message":"Meta catalog template ads can now be configured with an explicit format via `catalogFormat` on `POST /v1/ads/create`.\n\nThis lets you control the template creative layout Meta uses for catalog ads (goal `catalog_sales`, or `traffic` with `promotedObject.productSetId`, or attach via `adSetId` + `promotedObject.productSetId`).\n\nSet `catalogFormat` to:\n• `single_image`\n• `carousel_images_multi_items`\n• `carousel_images_single_item`\n• `carousel_slideshows`\n\nAlso added to `GET /v1/ads` responses:\n• `ads[].creative.creativeFeatures` (Meta-only; echoed when created via Zernio)\n• `ads[].creative.catalog` (Meta-only; catalog template details when created via Zernio)","platforms":["ads"],"url":"https://docs.zernio.com/changelog#1792191355860"},{"id":"1792175339086","date":"2026-10-16T18:28:59.086Z","type":"improvement","message":"OAuth connect URLs now support an optional **branded welcome screen** before sending users to the platform’s authorization dialog.\n\nEnable it with `welcome=true` on `GET /v1/connect/{platform}`. You can optionally customize the screen via `brandName`, `logoUrl`, `primaryColor`, and `welcomeTheme` (URL-encoded JSON).\n\nNotes:\n• `welcome=true` cannot be combined with `headless=true` or `signup=hosted`\n\nActivity logs now support better filtering and correlation.\n\nOn `GET /v1/logs`:\n• New filter: `profileId`\n• New fields in `200.logs[]`: `profile_id`, `operation_id`","platforms":["general"],"url":"https://docs.zernio.com/changelog#1792175339086"},{"id":"1792166265239","date":"2026-10-16T15:57:45.239Z","type":"improvement","message":"GET `/v1/inbox/comments/{postId}` now returns `200.meta.postUrn` for LinkedIn.\n\nThis exposes the exact LinkedIn post URN that was queried (after resolving `postId`), which helps when you pass a bare number and it’s interpreted as `urn:li:activity:&lt;n&gt;`.\n\nNew field:\n• `meta.postUrn` (LinkedIn only) — the resolved post URN used for the request","platforms":["inbox"],"url":"https://docs.zernio.com/changelog#1792166265239"},{"id":"1792130621532","date":"2026-10-16T06:03:41.532Z","type":"improvement","message":"`POST /v1/posts/{postId}/update-metadata` now returns `429` when YouTube rate-limits thumbnail uploads (`uploadRateLimitExceeded`).\n\nThis can happen when updating `thumbnailUrl`; handle `429` by retrying later (not immediately) to avoid staying over the channel limit.\n\nKey behavior:\n• Thumbnail updates via `thumbnailUrl` may return `429`\n• Other temporary failures still return `500` (retryable)","platforms":["publishing"],"url":"https://docs.zernio.com/changelog#1792130621532"},{"id":"1792093346444","date":"2026-10-15T19:42:26.444Z","type":"improvement","message":"Error responses for `POST /v1/inbox/conversations/{conversationId}/messages` now include additional platform diagnostics when the send is rejected by the platform.\n\nOn `400` responses with `type`=`platform_error` and `code`=`platform_api_error`, `platformError` may now include:\n• `platformError.message` (TikTok error message)\n• `platformError.request_id` (TikTok request id for support tickets)\n\nExisting integrations continue to work; these fields are optional and can be logged for troubleshooting.","platforms":["inbox"],"url":"https://docs.zernio.com/changelog#1792093346444"},{"id":"1792068213647","date":"2026-10-15T12:43:33.647Z","type":"new_feature","message":"You can now **delete an inbox conversation** from Zernio storage via `DELETE /v1/inbox/conversations/{conversationId}`.\n\nThis permanently removes Zernio’s stored copy of the conversation and its messages (the thread is not deleted on WhatsApp/Messenger/Instagram), and unlinks the contact channel; the contact record remains.\n\nCall with:\n• `conversationId` (path)\n• `accountId` (query)\n\nSuccess response includes `data.id`, `data.accountId`, `data.platform`, `data.deletedMessages`.","platforms":["inbox"],"url":"https://docs.zernio.com/changelog#1792068213647"},{"id":"1792044265291","date":"2026-10-15T06:04:25.291Z","type":"improvement","message":"Instagram account selection now may include follower counts in the response from `GET /v1/connect/instagram/select-account`.\n\nWhen Meta returns it, you’ll receive `200.pages[].instagram_business_account.followers_count` (integer), which you can use to show richer account details during the Page/IG account picker step.","platforms":["instagram"],"url":"https://docs.zernio.com/changelog#1792044265291"},{"id":"1792036040491","date":"2026-10-15T03:47:20.491Z","type":"improvement","message":"GET `/v1/ads/accounts` now supports `inScopeOnly` (Google Ads only).\n\nWhen `inScopeOnly=true`, the response includes only Google Ads customers that this connection will accept as `adAccountId`/`customerId` on other `/v1/ads` endpoints (i.e. inside its ad-account selection). Without it, the list may include customers reachable by the Google login but not usable on this connection.\n\nUse: `inScopeOnly=true` (default: `false`). Ignored on non-Google platforms.","platforms":["ads"],"url":"https://docs.zernio.com/changelog#1792036040491"},{"id":"1792017741452","date":"2026-10-14T22:42:21.452Z","type":"improvement","message":"GET `/v1/ads/{adId}` now returns `darkPost` when `{adId}` is a Facebook placement dark post that can’t be mapped to a single ad.\n\nThis lets you detect the “dark post with no resolvable ad” case and still proceed with comment moderation by using the same id with the ad comments endpoint.\n\nIn this case the response is `ad: null` plus `darkPost` with key fields like `darkPost.platform` (`facebook`), `darkPost.platformPostId`, `darkPost.pageId`, `darkPost.platformAdAccountIds`, `darkPost.firstSeenAt`, `darkPost.lastActivityAt`.","platforms":["meta-ads","tiktok-ads","ads"],"url":"https://docs.zernio.com/changelog#1792017741452"},{"id":"1792006435820","date":"2026-10-14T19:33:55.820Z","type":"improvement","message":"Instagram Business Discovery now returns richer media details in `GET /v1/accounts/{accountId}/instagram/business-discovery`.\n\nYou can now retrieve temporary CDN URLs and extra metadata for each returned media item (useful for previews, downloading assets, and handling carousels).\n\nNew fields on `200.media[]`:\n• `mediaUrl` (temporary; may be `null` e.g. some licensed-music videos)\n• `thumbnailUrl` (VIDEO cover frame)\n• `viewCount` (Reels only)\n• `children` (carousel slides; `null` unless `mediaType` is `CAROUSEL_ALBUM`)\n\nNote: media URLs are temporary, and fields Meta doesn’t return come back as `null` (not omitted).","platforms":["instagram"],"url":"https://docs.zernio.com/changelog#1792006435820"},{"id":"1792005961347","date":"2026-10-14T19:26:01.347Z","type":"improvement","message":"Ads analytics breakdowns now return extra metadata to help you handle unsupported/partial breakdown results on Meta/TikTok/LinkedIn.\n\nOn `GET /v1/ads/campaigns/{campaignId}/analytics` and `GET /v1/ads/{adId}/analytics`, the response may now include:\n• `analytics.unsupportedBreakdowns` - requested breakdowns the platform can’t provide\n• `analytics.breakdownHints` - suggested alternatives for unsupported breakdowns\n• `analytics.breakdownErrors` - TikTok breakdowns that failed, while the response stays `200`\n• `analytics.breakdownDroppedMetrics` - TikTok metrics refused for a breakdown (derived row values become `null`)\n• `analytics.breakdownsRange` - TikTok-only window used when your requested range exceeds 365 days (summary/daily still cover the full range)\n\nUnknown breakdown names still return `400`.","platforms":["ads"],"url":"https://docs.zernio.com/changelog#1792005961347"},{"id":"1792004310547","date":"2026-10-14T18:58:30.547Z","type":"improvement","message":"Responses from `GET /v1/ads` now include `profileId` on each ad (`200.ads[].profileId`).\n\nUse `profileId` to attribute each ad to the owning profile (especially in multi-profile setups), even when `accountId`/connections change over time.","platforms":["ads"],"url":"https://docs.zernio.com/changelog#1792004310547"},{"id":"1791922998439","date":"2026-10-13T20:23:18.439Z","type":"improvement","message":"`POST /v1/whatsapp/phone-numbers/kyc` and `POST /v1/whatsapp/phone-numbers/{id}/remediate` now include a `503` `TemporarilyUnavailable` response.\n\nThis indicates the service may be temporarily unable to process KYC submissions/remediations; clients should treat it as transient and retry with backoff.\n\nNew response:\n• `503` - `TemporarilyUnavailable`","platforms":["whatsapp"],"url":"https://docs.zernio.com/changelog#1791922998439"},{"id":"1791922084628","date":"2026-10-13T20:08:04.628Z","type":"improvement","message":"GET `/v1/analytics` now supports filtering by post tags via a new optional query parameter `tags`.\n\nUse `tags` to request analytics only for posts created through Zernio that contain every listed tag (exact, case-sensitive match). This filter is ignored when `postId` is set.\n\nKey details:\n• `tags`: comma-separated list (max 20)\n• If more than 200 published posts match within the date range, the API returns `400` asking you to narrow `fromDate`/`toDate`","platforms":["analytics"],"url":"https://docs.zernio.com/changelog#1791922084628"},{"id":"1791918493371","date":"2026-10-13T19:08:13.371Z","type":"improvement","message":"`GET /v1/inbox/comments` now documents a `400` `BadRequest` response for invalid requests.\n\nThis can occur when using `accountId` with `platform=metaads` and the `accountId` is not a Meta Ads account ID, or a Facebook/Instagram account ID tied to a Meta Ads connection (non-Meta platforms return `400`).\n\nIf you validate inputs client-side, ensure `platform=metaads` is only paired with supported `accountId` values, and handle `400` alongside `401`/`403` in your error handling.","platforms":["inbox"],"url":"https://docs.zernio.com/changelog#1791918493371"},{"id":"1791915396494","date":"2026-10-13T18:16:36.494Z","type":"improvement","message":"TikTok campaigns and ad groups now include an `isSmartPlus` flag in ads listing/tree responses.\n\nUse this to detect Upgraded Smart+ objects and adjust your UI/automation (some edits may be restricted on Smart+).\n\nNew fields:\n• `GET /v1/ads/campaigns` → `campaigns[].isSmartPlus`\n• `GET /v1/ads/tree` → `campaigns[].isSmartPlus`\n• `GET /v1/ads/tree` → `campaigns[].adSets[].isSmartPlus`","platforms":["ads"],"url":"https://docs.zernio.com/changelog#1791915396494"},{"id":"1791914152123","date":"2026-10-13T17:55:52.123Z","type":"improvement","message":"Meta carousel ads now support controlling card order via `optimizeCardOrder` on create endpoints.\n\nBy default Meta may reorder carousel cards for performance. Set `optimizeCardOrder: false` to keep the order you send.\n\nAvailable on:\n• `POST /v1/ads/create`\n• `POST /v1/ads/creatives`\n• `POST /v1/ads/messaging`\n• `POST /v1/ads/call`\n• `POST /v1/ads/ctwa`\n\nRules:\n• Only valid when `carouselCards` is present (otherwise 400)\n• `optimizeCardOrder: false` limits Meta to max 5 cards (so >5 cards with `false` is a 400)\n• Omit the field to use Meta’s default (`true`)","platforms":["meta-ads","ads"],"url":"https://docs.zernio.com/changelog#1791914152123"},{"id":"1791849573843","date":"2026-10-12T23:59:33.843Z","type":"improvement","message":"You can now delete **X (twitter)** campaigns via `DELETE /v1/ads/campaigns/{campaignId}`.\n\nFor EMPTY campaigns (zero ads), you can pass `accountId` either in the JSON body or on the query string, and on X you may also need `adAccountId` (when the campaign was never synced).\n\nNew options:\n• `platform` now supports `twitter`\n• `adAccountId` (X only) added for deleting an empty, never-synced campaign\n\nAlso, `POST /v1/ads/boost` adds `lifetimeBudgetCap` (X only): set a lifetime total cap (whole currency units) while using `budgetType=daily`.","platforms":["ads"],"url":"https://docs.zernio.com/changelog#1791849573843"},{"id":"1791835348791","date":"2026-10-12T20:02:28.791Z","type":"improvement","message":"Facebook comment webhooks now include `comment.ad.isDarkPost` in `WebhookPayloadComment`.\n\nThis lets you tell whether a comment came from an unpublished ad creative (dark post) vs a boosted organic post when `comment.ad` is present.\n\nNew field:\n• `comment.ad.isDarkPost` (boolean) — `true` = dark post, `false` = boosted organic post","platforms":["meta-ads","ads","inbox","webhooks"],"url":"https://docs.zernio.com/changelog#1791835348791"},{"id":"1791835272811","date":"2026-10-12T20:01:12.811Z","type":"improvement","message":"Support thread run listings now include the original question via `question` on each run returned by `GET /v1/support/runs`.\n\nThis lets you render a full Q&A history (question + answer) when paging through a thread.\n\nNew field: `runs[].question` (string | null).","platforms":["general"],"url":"https://docs.zernio.com/changelog#1791835272811"},{"id":"1791828177930","date":"2026-10-12T18:02:57.930Z","type":"improvement","message":"Webhook payloads now include **profile routing info** via a new `profiles` field.\n\nWhen present, `profiles` lists the profile(s) the event belongs to, including the `externalId` you set on the profile (or `null` if none). This helps route events to the right tenant without storing Zernio profile IDs.\n\nNew field:\n• `profiles`: `[{ id: string, externalId: string|null }]`\n\nAdded to many webhook payloads, including:\n• `WebhookPayloadPost`, `WebhookPayloadPostPlatform`\n• `WebhookPayloadAccountConnected`, `WebhookPayloadAccountDisconnected`\n• `WebhookPayloadMessage*`, `WebhookPayloadConversation*`\n• `WebhookPayloadComment`, `WebhookPayloadLead`, `WebhookPayloadReview*`","platforms":["whatsapp","meta-ads","ads","inbox","webhooks"],"url":"https://docs.zernio.com/changelog#1791828177930"},{"id":"1791828080054","date":"2026-10-12T18:01:20.054Z","type":"new_feature","message":"Added `GET /v1/support/runs` to list a support thread’s runs (Ana’s answers), returned oldest-first.\n\nUse it to page through long threads and retrieve runs kept for 90 days.\n\nKey params:\n• `threadId` (required)\n• `limit` (1–50, default `20`)\n• `cursor` (pass prior `nextCursor`)\n\nResponse includes `runs` and `nextCursor` (null when there are no more pages).","platforms":["general"],"url":"https://docs.zernio.com/changelog#1791828080054"},{"id":"1791826939892","date":"2026-10-12T17:42:19.892Z","type":"improvement","message":"Profiles now support **external IDs** so you can map a Zernio profile to your own tenant/client identifier and look it up later.\n\nYou can:\n• Set `externalId` when creating a profile via `POST /v1/profiles`\n• Update/clear it via `PUT /v1/profiles/{profileId}` (set `externalId` to `null` to clear)\n• Filter profiles by it via `GET /v1/profiles?externalId=...`\n\nIf `externalId` is already used by another profile of the same owner, you’ll get `409` with code `profile_external_id_conflict` (and `details.existingProfileId`).","platforms":["general"],"url":"https://docs.zernio.com/changelog#1791826939892"},{"id":"1791824444558","date":"2026-10-12T17:00:44.558Z","type":"improvement","message":"**Meta (Facebook/Instagram) ads are now supported** in `GET /v1/ads/{adId}/review`.\n\nYou can now read Meta’s live review status and feedback for an ad, including placement-specific rejections, without re-enabling a paused ad.\n\nNew Meta-only fields in `200.review`:\n• `reviewFeedback` (from `ad_review_feedback`): `global` and `placementSpecific`\n• `issues` (from `issues_info`)\n\nMeta uses `reviewStatus` = `effective_status` (e.g. `ACTIVE`, `PAUSED`, `PENDING_REVIEW`, `DISAPPROVED`, `PREAPPROVED`, `WITH_ISSUES`, `IN_PROCESS`, ...). Placement-level feedback is reflected in `rejections` and `forbiddenPlacements`.\n\nThe endpoint may now return `429` (`RateLimited`).","platforms":["ads"],"url":"https://docs.zernio.com/changelog#1791824444558"},{"id":"1791798946892","date":"2026-10-12T09:55:46.892Z","type":"improvement","message":"`POST /v1/inbox/comments/{postId}/{commentId}/private-reply` now returns `502` when Meta errors after receiving the reply and delivery can’t be confirmed.\n\nThe `502` response indicates the private reply outcome is unknown (it’s usually delivered), so avoid blind retries and check the conversation first.\n\nOn `502`, the error payload includes `details.privateReplyOutcome` = `\"unknown\"`.","platforms":["inbox"],"url":"https://docs.zernio.com/changelog#1791798946892"},{"id":"1791775898739","date":"2026-10-12T03:31:38.739Z","type":"improvement","message":"YouTube analytics endpoints now include an explicit `429` `RateLimited` response.\n\nThis makes rate limiting behavior easier to detect and handle when calling these endpoints:\n• `GET /v1/analytics/youtube/daily-views`\n• `GET /v1/analytics/youtube/video-retention`\n\nIf you see `429`, back off and retry according to the `RateLimited` response.","platforms":["youtube","analytics"],"url":"https://docs.zernio.com/changelog#1791775898739"},{"id":"1791649528932","date":"2026-10-10T16:25:28.932Z","type":"improvement","message":"Account health responses now include **analytics sync status** via `analyticsSync`.\n\nThis lets you detect failing/stalled analytics syncs; a failing or stalled sync can raise an otherwise healthy account to at least `warning`.\n\nNew fields:\n• `GET /v1/accounts/health` → `accounts[].analyticsSync`\n• `GET /v1/accounts/{accountId}/health` → `analyticsSync`\n\n`analyticsSync.status` is one of: `ok`, `failing`, `stalled`, `never_synced`\n\n`analyticsSync.lastSyncedAt` is the last successful sync time (or `null` if never synced / latest attempt failed).","platforms":["analytics"],"url":"https://docs.zernio.com/changelog#1791649528932"},{"id":"1791621653431","date":"2026-10-10T08:40:53.431Z","type":"new_feature","message":"GET `/v1/ads/targeting/search` now supports TikTok lookups for **Additional Interests** and **Hashtags**.\n\nUse `dimension` to fetch targetable TikTok options beyond standard `interest`/`behavior`:\n• `dimension=interestKeyword`\n• `dimension=hashtag`\n\nResponses may now include `results[].status` for these TikTok dimensions (e.g. `EFFECTIVE`/`INEFFECTIVE` for `interestKeyword`, `ONLINE`/`OFFLINE` for `hashtag`) so you can filter to targetable ids.","platforms":["ads"],"url":"https://docs.zernio.com/changelog#1791621653431"},{"id":"1791546743582","date":"2026-10-09T11:52:23.582Z","type":"improvement","message":"`GET /v1/changelog` now includes an `impact` filter, and each changelog entry now returns an `impact` value.\n\nUse `impact` to fetch only entries that match a specific integration impact:\n• `none`\n• `additive`\n• `action_required`\n\nIn the response, `entries[].impact` indicates the computed impact (may be `null` for older entries). Operation refs in `entries[].changes.endpoints.*[]` also include `tags` and `xPlatforms` for easier routing/grouping.","platforms":["general"],"url":"https://docs.zernio.com/changelog#1791546743582"},{"id":"1791535732553","date":"2026-10-09T08:48:52.553Z","type":"improvement","message":"`GET /v1/inbox/comments/{postId}` now includes `post` metadata for **Facebook** and **Instagram** (in addition to Reddit).\n\nThis lets you render a preview of the post being commented on (text, permalink, thumbnail/media) without an extra API call.\n\n`post` can now be:\n• Reddit post shape (unchanged)\n• Facebook/Instagram post shape with key fields: `id`, `permalink`, `text`, `thumbnailUrl`, `mediaUrl`, `mediaType`, `productType`, `createdAt`\n\nInstagram `productType` values: `AD`, `FEED`, `REELS`, `STORY`.","platforms":["instagram","reddit","inbox"],"url":"https://docs.zernio.com/changelog#1791535732553"},{"id":"1791532800115","date":"2026-10-09T08:00:00.116Z","type":"new_feature","message":"New endpoint `GET /v1/ads/targeting/browse` to browse Meta detailed-targeting categories as a flat list you can render as a tree.\n\nUse it to display available targeting options without keywords; combine with `GET /v1/ads/targeting/search` for open-ended categories and name-based lookup.\n\nRequired query params:\n• `accountId` (connected Meta account)\n• `adAccountId` (format `act_&lt;digits&gt;`)\n\nOptional filters:\n• `type` (e.g. `interests`, `behaviors`, `income`, `life_events`, `education_statuses`, `relationship_statuses`)\n• `parentNodeId` (return descendants of an organizational node)\n• `selectable` (`true` selectable entities only, `false` organizational nodes only)\n\nResponse returns `nodes[]` with `nodeId`, `parentNodeId`, `id` (Meta targeting id or null), `type`, `path`, `selectable`, plus optional audience size bounds. No pagination (catalog is returned in one response).","platforms":["meta-ads","ads"],"url":"https://docs.zernio.com/changelog#1791532800115"},{"id":"1791532013706","date":"2026-10-09T07:46:53.706Z","type":"breaking_change","message":"GET `/v1/connect/{platform}/ads` now returns `409` `reconnect_required` in a specific case: when you call with `force=true` and `accountId` points to the posting account the ads connection depends on, but that posting account is no longer connected.\n\nThis prevents the API from reconnecting a different posting account implicitly; you must reconnect the posting account first.\n\nKey params:\n• `force=true`\n• `accountId=&lt;posting SocialAccount ID&gt;`\n\nUpdated `409` cases include:\n• `ambiguous_account`\n• `reconnect_required`","platforms":["ads"],"url":"https://docs.zernio.com/changelog#1791532013706"},{"id":"1791531107471","date":"2026-10-09T07:31:47.471Z","type":"new_feature","message":"POST `/v1/webhooks/test` now supports sending a **sample payload for a specific webhook event** (not just `webhook.test`).\n\nThis lets you exercise handlers for events that are hard to trigger in real life (e.g. outages/lifecycle events). Samples are delivered like real webhooks (same headers) but include `test: true` and placeholder ids (never your data), and they appear in webhook logs.\n\nUse:\n• `webhookId` (required)\n• `event` (optional) — one of: `post.scheduled`, `post.published`, `post.failed`, `post.partial`, `post.cancelled`, `post.recycled`, `post.platform.published`, `post.platform.failed`, `post.platform.deleted`, `post.tiktok.url_resolved`, `post.external.created`, `post.external.updated`, `post.external.deleted`, `account.connected`, `account.disconnected`, `account.ads.initial_sync_completed`, `account.ads.sync_failed`, `account.ads.sync_recovered`, `analytics.synced`, `message.received`, `message.sent`, `message.edited`, `message.deleted`, `message.delivered`, `message.read`, `message.played`, `message.failed`, `reaction.received`, `referral.received`, `conversation.started`, `conversation.control_changed`, `contact.tag_added`, `contact.tag_removed`, `contact.field_changed`, `sequence.enrolled`, `sequence.exited`, `workflow.run.started`, `workflow.run.completed`, `workflow.run.failed`, `support.run.completed`, `support.run.failed`, `call.received`, `call.ended`, `call.failed`, `call.permission_request`, `comment.received`, `review.new`, `review.updated`, `ad.status_changed`, `ad.video.processed`, `lead.received`, `whatsapp.template.status_updated`, `whatsapp.template.category_updated`, `whatsapp.account.name_status_updated`, `whatsapp.account.quality_updated`, `whatsapp.account.status_updated`, `whatsapp.account.alert_received`, `whatsapp.contact.identity_changed`, `whatsapp.number.activated`, `whatsapp.number.declined`, `whatsapp.number.action_required`, `whatsapp.automatic_event`, `whatsapp.number.verification_required`, `whatsapp.number.suspended`, `whatsapp.number.reactivated`, `whatsapp.number.released`, `whatsapp.number.kyc_submitted`, `phone_number.stock_available`, `sms.registration.action_required`, `sms.registration.status_updated`, `branded_calling.identity.status_updated`, `branded_calling.identity.action_required`, `branded_calling.number.status_updated`, `rcs.agent.status_updated`, `verification.approved`, `verification.failed`, `commerce.product.created`, `commerce.product.updated`, `commerce.product.deleted`, `api.changelog.published`\n\nNote: invalid `event` now returns `400` (omit `event` to send `webhook.test`).","platforms":["tiktok","whatsapp","meta-ads","ads","inbox","telephony","commerce","analytics","webhooks"],"url":"https://docs.zernio.com/changelog#1791531107471"},{"id":"1791515926362","date":"2026-10-09T03:18:46.362Z","type":"improvement","message":"POST `/v1/ads/boost` now supports using an X Ads connection as `accountId`.\n\nFor X, `accountId` can be either the posting X account or its X Ads connection; Zernio will resolve the other from the same profile.\n\nIf you send a Zernio `postId` while using an X Ads connection, it resolves via the profile’s linked X account. If the profile has no linked X account, the request returns `400` and you must use `platformPostId` instead.","platforms":["x","x-ads","ads"],"url":"https://docs.zernio.com/changelog#1791515926362"},{"id":"1791502514886","date":"2026-10-08T23:35:14.886Z","type":"improvement","message":"Post targets now include publishing attempt/retry counters in `PlatformTarget` responses.\n\nThis helps you debug why a target is still processing/failed and distinguish pipeline re-claims vs platform-level re-sends (Instagram).\n\nNew fields:\n• `publishAttempts` (integer) — number of times the pipeline claimed the target to publish since creation/last manual retry\n• `publishRetries` (integer) — _Instagram only_, automatic re-sends of `media_publish` on the same container after transient errors (absent when none was scheduled)","platforms":["instagram","publishing"],"url":"https://docs.zernio.com/changelog#1791502514886"},{"id":"1791477951120","date":"2026-10-08T16:45:51.120Z","type":"improvement","message":"Several phone-number endpoints may now return `403` when the API key is scoped to profiles that don’t include the target number.\n\nThis makes authorization failures explicit (instead of only `401`/`404`), so clients should handle `403` as “not permitted for this number/profile scope”.\n\nNew/updated response:\n• `403` - `The API key is scoped to profiles that do not include this number.`\n\nApplies to remediation and messaging flows (e.g. `/v1/phone-numbers/{id}/remediate`, `/remediate/reply`, `/remediate/respond`), voice (`/voice`), SMS (`/sms`, `/sms/reuse-registration`), SIP trunk attach/detach (`/sip-trunk`), WhatsApp verification (`/whatsapp/request-code`) and caller-id verification endpoints, plus deprecated WhatsApp alias paths.","platforms":["whatsapp","telephony"],"url":"https://docs.zernio.com/changelog#1791477951120"},{"id":"1791476748211","date":"2026-10-08T16:25:48.211Z","type":"improvement","message":"Some phone-number reads can now return **403 Forbidden** when the API key is scoped to profiles that don’t include the requested number.\n\nThis makes the “not allowed for this key” case explicit (instead of only `401`/`404`), so clients should handle `403` separately from “not found”.\n\nApplies to:\n• `GET /v1/phone-numbers/{id}` → `403`\n• `GET /v1/phone-numbers/{id}/whatsapp/calling` → `403`","platforms":["whatsapp","telephony"],"url":"https://docs.zernio.com/changelog#1791476748211"},{"id":"1791469939935","date":"2026-10-08T14:32:19.936Z","type":"new_feature","message":"TikTok ad metrics now include additional video retention fields in `AdMetrics`: `video2SecWatchedActions`, `video6SecWatchedActions`, `video6SecFocusedViews`.\n\nThese let you compute hook/hold-style video performance directly from API responses (TikTok only).\n\nNew fields:\n• `video2SecWatchedActions` - plays ≥ 2s (replays excluded)\n• `video6SecWatchedActions` - plays ≥ 6s (replays excluded)\n• `video6SecFocusedViews` - TikTok “engaged_view” (≥ 6s or interaction in first 6s)\n\nBackfill note: these were added in 2026-10 and sync re-fetches only the last 7 days, so older days may read `0` until the ad is re-synced from scratch.","platforms":["tiktok","tiktok-ads","ads","analytics"],"url":"https://docs.zernio.com/changelog#1791469939935"},{"id":"1791456088732","date":"2026-10-08T10:41:28.732Z","type":"improvement","message":"TikTok ads now return richer conversion metrics in `AdMetrics`.\n\nYou can now read TikTok website + app funnel events and (when provided by the advertiser) per-event values, enabling more complete TikTok ROAS and funnel reporting.\n\nKey updates:\n• `AdMetrics.actions` now includes TikTok website (pixel) events: `complete_payment`, `initiate_checkout`, `add_to_cart`, `view_content`, `lead` (plus existing app events like `purchase`)\n• `AdMetrics.actionValues` now supports TikTok (values only present when the event includes a value)\n• `AdMetrics.purchaseValue` for TikTok now includes app `purchase` value + website `complete_payment` value (from 2026-10-08)\n• `AdMetrics.funnel` now populates for TikTok (from 2026-10-08): `contentViews`, `cartAdds`, `checkoutsInitiated`, `purchases`, `leads`\n\nNote: TikTok history/backfill—these website event counts/values were missing before 2026-10-08, and each sync re-fetches only the last 7 days, so older days may remain absent until a full re-sync.","platforms":["tiktok","tiktok-ads","ads","analytics"],"url":"https://docs.zernio.com/changelog#1791456088732"},{"id":"1791452059810","date":"2026-10-08T09:34:19.810Z","type":"improvement","message":"Instagram Login connections now support forcing a fresh login via `GET /v1/connect/instagram`.\n\nUse this to prevent Instagram from silently using whatever account is currently logged in on the device (common when users manage multiple IG accounts on one phone).\n\nNew parameter:\n• `forceReauth`=`true` | `false` (default `false`) — Instagram Login only (`loginMethod=instagram_login`); ignored for other platforms.\n\nTip: pair `forceReauth=true` with `expectedUsername` to also reject a wrong account at the end of the flow.","platforms":["instagram"],"url":"https://docs.zernio.com/changelog#1791452059810"},{"id":"1791445933707","date":"2026-10-08T07:52:13.708Z","type":"improvement","message":"Facebook/Instagram connect selection endpoints now support sending the OAuth user token via `X-Temp-Token` (header) instead of `tempToken` in the query/body.\n\nThis lets you avoid putting access tokens in URLs (which often end up in logs) and also supports the Zernio-hosted picker flow via `connectFlow`.\n\nChanges:\n• `GET /v1/connect/facebook/select-page`: `tempToken` can be omitted if you send `X-Temp-Token`; new `connectFlow` (query)\n• `POST /v1/connect/facebook/select-page`: classic flow `tempToken` is now optional if using `X-Temp-Token`; new `connectFlow` (body)\n• `GET /v1/connect/instagram/select-account`: `tempToken` can be omitted if you send `X-Temp-Token`; new `connectFlow` (query)\n• `POST /v1/connect/instagram/select-account`: `tempToken` is now optional if using `X-Temp-Token`; new `connectFlow` (body)\n\nIf you currently pass `tempToken` in the query string, prefer `X-Temp-Token` going forward.","platforms":["instagram","facebook"],"url":"https://docs.zernio.com/changelog#1791445933707"},{"id":"1791411435102","date":"2026-10-07T22:17:15.102Z","type":"improvement","message":"**Error handling updated** for Pinterest boards and YouTube playlists listing endpoints.\n\nThese endpoints now return more explicit auth/connection failure reasons so clients can distinguish invalid API credentials from revoked/expired social tokens and prompt a reconnect.\n\nPinterest boards: `GET /v1/accounts/{accountId}/pinterest-boards`\n• `401` - missing/invalid API key (`missing_credentials` / `invalid_credentials`) or Pinterest token revoked/expired (`reconnect_required`)\n• `409` - no stored access token (`ads_connection_required`)\n• `502` - Pinterest rejected/failed the request\n\nYouTube playlists: `GET /v1/accounts/{accountId}/youtube-playlists`\n• `401` - missing/invalid API key (`missing_credentials` / `invalid_credentials`) or YouTube token revoked/expired (`reconnect_required`)","platforms":["youtube","pinterest"],"url":"https://docs.zernio.com/changelog#1791411435102"},{"id":"1791404883456","date":"2026-10-07T20:28:03.456Z","type":"improvement","message":"WhatsApp template sends now return a clearer `502` when the connected WhatsApp account is missing a Business Account ID, so the approved template definition can’t be looked up (no message is sent).\n\nThis affects template lookup failures on:\n• `POST /v1/inbox/conversations`\n• `POST /v1/inbox/conversations/{conversationId}/messages`\n\nIf you see `502` with a WhatsApp template send, check that the account has a valid WABA/Business Account ID; a missing approved template language variant is still a `400`.","platforms":["whatsapp","inbox"],"url":"https://docs.zernio.com/changelog#1791404883456"},{"id":"1791403472799","date":"2026-10-07T20:04:32.799Z","type":"new_feature","message":"New endpoint: `GET /v1/reddit/comments/{postId}` to read a Reddit post’s comments (fetched from Reddit on each call).\n\nReturns a flattened thread in reply order; rebuild the tree using `parentId` (post replies use `t3_…`, comment replies use `t1_…`). Use `more` + `commentId` to expand truncated threads.\n\nKey params:\n• `postId` (path) – with or without `t3_`\n• `accountId` (query, required)\n• `sort` (query) – `new` | `top` | `best` | `controversial` | `old` | `qa`\n• `limit` (query, max 100)\n• `commentId` (query) – fetch a specific comment + its replies (including ids from `more`)\n\nResponse shape:\n• `post` (nullable)\n• `items` (array of `RedditComment`)\n• `more` (array of omitted comment ids)\n\nErrors: `403`/`404` may return `platform_api_error` when Reddit refuses or no longer serves the thread.","platforms":["reddit"],"url":"https://docs.zernio.com/changelog#1791403472799"},{"id":"1791401390632","date":"2026-10-07T19:29:50.632Z","type":"improvement","message":"CTWA ad creation responses now may include `warnings` on success.\n\nThis lets you detect cases where Meta created the ad set differently than requested (e.g. the ad set was created without the requested `whatsappPhoneNumber` in `promoted_object`, while the ads still carry it on their WhatsApp button).\n\nCheck `warnings: string[]` in:\n• `CtwaSingleResponse`\n• `CtwaMultiResponse`","platforms":["whatsapp","ads"],"url":"https://docs.zernio.com/changelog#1791401390632"},{"id":"1791390883438","date":"2026-10-07T16:34:43.438Z","type":"improvement","message":"Outbound call origination now supports a configurable ring timeout via `ringTimeoutSeconds` on `POST /v1/voice/calls` and `POST /v1/voice/calls/web/dial`.\n\nUse it to control how long the callee rings before the call ends as `no_answer` (carrier may end sooner).\n\nNew parameter: `ringTimeoutSeconds` (integer, min `5`, max `120`, default `30`).\n\nThese endpoints may now also return `400` (`BadRequest`) for invalid requests.","platforms":["telephony"],"url":"https://docs.zernio.com/changelog#1791390883438"},{"id":"1791368592400","date":"2026-10-07T10:23:12.400Z","type":"new_feature","message":"**Google support added** to two ads endpoints.\n\n`GET /v1/ads/{adId}/review` now works on Google by reading `ad_group_ad.policy_summary` live, returning approval/review details and policy topics.\n\nNew/Google-only fields in `review`:\n• `approvalStatus` = `APPROVED` | `APPROVED_LIMITED` | `AREA_OF_INTEREST_ONLY` | `DISAPPROVED` | `UNKNOWN`\n• `reviewStatus` (Google) = `REVIEW_IN_PROGRESS` | `REVIEWED` | `UNDER_APPEAL` | `ELIGIBLE_MAY_SERVE`\n• `policyTopics[]` with `type` = `PROHIBITED` | `LIMITED` | `FULLY_LIMITED` | `DESCRIPTIVE` | `BROADENING` | `AREA_OF_INTEREST_ONLY` plus Google `evidences`/`constraints` verbatim\nNote: `forbidden*` arrays remain TikTok-only (empty on Google). `501` is now “TikTok and Google”.\n\n`GET /v1/ads/activity` now also supports Google by reading the customer `change_event` history (last 30 days) and mapping it onto the Meta row shape.\n\nKey behavior/fields:\n• `adAccountId` can be Meta `act_&lt;n&gt;` or Google customer id (digits)\n• Google keeps 30 days: `since` defaults to 29 days ago; older `since` returns `400`\n• Google rows include `changed_fields[]` and `extra_data` = JSON string `{ old, new }`\n• `501` is now “Meta and Google”","platforms":["tiktok","meta-ads","google-ads","tiktok-ads","ads"],"url":"https://docs.zernio.com/changelog#1791368592400"},{"id":"1791367066568","date":"2026-10-07T09:57:46.568Z","type":"breaking_change","message":"`POST /v1/ads/conversions/actions` now lets you set more Google Ads conversion action settings at creation.\n\nYou can optionally configure category, counting, value settings, lookback windows, and whether the action is primary for its goal.\n\nNew optional fields:\n• `category`: `DEFAULT`, `PAGE_VIEW`, `PURCHASE`, `SIGNUP`, `DOWNLOAD`, `ADD_TO_CART`, `BEGIN_CHECKOUT`, `SUBSCRIBE_PAID`, `PHONE_CALL_LEAD`, `IMPORTED_LEAD`, `SUBMIT_LEAD_FORM`, `BOOK_APPOINTMENT`, `REQUEST_QUOTE`, `GET_DIRECTIONS`, `OUTBOUND_CLICK`, `CONTACT`, `ENGAGEMENT`, `STORE_VISIT`, `STORE_SALE`, `QUALIFIED_LEAD`, `CONVERTED_LEAD`\n• `countingType`: `ONE_PER_CLICK` | `MANY_PER_CLICK`\n• `defaultCurrency` (ISO-4217)\n• `clickThroughLookbackWindowDays` (1–90)\n• `viewThroughLookbackWindowDays` (1–30)\n• `primaryForGoal`\n\n`PATCH /v1/ads/conversions/actions/{actionId}` now updates multiple conversion action settings (not just `primaryForGoal`).\n\nSend `accountId` plus at least one field to update (`minProperties: 2`). Supported fields include `name`, `primaryForGoal`, `category`, `countingType`, `defaultValue`, `defaultCurrency`, `alwaysUseDefaultValue`, `clickThroughLookbackWindowDays`, `viewThroughLookbackWindowDays`, and `status`.\n\n`status`: `ENABLED` | `REMOVED` (note: `REMOVED` must be sent alone; `ENABLED` restores a removed action).","platforms":["google-ads","ads"],"url":"https://docs.zernio.com/changelog#1791367066568"},{"id":"1791364691154","date":"2026-10-07T09:18:11.154Z","type":"new_feature","message":"Google Ads now supports **shared campaign budgets**.\n\nYou can list and create shared budgets, then move campaigns onto them via the campaign update endpoint.\n\nNew endpoints:\n• `GET /v1/ads/shared-budgets` with `accountId` (required) and optional `adAccountId`\n• `POST /v1/ads/shared-budgets` body: `accountId`, `name`, `amount`, optional `adAccountId`, `type` (`daily` | `lifetime`; only `daily` is accepted, `lifetime` returns 422)\n\nCampaign updates (Google):\n• `PUT /v1/ads/campaigns/{campaignId}` now accepts `sharedBudgetId` (`\"123\"` or `null`)\n• Moving onto a shared budget requires `allowSharedBudgetUpdate: true` (otherwise 409)\n• `sharedBudgetId` cannot be sent together with `budget`\n• `sharedBudgetId: null` moves the campaign back to its own budget and requires `budget` (Google: `daily`) in the same request\n\nOnly available for Google Ads; other platforms return 501.","platforms":["google-ads","ads"],"url":"https://docs.zernio.com/changelog#1791364691154"},{"id":"1791364481922","date":"2026-10-07T09:14:41.922Z","type":"breaking_change","message":"iMessage sender ordering can now return `sender.status=awaiting_payment` from `POST /v1/imessage/senders/order`.\n\nIf the first-month card charge confirmation takes longer than the request, the order is accepted (202) and stays in `awaiting_payment`; once payment confirms, the number is ordered automatically (usually under 5 minutes). Poll `GET /v1/imessage/senders/{senderId}` until it moves to `ordering`/`activating`/`active`.\n\nUpdated `ImessageSenderLifecycle.status` enum:\n`ordering`, `awaiting_payment`, `activating`, `active`, `suspended`, `canceled`, `failed`\n\nNote: `409` no longer includes the prior “retry within the hour with `purchaseIntentId`” case; use `purchaseIntentId` for idempotent retries as before.","platforms":["imessage"],"url":"https://docs.zernio.com/changelog#1791364481922"},{"id":"1791364136205","date":"2026-10-07T09:08:56.205Z","type":"improvement","message":"GET `/v1/ads/ad-sets` now returns **platform-native targeting** in `targeting` across all supported ads platforms (not just Meta/LinkedIn).\n\nThis makes it possible to read back the actual synced targeting shape per platform (useful for audits, diffing, and “read-modify-write” flows) from the ad set list response.\n\nKey shapes you’ll see in `targeting`:\n• TikTok: adgroup/get targeting fields (e.g. `placements`, `location_ids`, `age_groups`, `audience_ids`, `excluded_audience_ids`)\n• Google: `campaignCriteria` + `adGroupCriteria` (criterion types like `LOCATION`, `LANGUAGE`, `DEVICE`, `AGE_RANGE`, `GENDER`, `USER_LIST`, `INCOME_RANGE`, `PARENTAL_STATUS`)\n• Pinterest: `targeting_spec` verbatim\n• X: `targeting_criteria` verbatim\n• OpenAI: campaign `targeting` + ad group `context_hints`\n\n`targeting` can still be `null` for ad sets not re-synced yet (or if targeting reads have consistently failed).","platforms":["tiktok","x","linkedin","pinterest","meta-ads","google-ads","tiktok-ads","linkedin-ads","pinterest-ads","x-ads","ads"],"url":"https://docs.zernio.com/changelog#1791364136205"},{"id":"1791363833169","date":"2026-10-07T09:03:53.169Z","type":"new_feature","message":"Google Ads now supports **Target Impression Share** bidding across portfolio strategies and Search campaigns.\n\nYou can create/update portfolio bid strategies with `type=TARGET_IMPRESSION_SHARE`, and set campaign-level Target Impression Share or Manual CPC directly when creating/updating campaigns.\n\nNew/updated fields:\n• `POST /v1/ads/bid-strategies` / `PATCH /v1/ads/bid-strategies/{strategyId}`: `type` now includes `TARGET_IMPRESSION_SHARE`, plus `targetImpressionShare`\n• `POST /v1/ads/campaigns`: `targetImpressionShare`, `manualCpc`, `networkSettings`\n• `PUT /v1/ads/campaigns/{campaignId}`: `targetImpressionShare`, `manualCpc`, `networkSettings`, `trackingUrlTemplate`, `finalUrlSuffix`\n• `PATCH /v1/ads/accounts` (Google only): `trackingUrlTemplate`, `finalUrlSuffix`\n• `POST /v1/ads/ad-sets` (Google only): `maxCpc`\n\nTarget Impression Share config (`targetImpressionShare`):\n• `location`: `ANYWHERE_ON_PAGE` | `TOP_OF_PAGE` | `ABSOLUTE_TOP_OF_PAGE`\n• `percent` (0-100)\n• `maxCpc` (>0)\n\nNotes:\n• `targetImpressionShare` is exclusive with `bidStrategy`, `portfolioBidStrategyId` and `manualCpc` on Google campaigns.\n• Manual CPC uses `manualCpc: { maxCpc }` and can be paired with `POST /v1/ads/ad-sets` `maxCpc` to avoid Google’s default 0.01 ad group bid.","platforms":["google-ads","ads"],"url":"https://docs.zernio.com/changelog#1791363833169"},{"id":"1791361684379","date":"2026-10-07T08:28:04.379Z","type":"new_feature","message":"Google Ads campaign targeting now supports **excluded (negative) locations** on campaign targeting read/write.\n\nYou can now fetch and manage the campaign’s location exclusions separately from included locations.\n\nGET `/v1/ads/campaigns/{campaignId}/targeting` adds:\n• `excludedLocations` (negative location criteria)\n• `excludedLocationsEditable` (whether PUT accepts `excludedLocations`; `false` on Demand Gen)\n\nPUT `/v1/ads/campaigns/{campaignId}/targeting` accepts:\n• `targeting.excludedLocations` (same shapes as `locations`: country-code array or `{ countries, regions, cities, zips, metros }`)\n\nNotes:\n• `excludedLocations` replaces the excluded set; `[]` (or `{}`) removes all exclusions\n• A place can’t be both included and excluded; conflicting requests return `400`\n• Demand Gen: `excludedLocations` is not supported yet and returns `400`","platforms":["google-ads","ads"],"url":"https://docs.zernio.com/changelog#1791361684379"},{"id":"1791361323941","date":"2026-10-07T08:22:03.941Z","type":"new_feature","message":"`GET /v1/ads/accounts/live` now supports **TikTok** in addition to Meta, letting you read campaigns + ad sets (TikTok ad groups) live from the platform before writes.\n\nUse the same request shape with `adAccountId` as either Meta `act_&lt;n&gt;` or a TikTok advertiser id (digits). Response `platform` is now `facebook` or `tiktok`.\n\nTikTok-specific notes:\n• `status` accepts a single value only: `ACTIVE` | `PAUSED` | `DELETED` (multiple values or `IN_PROCESS`/`WITH_ISSUES`/`CAMPAIGN_PAUSED`/`ARCHIVED` return 400)\n• Pagination: when using `after` on TikTok, you must send the same `limit` that produced the cursor (changing `limit` returns 400)\n• New field: `budgetMode` (TikTok `budget_mode`) on campaigns and ad sets\n• On TikTok, `budgetRemaining` and `spendCap` are always `null`, and campaign `bidStrategy` is `null`","platforms":["tiktok","meta-ads","tiktok-ads","ads"],"url":"https://docs.zernio.com/changelog#1791361323941"},{"id":"1791358912510","date":"2026-10-07T07:41:52.510Z","type":"improvement","message":"`PlatformAnalytics` now distinguishes Facebook posts you don’t own when syncing analytics.\n\nYou can detect collab/visitor posts where analytics can’t be read with the connected Page token.\n\nNew/updated fields:\n• `errorCode` adds `not_post_owner` (enum: `post_not_found`, `not_post_owner`, `permission_missing`, `null`)\n• `isOwner` (Facebook only): `true` when the connected Page authored the post, `false` when another author is reported, `null` when unknown/other platforms","platforms":["facebook","analytics"],"url":"https://docs.zernio.com/changelog#1791358912510"},{"id":"1791357502800","date":"2026-10-07T07:18:22.800Z","type":"new_feature","message":"Google Ads campaign/ad-group assets now support **image assets**.\n\nYou can attach public image URLs as assets, and list attached images (returned as Google-hosted copies with dimensions).\n\nAttach images with:\n• `POST /v1/ads/campaigns/{campaignId}/assets` → `images` (array of image URLs)\n• `POST /v1/ads/ad-sets/{adSetId}/assets` → `images` (array of image URLs)\n\nList images via:\n• `GET /v1/ads/campaigns/{campaignId}/assets` → `images`\n• `GET /v1/ads/ad-sets/{adSetId}/assets` → `images`\n\nNotes:\n• `images`: 1.91:1 (min 600x314) or 1:1 (min 300x300), up to 5 MB each\n• Response includes `imageUrl`, `width`, `height` and `imageAssetResourceNames`\n• Google may show images only for eligible accounts","platforms":["google-ads","ads","publishing"],"url":"https://docs.zernio.com/changelog#1791357502800"},{"id":"1791354660996","date":"2026-10-07T06:31:00.996Z","type":"improvement","message":"Facebook page selection now returns `truncated` in `GET /v1/connect/facebook/select-page`.\n\nThis indicates when the returned `pages` list is incomplete because Meta hit a time budget or the 10,000 Page cap, so you can handle large accounts safely.\n\nNew field:\n• `truncated` (boolean) — `true` means `pages` is incomplete. Don’t prompt users to reconnect with fewer Pages selected (Meta replaces the Page grant on re-authorization, and unticked Pages lose access).","platforms":["facebook"],"url":"https://docs.zernio.com/changelog#1791354660996"},{"id":"1791323269315","date":"2026-10-06T21:47:49.315Z","type":"improvement","message":"GET `/v1/ads/{adId}/media` now returns media for **boosted Instagram posts** more explicitly.\n\nIf an ad creative has no image of its own, the endpoint returns the original post’s media (single image/video, or carousel children). If the connection can’t read the Instagram post, it falls back to a 1080px rendering of the creative.\n\nResponse fields to use:\n• `media[].type` (`image` | `video`)\n• `media[].url`\n• `media[].thumbnailUrl` (videos)\n• `media[].index` (carousel children)","platforms":["instagram","meta-ads","ads","inbox","webhooks"],"url":"https://docs.zernio.com/changelog#1791323269315"},{"id":"1791309514666","date":"2026-10-06T17:58:34.666Z","type":"improvement","message":"`PlatformAnalytics` now includes **errorCode** alongside `errorMessage`.\n\nThis provides a stable, machine-readable reason you can use to branch on common analytics/publishing error cases (instead of parsing `errorMessage`).\n\nNew field: `errorCode` ∈ `post_not_found`, `permission_missing`, `null` (more values may be added).","platforms":["publishing","analytics"],"url":"https://docs.zernio.com/changelog#1791309514666"},{"id":"1791308222186","date":"2026-10-06T17:37:02.186Z","type":"breaking_change","message":"Several Google Ads DELETE endpoints now take their fields in the **query string** (a JSON body with the same fields is still accepted; if both are sent, the query string is ignored).\n\nThis makes these DELETE calls easier to use from clients/tools that don’t reliably send bodies on DELETE.\n\nUpdated endpoints:\n• `DELETE /v1/ads/campaigns/{campaignId}/assets` now uses `accountId`, `adAccountId`/`customerId`, `assetResourceNames[]`, `campaignAssetResourceNames[]`\n• `DELETE /v1/ads/ad-sets/{adSetId}/assets` now uses `accountId`, `adAccountId`/`customerId`, `assetResourceNames[]`, `adGroupAssetResourceNames[]`\n• `DELETE /v1/ads/labels/{labelId}/assignments` now uses `accountId`, `adAccountId`/`customerId`, plus one or more of `campaignIds[]`, `adSetIds[]`, `adIds[]`, `keywordIds[]`\n• `DELETE /v1/ads/accounts/callouts`, `/sitelinks`, `/structured-snippets` now use `accountId`, `adAccountId`/`customerId`, `assetId`\n\nAlso:\n• `POST /v1/ads/campaigns/bulk-status` now accepts platform aliases in `platform`: `googleads`, `tiktokads`, `linkedinads`, `pinterestads`, `xads`, `openaiads` (alongside `google`, `tiktok`, `linkedin`, `pinterest`, `twitter`, `openai`; `metaads` is not accepted)\n• `PATCH /v1/ads/{adId}/tracking-tags` (Google) is documented as writing at the **campaign** level (applies to all ads in the campaign)\n• `GET /v1/ads/keywords` and `KeywordEntry.matchType` are now documented as case-insensitive (stored lowercase)","platforms":["tiktok","x","linkedin","pinterest","google-ads","tiktok-ads","linkedin-ads","pinterest-ads","x-ads","ads"],"url":"https://docs.zernio.com/changelog#1791308222186"},{"id":"1791306594740","date":"2026-10-06T17:09:54.740Z","type":"improvement","message":"Comment automation responses now clarify that on reply-only platforms (`tiktok`, `threads`, `linkedin`, `youtube`) all DM-leg fields are **omitted** (not just empty).\n\nIf your client assumes these fields exist, update parsing to treat them as optional based on `platform`.\n\nKey detail:\n• `dmMessage` (and related DM fields) are omitted on `platform` = `tiktok`/`threads`/`linkedin`/`youtube`\n\nAlso, `PATCH /v1/comment-automations/{automationId}` can now re-bind an automation to a different post/story by sending any of:\n• `platformPostId`, `postId`, `postTitle` (they move as a unit; omitted ones are cleared)\n\nRebinding can return `409` if another active automation already owns the target post.","platforms":["threads","tiktok","linkedin","youtube","inbox"],"url":"https://docs.zernio.com/changelog#1791306594740"},{"id":"1791305390307","date":"2026-10-06T16:49:50.307Z","type":"improvement","message":"OAuth connect now supports **account mismatch protection** on `GET /v1/connect/{platform}`.\n\nYou can prevent connecting the “wrong” social account by requiring the authorized login to match an expected id and/or handle; if it doesn’t match, the flow ends with `error=account_mismatch` and nothing is written.\n\nNew query params:\n• `expectedPlatformUserId` (max 100)\n• `expectedUsername` (max 100, case-insensitive, leading `@` ignored)\n\nNotes:\n• If both are provided, both must match.\n• In headless mode, these markers are carried via the returned `redirect_url` (pass it through unchanged).\n\nInstagram Facebook-Login selection now returns a dedicated mismatch error:\n• `POST /v1/connect/instagram/select-account` can return `409` with `code=account_mismatch`.\n\nAccounts now expose a stable platform id for matching:\n• `SocialAccount.platformUserId`","platforms":["instagram"],"url":"https://docs.zernio.com/changelog#1791305390307"},{"id":"1791303948731","date":"2026-10-06T16:25:48.731Z","type":"improvement","message":"Meta carousel ads now return per-card creative details via `creative.carouselCards`.\n\nThis lets you render carousel previews (and inspect card-level links/CTAs) directly from Zernio responses, without reconstructing cards from raw Meta creative payloads.\n\nNew field:\n• `creative.carouselCards[]` with `imageUrl`, `imageHash`, `headline`, `description`, `linkUrl`, `callToAction`, `videoId`\n\nOnly present for Meta carousel ads; absent for non-carousel ads/other platforms. Older synced carousels will populate `carouselCards` on their next sync.","platforms":["meta-ads","ads"],"url":"https://docs.zernio.com/changelog#1791303948731"},{"id":"1791301342306","date":"2026-10-06T15:42:22.306Z","type":"new_feature","message":"Whop Ads is now supported for tracking tags (Whop Pixel) via the tracking-tags API.\n\nYou can connect a Whop account using an Account API key, then list/install the Whop Pixel on Shopify or WordPress. The Whop account id (`biz_...`) is used as the pixel/tag id.\n\nConnect Whop:\n• `POST /v1/connect/whop-ads/credentials`\n• Body: `apiKey`, `profileId` (optional: `state`, `redirect_url`)\n• Returns: `accountId` (Zernio ads account), `whopAccountId` (`biz_...`)\n\nTracking tags now accept `platform=whopads` on:\n• `GET /v1/accounts/{accountId}/tracking-tags`\n• `GET /v1/accounts/{accountId}/tracking-tags/{tagId}`\n• `POST /v1/accounts/{accountId}/tracking-tags/{tagId}/install`\n• `DELETE /v1/accounts/{accountId}/tracking-tags/{tagId}/install`\n• `GET /v1/accounts/{accountId}/tracking-tags/{tagId}/stats`\n\nAccounts health filtering also adds `platform=whopads` on `GET /v1/accounts/health`.","platforms":["ads","commerce","analytics"],"url":"https://docs.zernio.com/changelog#1791301342306"},{"id":"1791301258328","date":"2026-10-06T15:40:58.328Z","type":"new_feature","message":"Inbox now supports **Facebook/Instagram Message Requests**: list them with `GET /v1/inbox/conversations` using `folder=requests`, then accept by replying via `POST /v1/inbox/conversations/{conversationId}/accept` (body: `accountId`, `message`; supports `Idempotency-Key`).\n\nYou can now manage **Facebook Messenger greeting** and **ice breakers**:\n• `GET|PUT|DELETE /v1/accounts/{accountId}/messenger-greeting`\n• `GET|PUT|DELETE /v1/accounts/{accountId}/messenger-ice-breakers`\nIce breaker payloads can start workflows with `zernio:workflow:&lt;workflowId&gt;`.\n\nWhatsApp now supports **conversational automation** (prompts + slash commands):\n• `GET /v1/whatsapp/conversational-automation?accountId=...`\n• `POST /v1/whatsapp/conversational-automation` (body: `accountId`, optional `enable_welcome_message`, `prompts`, `commands`)\n• `DELETE /v1/whatsapp/conversational-automation?accountId=...`\n\nWorkflows can now be started via API with `POST /v1/workflows/{workflowId}/trigger`. Provide exactly one target: `conversationId` | `contactId` | `to` (WhatsApp only), plus optional `variables`.\n\nWebhook subscriptions can now include new events: `contact.tag_added`, `contact.tag_removed`, `contact.field_changed`, `sequence.enrolled`, `sequence.exited`, `workflow.run.started`, `workflow.run.completed`, `workflow.run.failed`.\n\nNote: sending to Meta threads you don’t own can now return `409` with code `not_thread_owner`; take control first via `POST /v1/inbox/conversations/{conversationId}/thread-control`.","platforms":["instagram","facebook","threads","whatsapp","ads","inbox","webhooks"],"url":"https://docs.zernio.com/changelog#1791301258328"},{"id":"1791300212162","date":"2026-10-06T15:23:32.162Z","type":"new_feature","message":"New endpoint: `GET /v1/analytics/instagram/online-followers`.\n\nYou can now fetch how many Instagram followers were online per hour for each day (up to the last 30 days of Meta-provided data), useful for picking optimal posting times.\n\nUse `accountId` (query) to select the Instagram SocialAccount.\nReturns `platform`=`instagram`, `metric`=`online_followers`, and `days[]` with `endTime` + `hours` keyed `\"0\"`…`\"23\"`.\n\nNotes: data can be delayed up to 48h; accounts with &lt;100 followers may return an empty `days`; requires the Analytics add-on (may return `402` with `code`=`analytics_addon_required`).","platforms":["instagram","analytics"],"url":"https://docs.zernio.com/changelog#1791300212162"},{"id":"1791296757339","date":"2026-10-06T14:25:57.339Z","type":"new_feature","message":"Video media items now support **subtitle/closed-caption tracks** via `MediaItem.subtitles`.\n\nProvide 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.\n\nUse:\n• `subtitles` (array, max 20)\n• `subtitles[].url` (public direct http(s) URL; redirects refused)\n• `subtitles[].language` (BCP-47, e.g. `en`, `pt-BR`)\n\nUploads via `POST /v1/media/presign` now accept `contentType` values: `application/x-subrip`, `text/vtt`.\n\nValidation: unreachable/redirecting files, >1 MB, invalid SRT/VTT, or platform-limit violations are rejected with `400` (`INVALID_SUBTITLES`).","platforms":["publishing"],"url":"https://docs.zernio.com/changelog#1791296757339"},{"id":"1791294992234","date":"2026-10-06T13:56:32.234Z","type":"improvement","message":"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).\n\nBoth 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.\n\nUse:\n• `GET /v1/accounts/{accountId}/tiktok/commercial-music` → `tracks[].clip.id`\n• Post creation → `tiktokSettings.musicSoundInfo.musicSoundId`","platforms":["tiktok","tiktok-ads","publishing"],"url":"https://docs.zernio.com/changelog#1791294992234"},{"id":"1791284063869","date":"2026-10-06T10:54:23.869Z","type":"improvement","message":"LinkedIn boosts/ads created with `adStatus: PAUSED` now create the LinkedIn creative as `DRAFT` (LinkedIn does not allow `PAUSED` until review is `APPROVED`).\n\nA `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`.\n\nApplies to:\n• `POST /v1/ads/boost` with `adStatus`\n• `POST /v1/ads/create` (attach shape via `adSetId`) with `adStatus`\n\nKey field: `adStatus` (`ACTIVE` | `PAUSED`)","platforms":["linkedin","linkedin-ads","ads"],"url":"https://docs.zernio.com/changelog#1791284063869"},{"id":"1791280213720","date":"2026-10-06T09:50:13.720Z","type":"breaking_change","message":"DELETE `/v1/ads/{adId}` can now also clean up empty parent objects on Meta.\n\nWhen 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.\n\nIf you want to delete a whole campaign explicitly, use `DELETE /v1/ads/campaigns/{campaignId}`.\n\nAlso, `POST /v1/ads/boost` now supports per-level publish control:\n• `campaignStatus`: `ACTIVE` | `PAUSED`\n• `adSetStatus`: `ACTIVE` | `PAUSED`\n• `adStatus`: `ACTIVE` | `PAUSED`\n\nThese override `status` for their respective level (and are rejected with `adSetId`/`existingCampaignId` where that level isn’t created).","platforms":["meta-ads","ads"],"url":"https://docs.zernio.com/changelog#1791280213720"},{"id":"1791278111858","date":"2026-10-06T09:15:11.858Z","type":"new_feature","message":"Messaging ads now support **carousel creatives** on `POST /v1/ads/messaging` via `carouselCards`.\n\nYou 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`.\n\nUse `carouselCards` (2–10 items) with:\n• `imageUrl` (required)\n• `headline` (optional)\n• `description` (optional)\n\nNotes:\n• `carouselCards` replaces `imageUrl`/`video` (sending either is a 400)\n• Requires `body`\n• Not compatible with `placementAssets` or existing-post fields (`platformPostId`/`objectStoryId`/`existingPostId`)\n• `validateOnly: true` now supports carousel validation","platforms":["instagram","facebook","whatsapp","meta-ads","ads"],"url":"https://docs.zernio.com/changelog#1791278111858"},{"id":"1791268908303","date":"2026-10-06T06:41:48.303Z","type":"improvement","message":"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).\n\nThis helps surface sender-scope issues explicitly instead of failing in other ways.\n\nHandle the new response:\n• `403` - profile-scoped key cannot use the provided `from`","platforms":["telephony"],"url":"https://docs.zernio.com/changelog#1791268908303"},{"id":"1791240123054","date":"2026-10-05T22:42:03.054Z","type":"improvement","message":"`comment.received` webhook payload now includes Instagram ad context `comment.ad.originalMediaId`.\n\nThis lets you correlate comments on paid Instagram content to the original media Meta reports for the ad (when Meta includes it).\n\nNew field:\n• `comment.ad.originalMediaId` (Instagram only, optional)\n\nEvent: `comment.received`","platforms":["instagram","meta-ads","ads","inbox","webhooks"],"url":"https://docs.zernio.com/changelog#1791240123054"},{"id":"1791235797301","date":"2026-10-05T21:29:57.301Z","type":"improvement","message":"Google Ads keyword endpoints now return more specific **429 rate limit** details for Zernio burst limits.\n\nWhen 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.\n\nLook for `details.budgetScope`:\n• `account` - 15 requests/min per connected Google Ads account\n• `user` - 120 requests/min per Zernio user across all their Google Ads accounts\n\nApplies to `POST /v1/ads/keywords/ideas` and `POST /v1/ads/keywords/historical-metrics` (429 responses).","platforms":["google-ads","ads"],"url":"https://docs.zernio.com/changelog#1791235797301"},{"id":"1791207308864","date":"2026-10-05T13:35:08.864Z","type":"breaking_change","message":"`GET /v1/analytics/post-timeline` now returns `follows` as nullable (`integer` | `null`).\n\nThis reflects platform limitations: for some Meta media types the follows metric isn’t available, so it will be `null` instead of `0`.\n\nUpdate your parsing/aggregation logic to handle:\n• `follows: null` (not reported)\n• `follows: 0` (reported, but zero)\n\nKey field: `timeline[].follows`","platforms":["analytics"],"url":"https://docs.zernio.com/changelog#1791207308864"},{"id":"1791206965172","date":"2026-10-05T13:29:25.172Z","type":"new_feature","message":"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.\n\nUse it when you need current `daily_budget`/`lifetime_budget`, bid settings, targeting, etc. before making writes (e.g. spend gates).\n\nRequired params:\n• `accountId` (Zernio SocialAccount id)\n• `adAccountId` (Meta `act_&lt;n&gt;`)\n\nOptional params:\n• `status` (comma-separated Meta `effective_status`; defaults to all except `DELETED`, `ARCHIVED`; ad sets also support `CAMPAIGN_PAUSED`)\n• `limit` (1–500, default 200)\n• `level` = `campaign` | `adSet` (required with `after`)\n• `after` (cursor from `paging.campaigns.after` / `paging.adSets.after`)\n\nNotes:\n• Budgets/bids are returned in whole units of `currency` (Meta minor units converted).\n• Other platforms return `501` (Meta only today).","platforms":["meta-ads","ads"],"url":"https://docs.zernio.com/changelog#1791206965172"},{"id":"1791205682991","date":"2026-10-05T13:08:02.991Z","type":"new_feature","message":"Meta ad video uploads now support **async processing**, with a new status endpoint and webhook.\n\nUse `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.\n\nNew endpoint: `GET /v1/ads/videos/{videoId}`\nRequired: `videoId` (path), `accountId` (query), `adAccountId` (query)\nReturns `video.status`: `processing` | `ready` | `error` plus `platformStatus`, `processingProgress` (0-100), `error`, `thumbnailUrl`.\n\nUpload responses:\n• `201` (default): `video.status: ready`\n• `202` (`async: true`): `video.status: processing`\n\nWebhook: subscribe to `ad.video.processed` (fires for `async: true` uploads) with `video.status`: `ready` | `error`.","platforms":["meta-ads","ads","publishing","webhooks"],"url":"https://docs.zernio.com/changelog#1791205682991"},{"id":"1791205315684","date":"2026-10-05T13:01:55.684Z","type":"new_feature","message":"You can now filter `GET /v1/ads/campaigns` by a specific platform campaign ID using `campaignId`.\n\nThis 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`.\n\nUse: `campaignId=&lt;platformCampaignId&gt;`\n\nAd set duplication now supports TikTok via `POST /v1/ads/ad-sets/{adSetId}/duplicate`.\n\nSet `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).","platforms":["instagram","tiktok","meta-ads","tiktok-ads","ads"],"url":"https://docs.zernio.com/changelog#1791205315684"},{"id":"1791197742322","date":"2026-10-05T10:55:42.322Z","type":"improvement","message":"POST `/v1/ads/create` now supports per-level status controls via `campaignStatus`, `adSetStatus`, and `adStatus` (each: `ACTIVE` | `PAUSED`).\n\nThis 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.\n\nUse:\n• `campaignStatus` to pause/activate only the new campaign (ignored when using `existingCampaignId` or `adSetId`)\n• `adSetStatus` to pause/activate only the new ad set (400 with `adSetId`)\n• `adStatus` to pause/activate only the new ad (works with `adSetId`; X returns 400)\n\nPrecedence: 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`.","platforms":["ads","webhooks"],"url":"https://docs.zernio.com/changelog#1791197742322"},{"id":"1791197151899","date":"2026-10-05T10:45:51.899Z","type":"breaking_change","message":"GET `/v1/accounts/{accountId}/facebook-page` now returns the Instagram professional account linked to each Facebook Page via `pages[].instagramAccount`.\n\nThis 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.\n\nKey fields:\n• `pages[].instagramAccount` (object|null) with `id` and `username`\n• `selectedPageId` is now `string|null` (null for classic `metaads` connections)\n• If your cached list predates this field, call with `refresh=true` to populate it\n\nPUT `/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.","platforms":["instagram","facebook","meta-ads","ads"],"url":"https://docs.zernio.com/changelog#1791197151899"},{"id":"1791196221644","date":"2026-10-05T10:30:21.644Z","type":"improvement","message":"Rate limiting is now standardized across Meta ads endpoints, and `GET /v1/ads/insights` documents consistent `429` behavior.\n\nFor 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).\n\nHandle `429` by reading:\n• `Retry-After` (seconds)\n• JSON error body: `ErrorResponse`\n\nApplies 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`.","platforms":["meta-ads","ads","analytics"],"url":"https://docs.zernio.com/changelog#1791196221644"},{"id":"1791190286644","date":"2026-10-05T08:51:26.644Z","type":"new_feature","message":"New endpoint: `GET /v1/accounts/{accountId}/instagram/business-discovery`.\n\nYou 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).\n\nCall with:\n• `accountId` (path) - a connected Instagram account via **Facebook Login**\n• `username` (query) - handle with/without `@`\n• `limit` (query, default `12`, max `25`)\n\nResponse includes `profile` and `media` (most recent first). Media fields include `mediaType` (`IMAGE`/`VIDEO`/`CAROUSEL_ALBUM`) and `mediaProductType` (`FEED`/`REELS`); `likeCount` is `null` when hidden.\n\nNote: requires Facebook Login; classic Instagram Login will return 400 `instagram_business_discovery_requires_facebook_login`.","platforms":["instagram"],"url":"https://docs.zernio.com/changelog#1791190286644"},{"id":"1791125974637","date":"2026-10-04T14:59:34.637Z","type":"new_feature","message":"New endpoint: `GET /v1/analytics/facebook/demographics` returns Facebook Page follower demographics (country/city) from Meta’s latest daily snapshot.\n\nUse `accountId` (required) and optional `breakdown` (`country`, `city`; defaults to both). Response includes `snapshotDate` (may be `null`) and `demographics.country`/`demographics.city` arrays.\n\nRequires Analytics access; missing access returns `402` (`analytics_addon_required`).\n\nAlso 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.","platforms":["facebook","google-ads","ads","analytics"],"url":"https://docs.zernio.com/changelog#1791125974637"},{"id":"1791112215827","date":"2026-10-04T11:10:15.827Z","type":"new_feature","message":"Custom audiences now support new engagement audience types: `tiktok_engagement` and `pinterest_engagement`.\n\nYou can create and list these via `POST /v1/ads/audiences` and `GET /v1/ads/audiences` using `type` = `tiktok_engagement` or `pinterest_engagement`.\n\nKey request fields:\n• TikTok engagement: `type=tiktok_engagement`, `retentionDays`, `source` (`ads` | `organic_video` | `live_video` | `business_account`), `event`, plus `sourceIds`/`identityId`/`identityType` as required\n• Pinterest engagement: `type=pinterest_engagement`, optional `engagerType`, `engagementType` (`click` | `save` | `closeup` | `comment` | `like`), and at least one of `engagementDomains`/`campaignIds`/`adIds`/`pinIds`\n\nAlso 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`).","platforms":["tiktok","pinterest","google-ads","tiktok-ads","pinterest-ads","ads"],"url":"https://docs.zernio.com/changelog#1791112215827"},{"id":"1791112140004","date":"2026-10-04T11:09:00.004Z","type":"improvement","message":"GET `/v1/analytics/post-timeline` now returns Facebook Reels retention metrics via two new fields: `replays` and `retentionCurve`.\n\nThis 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.\n\nNew fields:\n• `replays` - Facebook Reels replay count\n• `retentionCurve` - map of `\"second\"` → `fraction` (0..1) still watching at that second","platforms":["analytics"],"url":"https://docs.zernio.com/changelog#1791112140004"},{"id":"1791069569304","date":"2026-10-03T23:19:29.304Z","type":"improvement","message":"`igReelsAvgWatchTime` and `igReelsVideoViewTotalTime` analytics now cover more than Instagram Reels.\n\nThese watch-time metrics are now reported for **Instagram Reels**, **Facebook Reels**, and **TikTok videos (business accounts)**, instead of Instagram Reels only.\n\nKey fields:\n• `PostAnalytics.igReelsAvgWatchTime` (ms per play)\n• `PostAnalytics.igReelsVideoViewTotalTime` (ms total, incl. replays)\n• `AnalyticsDeltaEntry.metrics.igReelsAvgWatchTime`\n• `AnalyticsDeltaEntry.metrics.igReelsVideoViewTotalTime`","platforms":["instagram","tiktok","analytics"],"url":"https://docs.zernio.com/changelog#1791069569304"},{"id":"1791027729319","date":"2026-10-03T11:42:09.319Z","type":"new_feature","message":"Meta attach mode (`adSetId`) now supports setting a call number via `phoneNumber`.\n\nUse 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.\n\nSet `phoneNumber` as E.164:\n• `+4712345678` (pattern `^\\+[1-9]\\d{6,14}$`)\n\nConstraints:\n• Only allowed with `adSetId` (Meta attach shape)\n• Rejected with `existingCreativeId`\n• Rejected on non-call ad sets and on non-Meta platforms","platforms":["meta-ads","ads","telephony"],"url":"https://docs.zernio.com/changelog#1791027729319"},{"id":"1791024218260","date":"2026-10-03T10:43:38.260Z","type":"new_feature","message":"X ads created via `POST /v1/ads/create` now support **website cards**.\n\nYou can create an X website card by sending `imageUrl` + `headline` + `linkUrl` together (the URL is not added to the post text).\n\nX rules:\n• Website card requires `imageUrl`, `headline`, and `linkUrl` (sending only some of them returns 400)\n• `description`, `video`, and `callToAction` are rejected with 400 on X","platforms":["x","x-ads","ads","publishing"],"url":"https://docs.zernio.com/changelog#1791024218260"},{"id":"1791023417995","date":"2026-10-03T10:30:17.995Z","type":"new_feature","message":"Google ads targeting now supports **interests** via `targeting.interests`.\n\nOn Google, interests are applied as ad-group interest criteria (affinity + in-market) for newly created Search/Display ad groups.\n\nUse `targeting.interests` = `[{ id, name? }]`.\n\nConstraints on Google:\n• Not supported on `Performance Max` (400)\n• Not supported on `Demand Gen` (use Demand Gen audience `userInterests`)\n• Not allowed when attaching to an existing ad group via `adSetId` (400)\n• `interests` are **create-only** on Google","platforms":["google-ads","ads"],"url":"https://docs.zernio.com/changelog#1791023417995"},{"id":"1791009373589","date":"2026-10-03T06:36:13.589Z","type":"new_feature","message":"TikTok now supports **Smart Targeting** on ad groups via `smartTargeting`.\n\nThis lets TikTok expand delivery beyond your selected audiences/interests using TikTok’s smart targeting flags.\n\nAvailable on:\n• `PUT /v1/ads/ad-sets/{adSetId}` (TikTok only; 400 on other platforms)\n• `POST /v1/ads/boost` (TikTok only; not allowed with `smartPlus: true` or when attaching via `adSetId`)\n• `POST /v1/ads/create` (TikTok only; not allowed with `smartPlus: true` or when attaching via `adSetId`)\n\nSet:\n• `smartTargeting.audience` (boolean)\n• `smartTargeting.interestsBehaviors` (boolean)\n\nOnly 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).","platforms":["tiktok","tiktok-ads","ads"],"url":"https://docs.zernio.com/changelog#1791009373589"},{"id":"1791007946702","date":"2026-10-03T06:12:26.702Z","type":"improvement","message":"GET `/v1/ads/targeting/search` now supports TikTok advertiser scoping via a new optional `adAccountId` query parameter.\n\nIf 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).\n\nUse:\n• `adAccountId` - TikTok advertiser id to search as (optional; invalid/unowned advertiser returns `400`)","platforms":["tiktok","tiktok-ads","ads"],"url":"https://docs.zernio.com/changelog#1791007946702"},{"id":"1791006962849","date":"2026-10-03T05:56:02.849Z","type":"improvement","message":"GET `/v1/ads/targeting/search` now returns `platformId` on `country` results.\n\nThis 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.\n\nKey fields on a `country` result:\n• `id` = ISO country code (e.g. `GB`)\n• `platformId` = platform-native country id (e.g. TikTok `location_id`/GeoNames id, Google geo target constant id, LinkedIn geo URN)\n\nOnly present when `type` is `country` (dimension `geo`).","platforms":["tiktok","linkedin","google-ads","tiktok-ads","linkedin-ads","ads"],"url":"https://docs.zernio.com/changelog#1791006962849"},{"id":"1791006168009","date":"2026-10-03T05:42:48.010Z","type":"improvement","message":"GET `/v1/ads/ad-sets` now returns TikTok live configuration details when you pass `live=true`.\n\nFor 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.).\n\nNew fields (TikTok only, only with `live=true` and only on rows read live):\n• `nativeSettings` - TikTok `adgroup/get` record verbatim (+ `advertiser_currency`, `advertiser_timezone`)\n• `configReadAt` - timestamp when `nativeSettings` was read (null if not read now)\n\nAlso added to the `AdCampaign` schema for TikTok live reads:\n• `nativeSettings`\n• `configReadAt`","platforms":["tiktok","tiktok-ads","ads"],"url":"https://docs.zernio.com/changelog#1791006168009"},{"id":"1790982455999","date":"2026-10-02T23:07:35.999Z","type":"new_feature","message":"New endpoint: `GET /v1/ads/{adId}/review` returns the platform’s review verdict for an ad.\n\nThis lets you read TikTok’s audit outcome (including partial delivery restrictions and detailed rejection reasons) without re-enabling a paused ad.\n\nKey params/fields:\n• Path: `adId` (Zernio ad id or platform ad id)\n• Response: `review.approved`, `review.reviewStatus` (`ALL_AVAILABLE` | `PART_AVAILABLE` | `UNAVAILABLE`)\n• Restrictions: `review.forbiddenPlacements`, `review.forbiddenAges`, `review.forbiddenLocations`, `review.forbiddenOperatingSystems`\n• Rejections: `review.rejections[]` with `reasons[]`, `suggestion`, and reviewed `content`\n\nTikTok-only: other platforms return `501`. `404` if the ad isn’t found, has no TikTok ad id yet, or TikTok has no review record.","platforms":["tiktok","tiktok-ads","ads"],"url":"https://docs.zernio.com/changelog#1790982455999"},{"id":"1790971715190","date":"2026-10-02T20:08:35.190Z","type":"improvement","message":"GET `/v1/ads/ad-sets` now returns TikTok’s applied **optimization goal** and **billing event** when using `live=true`.\n\nThis lets you verify what TikTok actually applied (from `adgroup/get`), not just what was requested.\n\nWith `platform=tiktok` and `live=true` (requires `campaignId` or `adSetId`), rows read live may include:\n• `optimizationGoal` (e.g. `ENGAGED_VIEW`, `ENGAGED_VIEW_FIFTEEN`, `CLICK`, `CONVERT`)\n• `billingEvent` (e.g. `CPV`, `CPC`, `OCPM`)\n\nOnly present for TikTok and only on rows that were read live (others will be absent/null).","platforms":["tiktok","tiktok-ads","ads"],"url":"https://docs.zernio.com/changelog#1790971715190"},{"id":"1790938676672","date":"2026-10-02T10:57:56.672Z","type":"breaking_change","message":"Google Business Profile, Discord, and Slack connect endpoints now support **batch connecting** multiple destinations in one call.\n\nYou 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.\n\nGoogle Business Profile: `POST /v1/connect/googlebusiness/select-location`\n• Request now requires only `profileId` + `pendingDataToken`\n• Send `locationId` or `locations` (not both)\n• `locations[]`: `{ locationId, accountId? }`\n• New response fields for batch: `accounts`, `failed`; new `422` when none connect\n\nDiscord: `POST /v1/connect/discord`\n• `channelId` is no longer required (breaking)\n• Send `channelId` or `channelIds` (not both)\n• New `redirect_url` (only for `channelIds`) and new `422` when none connect\n\nSlack: `POST /v1/connect/slack`\n• `channelId` is no longer required (breaking)\n• Send `channelId` or `channelIds` (not both)\n• New `redirect_url` (only for `channelIds`) and new `422` when none connect","platforms":["discord","slack","google-business"],"url":"https://docs.zernio.com/changelog#1790938676672"},{"id":"1790938569245","date":"2026-10-02T10:56:09.245Z","type":"improvement","message":"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.\n\nThis also clarifies failure/retry behavior so clients can handle billing and idempotent retries correctly.\n\nKey updates:\n• `402` now also covers first-month card declines (`code: payment_required`); _nothing is ordered_ — update Billing and order again\n• `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\n• `502`: provider rejection; nothing charged, or any first month already billed is returned as **account credit**\n\nUse `purchaseIntentId` as the idempotency key for safe retries.","platforms":["imessage"],"url":"https://docs.zernio.com/changelog#1790938569245"},{"id":"1790936343188","date":"2026-10-02T10:19:03.188Z","type":"improvement","message":"`GET /v1/accounts` now supports server-side **search**, **category filtering**, and **sorting** for easier account discovery (especially when combined with pagination).\n\nNew query params:\n• `search` - case-insensitive match on username/display name/platform user id, or exact account id\n• `category` - `social` | `ads` | `communication` | `blogs`\n• `sort` - `account` | `platform` | `profile` | `status` | `connected`\n• `order` - `asc` | `desc` (default `asc`)","platforms":["general"],"url":"https://docs.zernio.com/changelog#1790936343188"},{"id":"1790931665858","date":"2026-10-02T09:01:05.858Z","type":"improvement","message":"Error responses now may include a documentation URL via `docUrl` in `ErrorResponse`.\n\nWhen present, `docUrl` points to a page describing how to resolve the specific error.\n\nNew field:\n• `docUrl` (string)","platforms":["general"],"url":"https://docs.zernio.com/changelog#1790931665858"},{"id":"1790928340448","date":"2026-10-02T08:05:40.448Z","type":"new_feature","message":"New endpoint: `POST /v1/phone-numbers/{id}/whatsapp/request-code` to request (or restart) WhatsApp verification for a Zernio-hosted number.\n\nThis 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.\n\nOptional body: `method` = `SMS` | `VOICE` (omit to let Zernio choose).\n\nSuccess response may include: `alreadyVerified`, `replaced`, `newPhoneNumber` (when a never-live number is replaced).\n\nPossible conflicts: `409` with `code` such as `number_not_whatsapp_eligible` or `whatsapp_number_in_use`; `429` may include `retryAt`.","platforms":["whatsapp","telephony"],"url":"https://docs.zernio.com/changelog#1790928340448"},{"id":"1790921391646","date":"2026-10-02T06:09:51.646Z","type":"improvement","message":"Google ads now return YouTube video IDs on video-based ads via `creative.youtubeVideoIds`.\n\nThis 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.\n\nNew field:\n• `creative.youtubeVideoIds` (array of strings) — absent on ads without a video","platforms":["youtube","google-ads","ads"],"url":"https://docs.zernio.com/changelog#1790921391646"},{"id":"1790908085419","date":"2026-10-02T02:28:05.419Z","type":"breaking_change","message":"`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.\n\nThis 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.\n\nIf you were checking for `ACTIVE` (or other normalized values), update your logic to handle platform-specific statuses from `campaign.platform` + `campaign.status`.","platforms":["meta-ads","google-ads","ads","analytics"],"url":"https://docs.zernio.com/changelog#1790908085419"},{"id":"1790873486539","date":"2026-10-01T16:51:26.539Z","type":"breaking_change","message":"Profiles can now hold **multiple accounts per platform** in the connect flow.\n\nConnecting 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.\n\nKey changes:\n• `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\n• `POST /v1/connect/instagram/select-account`: no longer replaces the existing Instagram account on the profile; it adds/refreshes instead\n• `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)\n• `POST /v1/imessage/senders`: a profile can now register several iMessage senders; re-registering the same `sender` refreshes\n• `POST /v1/rcs/agents`: a profile can now hold several RCS agents\n\nNote: 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).","platforms":["instagram","facebook","linkedin","whatsapp","google-business","imessage","telephony"],"url":"https://docs.zernio.com/changelog#1790873486539"},{"id":"1790871271944","date":"2026-10-01T16:14:31.944Z","type":"new_feature","message":"GET `/v1/accounts` now supports a per-profile preview mode via `profileIds` and `perProfile`.\n\nThis lets you request a compact “preview” set of accounts per profile (useful for dashboards/lists) and also returns per-profile totals.\n\nNew query params:\n• `profileIds` — comma-separated profile IDs (up to 50)\n• `perProfile` — integer (1–24); requires `profileIds`; cannot be combined with `page`/`limit`\n\nNew response field (only with `profileIds` + `perProfile`):\n• `profileTotals` — map of `{ profileId: count }` for accounts matching the filters\n\nSchema addition:\n• `Profile.accountCount` — connected account count shown in the profile list","platforms":["general"],"url":"https://docs.zernio.com/changelog#1790871271944"},{"id":"1790866083914","date":"2026-10-01T14:48:03.914Z","type":"improvement","message":"Billing for branded calling identities created via `POST /v1/branded-calling/identities` has changed.\n\nYou 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.\n\nApplies to identities created with `POST /v1/branded-calling/identities` (status starts as `requested`).","platforms":["telephony"],"url":"https://docs.zernio.com/changelog#1790866083914"},{"id":"1790857620472","date":"2026-10-01T12:27:00.472Z","type":"improvement","message":"`GET /v1/changelog` no longer requires an API key and is now rate-limited (120 requests/min per address).\n\nIf you exceed the limit, the endpoint can return `429` (`RateLimited`), so make sure your client handles `429` responses (e.g., backoff/retry).","platforms":["general"],"url":"https://docs.zernio.com/changelog#1790857620472"},{"id":"1790856557729","date":"2026-10-01T12:09:17.729Z","type":"new_feature","message":"API changelog is now available via `GET /v1/changelog`.\n\nYou 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.\n\nQuery params:\n• `type`: `new_feature` | `breaking_change` | `improvement` | `deprecation` | `minor`\n• `platform`: slug (e.g. `whatsapp`)\n• `before`: cursor (date-time)\n• `limit`: 1–100\n\nWebhooks can now subscribe to `api.changelog.published` by including it in `events` on `POST /v1/webhooks/settings` or `PUT /v1/webhooks/settings`.","platforms":["whatsapp","webhooks"],"url":"https://docs.zernio.com/changelog#1790856557729"},{"id":"1790853866459","date":"2026-10-01T11:24:26.459Z","type":"breaking_change","message":"`POST /v1/connect/linkedin/select-organization` now supports connecting multiple LinkedIn accounts (personal and/or organizations) from a single sign-in via `selections`.\n\nBreaking 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.\n\nUse `selections` (1..25 items), each with:\n• `accountType` = `personal` | `organization`\n• `selectedOrganization` (for `organization`)\n\nWhen 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`).","platforms":["linkedin"],"url":"https://docs.zernio.com/changelog#1790853866459"},{"id":"1790853274118","date":"2026-10-01T11:14:34.118Z","type":"improvement","message":"Facebook and Instagram connect flows now support connecting **multiple Pages/accounts in one request** via `pageIds`.\n\nSend `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`).\n\nApplies to:\n• `POST /v1/connect/facebook/select-page`: `pageIds` added; `pageId` is now optional (send `pageId` or `pageIds`, not both)\n• `POST /v1/connect/instagram/select-account`: `pageIds` added; `pageId` is now optional (send `pageId` or `pageIds`, not both)\n\nNew error case for `pageIds`: `422` when none of the requested Pages/accounts could be connected (see `details.failed` for per-ID reasons).","platforms":["instagram","facebook"],"url":"https://docs.zernio.com/changelog#1790853274118"},{"id":"1790852405149","date":"2026-10-01T11:00:05.149Z","type":"breaking_change","message":"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`.\n\nFor 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.","platforms":["x","x-ads","ads"],"url":"https://docs.zernio.com/changelog#1790852405149"},{"id":"1790850806406","date":"2026-10-01T10:33:26.406Z","type":"new_feature","message":"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.\n\nQuery params:\n• `fromDate` (required, YYYY-MM-DD), `toDate` (required, YYYY-MM-DD, max 366 days)\n• `profileId` (default `all`), `platform` (default `all`)\n• `compare`: `previous_period`\n• `topPosts` (0–25, default 5), `recentPosts` (0–25, default 10)\n\nResponse 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).","platforms":["analytics"],"url":"https://docs.zernio.com/changelog#1790850806406"},{"id":"1790848687068","date":"2026-10-01T09:58:07.068Z","type":"improvement","message":"Google ads targeting now supports **age** and **gender** on Google **Search/Display**.\n\nYou can now include `ageMin`/`ageMax` and `gender` in `TargetingSpec` for Search/Display; Google will exclude ranges/genders outside your request.\n\nUse:\n• `ageMin`, `ageMax`\n• `gender`: `all`, `male`, `female`\n\nNote: `ageMin`/`gender` are still rejected (400) on Google **Performance Max** and **Demand Gen**.","platforms":["google-ads","ads"],"url":"https://docs.zernio.com/changelog#1790848687068"},{"id":"1790847430704","date":"2026-10-01T09:37:10.704Z","type":"improvement","message":"Pinterest ads now support language targeting via `targeting.languages`.\n\nYou can restrict the audience by language on Pinterest (previously this field was rejected with 400).\n\nUse `targeting.languages` as an array of language codes (e.g. `[\"en\"]`).","platforms":["pinterest","pinterest-ads","ads"],"url":"https://docs.zernio.com/changelog#1790847430704"},{"id":"1790846706524","date":"2026-10-01T09:25:06.524Z","type":"improvement","message":"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`).\n\nYou can now set:\n• `ageMin` / `ageMax` on LinkedIn and X (in addition to Meta/TikTok/Pinterest)\n• `gender` on LinkedIn and X (in addition to Meta/TikTok/Pinterest); values: `all`, `male`, `female`\n• `languages` on TikTok, LinkedIn, and X (in addition to Meta/Google)\n\nNote: `ageMin`/`ageMax` and `gender` are still rejected on Google; `languages` is still rejected on Pinterest. Unsupported values return 400 with details.","platforms":["tiktok","x","linkedin","pinterest","meta-ads","google-ads","tiktok-ads","linkedin-ads","pinterest-ads","x-ads","ads"],"url":"https://docs.zernio.com/changelog#1790846706524"},{"id":"1790845647434","date":"2026-10-01T09:07:27.434Z","type":"breaking_change","message":"`GET /v1/connect/{platform}/ads` now uses `accountId` to select the posting account for same-token platforms when a profile has multiple active accounts.\n\nIf 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.\n\nNew failure case: `409` when the selection is ambiguous (`error_reason=ambiguous_account`).","platforms":["instagram","linkedin","pinterest","meta-ads","linkedin-ads","pinterest-ads","ads"],"url":"https://docs.zernio.com/changelog#1790845647434"},{"id":"1790844552064","date":"2026-10-01T08:49:12.064Z","type":"breaking_change","message":"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).\n\nThis makes cross-platform targeting stricter: if you send unsupported fields for a given platform, the request will fail instead of being dropped/ignored.\n\nNotable platform restrictions called out in `TargetingSpec`:\n• `ageMin` / `gender`: rejected on Google, LinkedIn, X, OpenAI\n• `languages`: only applied on Meta + Google; rejected on TikTok, LinkedIn, Pinterest, X, OpenAI\n• `interests`: rejected on Google and OpenAI\n\nAlso clarified: `userOs` and `userDevice` are **Meta only**.","platforms":["tiktok","x","linkedin","pinterest","meta-ads","google-ads","tiktok-ads","linkedin-ads","pinterest-ads","x-ads","ads"],"url":"https://docs.zernio.com/changelog#1790844552064"},{"id":"1790839498415","date":"2026-10-01T07:24:58.415Z","type":"new_feature","message":"Meta ads now support a new goal: `page_visits`.\n\nUse it to create Meta \"Page visits and followers\" campaigns/ads (Traffic objective with Facebook Page destination).\n\nSet `goal` to `page_visits` on:\n• `POST /v1/ads/campaigns`\n• `POST /v1/ads/create`\n\nEnum update: `goal` now includes `page_visits` (in addition to existing values).","platforms":["facebook","meta-ads","ads"],"url":"https://docs.zernio.com/changelog#1790839498415"},{"id":"1790831565765","date":"2026-10-01T05:12:45.765Z","type":"improvement","message":"GET `/v1/ads/accounts` now returns TikTok advertiser balance details in each item.\n\nThis 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.\n\nNew response fields (TikTok only):\n• `balance`\n• `cashBalance`\n• `grantBalance`","platforms":["tiktok","tiktok-ads","ads"],"url":"https://docs.zernio.com/changelog#1790831565765"},{"id":"1790805654590","date":"2026-09-30T22:00:54.590Z","type":"new_feature","message":"LinkedIn now supports **behavior targeting** via `TargetingSpec.behaviors`.\n\nYou can search LinkedIn member behaviors (e.g. Frequent Travelers, Job Seekers) and then pass the returned IDs in your targeting when creating ads.\n\nUse:\n• `GET /v1/ads/targeting/search` with `dimension=behavior`\n• then set `targeting.behaviors` (or top-level `behaviors` on `POST /v1/ads/create`) to `[{ id }]` (IDs look like `urn:li:memberBehavior:9`)","platforms":["linkedin","linkedin-ads","ads"],"url":"https://docs.zernio.com/changelog#1790805654590"},{"id":"1790805196090","date":"2026-09-30T21:53:16.090Z","type":"improvement","message":"Account health responses now include **automation DM retry timing** when Meta temporarily refuses messaging.\n\nWhen `messagingRestriction` is present, you may also receive `pausedUntil` (date-time) to indicate when held automation DMs will be retried next.\n\nNew field:\n• `messagingRestriction.pausedUntil` - next retry time (or `null` when nothing is held)\n\nAvailable on:\n• `GET /v1/accounts/health`\n• `GET /v1/accounts/{accountId}/health`","platforms":["facebook"],"url":"https://docs.zernio.com/changelog#1790805196090"},{"id":"1790801068391","date":"2026-09-30T20:44:28.391Z","type":"new_feature","message":"Comment-to-DM automations now support **tap-to-unlock** gating for Instagram via `audience.tapToUnlock`.\n\nWhen enabled, Zernio sends the `followGate.message` + button to every commenter and only delivers the real DM after they tap, with no follow check.\n\nSet:\n• `audience.tapToUnlock: true`\n• `followGate.message` (optional; used for `whenUnknown=verify` and `tapToUnlock=true`)\n• `followGate.buttonLabel` (optional; defaults to “Unlock” for tap-to-unlock)\n\nNotes:\n• `whenUnknown` is ignored when `tapToUnlock` is true\n• Cannot be combined with `followerStatus` != `any` or with `minFollowerCount` (400)\n• Sending `audience` in a PATCH without `tapToUnlock` clears it","platforms":["instagram","inbox"],"url":"https://docs.zernio.com/changelog#1790801068391"},{"id":"1790798531894","date":"2026-09-30T20:02:11.894Z","type":"new_feature","message":"TikTok now supports **behavior targeting** via `behaviors` in `POST /v1/ads/create` (and in `TargetingSpec` / saved targeting).\n\nYou 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.\n\nSet:\n• `behaviors: [{ id: \"video:...\" }]` or `behaviors: [{ id: \"creator:...\" }]`\n\nNote: TikTok behavior targeting uses TikTok placement only.","platforms":["tiktok","tiktok-ads","ads"],"url":"https://docs.zernio.com/changelog#1790798531894"},{"id":"1790797348341","date":"2026-09-30T19:42:28.341Z","type":"new_feature","message":"Webhooks now support **voice message playback status** via the new event `message.played`.\n\nThis lets you track when an outgoing WhatsApp voice message is played, similar to existing delivery/read status updates.\n\nSubscribe by adding `message.played` to `events` when calling `POST /v1/webhooks/settings` (or update via `PUT /v1/webhooks/settings`).\n\nThe shared delivery-status webhook payload `WebhookPayloadMessageDeliveryStatus.event` now includes: `message.delivered`, `message.read`, `message.played`, `message.failed`.\n\nIn `GET /v1/inbox/conversations/{conversationId}/messages`, `deliveryStatus` can now be `played` (in addition to `sent`, `delivered`, `read`, `failed`, `deleted`).","platforms":["whatsapp","inbox","webhooks"],"url":"https://docs.zernio.com/changelog#1790797348341"},{"id":"1790795305011","date":"2026-09-30T19:08:25.012Z","type":"improvement","message":"WhatsApp `message.received` webhooks now include the unsupported content kind in `metadata.unsupported.type`.\n\nThis helps you distinguish what WhatsApp couldn’t deliver (e.g. view-once, polls, edits) instead of only seeing the generic `[Unsupported message]` placeholder.\n\nWhen `metadata.unsupported` is present, you can now read:\n• `metadata.unsupported.code`\n• `metadata.unsupported.title`\n• `metadata.unsupported.details`\n• `metadata.unsupported.type` (e.g. `view_once`, `poll_creation`, `group_invite`, `edit`)","platforms":["whatsapp","webhooks"],"url":"https://docs.zernio.com/changelog#1790795305011"},{"id":"1790793093356","date":"2026-09-30T18:31:33.356Z","type":"improvement","message":"GET `/v1/inbox/conversations/search` now returns additional WhatsApp participant identifiers in each `conversation`.\n\nThis helps you anchor identity more reliably across conversations (stable ID) and optionally display the user’s current WhatsApp username.\n\nNew fields (WhatsApp only):\n• `businessScopedUserId` — Meta BSUID (stable identity anchor; present when provided by Meta)\n• `whatsappUsername` — participant username (not stable; captured from inbound messages, may be null until next inbound)","platforms":["whatsapp","inbox"],"url":"https://docs.zernio.com/changelog#1790793093356"},{"id":"1790790319621","date":"2026-09-30T17:45:19.621Z","type":"improvement","message":"WhatsApp inbound **message.received** webhooks now include forwarded indicators and CTWA welcome message context.\n\nThis lets you detect forwarded content for moderation/routing, and capture the greeting text Meta showed on Click-to-WhatsApp ads for attribution.\n\nNew fields in `WebhookPayloadMessage.metadata`:\n• `forwarded` (boolean)\n• `frequentlyForwarded` (boolean)\n• `referral.welcome_message.text` (string)\n\nWhatsApp audio attachments now include voice-note metadata:\n• `message.attachments[].payload.voice` (boolean) — true for WhatsApp voice notes, false for regular audio files","platforms":["whatsapp","ads","inbox","webhooks"],"url":"https://docs.zernio.com/changelog#1790790319621"},{"id":"1790788206545","date":"2026-09-30T17:10:06.545Z","type":"improvement","message":"WhatsApp conversations now include participant identity/display fields in the Inbox conversation APIs.\n\nThis helps you anchor WhatsApp users to Meta’s stable BSUID (and optionally show their WhatsApp username) when listing or fetching a conversation.\n\nNew fields:\n• `GET /v1/inbox/conversations` → `businessScopedUserId`, `whatsappUsername`\n• `GET /v1/inbox/conversations/{conversationId}` → `businessScopedUserId`, `whatsappUsername`\n\nNotes:\n• `businessScopedUserId` is the recommended stable identity anchor (WhatsApp only)\n• `whatsappUsername` (e.g. `jane.shop`) is for display and can change","platforms":["whatsapp","inbox"],"url":"https://docs.zernio.com/changelog#1790788206545"},{"id":"1790784497711","date":"2026-09-30T16:08:17.711Z","type":"improvement","message":"OAuth connect now supports **safe reconnect** via `reconnectAccountId` on `GET /v1/connect/{platform}`.\n\nUse 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`.\n\nNew parameter:\n• `reconnectAccountId` — Zernio account id to refresh (must be same `platform` and on the same `profileId`, otherwise 400)\n\nNotes:\n• In `headless=true` flows, keep the returned `redirect_url` unchanged when calling the selection endpoints so the marker is preserved.\n• On X (twitter), `reconnectAccountId` counts toward the 500-char OAuth `state` limit.","platforms":["x"],"url":"https://docs.zernio.com/changelog#1790784497711"},{"id":"1790783781537","date":"2026-09-30T15:56:21.537Z","type":"new_feature","message":"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).\n\nQuery params:\n• `accountId` (required)\n• `start`, `end` (required, ISO 8601)\n• `granularity` (required): `HALF_HOUR` | `DAILY` | `MONTHLY`\n• Optional breakdowns via `dimensions`: `COUNTRY`, `PHONE`, `PRICING_CATEGORY`, `PRICING_TYPE`, `TIER`\n• Optional filters: `metricTypes` (`COST`, `VOLUME`), `pricingTypes` (`REGULAR`, `FREE_CUSTOMER_SERVICE`, `FREE_ENTRY_POINT`), `pricingCategories`, `countryCodes`\n\nResponse includes `dataPoints` with `start`/`end`, optional dimension fields, plus `volume` and `cost`.","platforms":["whatsapp","analytics"],"url":"https://docs.zernio.com/changelog#1790783781537"},{"id":"1790782962797","date":"2026-09-30T15:42:42.797Z","type":"improvement","message":"`messagingRestriction` in account health responses has been expanded to cover WhatsApp and now returns both Meta subcodes and WhatsApp Cloud API error codes.\n\nThis helps you detect messaging blocks/restrictions across Facebook/Instagram and WhatsApp from observed send/delivery failures (not a live probe).\n\nIn `GET /v1/accounts/health` and `GET /v1/accounts/{accountId}/health`, `messagingRestriction` now includes:\n• `code` (WhatsApp Cloud API; nullable)\n• `subcode` (Meta FB/IG; nullable)\n• `message` (nullable)\n• `firstSeenAt`, `lastSeenAt`\n\nWhatsApp codes tracked: `131042`, `131031`, `368`. FB/IG subcodes tracked: `2534122`, `1893063`, `2534029`.","platforms":["instagram","whatsapp"],"url":"https://docs.zernio.com/changelog#1790782962797"},{"id":"1790782523935","date":"2026-09-30T15:35:23.935Z","type":"new_feature","message":"Webhooks now support new WhatsApp account-level events: `whatsapp.account.quality_updated`, `whatsapp.account.status_updated`, `whatsapp.account.alert_received`.\n\nUse 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.\n\nSubscribe by including the events in `events` when calling `POST /v1/webhooks/settings` or `PUT /v1/webhooks/settings`:\n• `whatsapp.account.quality_updated`\n• `whatsapp.account.status_updated`\n• `whatsapp.account.alert_received`","platforms":["whatsapp","webhooks"],"url":"https://docs.zernio.com/changelog#1790782523935"},{"id":"1790781947890","date":"2026-09-30T15:25:47.890Z","type":"new_feature","message":"Webhooks now support a new WhatsApp event: `whatsapp.contact.identity_changed`.\n\nThis 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.\n\nTo subscribe, include `whatsapp.contact.identity_changed` in `events` when calling `POST /v1/webhooks/settings` (or update via `PUT /v1/webhooks/settings`).\n\nPayload key fields:\n• `reason`: `user_changed_number` | `user_changed_user_id` | `user_identity_changed` | `user_id_update`\n• `previous`, `current`: `phoneNumber`, `businessScopedUserId`, `parentBusinessScopedUserId`, `whatsappUsername`\n• `contactId`, `conversationId`\n• `changedAt`, `timestamp`","platforms":["whatsapp","inbox","telephony","webhooks"],"url":"https://docs.zernio.com/changelog#1790781947890"},{"id":"1790781450324","date":"2026-09-30T15:17:30.324Z","type":"new_feature","message":"WhatsApp webhooks now include Meta billing details via `pricing` and `billingConversation` on `message.sent`, `message.delivered`, `message.read`, and `message.failed`.\n\nThis 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).\n\n`pricing`:\n• `billable`\n• `pricingModel` (`PMP` | `CBP`)\n• `category` (e.g. `marketing`, `utility`, `authentication`, `service`)\n• `type` (`regular` | `free_customer_service` | `free_entry_point`)\n\n`billingConversation`:\n• `id`\n• `expiresAt`\n• `originType` (e.g. `utility`)","platforms":["whatsapp","webhooks"],"url":"https://docs.zernio.com/changelog#1790781450324"},{"id":"1790779848500","date":"2026-09-30T14:50:48.500Z","type":"improvement","message":"Switching destinations can now return **409 Conflict** when the target is already connected by another account on the same profile.\n\nApplies to:\n• `PUT /v1/accounts/{accountId}/facebook-page`\n• `PUT /v1/accounts/{accountId}/linkedin-organization`\n• `PUT /v1/accounts/{accountId}/gmb-locations`\n\nHandle `409` with `ErrorResponse` when you hit `profile_platform_conflict`.","platforms":["facebook","linkedin","google-business"],"url":"https://docs.zernio.com/changelog#1790779848500"},{"id":"1790779652420","date":"2026-09-30T14:47:32.420Z","type":"new_feature","message":"GET `/v1/ads/tree` now supports incremental syncing and bulk campaign filtering.\n\nYou 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.\n\nNew/updated query params:\n• `updatedSince` (ISO 8601 date-time, e.g. `2026-09-30T10:00:00Z`)\n• `campaignId` now accepts multiple platform campaign IDs (comma-separated, up to 100): `?campaignId=123,456`","platforms":["ads"],"url":"https://docs.zernio.com/changelog#1790779652420"},{"id":"1790777531559","date":"2026-09-30T14:12:11.559Z","type":"breaking_change","message":"Multi-account posts now require disambiguation via `accountId` on some write actions.\n\nIf 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`).\n\nChanges:\n• `POST /v1/posts/{postId}/edit`: `accountId` is required when the post has multiple accounts on that `platform`; otherwise returns `409`\n• `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`\n• `POST /v1/posts/{postId}/unpublish`: new optional `accountId` to delete a specific account’s copy; returns `409` if ambiguous\n\nPlatforms:\n• `/edit` `platform` enum: `twitter`, `discord`, `facebook`, `reddit`, `linkedin`, `telegram`, `pinterest`, `googlebusiness`, `youtube`, `slack`\n• `/unpublish` `platform` enum: `threads`, `facebook`, `twitter`, `linkedin`, `youtube`, `pinterest`, `reddit`, `bluesky`, `googlebusiness`, `telegram`","platforms":["threads","linkedin","youtube","pinterest","reddit","bluesky","telegram","discord","slack","google-business"],"url":"https://docs.zernio.com/changelog#1790777531559"},{"id":"1790765878725","date":"2026-09-30T10:57:58.725Z","type":"improvement","message":"Google Ads keyword data now refreshes **daily** instead of weekly on `GET /v1/ads/keywords`.\n\nNew/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).\n\nAlso updated `429` rate-limit semantics for Google keyword planner + insights endpoints:\n• `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\n• `GET /v1/ads/insights`: `429` now calls out Google per-user burst limit vs Google rate limit\n\nError details for Zernio Google Ads burst limits are now represented as `details.budgetScope` = `user` (previously could be `user` or `platform`).","platforms":["google-ads","ads","analytics"],"url":"https://docs.zernio.com/changelog#1790765878725"},{"id":"1790756741249","date":"2026-09-30T08:25:41.249Z","type":"improvement","message":"WhatsApp OTP verifications now return delivery telemetry via `deliveryStatus` and `deliveryErrorCode` on the `Verification` object.\n\nThis 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.\n\nNew fields:\n• `deliveryStatus`: `delivered` | `read` | `failed` | `null`\n• `deliveryErrorCode`: `integer` | `null`\n\nAlso, `OwnedPhoneNumber` now includes `metaPoolAddRejectedAt` (`date-time`) when WhatsApp reports the number is registered to another WhatsApp account during connect.","platforms":["whatsapp","telephony"],"url":"https://docs.zernio.com/changelog#1790756741249"},{"id":"1790738147369","date":"2026-09-30T03:15:47.369Z","type":"breaking_change","message":"`MetaCustomerLifecycle` validation rules changed for Meta Sales ad sets (`ad_set_goal`).\n\nAudience ID fields are now required/forbidden depending on `strategy`, matching Meta’s 400 behavior.\n\nSet `strategy` to one of: `all_customers`, `new_customers`, `new_customers_excluding_engaged`\n\nRules:\n• `all_customers`: `existingCustomerAudienceIds` not allowed\n• `new_customers`: `existingCustomerAudienceIds` required\n• `new_customers_excluding_engaged`: `existingCustomerAudienceIds` required and `engagedAudienceIds` required (and `engagedAudienceIds` not allowed for the other strategies)","platforms":["meta-ads","ads"],"url":"https://docs.zernio.com/changelog#1790738147369"},{"id":"1790737177895","date":"2026-09-30T02:59:37.895Z","type":"new_feature","message":"Meta ad set updates now support **Customer Lifecycle Strategy** via `platformSpecificData.customerLifecycle` on `PUT /v1/ads/ad-sets/{adSetId}`.\n\nThis lets you set Meta’s Sales ad set goal to optimize for all customers vs acquiring new customers (optionally excluding engaged audiences).\n\nSet `platformSpecificData.customerLifecycle.strategy` to:\n• `all_customers`\n• `new_customers`\n• `new_customers_excluding_engaged`\n\nOptional (depending on strategy):\n• `existingCustomerAudienceIds`\n• `engagedAudienceIds`","platforms":["meta-ads","ads"],"url":"https://docs.zernio.com/changelog#1790737177895"},{"id":"1790715399451","date":"2026-09-29T20:56:39.451Z","type":"improvement","message":"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.\n\nIf you receive `409` (Business Login connection needs reconnecting), you can either:\n• Reconnect with `loginMode=business`\n• Or switch to standard login by passing `force=true` (or disconnecting the business connection first)\n\nKey params: `loginMode` (`classic` | `business`), `force` (`true` | `false`)","platforms":["meta-ads","ads"],"url":"https://docs.zernio.com/changelog#1790715399451"},{"id":"1790702871972","date":"2026-09-29T17:27:51.972Z","type":"breaking_change","message":"Profiles now support a default timezone via `timezone`.\n\nWhen 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).\n\nSet the profile default timezone with:\n• `POST /v1/profiles` → `timezone`\n• `PUT /v1/profiles/{profileId}` → `timezone` (set `null` to go back to UTC)\n\nBulk CSV uploads also now default `schedule_time` to the profile timezone when `tz` is omitted (else UTC).","platforms":["publishing"],"url":"https://docs.zernio.com/changelog#1790702871972"},{"id":"1790700353090","date":"2026-09-29T16:45:53.090Z","type":"new_feature","message":"OAuth connect now supports requesting a **reduced permission set** via `scopes` on `GET /v1/connect/{platform}`.\n\nThis 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).\n\nUse `scopes` as a comma-separated list:\n• `posting`, `analytics`, `comments`, `messaging`, `ads`\n\nNotes:\n• Omit `scopes` to request the full permission set (current default behavior).\n• Rejected with 400 `INVALID_FIELD_VALUE` on platforms that can’t be reduced: `bluesky`, `telegram`, `discord`, `snapchat`, `whatsapp`.\n\nSlack 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).","platforms":["bluesky","telegram","snapchat","whatsapp","discord","slack","ads","inbox","analytics"],"url":"https://docs.zernio.com/changelog#1790700353090"},{"id":"1790696634701","date":"2026-09-29T15:43:54.701Z","type":"improvement","message":"LinkedIn connect now returns `409` when a `tempToken` was already used or has expired.\n\nThis 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.\n\nApplies to `POST /v1/connect/linkedin/select-organization` with error code `oauth_sign_in_consumed`.\n\nPhone number availability responses now include more detailed area metadata.\n\nIn `GET /v1/phone-numbers/availability` (and deprecated alias `GET /v1/whatsapp/phone-numbers/availability`), `areaAvailability.*` items may now include:\n• `ndcs` (array of all area codes for the city)\n• `aliases` (array of alternate area names)","platforms":["linkedin","whatsapp","telephony"],"url":"https://docs.zernio.com/changelog#1790696634701"},{"id":"1790695012072","date":"2026-09-29T15:16:52.072Z","type":"improvement","message":"`GET /v1/phone-numbers/availability` (and the deprecated alias `GET /v1/whatsapp/phone-numbers/availability`) now returns detailed per-area stock state via `areaAvailability`.\n\nThis 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.\n\nNew response object: `areaAvailability`\n• `inStock` (items: `ndc`, `name`, `count`)\n• `preOrder` (items: `ndc`, `name`)\n• `outOfStock` (items: `ndc`, `name`, `listed`)\n\nNotes:\n• `areaOptions` is now documented as equal to `areaAvailability.inStock`\n• `soldOutAreas` is now described as `areaAvailability.preOrder` + `areaAvailability.outOfStock` (kept for older clients)\n\n`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.","platforms":["whatsapp","telephony"],"url":"https://docs.zernio.com/changelog#1790695012072"},{"id":"1790686163511","date":"2026-09-29T12:49:23.511Z","type":"improvement","message":"CTWA ad creation now supports a fuller campaign/ad set + targeting surface in `CtwaAdRequestBody`, aligned with `POST /v1/ads/create`.\n\nYou can now schedule delivery, choose where budget lives, add ads under an existing campaign, and use advanced targeting options.\n\nNew/expanded fields include:\n• Creative: `description` (link description; not allowed with existing post creatives)\n• Campaign/ad set: `startDate`, `budgetLevel` (`adset` | `campaign`), `existingCampaignId`\n• 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`\n\nNote: when using `adSetId` (attach mode) or `existingCampaignId`, more fields are now explicitly rejected with a 400 per the updated validation rules.","platforms":["meta-ads","ads"],"url":"https://docs.zernio.com/changelog#1790686163511"},{"id":"1790685275949","date":"2026-09-29T12:34:35.949Z","type":"breaking_change","message":"**TikTok conversion event deletion is now reported as unsupported** on `DELETE /v1/accounts/{accountId}/tracking-tags/{tagId}/events/{eventId}`.\n\nTikTok 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.\n\nHandle `501` for TikTok when calling `DELETE /v1/accounts/{accountId}/tracking-tags/{tagId}/events/{eventId}`; successful responses still return `state` = `deleted` | `archived` | `disabled`.","platforms":["tiktok","tiktok-ads","ads"],"url":"https://docs.zernio.com/changelog#1790685275949"},{"id":"1790684777753","date":"2026-09-29T12:26:17.753Z","type":"improvement","message":"`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).\n\nThis makes it easier to reuse existing Instagram posts/reels for CTWA/messaging ads, and you can still customize the chat greeting.\n\nUse:\n• `platformPostId` (IG or FB post/reel ID)\n• Optional `welcomeMessage` (allowed with existing post references)\n\nReminder: existing post references are still mutually exclusive with fresh creative fields (`headline`, `body`, `imageUrl`, `video`) and with each other (`platformPostId` vs `objectStoryId`).","platforms":["instagram","whatsapp","meta-ads","ads"],"url":"https://docs.zernio.com/changelog#1790684777753"},{"id":"1790682414297","date":"2026-09-29T11:46:54.297Z","type":"improvement","message":"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.\n\nIn 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.\n\nHandle `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`).\n\nApplies to `POST /v1/phone-numbers/kyc` and the deprecated alias `POST /v1/whatsapp/phone-numbers/kyc`.","platforms":["whatsapp","telephony"],"url":"https://docs.zernio.com/changelog#1790682414297"},{"id":"1790681521643","date":"2026-09-29T11:32:01.643Z","type":"improvement","message":"Commerce endpoints now document required store capabilities and platform support, and `CommerceCapability` adds new values: `collections.metafields`, `discounts.codes`.\n\nThis clarifies which operations may return `400 platform_not_supported` vs `403 insufficient_permissions`, and which features are Shopify-only vs available on WooCommerce.\n\nKey notes:\n• Collection metafields endpoints (`GET/PUT/DELETE /v1/commerce/collections/{collectionId}/metafields`) require `collections.metafields`; WooCommerce returns `400 platform_not_supported`\n• Discount code management (`POST /v1/commerce/discounts/{discountId}/codes`) requires `discounts.codes`; WooCommerce does not support it\n• Pages endpoints require `pages.read`/`pages.write`\n• Navigation endpoints (`/v1/commerce/menus`, `/v1/commerce/redirects`) are Shopify-only and require `navigation.read`/`navigation.write`","platforms":["commerce"],"url":"https://docs.zernio.com/changelog#1790681521643"},{"id":"1790680568837","date":"2026-09-29T11:16:08.837Z","type":"improvement","message":"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`).\n\nPre-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.\n\nHandle new/expanded `409` error codes:\n• `area_code_unavailable` - requested `areaCode` (or address-covered area) has no deliverable inventory and can’t be pre-ordered\n• `country_out_of_stock` - the whole country/type pool has nothing deliverable and can’t be pre-ordered\n\nWhen inventory is temporarily held due to recent WhatsApp registration failures, `details.undeliverableUntil` is returned as an ISO timestamp in the error details.","platforms":["whatsapp","telephony"],"url":"https://docs.zernio.com/changelog#1790680568837"},{"id":"1790673287678","date":"2026-09-29T09:14:47.678Z","type":"improvement","message":"`POST /v1/ads/boost` and `POST /v1/ads/create` now have clarified, consistent semantics for `status` when creating ads.\n\nWhen 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).\n\nKey behavior:\n• New campaign created: campaign is paused; ad set + ad are on\n• With `existingCampaignId`: new ad set is paused; its ad is on\n• With `adSetId`: the new ad itself is paused\n\nValues: `status` = `ACTIVE` | `PAUSED`\n\nAlso 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).","platforms":["ads"],"url":"https://docs.zernio.com/changelog#1790673287678"},{"id":"1790672871382","date":"2026-09-29T09:07:51.382Z","type":"new_feature","message":"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.\n\nEndpoints:\n• `GET /v1/imessage/sandbox/contacts` — returns the sandbox line (`sandbox.accountId`, `sandbox.handle`, `sandbox.contactLimit`) and your `contacts`\n• `POST /v1/imessage/sandbox/contacts` — add a contact with `handle` (E.164 phone like `+15551234567` or Apple ID email)\n• `DELETE /v1/imessage/sandbox/contacts/{contactId}` — remove a sandbox contact\n\nContact fields:\n• `status`: `pending` | `active`\n• Activate by sending `joinText` from the contact to the sandbox line (or use `joinLink`)\n• Reply window is based on `lastInboundAt` (24 hours after each inbound message)","platforms":["imessage","inbox","telephony","webhooks"],"url":"https://docs.zernio.com/changelog#1790672871382"},{"id":"1790672172309","date":"2026-09-29T08:56:12.309Z","type":"breaking_change","message":"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.\n\nStart by fetching store capabilities so you can detect required permissions up front.\n\nKey endpoints:\n• Store + capabilities: `GET /v1/commerce/store` (`accountId`) → `capabilities`, `missingCapabilities`, `grantPermissionsUrl`\n• 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`\n• Collections: `GET/POST /v1/commerce/collections`, `PATCH/DELETE /v1/commerce/collections/{collectionId}`, `POST /v1/commerce/collections/{collectionId}/products`, `POST /v1/commerce/collections/{collectionId}/reorder`\n• Inventory: `GET /v1/commerce/inventory` (`accountId`, `productId`), `POST /v1/commerce/products/{productId}/inventory` (`mode`: `set`/`adjust`)\n• Discounts: `GET/POST /v1/commerce/discounts`, `PATCH/DELETE /v1/commerce/discounts/{discountId}`, `POST /v1/commerce/discounts/{discountId}/state`, `POST /v1/commerce/discounts/{discountId}/codes`\n• Meta catalog sync: `POST /v1/commerce/catalog-syncs` (`accountId`, `catalogAccountId`, `catalogId`), `POST /v1/commerce/catalog-syncs/{syncId}/run`\n\nWebhooks: you can now subscribe to product lifecycle events via `POST /v1/webhooks/settings` / `PUT /v1/webhooks/settings` with:\n• `commerce.product.created`\n• `commerce.product.updated`\n• `commerce.product.deleted`\n\nBreaking change (Ads): campaign/ad set status toggles now write _only_ the campaign/ad set switch and no longer cascade to child ad sets/ads.\n• Campaign: `PUT /v1/ads/campaigns/{campaignId}/status`\n• Bulk: `POST /v1/ads/campaigns/bulk-status`\n• Ad set: `PUT /v1/ads/ad-sets/{adSetId}/status` and `PUT /v1/ads/ad-sets/{adSetId}`\nIf you relied on cascade resume/pause, also toggle children explicitly (e.g. `PUT /v1/ads/ad-sets/{adSetId}/status`, `PUT /v1/ads/{adId}/status`).","platforms":["meta-ads","ads","commerce","webhooks"],"url":"https://docs.zernio.com/changelog#1790672172309"},{"id":"1790670622441","date":"2026-09-29T08:30:22.441Z","type":"improvement","message":"POST `/v1/whatsapp/templates` can now return `502` when Meta rejects the request or is unreachable.\n\nThis makes Meta-side failures explicit so you can distinguish them from validation errors and apply retries/backoff.\n\nHandle:\n• `502` - Meta rejected the request or was unreachable (Meta 4xx statuses are forwarded as-is)","platforms":["whatsapp"],"url":"https://docs.zernio.com/changelog#1790670622441"},{"id":"1790670018650","date":"2026-09-29T08:20:18.650Z","type":"breaking_change","message":"TikTok no longer supports **behavior targeting**.\n\nRequests that include `TargetingSpec.behaviors` for TikTok ads will now be rejected with `400`.\n\nImpacted surfaces:\n• `POST /v1/ads/create` (via `targeting.behaviors` or top-level `behaviors`)\n• `GET /v1/ads/targeting/search` with `dimension=behavior` (now Meta-only)\n\nIf 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.","platforms":["tiktok","tiktok-ads","ads"],"url":"https://docs.zernio.com/changelog#1790670018650"},{"id":"1790669743752","date":"2026-09-29T08:15:43.752Z","type":"new_feature","message":"Facebook Messenger accounts now support managing the **Get Started** button via new endpoints.\n\nThis lets you read/set/remove the page’s Get Started postback payload (required by Meta before a persistent menu can be shown).\n\nNew endpoints:\n• `GET /v1/accounts/{accountId}/messenger-get-started` → returns `data` (or `null`) with `payload`\n• `PUT /v1/accounts/{accountId}/messenger-get-started` with `payload` (1–1000 chars), e.g. `GET_STARTED` or `zernio:workflow:&lt;workflowId&gt;`\n• `DELETE /v1/accounts/{accountId}/messenger-get-started` (returns `409` if a persistent menu is still set)\n\nAlso updated: `PUT /v1/accounts/{accountId}/messenger-menu` now returns `409` if the page has no Get Started button set.","platforms":["facebook"],"url":"https://docs.zernio.com/changelog#1790669743752"},{"id":"1790668654503","date":"2026-09-29T07:57:34.503Z","type":"improvement","message":"OpenAI Ads tracking tags now return more complete conversion visibility.\n\n`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).\n\n`GET /v1/accounts/{accountId}/tracking-tags/{tagId}/events` clarifies OpenAI Ads attribution windows: `clickWindowDays` and `viewWindowDays` (0 = off).","platforms":["ads","analytics"],"url":"https://docs.zernio.com/changelog#1790668654503"},{"id":"1790665598391","date":"2026-09-29T07:06:38.391Z","type":"improvement","message":"Campaign/ad set/ad status endpoints now support optional **live status reads** to reduce stale switch/status data.\n\nUse `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).\n\nNew/updated parameters:\n• `GET /v1/ads/campaigns`: `live=true` (requires `limit&lt;=20`)\n• `GET /v1/ads/ad-sets`: `live=true` (requires `campaignId` or `adSetId`; reads live for first 20 rows)\n• `GET /v1/ads/{adId}`: `live=true`\n\nNew response fields:\n• Campaigns: `statusReadAt` (only when `live=true`)\n• Ad sets list: `statusReadAt` per row (only when `live=true`)\n• Ad details: `statusReadAt` (only when `live=true`)\n\nStatus write behavior is now more immediately consistent:\n• `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.","platforms":["ads"],"url":"https://docs.zernio.com/changelog#1790665598391"},{"id":"1790635696020","date":"2026-09-28T22:48:16.020Z","type":"improvement","message":"Date/time handling is now explicitly defined for ad scheduling and duplication, including how values without a timezone offset are interpreted.\n\nThis helps avoid accidental off-by-timezone starts/ends when using `startTime`/`endTime` (duplicate) and `startDate`/`endDate` (create/boost).\n\nKey rules:\n• `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.\n• `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.\n• `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.\n• `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).","platforms":["tiktok","x","linkedin","pinterest","meta-ads","google-ads","tiktok-ads","linkedin-ads","pinterest-ads","x-ads","ads"],"url":"https://docs.zernio.com/changelog#1790635696020"},{"id":"1790632487624","date":"2026-09-28T21:54:47.624Z","type":"improvement","message":"`startDate` / `endDate` handling was updated for ads scheduling.\n\nFor 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.\n\nApplies to:\n• `PUT /v1/ads/ad-sets/{adSetId}` via `platformSpecificData.startDate` / `platformSpecificData.endDate` (Meta)\n• `POST /v1/ads/boost` via `startDate` / `endDate` (Meta + TikTok)\n• `POST /v1/ads/create` via `startDate` / `endDate` (Meta + TikTok)\n\nIf 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`).","platforms":["tiktok","meta-ads","tiktok-ads","ads"],"url":"https://docs.zernio.com/changelog#1790632487624"},{"id":"1790631387364","date":"2026-09-28T21:36:27.364Z","type":"breaking_change","message":"`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).\n\nThe response no longer returns `message` when nothing was written; callers should rely on `status`, `updated`, `skipped`, and `skippedReasons`.\n\nSet `status` to `active` or `paused` (with required `platform`).\n\n`PUT /v1/ads/{adId}/status` now returns the post-write platform read-back:\n• `status` (delivery status)\n• `configuredStatus` (the ad’s own switch; may be `null` where unsupported)\n\nSkips are now based on the ad’s own switch already matching the target (not the rolled-up delivery `status`).","platforms":["ads"],"url":"https://docs.zernio.com/changelog#1790631387364"},{"id":"1790625567418","date":"2026-09-28T19:59:27.418Z","type":"improvement","message":"TikTok Ads now exposes the platform’s own on/off switches separately from delivery status.\n\nYou can distinguish ancestor-cascaded delivery `status` from the entity’s own toggle:\n• Ad: `configuredStatus` now maps TikTok `operation_status` (`ENABLE` → `ACTIVE`, `DISABLE` → `PAUSED`)\n• Campaign rollups: `platformCampaignStatus` now includes TikTok campaign `operation_status` (`ENABLE`/`DISABLE`)\n• Ad sets list: `platformAdSetStatus` now documents TikTok ad group `operation_status` (`ENABLE`/`DISABLE`)\n\nAlso clarified scheduling behavior: `startDate`/`endDate` are stored/read as UTC instants, and TikTok inputs without an offset are interpreted in the ad account timezone.","platforms":["tiktok","tiktok-ads","ads"],"url":"https://docs.zernio.com/changelog#1790625567418"},{"id":"1790621885751","date":"2026-09-28T18:58:05.751Z","type":"new_feature","message":"Tracking tags + conversion events now support **X Ads** (`platform: xads`) on the existing tracking-tag endpoints.\n\nYou can now list/read/create X Pixels and manage X web event tags via the same `/v1/accounts/{accountId}/tracking-tags` API surface.\n\nKey behavior for X Ads:\n• 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.\n• 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`.\n• Read pixel: `GET /v1/accounts/{accountId}/tracking-tags/{tagId}` where `tagId` is the X ad account id; returns `code`, `siteTagId`, and `events`.\n• Update pixel: `PATCH /v1/accounts/{accountId}/tracking-tags/{tagId}` supports `firstPartyCookieStatus` = `first_party_cookie_enabled` | `first_party_cookie_disabled`.\n• 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`.\n• Stats: `GET /v1/accounts/{accountId}/tracking-tags/{tagId}/stats` returns X web event tag health rows (no per-event counts).","platforms":["x","x-ads","ads","analytics"],"url":"https://docs.zernio.com/changelog#1790621885751"},{"id":"1790621145428","date":"2026-09-28T18:45:45.428Z","type":"new_feature","message":"Tracking tags now support **TikTok Ads** (`platform=tiktokads`) across the tracking-tags API (pixels, sharing, events, and stats).\n\nYou can now manage TikTok Pixels via:\n• `GET /v1/accounts/{accountId}/tracking-tags` (optionally `adAccountId=&lt;numeric advertiser_id&gt;`)\n• `GET /v1/accounts/{accountId}/tracking-tags/{tagId}` (searches advertisers if `adAccountId` omitted; TikTok has no `lastFiredTime`)\n• `POST /v1/accounts/{accountId}/tracking-tags` (`adAccountId`=numeric advertiser_id; `name` must be unique, max 40 chars; no delete API)\n\nSharing is supported via Business Center:\n• `GET|POST|DELETE /v1/accounts/{accountId}/tracking-tags/{tagId}/shared-accounts` (TikTok pixels must be Business Center assets; otherwise 400)\n\nConversion events and stats:\n• `GET|POST /v1/accounts/{accountId}/tracking-tags/{tagId}/events` (TikTok event list may lag 2–4 hours)\n• `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)\n• `GET /v1/accounts/{accountId}/tracking-tags/{tagId}/stats` (TikTok per-event daily counts; defaults last 7 days; max 30 days; `aggregation` not accepted)\n\nNote: TikTok connections authorized before the Pixel Management permission (about 2026-05-28) may return `403` until reconnected.","platforms":["tiktok","tiktok-ads","ads","analytics"],"url":"https://docs.zernio.com/changelog#1790621145428"},{"id":"1790620397884","date":"2026-09-28T18:33:17.884Z","type":"new_feature","message":"Pinterest Ads is now supported for Conversions API + event quality.\n\nYou can now send server-side conversion events to Pinterest, list Pinterest conversion destinations (ad accounts), and read Pinterest event quality metrics.\n\nUse `POST /v1/ads/conversions` with:\n• `accountId` = a `pinterestads` SocialAccount\n• `destinationId` = numeric Pinterest ad account id (e.g. `549755885175`)\n• Pinterest requirements: each event needs `user.email` OR (`user.ipAddress` + `user.userAgent`)\n• Pinterest click id: `user.clickIds.epik` (sent as `click_id`)\n• `testCode` on Pinterest sends with `test=true`\n• `consent.adUserData`: `DENIED` sets Pinterest `opt_out`\n\nEvent quality now supports Pinterest via `GET /v1/ads/conversions/quality`:\n• `accountId` = `metaads` or `pinterestads`\n• `destinationId` = Meta pixel/dataset id OR Pinterest numeric ad account id\n\nConversion destinations now include Pinterest via `GET /v1/accounts/{accountId}/conversion-destinations` (platform enum now includes `pinterestads`).","platforms":["pinterest","meta-ads","pinterest-ads","ads","analytics"],"url":"https://docs.zernio.com/changelog#1790620397884"},{"id":"1790619749765","date":"2026-09-28T18:22:29.765Z","type":"new_feature","message":"Tracking Tags now support managing tag access and viewing tag sharing via new endpoints.\n\nYou can list and assign users who have access to a tag (platforms without support return `501` with `platform_not_supported`).\n\nNew endpoints:\n• `GET /v1/accounts/{accountId}/tracking-tags/{tagId}/users` — returns `users[]` (`TrackingTagUser`: `id`, `name`, `tasks[]`)\n• `POST /v1/accounts/{accountId}/tracking-tags/{tagId}/users` — body `userId`, `tasks[]` (Meta tasks: `AA_ANALYZE`, `ADVERTISE`, `ANALYZE`, `EDIT`, `UPLOAD`)\n• `DELETE /v1/accounts/{accountId}/tracking-tags/{tagId}/users/{userId}` — removes access where supported (may return `501`)\n• `GET /v1/accounts/{accountId}/tracking-tags/{tagId}/partners` — returns `partners[]` (`TrackingTagPartner`: `id`, `name`)\n\nNotes: some platforms require additional permissions and may return `403` prompting reconnect (e.g., Meta requires `business_management`).","platforms":["meta-ads","ads"],"url":"https://docs.zernio.com/changelog#1790619749765"},{"id":"1790619246428","date":"2026-09-28T18:14:06.428Z","type":"new_feature","message":"Tracking tags and conversion events now support **Google Ads** (`googleads`).\n\nYou can now create/read Google tags and manage WEBPAGE conversion actions via the existing tracking-tag endpoints.\n\nCreate a Google tag (idempotent per account) with `POST /v1/accounts/{accountId}/tracking-tags` using:\n• `adAccountId` = 10-digit Google Ads customer id\n• `name` = used when enabling tracking by creating the first WEBPAGE action\n\nUpdate Google tag settings with `PATCH /v1/accounts/{accountId}/tracking-tags/{tagId}`:\n• `autoTagging` (gclid auto-tagging on/off)\n\nList/create/update/delete Google conversion actions via:\n• `GET /v1/accounts/{accountId}/tracking-tags/{tagId}/events`\n• `POST /v1/accounts/{accountId}/tracking-tags/{tagId}/events`\n• `PATCH /v1/accounts/{accountId}/tracking-tags/{tagId}/events/{eventId}`\n• `DELETE /v1/accounts/{accountId}/tracking-tags/{tagId}/events/{eventId}`\n\nNew event fields you may see for Google: `alwaysUseDefaultValue`, `primary`, `countingType` (`one` | `every`). Deleting archives actions; `enabled: true` can restore archived ones.","platforms":["google-ads","ads"],"url":"https://docs.zernio.com/changelog#1790619246428"},{"id":"1790618537342","date":"2026-09-28T18:02:17.342Z","type":"improvement","message":"GET `/v1/ads/accounts` now always returns Meta/X usability fields.\n\nFor Meta and X ad accounts, `selectable` and `unusableReason` are guaranteed to be present on every returned account, making it easier to reliably determine if an account can run ads.\n\nKey fields:\n• `selectable` (boolean)\n• `unusableReason` (string|null)","platforms":["x","meta-ads","x-ads","ads"],"url":"https://docs.zernio.com/changelog#1790618537342"},{"id":"1790618160627","date":"2026-09-28T17:56:00.627Z","type":"new_feature","message":"Tracking tags now support **Pinterest Ads** (platform `pinterestads`) across the tracking tag lifecycle.\n\nYou can now list/create/read Pinterest conversion tags, install them on Shopify/WordPress, and fetch tag health/stats.\n\nKey changes:\n• `TrackingTag.platform` enum includes `pinterestads`\n• Supported endpoints: `GET /v1/accounts/{accountId}/tracking-tags`, `POST /v1/accounts/{accountId}/tracking-tags`, `GET /v1/accounts/{accountId}/tracking-tags/{tagId}`, `POST /v1/accounts/{accountId}/tracking-tags/{tagId}/install`, `DELETE /v1/accounts/{accountId}/tracking-tags/{tagId}/install`, `GET /v1/accounts/{accountId}/tracking-tags/{tagId}/stats`\n• Pinterest uses numeric `adAccountId` (no prefix); `id` equals `siteTagId`\n\nNotes:\n• `PATCH /v1/accounts/{accountId}/tracking-tags/{tagId}` returns `501` for `pinterestads` (no update API)\n• Creating Pinterest tags is not idempotent; list first and don’t retry blindly","platforms":["pinterest","pinterest-ads","ads","commerce","analytics"],"url":"https://docs.zernio.com/changelog#1790618160627"},{"id":"1790618002420","date":"2026-09-28T17:53:22.420Z","type":"new_feature","message":"Tracking tags now support platform health checks via `GET /v1/accounts/{accountId}/tracking-tags/{tagId}/diagnostics`.\n\nUse it to fetch per-platform diagnostics for a tag (e.g. Meta pixel checks from Events Manager). Platforms without diagnostics return `501` (`platform_not_supported`).\n\nResponse includes:\n• `platform`\n• `checks[]` with `key`, `title`, `result` (Meta: `passed`/`failed`/`warning`), optional `actionUrl`\n\nMeta conversion events via tracking tags were also expanded:\n• `GET /v1/accounts/{accountId}/tracking-tags/{tagId}/events` now documents Meta custom conversions (scoped by `adAccountId`; archived included as `status: archived`)\n• `POST /v1/accounts/{accountId}/tracking-tags/{tagId}/events` supports Meta custom conversion creation with `urlContains` and idempotency by `name`\n• `TrackingTagEvent` / `TrackingTagEventInput` add `urlContains` (case-insensitive URL match)\n• `PATCH .../events/{eventId}` (Meta): only `name` and `defaultValue` are updatable; changing `type`/`siteEvent`/`urlContains` returns `400`\n• `DELETE .../events/{eventId}` (Meta): archives the conversion (`state: archived`)","platforms":["meta-ads","ads","analytics"],"url":"https://docs.zernio.com/changelog#1790618002420"},{"id":"1790616913087","date":"2026-09-28T17:35:13.087Z","type":"new_feature","message":"Tracking tags now support **LinkedIn Ads** (`linkedinads`) across the tracking-tag lifecycle.\n\nYou can manage the LinkedIn **Insight Tag** (list/create/get, share access across ad accounts, install on Shopify/WordPress, and manage conversion rules) using the existing tracking-tag endpoints.\n\nKey details:\n• Responses may now return `platform` = `linkedinads`\n• `GET /v1/accounts/{accountId}/tracking-tags`: `adAccountId` can be a numeric id or `urn:li:sponsoredAccount:{id}`; omit to scan all active ad accounts\n• `POST /v1/accounts/{accountId}/tracking-tags`: creates the ad account’s Insight Tag (idempotent per ad account); `name` is ignored\n• `PATCH /v1/accounts/{accountId}/tracking-tags/{tagId}`: LinkedIn supports `firstPartyCookieStatus` = `first_party_cookie_enabled` | `first_party_cookie_disabled` (`empty` returns 400)\n• Sharing endpoints now support LinkedIn: `.../shared-accounts` `POST` grants `USE_ONLY`; `DELETE` returns 400 instead of revoking the last remaining ad account (to avoid orphaning the tag)\n• Store install endpoints now support LinkedIn: `.../{tagId}/install` on Shopify/WordPress\n• Conversion rules are available via `.../{tagId}/events`; LinkedIn delete disables the rule and returns `state` = `disabled`\n• `.../{tagId}/stats` supports LinkedIn health rows (domains + conversion rules); `startTime`/`endTime` are ignored","platforms":["linkedin","linkedin-ads","ads","commerce","analytics"],"url":"https://docs.zernio.com/changelog#1790616913087"},{"id":"1790616125584","date":"2026-09-28T17:22:05.584Z","type":"improvement","message":"OpenAI Ads conversion events are now supported via tracking tag events: you can **list** and **create** pixel-backed event settings, but **update**/**delete** are not available (returns `501`).\n\nList: `GET /v1/accounts/{accountId}/tracking-tags/{tagId}/events` returns OpenAI event settings where the source is this pixel, including `siteEventId` (event name) and `clickWindowDays`.\n\nCreate: `POST /v1/accounts/{accountId}/tracking-tags/{tagId}/events` (OpenAI) accepts only `name`, `type`, `siteEvent`. `type` can be a standard event (`order_created`, `lead_created`, `items_added`, `contents_viewed`, `checkout_started`, `registration_completed`, `subscription_created`, `trial_started`, `appointment_scheduled`, `page_viewed`, `app_installed`, `app_opened`) or a custom event name (stored lowercase).\n\nUpdate/Delete: `PATCH /v1/accounts/{accountId}/tracking-tags/{tagId}/events/{eventId}` and `DELETE /v1/accounts/{accountId}/tracking-tags/{tagId}/events/{eventId}` return `501` for OpenAI Ads—create a new event instead, and archive in OpenAI Ads Manager.","platforms":["ads"],"url":"https://docs.zernio.com/changelog#1790616125584"},{"id":"1790615704680","date":"2026-09-28T17:15:04.680Z","type":"new_feature","message":"Tracking tags now support **OpenAI Ads** (`platform=openaiads`) across read/install/stats.\n\nYou can now fetch a single OpenAI pixel via `GET /v1/accounts/{accountId}/tracking-tags/{tagId}`. For OpenAI Ads, `tagId` can be the pixel API id (`cds_...`) or `pixel_id`; the response includes `code` (official `oaiq` base code + `page_viewed`) and `events` resolved from the pixel list.\n\nShopify/WordPress install + remove now return `platform` with `openaiads`:\n• `POST /v1/accounts/{accountId}/tracking-tags/{tagId}/install`\n• `DELETE /v1/accounts/{accountId}/tracking-tags/{tagId}/install`\n\nStats now support OpenAI Ads via `GET /v1/accounts/{accountId}/tracking-tags/{tagId}/stats`: it returns the recent Pixel SDK events stream (last ~15 minutes, up to 50). `startTime`/`endTime` are invalid for OpenAI Ads (400), and accounts without the stream get `422 feature_not_available`.\n\nNote: updating pixels remains Meta-only; OpenAI Ads pixel updates return `501`.","platforms":["ads","commerce","analytics"],"url":"https://docs.zernio.com/changelog#1790615704680"},{"id":"1790615234282","date":"2026-09-28T17:07:14.282Z","type":"new_feature","message":"RCS support is now available via new **RCS** endpoints to request/manage agents and send messages.\n\nYou can request an agent, upload compliant assets, invite test phones, check recipient capability, and send RCS (with optional SMS fallback).\n\nKey endpoints:\n• `POST /v1/rcs/agents` (`Idempotency-Key`) with `profileId`, `displayName`, `useCase` (`MULTI_USE`, `PROMOTIONAL`, `TRANSACTIONAL`, `OTP`) and exactly one of `brandId` or `brand`\n• `POST /v1/rcs/messages` (`Idempotency-Key`) with `agentId`, `to`, and exactly one of `text` or `content` (rich cards/carousels/media + suggestions)\n• `POST /v1/rcs/assets` (multipart) with `file` and `kind` (`logo`, `banner`)\n• `POST /v1/rcs/agents/{agentId}/test-devices` with `phoneNumber` (E.164)\n• `GET /v1/rcs/capabilities` with `agentId` and `numbers` (comma-separated, max 100)\n\nWebhooks: you can now subscribe to `rcs.agent.status_updated`, and RCS is included in inbox webhooks for `message.delivered`, `message.read`, and `message.failed`.\n\nInbox search: `GET /v1/inbox/conversations/search` now accepts `platform=rcs`.\n\nTracking Tags: new conversion-event endpoints were added:\n• `GET/POST /v1/accounts/{accountId}/tracking-tags/{tagId}/events`\n• `PATCH/DELETE /v1/accounts/{accountId}/tracking-tags/{tagId}/events/{eventId}`\nUse `adAccountId` when required by the platform; platforms without event objects may return `501`.","platforms":["publishing","inbox","telephony","webhooks"],"url":"https://docs.zernio.com/changelog#1790615234282"},{"id":"1790613885852","date":"2026-09-28T16:44:45.852Z","type":"new_feature","message":"Tracking tags now include **Google Ads** support in the tracking-tag APIs.\n\nYou can now see `platform: googleads` in responses from:\n• `GET /v1/accounts/{accountId}/tracking-tags`\n• `POST /v1/accounts/{accountId}/tracking-tags`\n• `GET /v1/accounts/{accountId}/tracking-tags/{tagId}`\n• `PATCH /v1/accounts/{accountId}/tracking-tags/{tagId}`\n• `.../install` and `.../stats`\n\nAlso updated `TrackingTag` event fields:\n• `events[].siteEvent` is now an enum: `page_view`, `view_content`, `add_to_cart`, `search`, `initiate_checkout`, `add_payment_info`, `purchase`\n• New `events[].siteEventId` for the platform-specific event identifier (e.g. Google conversion label).","platforms":["google-ads","ads","analytics"],"url":"https://docs.zernio.com/changelog#1790613885852"},{"id":"1790613225743","date":"2026-09-28T16:33:45.743Z","type":"new_feature","message":"Tracking tags now support **TikTok Ads** in the tracking tag (pixel) APIs.\n\nYou can now list/create/fetch/update tags and manage store installs/stats with `platform` including `tiktokads`.\n\nUpdated `platform` enum values:\n• `metaads`\n• `openaiads`\n• `tiktokads`\n\nAffected responses include:\n• `GET /v1/accounts/{accountId}/tracking-tags`\n• `POST /v1/accounts/{accountId}/tracking-tags`\n• `GET/PATCH /v1/accounts/{accountId}/tracking-tags/{tagId}`\n• `GET/POST/DELETE /v1/accounts/{accountId}/tracking-tags/{tagId}/install`\n• `GET /v1/accounts/{accountId}/tracking-tags/{tagId}/stats`","platforms":["tiktok","tiktok-ads","ads","analytics"],"url":"https://docs.zernio.com/changelog#1790613225743"},{"id":"1790612366085","date":"2026-09-28T16:19:26.085Z","type":"breaking_change","message":"Unsupported tracking-tag operations now return `501` (with `code=platform_not_supported`) instead of `405` across the tracking-tags API.\n\nThis is a breaking change if your client logic branches on `405` for “not supported”; update error handling to treat `501` as the unsupported-platform signal.\n\nPer-tag routes now accept optional `adAccountId` scoping where tag ids are ad-account-scoped:\n• `GET /v1/accounts/{accountId}/tracking-tags/{tagId}` (`adAccountId` query)\n• `PATCH /v1/accounts/{accountId}/tracking-tags/{tagId}` (`adAccountId` in body)\n• `GET /v1/accounts/{accountId}/tracking-tags/{tagId}/install` (`adAccountId` query)\n• `POST /v1/accounts/{accountId}/tracking-tags/{tagId}/install` (`adAccountId` in body)\n• `DELETE /v1/accounts/{accountId}/tracking-tags/{tagId}/install` (`adAccountId` query)\n• `GET /v1/accounts/{accountId}/tracking-tags/{tagId}/stats` (`adAccountId` query)\n\nResponse additions:\n• `TrackingTag` adds `siteTagId` and `events[]`; `platform` now includes `metaads`, `openaiads`\n• `StorePixelInstall` adds `tagPlatform`, `siteTagId`, and `tags[]` (Shopify always; WordPress on GET)\n\nShopify installs are now per-platform: installing a tag replaces only the previous tag of the same platform (other platforms’ tags are kept).","platforms":["ads","commerce","analytics"],"url":"https://docs.zernio.com/changelog#1790612366085"},{"id":"1790611941317","date":"2026-09-28T16:12:21.317Z","type":"improvement","message":"`POST /v1/accounts/{accountId}/business-partners` now supports many more Meta Page task values in `permittedTasks`.\n\nThis lets you grant additional Business Settings / New Pages Experience permissions (including PROFILE_PLUS tasks) when sharing a Page with a partner business.\n\nUse `permittedTasks` with values like:\n• `MODERATE_COMMUNITY`, `MANAGE_JOBS`, `READ_PAGE_MAILBOXES`, `VIEW_MONETIZATION_INSIGHTS`, `MANAGE_LEADS`\n• `PROFILE_PLUS_FULL_CONTROL`, `PROFILE_PLUS_FACEBOOK_ACCESS`, `PROFILE_PLUS_REVENUE`, `PROFILE_PLUS_MANAGE`, `PROFILE_PLUS_CREATE_CONTENT`, `PROFILE_PLUS_MODERATE`, `PROFILE_PLUS_MESSAGING`, `PROFILE_PLUS_ADVERTISE`, `PROFILE_PLUS_ANALYZE`\n\nNote: granting `MANAGE` also yields `MANAGE_LEADS`.","platforms":["facebook","analytics"],"url":"https://docs.zernio.com/changelog#1790611941317"},{"id":"1790610955562","date":"2026-09-28T15:55:55.562Z","type":"breaking_change","message":"`POST /v1/accounts/{accountId}/business-partners` now returns `200` when the partner portfolio already has access, with `alreadyShared: true` and the partner’s current `permittedTasks`.\n\nMeta no longer replaces tasks on a repeat grant. To change a partner’s tasks, revoke access and grant again.\n\nHandle both success statuses:\n• `201` - newly shared\n• `200` - already shared (`alreadyShared` = `true`)\n\nTasks are still granted via `permittedTasks`: `MANAGE`, `CREATE_CONTENT`, `MODERATE`, `MESSAGING`, `ADVERTISE`, `ANALYZE`.","platforms":["general"],"url":"https://docs.zernio.com/changelog#1790610955562"},{"id":"1790610705250","date":"2026-09-28T15:51:45.250Z","type":"breaking_change","message":"GET `/v1/connect/{platform}` now enforces an X (twitter) OAuth `state` length limit and may reject long `redirect_url` values.\n\nBecause X caps OAuth `state` at 500 characters (and `redirect_url` is carried inside it), calls can now fail with `400 INVALID_REDIRECT_URL` if the URL-encoded `redirect_url` is too long.\n\nLimits:\n• API callers: `redirect_url` (URL-encoded) max `258` chars\n• Dashboard sessions: max `310` chars\n• In `headless=true` flows, the appended `headless=true` counts toward the limit\n\nIf you hit this, shorten `redirect_url` (and/or reduce query params) for X connects.","platforms":["x"],"url":"https://docs.zernio.com/changelog#1790610705250"},{"id":"1790609943186","date":"2026-09-28T15:39:03.186Z","type":"new_feature","message":"Meta integrations now support **Page sharing with partner businesses** and **managing business-scoped users** for Pages and ad accounts.\n\nYou can list/share/revoke the business portfolios that can act on a Facebook Page behind an account:\n• `GET /v1/accounts/{accountId}/business-partners`\n• `POST /v1/accounts/{accountId}/business-partners` with `businessId` and optional `permittedTasks` (`MANAGE`, `CREATE_CONTENT`, `MODERATE`, `MESSAGING`, `ADVERTISE`, `ANALYZE`)\n• `DELETE /v1/accounts/{accountId}/business-partners` with `businessId`\n\nYou can also manage portfolio users and assign them tasks:\n• `GET /v1/ads/businesses/users` (`accountId`, `businessId`)\n• `GET/POST/DELETE /v1/ads/page-users` (assign tasks: `MANAGE`, `CREATE_CONTENT`, `MODERATE`, `MESSAGING`, `ADVERTISE`, `ANALYZE`)\n• `GET/POST/DELETE /v1/ads/accounts/users` (assign tasks: `MANAGE`, `ADVERTISE`, `ANALYZE`, `DRAFT`)\n\n**PATCH /v1/ads/accounts** now updates more ad account settings in one call. In addition to DSA defaults, you can set `name`, `spendCap` (number or `null`), and `resetAmountSpent`. The response may include `settings` (when name/spend cap fields were passed) and `dsaDefaults` (when `defaultDsaBeneficiary` was passed).","platforms":["facebook","meta-ads","ads"],"url":"https://docs.zernio.com/changelog#1790609943186"},{"id":"1790608352416","date":"2026-09-28T15:12:32.416Z","type":"new_feature","message":"**WordPress is now supported** for tracking tag installs via the existing install endpoints: `GET/POST/DELETE /v1/accounts/{accountId}/tracking-tags/{tagId}/install`.\n\nYou can now install/remove a Meta pixel on a connected WordPress site by creating/removing a Custom HTML widget, with optional verification and clearer failure reasons.\n\nKey changes:\n• `storeAccountId` now accepts Shopify or WordPress accounts\n• `POST` adds WordPress options: `sidebarId`, `verifyHomepage` (default `true`)\n• `GET` may include WordPress `install.preflight` with `ready`, `reason`, `sidebars`\n• `POST` WordPress response adds: `sidebarName`, `created`, `homepageCheck` (`found` | `not_found` | `unreachable` | `skipped`)\n• New WordPress failure: `422` `tracking_tag_install_blocked` with `details.reason` (`insufficient_permissions` | `scripts_stripped` | `wordpress_com_plan` | `no_widget_areas` | `widgets_api_unavailable`)\n• `DELETE` WordPress response adds `install.removed` (number of Zernio widgets deleted)\n\nSchema note: `StorePixelInstall.platform` now includes `shopify` | `wordpress`, and WordPress installs may return `siteUrl`, `method` (`wordpress_widget`), `widgetId`, `sidebarId`.","platforms":["meta-ads","ads","commerce"],"url":"https://docs.zernio.com/changelog#1790608352416"},{"id":"1790607328082","date":"2026-09-28T14:55:28.082Z","type":"new_feature","message":"New endpoint **POST** `/v1/branded-calling/share` creates a single-use share link (valid 7 days) so an end business can submit/complete a branded calling caller identity without a Zernio login.\n\nUse the request body to scope what the link is for:\n• `enterpriseId` - create a new identity for a registered business\n• `identityId` - complete an in-review identity (must be `requested` or `changes_requested`; not with `enterpriseId`)\n\nResponse returns `url` and `expiresAt`.","platforms":["telephony"],"url":"https://docs.zernio.com/changelog#1790607328082"},{"id":"1790607174182","date":"2026-09-28T14:52:54.182Z","type":"new_feature","message":"GET `/v1/ads/insights` now supports **TikTok (tiktok/tiktokads)** in addition to Meta and Google Ads.\n\nYou can run TikTok synchronous reports via the same endpoint and get raw TikTok rows back as `{ dimensions, metrics }` with page-number paging.\n\nTikTok queries use:\n• `adAccountId` (required) – advertiser id\n• `dataLevel` (required) – `AUCTION_ADVERTISER` | `AUCTION_CAMPAIGN` | `AUCTION_ADGROUP` | `AUCTION_AD`\n• `dimensions` (required) – 1-4 comma-separated dimensions\n• `metrics` (required) – comma-separated metrics\n• `fromDate` (required) + `toDate`\n• optional `reportType` – `BASIC` | `AUDIENCE`\n• optional `filtering` – JSON array `[{\"field\",\"operator\",\"value\"}]`\n\nTikTok responses add `paging.page`, `paging.pageSize`, `paging.totalRows`, `paging.totalPages`, and top-level `adAccountId`. TikTok may return `403` if the connection can’t read the advertiser.","platforms":["tiktok","google-ads","tiktok-ads","ads","analytics"],"url":"https://docs.zernio.com/changelog#1790607174182"},{"id":"1790606612355","date":"2026-09-28T14:43:32.355Z","type":"new_feature","message":"New endpoints let you manage **Meta pixel installs on connected Shopify stores** (via Shopify web pixel, no theme edits).\n\nYou can now install a pixel, check whether it’s the one the store fires, and remove it.\n\nEndpoints:\n• `POST /v1/accounts/{accountId}/tracking-tags/{tagId}/install` — install/update on a store (idempotent; installing a different tag replaces the previous one and returns `replacedTagId`)\n• `GET /v1/accounts/{accountId}/tracking-tags/{tagId}/install` — get install status (includes `installed` and `installedTagId`)\n• `DELETE /v1/accounts/{accountId}/tracking-tags/{tagId}/install` — remove from a store (idempotent; if store fires a different pixel returns `409`)\n\nKey params/fields:\n• `tagId` = Meta pixel id\n• `storeAccountId` (body for POST, query for GET/DELETE)\n• Response `platform`: `metaads`\n• Install object: `storeAccountId`, `platform`: `shopify`, `shopDomain`, `installed`, `installedTagId`, `webPixelId`\n\nNotes:\n• Meta-only; other platforms return `405`\n• Stores connected before pixel support may require re-approval: `409` `reconnect_required` with `details.authUrl`","platforms":["meta-ads","ads","commerce"],"url":"https://docs.zernio.com/changelog#1790606612355"},{"id":"1790605387506","date":"2026-09-28T14:23:07.506Z","type":"new_feature","message":"Branded Calling now supports a dry-run validation endpoint: `POST /v1/branded-calling/identities/preflight`.\n\nIt validates the exact body used by `POST /v1/branded-calling/identities` and returns review-style findings without creating an identity. Use it to catch issues that would be bounced in review.\n\nResponse includes:\n• `ok` (true when no `severity=block`)\n• `findings[]` with `code` (`display-name-mismatch`, `call-reasons-manual`, `authorizer-free-mail`, `authorizer-domain-mismatch`, `reference-phone-duplicate`, `reference-internal`, `reference-financial-free-mail`, `reference-timezone-invalid`, `logo-unreachable`), `severity` (`block`|`warn`), `field`, `message`.\n\nAlso added:\n• Idempotency support on `POST /v1/branded-calling/enterprises` and `POST /v1/branded-calling/identities` via `Idempotency-Key` (new `409` in-flight; `422` on key reuse with different body).\n• `GET /v1/usage/calls` now returns `brandedCalls` and `brandedCallUSD` (in `totals` and per-`groups[]`), where `brandedCallUSD` is already included in `billableUSD`.\n• `PATCH /v1/branded-calling/identities/{id}` can now return `422` if `reviewAnswers` includes a point id not on the open change request (or no open request).","platforms":["telephony","analytics"],"url":"https://docs.zernio.com/changelog#1790605387506"},{"id":"1790600437467","date":"2026-09-28T13:00:37.467Z","type":"new_feature","message":"POST `/v1/ads/messaging` now supports **per-placement creatives** via `placementAssets`, plus a **validate-only dry run** via `validateOnly`.\n\nYou can pin different images/videos per placement (e.g. Feed vs Stories/Reels) on a single messaging ad, and dry-run the full create (or attach via `adSetId`) without uploading/creating anything.\n\nUse `placementAssets` (replaces `imageUrl`/`video`):\n• Defaults: `defaultImageUrl` or `defaultVideoUrl` (exactly one mode)\n• Rules: `rules[].placements` + `rules[].imageUrl` or `rules[].videoUrl` (+ optional `thumbnailUrl`)\n\nConstraints:\n• Only with single `destination` (not `destinations`)\n• Not allowed with `creatives[]` or existing-post fields (`platformPostId`/`existingPostId`/`objectStoryId`)\n\nSet `validateOnly: true` to get a `200` response with per-node results (`campaign`, `adSet`, `creative`, `ad`), and nothing is created.","platforms":["meta-ads","ads","publishing"],"url":"https://docs.zernio.com/changelog#1790600437467"},{"id":"1790599420515","date":"2026-09-28T12:43:40.515Z","type":"improvement","message":"PSTN call cost estimates now account for **Branded Calling** when you provide a caller number.\n\nPass `from` to `GET /v1/voice/calls/estimate` to include the per-call Branded Calling surcharge when `from` is a verified branded number and `to` is a US destination.\n\nNew/updated fields:\n• Query param: `from`\n• Response breakdown: `breakdown.brandedCallUSD` (0 unless the surcharge applies)\n\nBranded Calling billing clarification: the $0.10 surcharge is counted on every outbound call from a verified branded number to a US destination (even if the callee’s carrier doesn’t display the branding).","platforms":["telephony"],"url":"https://docs.zernio.com/changelog#1790599420515"},{"id":"1790598890353","date":"2026-09-28T12:34:50.353Z","type":"new_feature","message":"Webhooks now support new **Branded Calling** events in `events` when creating/updating a webhook via `POST /v1/webhooks/settings` and `PUT /v1/webhooks/settings`.\n\nSubscribe to these new event types:\n• `branded_calling.identity.status_updated`\n• `branded_calling.identity.action_required`\n• `branded_calling.number.status_updated`","platforms":["telephony","webhooks"],"url":"https://docs.zernio.com/changelog#1790598890353"},{"id":"1790597094805","date":"2026-09-28T12:04:54.805Z","type":"new_feature","message":"New **Branded Calling** endpoints are available to register a business, create a caller identity (name/logo/call reasons), verify the authorizer email code, and attach US numbers for carrier vetting.\n\nRegister/list businesses:\n• `POST /v1/branded-calling/enterprises` (US/CA only; non-US/CA returns `422`)\n• `GET /v1/branded-calling/enterprises`, `GET /v1/branded-calling/enterprises/{id}`, `DELETE /v1/branded-calling/enterprises/{id}` (delete may return `409` if identities exist)\n\nCreate/manage identities:\n• `POST /v1/branded-calling/identities` with `enterpriseId`, `displayName`, `callReasons`, `authorizer`, `references` (may return `403` `usage_billing_required`)\n• `GET /v1/branded-calling/identities`, `GET /v1/branded-calling/identities/{id}`, `PATCH /v1/branded-calling/identities/{id}`, `DELETE /v1/branded-calling/identities/{id}`\nIdentity `status` values: `requested`, `changes_requested`, `rejected`, `pending_email_verification`, `in_review`, `verified`, `suspended`, `expired`, `permanently_rejected`\n\nEmail verification flow:\n• `POST /v1/branded-calling/identities/{id}/verify-email` (resend code)\n• `POST /v1/branded-calling/identities/{id}/verify-email/confirm` with `code` (6 digits)\n\nNumbers on an identity:\n• `GET /v1/branded-calling/identities/{id}/numbers`\n• `POST /v1/branded-calling/identities/{id}/numbers` with `phoneNumberIds` (1–15) and `signature.imageBase64`\n• `DELETE /v1/branded-calling/identities/{id}/numbers` with `phoneNumbers` (1–100)\nNumber `status` values: `submitted`, `in_review`, `verified`, `unsuccessful`, `suspended`, `expired`, `permanently_rejected`\n\nPre-approved call reasons:\n• `GET /v1/branded-calling/call-reasons` (matching these sets `callReasonsPreApproved=true`)","platforms":["telephony"],"url":"https://docs.zernio.com/changelog#1790597094805"},{"id":"1790592340181","date":"2026-09-28T10:45:40.181Z","type":"new_feature","message":"Google Ads recommendations are now available via new endpoints to list, apply, and dismiss optimization suggestions.\n\nList recommendations with `GET /v1/ads/recommendations` using `accountId` (required), optional `adAccountId` (or deprecated `customerId`), plus filters `campaignId` and `types`.\n\nApply up to 100 recommendations with `POST /v1/ads/recommendations/apply` (partial-failure). Each item requires `resourceName` and optional `parameters` (Google ApplyRecommendationOperation shape); omitting `parameters` applies Google’s suggested values. Note: applying is not reversible or idempotent.\n\nDismiss up to 100 recommendations with `POST /v1/ads/recommendations/dismiss` using `resourceNames` (partial-failure).\n\nRecommendation results return `status`: `applied` | `dismissed` | `failed` (with `error` when failed).","platforms":["google-ads","ads"],"url":"https://docs.zernio.com/changelog#1790592340181"},{"id":"1790591255870","date":"2026-09-28T10:27:35.870Z","type":"new_feature","message":"Webhooks now support ads sync health events: `account.ads.sync_failed` and `account.ads.sync_recovered`.\n\nUse these to detect when an ad account’s metrics may be stale (no successful sync for 24h / retry cap) and when syncing resumes.\n\nSubscribe via `POST /v1/webhooks/settings` or `PUT /v1/webhooks/settings` by adding to `events`:\n• `account.ads.sync_failed`\n• `account.ads.sync_recovered`\n\nPayloads include `account`, `adAccount`, `sync`, and `timestamp`. For `account.ads.sync_failed`, `sync.errorCategory` is one of `ad_account_not_listed` | `sync_error` | `stale`.","platforms":["ads","analytics","webhooks"],"url":"https://docs.zernio.com/changelog#1790591255870"},{"id":"1790589313817","date":"2026-09-28T09:55:13.817Z","type":"new_feature","message":"New endpoint: `POST /v1/feedback` to submit structured feedback (bugs, missing features, docs gaps) directly to the Zernio team.\n\nInclude context like `endpoint` and `requestId` (`x-request-id` from the failing call) to help us trace issues.\n\nRequest body:\n• `type`: `bug` | `missing_feature` | `docs` | `other`\n• `summary` (required, dedup key)\n• Optional: `details`, `endpoint`, `requestId`, `expected`, `actual`, `agent` (`name`, `model`)\n\nBehavior:\n• Submitting the same `summary` within 24h is idempotent: returns `200` with `duplicate: true` (otherwise `201`)\n• Rate limit: max 20 submissions per user per 24h (`429`)","platforms":["general"],"url":"https://docs.zernio.com/changelog#1790589313817"},{"id":"1790588609611","date":"2026-09-28T09:43:29.611Z","type":"new_feature","message":"GET `/v1/ads/tree` now supports **searching** the campaign tree via a new `search` query parameter.\n\nThis lets you filter the returned tree by campaign/ad set/ad name (case-insensitive substring), or by exact platform IDs, without paging through the full tree; matching also affects `pagination.total`.\n\nUse `search` (max 500 chars):\n• Substring match on names (campaign/ad set/ad)\n• Exact match on platform `campaignId` / `adsetId` / `adId`\n\nBehavior:\n• If a campaign name matches, it returns with all its ad sets and ads\n• If an ad set or ad matches, only that matching branch is returned","platforms":["ads"],"url":"https://docs.zernio.com/changelog#1790588609611"},{"id":"1790543870774","date":"2026-09-27T21:17:50.774Z","type":"breaking_change","message":"Google Demand Gen now has an additional constraint when adding an ad group to an existing campaign via `POST /v1/ads/create` with `campaignType`=`demand_gen` and `existingCampaignId`.\n\nIf the target Demand Gen campaign was migrated from Discovery and still targets locations/languages at the campaign level, sending geo or language fields on the new ad group will return `400`. In that case, the new ad group inherits the campaign’s existing targeting.\n\nWhen using `existingCampaignId`, avoid sending:\n• `countries`, `regions`, `cities`, `zips`, `metros`\n• `languages`\n\nApplies only to `campaignType`=`demand_gen` with `existingCampaignId`.","platforms":["google-ads","ads"],"url":"https://docs.zernio.com/changelog#1790543870774"},{"id":"1790543137021","date":"2026-09-27T21:05:37.021Z","type":"new_feature","message":"Google Demand Gen creation via `POST /v1/ads/create` now supports growing existing campaigns: add a new ad group to an existing Demand Gen campaign, or add a new ad to an existing Demand Gen ad group.\n\nUse `campaignType` = `demand_gen` with:\n• `existingCampaignId` to create a PAUSED ad group (with its own geo/languages/channels/audience) + its ad in one atomic request\n• `adSetId` to create a PAUSED ad in that existing ad group (creative only)\n\nNotes:\n• Both paths support `validateOnly`\n• In these “join existing” paths, budget/bidding/schedule fields are not accepted (400) because they’re owned by the existing campaign/ad group","platforms":["google-ads","ads"],"url":"https://docs.zernio.com/changelog#1790543137021"},{"id":"1790542616334","date":"2026-09-27T20:56:56.334Z","type":"improvement","message":"PUT `/v1/ads/campaigns/{campaignId}/targeting` now handles **Demand Gen** campaigns where Google requires `locations` and `languages` to be set on ad groups (not the campaign).\n\nIf the campaign has exactly one ad group, Zernio writes `locations`/`languages` there and the response includes `adGroupId` (campaign-level `locations`/`languages` read back may remain empty).\n\nWith multiple ad groups, the call returns `400` listing the ad groups; update targeting per ad group via `PUT /v1/ads/{adId}` `targeting` on an ad in that ad group. `devices` and `locationTargetingType` remain campaign-level (`presence` | `presence_or_interest`).","platforms":["google-ads","ads"],"url":"https://docs.zernio.com/changelog#1790542616334"},{"id":"1790542247640","date":"2026-09-27T20:50:47.640Z","type":"new_feature","message":"Google Demand Gen ads can now be updated via `PUT /v1/ads/{adId}` using the new `demandGen` payload.\n\nThis enables atomic updates to Demand Gen creative and (where supported) ad group settings like channels/audience, instead of requiring a new ad.\n\nUse `demandGen` with key fields like:\n• `headlines`, `descriptions`, `images`, `youtubeVideoIds`\n• `channels` (replaces ad group channel controls): `youtube_in_stream`, `youtube_in_feed`, `youtube_shorts`, `discover`, `gmail`, `display`\n• `audience` (build + attach) or `audienceId` (attach existing)\n\n`demandGen` is _Google Demand Gen only_; sending it on other platforms/channels returns `422`.","platforms":["youtube","google-ads","ads"],"url":"https://docs.zernio.com/changelog#1790542247640"},{"id":"1790541459693","date":"2026-09-27T20:37:39.693Z","type":"new_feature","message":"Google Demand Gen now supports creating **carousel ads** via `POST /v1/ads/create` when `campaignType` is `demand_gen`.\n\nYou can now create a Demand Gen campaign + ad group + carousel ad atomically by sending `demandGen.carouselCards` (2–10 cards), where each card supplies its own image and optional destination.\n\nUse:\n• `campaignType`: `demand_gen`\n• `demandGen.carouselCards`: 2–10 items\n• Each card: `headline` + `images` (one of `landscape`/`square`/`portrait`), optional `finalUrl` and `callToAction`\n\nCarousel-specific constraints:\n• `demandGen.headlines` must be exactly 1\n• `demandGen.descriptions` must be exactly 1\n• `demandGen.images` still requires `logo` (carousel card images live on each card)","platforms":["google-ads","ads"],"url":"https://docs.zernio.com/changelog#1790541459693"},{"id":"1790539107861","date":"2026-09-27T19:58:27.861Z","type":"improvement","message":"`GET /v1/ads/accounts/hierarchy` now returns **directly accessible** Google Ads customers via a new top-level field `directCustomers`.\n\nThis lets you list every customer the connected Google user can access directly (even if it also appears nested in `roots`) and see any unanswered manager invitations.\n\nNew response fields:\n• `directCustomers[]`\n• `directCustomers[].customerId`\n• `directCustomers[].manager`\n• `directCustomers[].pendingInvitations[]`\n• `directCustomers[].pendingInvitations[].managerCustomerId`\n• `directCustomers[].pendingInvitations[].managerLinkId`","platforms":["google-ads","ads"],"url":"https://docs.zernio.com/changelog#1790539107861"},{"id":"1790538736631","date":"2026-09-27T19:52:16.631Z","type":"new_feature","message":"POST `/v1/ads/create` now supports Google Ads **Demand Gen** campaigns via `campaignType` = `demand_gen`.\n\nThis lets you create a PAUSED Demand Gen campaign + 1 ad group + 1 ad atomically (multi-asset image ad, or video responsive ad when `demandGen.youtubeVideoIds` is provided). `validateOnly: true` is supported for Google `demand_gen`.\n\nUse:\n• `campaignType`: `display` | `search` | `pmax` | `demand_gen`\n• `demandGen` (required for `demand_gen`): `finalUrl`, `businessName`, `headlines`, `descriptions`, `images`\n• Optional channel controls: `demandGen.channels` = `youtube_in_stream` | `youtube_in_feed` | `youtube_shorts` | `discover` | `gmail` | `display`\n• Optional audience: `demandGen.audience` (or attach existing via `demandGen.audienceId`)\n\nNote: editing creative/targeting on an existing Demand Gen ad is not supported yet (returns `501`).","platforms":["youtube","google-ads","ads"],"url":"https://docs.zernio.com/changelog#1790538736631"},{"id":"1790538315422","date":"2026-09-27T19:45:15.422Z","type":"breaking_change","message":"Google Ads conversion goals are now manageable via new endpoints, and the Google Ads recommendations API has been removed.\n\nNew conversion goals endpoints:\n• List/update account-default goals: `GET /v1/ads/conversions/goals`, `PATCH /v1/ads/conversions/goals`\n  - Use `accountId` and optionally `adAccountId` (deprecated alias: `customerId`)\n  - Update payload uses `goals[]` items: `{ category, origin, biddable }`\n• Set a conversion action as primary/secondary: `PATCH /v1/ads/conversions/actions/{actionId}` with `primaryForGoal`\n• Manage custom conversion goals: `GET /v1/ads/conversions/custom-goals`, `POST /v1/ads/conversions/custom-goals`, `PATCH /v1/ads/conversions/custom-goals/{goalId}`, `DELETE /v1/ads/conversions/custom-goals/{goalId}`\n  - Create/update with `name` and `conversionActionIds[]`\n• Get/update campaign conversion goals: `GET /v1/ads/campaigns/{campaignId}/conversion-goals`, `PATCH /v1/ads/campaigns/{campaignId}/conversion-goals`\n  - Set `goalConfigLevel`: `CUSTOMER` | `CAMPAIGN`\n  - Optionally set/clear `customConversionGoalId`\n\nRemoved (breaking):\n• `GET /v1/ads/recommendations`\n• `POST /v1/ads/recommendations/apply`\n• `POST /v1/ads/recommendations/dismiss`\n\nAlso added to `ConversionAction`: `origin`, `primaryForGoal`.","platforms":["google-ads","ads"],"url":"https://docs.zernio.com/changelog#1790538315422"},{"id":"1790537625615","date":"2026-09-27T19:33:45.615Z","type":"new_feature","message":"Google Ads now supports **recommendations** (list/apply/dismiss) and **manager account (MCC) hierarchy + link management**.\n\nYou can fetch Google’s optimization recommendations (with estimated impact + type-specific payload), then apply or dismiss them.\n• List: `GET /v1/ads/recommendations` with `accountId`, optional `adAccountId` (or deprecated `customerId`), optional `campaignId`, `types`\n• Apply (up to 100, partial-failure): `POST /v1/ads/recommendations/apply` with `accountId`, optional `adAccountId`, `recommendations[].resourceName`, optional `recommendations[].parameters`\n• Dismiss (up to 100, partial-failure): `POST /v1/ads/recommendations/dismiss` with `accountId`, optional `adAccountId`, `resourceNames[]`\n\nYou can also read the live MCC/client tree and manage manager links (invite + accept/decline/cancel/unlink).\n• Hierarchy: `GET /v1/ads/accounts/hierarchy` with `accountId`, optional `adAccountId` (or deprecated `customerId`)\n• Invite link: `POST /v1/ads/accounts/manager-links` with `accountId`, `managerCustomerId`, `clientCustomerId`, optional `validateOnly`\n• Update link: `PATCH /v1/ads/accounts/manager-links` with `accountId`, `managerCustomerId`, `clientCustomerId`, `managerLinkId`, `action` (`accept`/`decline`/`cancel`/`unlink`), optional `validateOnly`","platforms":["google-ads","ads"],"url":"https://docs.zernio.com/changelog#1790537625615"},{"id":"1790536971333","date":"2026-09-27T19:22:51.333Z","type":"new_feature","message":"Google Ads label management is now supported via `/v1/ads/labels`.\n\nYou can create, update, delete, and attach/detach labels to campaigns, ad groups, ads, and keywords.\n\nNew endpoints:\n• `POST /v1/ads/labels` (create) with `accountId`, `name`, optional `adAccountId` (digits), `backgroundColor` (`#RRGGBB`), `description`\n• `PATCH /v1/ads/labels/{labelId}` (partial update) with `accountId` (+ optional fields)\n• `DELETE /v1/ads/labels/{labelId}` with query `accountId` (+ optional `adAccountId`)\n• `POST /v1/ads/labels/{labelId}/assignments` (attach) and `DELETE /v1/ads/labels/{labelId}/assignments` (detach) with `campaignIds`, `adSetIds` (ad group ids), `adIds` (`{adGroupId}~{adId}`), `keywordIds` (`{adGroupId}~{criterionId}`)\n\n`GET /v1/ads/labels` now supports:\n• Meta: `adAccountId=act_&lt;n&gt;` (existing paging via `limit`/`after`)\n• Google Ads: `adAccountId` as numeric customer id (optional if single customer). Response may include `cachedAt` and `stale`.\n\nFor Google Ads, `customerId` is now deprecated as an alias of `adAccountId`.","platforms":["meta-ads","google-ads","ads"],"url":"https://docs.zernio.com/changelog#1790536971333"},{"id":"1790536553208","date":"2026-09-27T19:15:53.208Z","type":"new_feature","message":"Performance Max campaigns now support full **asset group** management.\n\nYou can create, fetch, update, and remove asset groups, plus manage linked assets and (for retail) replace the listing-group tree.\n\nNew endpoints:\n• `POST /v1/ads/campaigns/{campaignId}/asset-groups` (create; `status`: `ENABLED` | `PAUSED`, default `PAUSED`; supports `validateOnly`)\n• `GET /v1/ads/campaigns/{campaignId}/asset-groups/{assetGroupId}` (returns `assetGroup` plus `cachedAt` and `stale`)\n• `PATCH /v1/ads/campaigns/{campaignId}/asset-groups/{assetGroupId}` (partial update; `validateOnly`; `path1`/`path2` can be set to `null` to clear)\n• `DELETE /v1/ads/campaigns/{campaignId}/asset-groups/{assetGroupId}` (removes on Google; optional `validateOnly` query)\n• `POST /v1/ads/campaigns/{campaignId}/asset-groups/{assetGroupId}/assets` (atomic `link`/`unlink`; supports `validateOnly`)\n• `PUT /v1/ads/campaigns/{campaignId}/asset-groups/{assetGroupId}/listing-group-filters` (replace retail listing-group `tree`; supports `validateOnly`)","platforms":["google-ads","ads","commerce"],"url":"https://docs.zernio.com/changelog#1790536553208"},{"id":"1790535574776","date":"2026-09-27T18:59:34.776Z","type":"improvement","message":"`PATCH /v1/ads/keywords/{keywordId}` and `DELETE /v1/ads/keywords/{keywordId}` now accept either Zernio’s keyword `id` or Google’s native composite id `{adSetId}~{platformCriterionId}` (from the tail of `resourceName`).\n\nThis lets you pause/enable or remove a keyword when you only have Google identifiers; a bare `platformCriterionId` is rejected because it’s only unique within an ad group.\n\nUse `keywordId` as one of:\n• `{zernioKeywordId}`\n• `{adSetId}~{platformCriterionId}`\n\n`AdKeyword` now includes `platformCriterionId` and `resourceName`.\n\n`DELETE` response now also returns: `platformCriterionId`, `resourceName`, `campaignId`, `adSetId` (alongside `keywordId`).","platforms":["google-ads","ads"],"url":"https://docs.zernio.com/changelog#1790535574776"},{"id":"1790468826324","date":"2026-09-27T00:27:06.324Z","type":"improvement","message":"Webhook post events now include structured failure info via `errorCategory` and `errorSource` on platform targets.\n\nThis makes it easier to programmatically classify failures and decide who needs to act (user vs platform vs Zernio), without parsing `error` text.\n\nAdded fields:\n• `WebhookPayloadPost.post.platforms[].errorCategory` = `auth_expired` | `user_content` | `user_abuse` | `account_issue` | `platform_rejected` | `platform_error` | `platform_rate_limit` | `quota_exhausted` | `system_error` | `unknown`\n• `WebhookPayloadPost.post.platforms[].errorSource` = `user` | `platform` | `system`\n\nAlso added for per-platform terminal webhooks:\n• `WebhookPayloadPostPlatform.post.platforms[].errorCategory`, `WebhookPayloadPostPlatform.post.platforms[].errorSource`\n• `WebhookPayloadPostPlatform.platform.errorCategory`, `WebhookPayloadPostPlatform.platform.errorSource` (present on `post.platform.failed`)","platforms":["webhooks"],"url":"https://docs.zernio.com/changelog#1790468826324"},{"id":"1790443382468","date":"2026-09-26T17:23:02.468Z","type":"improvement","message":"OpenAI Ads **conversions** campaigns can now explicitly choose the optimization event via `promotedObject.customEventType` on `POST /v1/ads/create` (and `POST /v1/ads/campaigns`).\n\nUse a value from `GET /v1/accounts/{accountId}/conversion-destinations` → `conversionEvents[]`, provided as the event’s `id` (exact), `eventType`, or `name` (case-insensitive). The event must be `optimizable: true`.\n\nIf `promotedObject.customEventType` is omitted, Zernio uses the most recently created optimizable event. If none exist (or the event is unknown/archived/custom), the create now returns `400`.","platforms":["ads"],"url":"https://docs.zernio.com/changelog#1790443382468"},{"id":"1790431942681","date":"2026-09-26T14:12:22.681Z","type":"new_feature","message":"ChatGPT (OpenAI) Ads now supports **campaign validation (dry-run)** on `POST /v1/ads/campaigns` via `validateOnly`.\n\nThis lets you validate `budgetAmount`/`budgetType`, `goal`, and conversion-event readiness without creating a campaign (returns `200` with `status` = `VALIDATED`).\n\nUse:\n• `validateOnly: true` (Meta + ChatGPT/OpenAI)\n• `goal` includes `conversions` (OpenAI requires an active standard conversion event)\n\nOpenAI conversion destinations now include their wired conversion events on `GET /v1/accounts/{accountId}/conversion-destinations`.\n\nRead:\n• `destinations[].conversionEvents[]` with `optimizable` (used to pick the optimization event for `goal: conversions`)\n\nChatGPT (OpenAI) Ads tracking now supports URL parameters:\n• `tracking.urlTags` (in `AdTracking`) appends to `linkUrl` via OpenAI’s query-string template; OpenAI macros like `{campaign_id}`, `{ad_group_id}`, `{ad_id}`, `{oppref}` are supported.\n\nAlso note: OpenAI budget behavior is now documented as accepting `budgetType` = `daily` or `lifetime` on create, but OpenAI does not allow switching from a daily cap back to lifetime later (returns `422`).","platforms":["ads"],"url":"https://docs.zernio.com/changelog#1790431942681"},{"id":"1790428088262","date":"2026-09-26T13:08:08.262Z","type":"improvement","message":"Google Ads queries on `GET /v1/ads/insights` now run against Google Ads API **v25**, so GAQL should be written against the v25 field reference.\n\nFor backward compatibility, legacy GAQL fields `campaign.start_date` / `campaign.end_date` (removed by Google) are rewritten to `campaign.start_date_time` / `campaign.end_date_time`. Responses still return `campaign.startDate` / `campaign.endDate` as `YYYY-MM-DD`.\n\nIn `WHERE`, only these operators are translated for `'YYYY-MM-DD'` literals: `=`, `&lt;`, `&lt;=`, `&gt;`, `&gt;=`, `BETWEEN`, `IS NULL`, `IS NOT NULL`. Other forms will return Google’s `400`. New code should select the `_date_time` fields directly.","platforms":["google-ads","ads","analytics"],"url":"https://docs.zernio.com/changelog#1790428088262"},{"id":"1790425930372","date":"2026-09-26T12:32:10.372Z","type":"breaking_change","message":"Google Local Services Ads leads no longer include the consumer email in lead contact details.\n\nIn `GET /v1/ads/local-services/leads`, `contact.email` is now always `null` (Google Ads API v25 stopped returning it), so don’t rely on it being populated.\n\nKey field: `data[].contact.email` (always `null`)","platforms":["google-ads","ads"],"url":"https://docs.zernio.com/changelog#1790425930372"},{"id":"1790414397404","date":"2026-09-26T09:19:57.404Z","type":"new_feature","message":"Reaction webhooks (`event` = `reaction.received`) now include **TikTok**.\n\nFor TikTok AI emoji reactions, `reaction.emoji` may be an empty string (TikTok does not send unicode for these).\n\nKey fields: `reaction.action` (`added` | `removed`), `reaction.platformMessageId`, `reaction.sender.id`, `reaction.reactedAt`.","platforms":["tiktok","webhooks"],"url":"https://docs.zernio.com/changelog#1790414397404"},{"id":"1790408681636","date":"2026-09-26T07:44:41.636Z","type":"improvement","message":"`GET /v1/accounts/health` now supports additional `platform` filter values.\n\nYou can filter account health results for newly supported platforms (including ads and CMS integrations) using `platform`.\n\nNew `platform` enum values:\n• `shopify`, `wordpress`\n• `linkedinads`, `metaads`, `pinterestads`, `tiktokads`, `xads`, `googleads`, `openaiads`","platforms":["tiktok","x","linkedin","pinterest","meta-ads","google-ads","tiktok-ads","linkedin-ads","pinterest-ads","x-ads","ads","commerce"],"url":"https://docs.zernio.com/changelog#1790408681636"},{"id":"1790408298804","date":"2026-09-26T07:38:18.804Z","type":"improvement","message":"`GET /v1/usage` metering now reports billed spend for additional product families: `imessage`, `messages`, `verify`, and `ads`.\n\nIf you consume metering data, you can now see these costs broken out in the same `days`/`totals` series and in invoice `lineItems`.\n\nNew/expanded fields to handle:\n• `UsageMetering.days[*]`: `imessage`, `messages`, `verify`, `ads`\n• `UsageMetering.totals`: `imessage`, `messages`, `verify`, `ads`\n• `UsageMetering.lineItems[*].product` enum now includes: `imessage`, `messages`, `verify`, `ads`\n• `UsageAttributionSlice.byProduct` now includes: `imessage`, `messages`, `ads` (and `verify` remains)\n\nIf your client validates enums or assumes a fixed set of product keys, update it to accept these new values.","platforms":["imessage","ads","telephony"],"url":"https://docs.zernio.com/changelog#1790408298804"},{"id":"1790407544374","date":"2026-09-26T07:25:44.374Z","type":"improvement","message":"Ad creation endpoints now support **idempotent retries** via `Idempotency-Key` on:\n• `POST /v1/ads/messaging`\n• `POST /v1/ads/call`\n• `POST /v1/ads/ctwa` (deprecated)\n\nThese endpoints are not idempotent at the Meta platform level (a blind retry can create duplicate campaigns/ad sets/ads). With `Idempotency-Key`, retries replay the original response instead of creating duplicates.\n\nUse `Idempotency-Key` (any unique string per create attempt). On replay, responses include `Idempotent-Replayed: true`.\n\nError behavior:\n• Reusing the same key with a different request body returns `422`\n• If the first request with that key is still in-flight, retries return `409` (retry after backoff)\n\nKeys are scoped to your credential and expire after 24h.","platforms":["whatsapp","meta-ads","ads"],"url":"https://docs.zernio.com/changelog#1790407544374"},{"id":"1790407204497","date":"2026-09-26T07:20:04.497Z","type":"improvement","message":"Google boosts now support creating ads as **PAUSED** via `POST /v1/ads/boost` using `status`.\n\nThis lets you create a boost without immediately spending, then activate it later.\n\nUse `status`:\n• `ACTIVE`\n• `PAUSED`\n\nOn Google, `PAUSED` is applied at the campaign level (ad group + ad are created enabled). To go live, activate the campaign with `PUT /v1/ads/campaigns/{campaignId}/status` and `active`.","platforms":["google-ads","ads"],"url":"https://docs.zernio.com/changelog#1790407204497"},{"id":"1790406855024","date":"2026-09-26T07:14:15.024Z","type":"breaking_change","message":"`validateOnly` is now explicitly limited by platform on two endpoints.\n\nOn `POST /v1/ads/campaigns`, sending `validateOnly: true` is Meta-only; any other platform now returns `501` with code `feature_not_available`.\n\nOn `POST /v1/ads/create`, `validateOnly: true` is supported only for `Meta` and `Google` when `campaignType: pmax`; any other platform (or Google `campaignType` != `pmax`) returns `501` `feature_not_available`.\n\nKey fields:\n• `validateOnly`\n• `campaignType` = `display` | `search` | `pmax`","platforms":["meta-ads","google-ads","ads"],"url":"https://docs.zernio.com/changelog#1790406855024"},{"id":"1790404956568","date":"2026-09-26T06:42:36.568Z","type":"new_feature","message":"OpenAI Ads campaigns now support updating the spend cap as **daily** or **lifetime** via `PUT /v1/ads/campaigns/{campaignId}`.\n\nFor OpenAI Ads, `budget.type` can be `daily` or `lifetime`, and sending a new budget replaces whichever cap the campaign previously had.\n\nUse:\n• `budget.amount` (whole currency units)\n• `budget.type`: `daily` | `lifetime`\n\nOpenAI Ads enforces a minimum `budget.amount` of 1; values below return `422`.","platforms":["ads"],"url":"https://docs.zernio.com/changelog#1790404956568"},{"id":"1790361771635","date":"2026-09-25T18:42:51.635Z","type":"breaking_change","message":"The `numbers` items returned by `GET /v1/phone-numbers` (and the deprecated alias `GET /v1/whatsapp/phone-numbers`) now use a new `OwnedPhoneNumber` schema with expanded telephony/WhatsApp/regulatory details.\n\nThis adds new fields you can use to track provisioning, verification, SMS/10DLC readiness, calling configuration, and regulatory review state in a single response.\n\nNotable additions/changes:\n• `profileId` is now `oneOf` `{ _id, name }` or a `string`\n• Regulatory review fields: `regulatoryReviewComment`, `regulatoryInfoStatus`, `verifyUrl`, `onfidoOpened`\n• Meta verification fields: `metaVerificationStatus` (`pending`/`code_requested`/`verified`/`expired`), `metaVerifiedAt`, `metaVerificationExpiresAt`\n• Ownership/linking fields: `socialAccountId`, `ownerAccountId`\n• SMS capability/registration fields: `smsCapable`, `mmsCapable`, `domesticOnly`, `smsRegistrationPending`, `smsSendApproved`, `smsBrandName`\n• Calling/PSTN config fields: `pstnVoiceEnabled`, `pstnForwardTo`, `voicemailEnabled`, `businessHoursEnabled`, `ivrEnabled`, plus related settings\n\nIf you deserialize strictly, update your models to accept the new `profileId` shape and ignore unknown fields.","platforms":["whatsapp","telephony"],"url":"https://docs.zernio.com/changelog#1790361771635"},{"id":"1790358674860","date":"2026-09-25T17:51:14.860Z","type":"new_feature","message":"New endpoint `GET /v1/phone-numbers/port-in/claims/{claimId}` resolves a portability `claimId` into the claimed `phoneNumber` (and `countryCode`). This supports keyless portability checks that return a claim you can later resolve (expires after 7 days).\n\n`POST /v1/phone-numbers/port-in/check` now supports keyless checks (no API key) for `phoneNumbers` with `maxItems=1`, and can optionally return claim links.\n\nRequest:\n• `claimLinks` (boolean) — when `true`, adds `claimId` and `claimUrl` to portable results even with an API key\n\nNew response fields per result:\n• `carrierName`\n• `messagingCapable`\n• `claimId` (keyless or `claimLinks=true` only)\n• `claimUrl` (keyless or `claimLinks=true` only)\n\nKeyless-only error:\n• `429` when per-IP/shared daily budget limits are hit (use an API key to avoid these limits).","platforms":["telephony"],"url":"https://docs.zernio.com/changelog#1790358674860"},{"id":"1790357807184","date":"2026-09-25T17:36:47.184Z","type":"improvement","message":"OAuth connect callbacks may now return `error=code_already_redeemed` on `GET /v1/connect/{platform}`.\n\nThis means the same authorization `code` was received more than once and an earlier request already processed it; it’s not a failure.\n\nHandle by treating it as idempotent: check `GET /v1/accounts` or wait for the `account.connected` webhook (instead of retrying the OAuth exchange).","platforms":["webhooks"],"url":"https://docs.zernio.com/changelog#1790357807184"},{"id":"1790352536063","date":"2026-09-25T16:08:56.063Z","type":"new_feature","message":"Webhooks now support a new SMS event: `sms.registration.status_updated`.\n\nSubscribe to it via `events` when creating/updating a webhook with `POST /v1/webhooks/settings` or `PUT /v1/webhooks/settings`.\n\nNew event enum value:\n• `sms.registration.status_updated`\n\n`GET /v1/sms/registrations` now returns additional status-tracking fields to help you understand review/rejection state and timelines:\n• `lastResponseAt`\n• `previouslyRejected`\n• `lastRejectedAt`\n• `rejectedBeforeSubmission`\n• `otpExpired`\n\n`POST /v1/sms/registrations/{id}/respond` can now also be used for some `rejected` registrations (when `reviewRequest` is set), and the response `status` may be `requested` or `rejected`.","platforms":["telephony","webhooks"],"url":"https://docs.zernio.com/changelog#1790352536063"},{"id":"1790352311468","date":"2026-09-25T16:05:11.468Z","type":"new_feature","message":"Webhooks now support a new event: `sms.registration.action_required`.\n\nThis lets you receive real-time notifications when an SMS carrier registration needs customer action.\n\nAdd it to `events` when creating/updating a webhook via `POST /v1/webhooks/settings` or `PUT /v1/webhooks/settings`.\n\nSMS registration APIs now expose reviewer change requests in more detail.\n\n`GET /v1/sms/registrations` and `GET /v1/sms/registrations/{id}` include:\n• `adminReviewNote`\n• `reviewRequest` (schema: `SmsRegistrationReviewRequest`)\n\nReplying to change requests via `POST /v1/sms/registrations/{id}/respond` now supports point-by-point answers:\n• `requestId`\n• `answers[]` with `pointId` and one of `text`, `url`, `files`\n\n`reviewRequest.points[].answer` can be: `text`, `link`, `file`, `link_or_file`.","platforms":["telephony","webhooks"],"url":"https://docs.zernio.com/changelog#1790352311468"},{"id":"1790348934613","date":"2026-09-25T15:08:54.613Z","type":"improvement","message":"YouTube connect can now fail with `error=no_youtube_channel` on `GET /v1/connect/{platform}`.\n\nThis means Google authorization succeeded, but the selected Google account has no YouTube channel we can connect (often due to choosing a personal identity instead of a Brand Account, insufficient Brand Account role, or no channel yet).\n\nHandle the redirect params:\n• `platform=youtube`\n• `error=no_youtube_channel`\n• `is_user_fixable=true`\n• `error_message` (present)","platforms":["youtube"],"url":"https://docs.zernio.com/changelog#1790348934613"},{"id":"1790348244157","date":"2026-09-25T14:57:24.157Z","type":"new_feature","message":"Google Ads campaigns now support **location targeting mode** via `locationTargetingType`.\n\nThis lets you choose whether location targeting reaches only people physically in the targeted locations, or also people showing interest in them.\n\nSet `locationTargetingType` to:\n• `presence`\n• `presence_or_interest`\n\nAvailable in:\n• `POST /v1/ads/create` (Google only; written on the new campaign)\n• `POST /v1/ads/boost` (Google only; written on the campaign the boost creates)\n• `GET /v1/ads/campaigns/{campaignId}/targeting` (now returns `locationTargetingType`)\n• `PUT /v1/ads/campaigns/{campaignId}/targeting` (can update `locationTargetingType`)\n\nNote: on `POST /v1/ads/create`, setting `locationTargetingType` is only allowed when creating a new Google campaign (requests that join an existing campaign return 400).","platforms":["google-ads","ads"],"url":"https://docs.zernio.com/changelog#1790348244157"},{"id":"1790345461491","date":"2026-09-25T14:11:01.491Z","type":"new_feature","message":"GET `/v1/inbox/comments/{postId}` now supports fetching replies via `commentId` (Facebook, Instagram, Reddit, TikTok).\n\nPass `commentId` to return a specific comment’s replies instead of the post’s top-level comments (paged by `limit`/`cursor`; Reddit returns the focused thread).\n\nUse:\n• `commentId` — get replies to a specific comment\n\nResponse change (Facebook/Instagram when `commentId` is set):\n• `comment` — the requested comment\n• `comments[]` — that comment’s replies\n\nFacebook still accepts a comment ID as `postId` for backwards compatibility, but prefer `commentId` (works on Instagram too).","platforms":["instagram","tiktok","reddit","inbox"],"url":"https://docs.zernio.com/changelog#1790345461491"},{"id":"1790344952019","date":"2026-09-25T14:02:32.019Z","type":"new_feature","message":"Phone number purchase now supports a **keyless claim flow**, and there’s a new endpoint to resolve claims.\n\nYou can let users browse numbers without an API key (masked results + signup claim links), then resolve or purchase using a `claimId`.\n\nNew endpoint:\n• `GET /v1/phone-numbers/claims/{claimId}` returns the claimed selection (`country`, `type`, optional `area`, optional `phoneNumber`).\n\nKeyless search updates:\n• `GET /v1/phone-numbers/available` works without an API key and can return `maskedNumber`, `claimId`, `claimUrl` (plus top-level `masked`, optional `near`, and an any-number `claimId`/`claimUrl`)\n• New query params: `masked` (force keyless shape even with a key) and `country=auto` (keyless only)\n\nPurchase updates:\n• `POST /v1/phone-numbers/purchase` adds `claimId` (keyless only; ignored when an API key is sent)\n• `401` responses now include `details.claimId` and `details.claimUrl` for the claim/signup handoff\n\nCatalog access:\n• `GET /v1/phone-numbers/countries` no longer requires an API key (public catalog)\n• `GET /v1/phone-numbers/availability` without a key returns cached data only and a reduced shape (for building an area picker pre-signup)","platforms":["telephony"],"url":"https://docs.zernio.com/changelog#1790344952019"},{"id":"1790344840242","date":"2026-09-25T14:00:40.242Z","type":"improvement","message":"`POST /v1/voice/calls/{id}/end` can now hang up an outbound call that is still ringing (not yet answered).\n\nThis lets you cancel a dialing call immediately so the callee stops ringing right away; the endpoint remains idempotent.\n\nUse: `id` (path)\nReturns: `success`, `callId`, `status` (e.g., `ending`)","platforms":["telephony"],"url":"https://docs.zernio.com/changelog#1790344840242"},{"id":"1790344320144","date":"2026-09-25T13:52:00.144Z","type":"new_feature","message":"GET `/v1/analytics` now includes **Facebook Page story analytics**.\n\nStories published through Zernio or natively are collected while live and returned like other posts (with `platformPostUrl` pointing to the story). Metrics refresh about hourly during the 24h lifetime, then remain at the final reading.\n\nMapping for stories:\n• `impressions` = story impressions\n• `reach` = story reach\n• `views` = story media views\n• `likes` = story reactions\n• `comments` = story replies\n• `shares` = story shares\n\nNote: `clicks` is always `0` for Facebook Page stories (Meta doesn’t expose link-clicks for stories).","platforms":["facebook","analytics"],"url":"https://docs.zernio.com/changelog#1790344320144"},{"id":"1790342272636","date":"2026-09-25T13:17:52.636Z","type":"improvement","message":"Google Ads OAuth can now fail with **missing Google permissions** when a user unticks required scopes on Google’s consent screen.\n\nIn this case, the callback redirects with `error=missing_google_permissions`, `platform=googleads`, `is_user_fixable=true`, and (when available) `missing_scopes` (comma-separated).\n\nHandle this on:\n• `GET /v1/connect/{platform}` (now explicitly includes Google Ads)\n• `GET /v1/connect/{platform}/ads` (Google Ads reconnect/health can require re-auth when the permission is missing)","platforms":["google-ads","ads"],"url":"https://docs.zernio.com/changelog#1790342272636"},{"id":"1790339366779","date":"2026-09-25T12:29:26.779Z","type":"improvement","message":"Google Ads ads-connection behavior changed on `GET /v1/connect/{platform}/ads` when `platform=googleads`.\n\nIf a `googleads` ads account already exists, the endpoint now returns `alreadyConnected: true` only when at least one customer account has been discovered. If the existing connection has `metadata.googleAdsCustomerIds` empty (or is flagged `needsReconnection`), it will return an `authUrl` instead so you can re-run OAuth and re-discover customers.\n\nWhen OAuth completes but no usable Google Ads customer accounts are found, the callback redirects with `error=google_ads_no_ad_accounts` and `platform=googleads` (with an `error_message` explaining why reachable customers were excluded).","platforms":["google-ads","ads"],"url":"https://docs.zernio.com/changelog#1790339366779"},{"id":"1790336243151","date":"2026-09-25T11:37:23.151Z","type":"improvement","message":"POST `/v1/inbox/conversations/{conversationId}/messages` now uses `429` for upstream platform rate limiting (in addition to WhatsApp template-definition lookup failures).\n\nWhen rate limited by the connected account’s upstream quota, responses include `code=rate_limited` and may include a `Retry-After` header (seconds). WhatsApp template lookup failures can still return `code=platform_api_error` (no message sent).\n\nHandle `429` by branching on `code`:\n• `rate_limited` — back off using `Retry-After`\n• `platform_api_error` — treat as platform rejection (WhatsApp template lookup)","platforms":["whatsapp","inbox"],"url":"https://docs.zernio.com/changelog#1790336243151"},{"id":"1790335682453","date":"2026-09-25T11:28:02.453Z","type":"breaking_change","message":"Meta Ads **business login** reconnect behavior changed on `GET /v1/connect/{platform}/ads`.\n\nReconnects with _some_ ad-account overlap are now accepted, allowing the new grant to add/drop/swap ad accounts; only zero-overlap reconnects are refused.\n\nKey behavior:\n• Use `loginMode=business` (Meta via `platform=facebook` or `platform=instagram`)\n• If the new grant has zero overlap, you’ll be redirected with `error=invalid_field_value` and `error_reason=reconnect_mismatch` (or `error=reconnect_required` when previous ad accounts can’t be read)\n• On an accepted reconnect, scope is rebuilt:\n  - If you pass `adAccountIds`, that becomes the new scope\n  - Otherwise, a stored scope keeps still-granted accounts + newly granted ones; unscoped connections follow the new grant\n\nRelevant params: `loginMode` (`classic`|`business`), `adAccountId`, `adAccountIds`.","platforms":["instagram","meta-ads","ads"],"url":"https://docs.zernio.com/changelog#1790335682453"},{"id":"1790335212185","date":"2026-09-25T11:20:12.185Z","type":"improvement","message":"GET `/v1/ads/accounts` now returns Meta-only `billingStatus` to clarify whether a missing `fundingSource` means “no payment method” vs “token can’t see billing”.\n\nUse `billingStatus` to decide whether to prompt for adding a payment method or treat billing as unknown.\n\n`billingStatus` values:\n• `ok` - Meta returned a funding source\n• `missing` - no payment method detected (ad creation can fail with Meta 100/1359188)\n• `unknown` - token/person can’t read billing fields\n\nAlso: Meta ad create failures (`POST /v1/ads/create`, `/v1/ads/boost`, `/v1/ads/ctwa`) may now include extra context in `ErrorResponse.details`: `stage`, `adAccountId`, `createdObjects` (with cleanup result), and `unconfirmedWrite` when Meta didn’t confirm a create.","platforms":["meta-ads","ads"],"url":"https://docs.zernio.com/changelog#1790335212185"},{"id":"1790333851981","date":"2026-09-25T10:57:31.981Z","type":"improvement","message":"`GET /v1/usage` and `GET /v1/usage-stats` now return `429` when the billing backend rate-limits reads (previously this could surface as `500`).\n\nHandle retries using the `Retry-After` response header (seconds) and the error code `rate_limited`.\n\n`POST /v1/connect/instagram/select-account` can now return an ads account id for ads connections.\n\nNew response fields:\n• `adsAccountId` — metaads SocialAccount ID to use with `/v1/ads` endpoints (when the flow is an ads connect)\n• `redirect_url` — may also carry `adsAccountId` on ads connect","platforms":["instagram","meta-ads","ads"],"url":"https://docs.zernio.com/changelog#1790333851981"},{"id":"1790333220468","date":"2026-09-25T10:47:00.468Z","type":"improvement","message":"OAuth connect redirects now include clarified correlation fields, including a destination-selection completion stage.\n\nUse `request_id` to correlate any redirect (success/failure) and also the `redirect_url` returned by selection endpoints (e.g. `POST /v1/connect/facebook/select-page`).\n\n`stage` can now be:\n• `authorize` - provider consent dialog returned an error/denial\n• `callback` - Zernio processed the returned code\n• `select_page` - destination-selection endpoint completed the flow","platforms":["facebook"],"url":"https://docs.zernio.com/changelog#1790333220468"},{"id":"1790332286237","date":"2026-09-25T10:31:26.237Z","type":"breaking_change","message":"OAuth connect redirects now include better **error correlation** and **provider error passthrough** on failures.\n\nError redirects from `GET /v1/connect/{platform}` may now include `request_id` and `stage` (e.g. `authorize`, `callback`), plus provider details for `oauth_denied`: `platform_error`, `platform_error_reason`, `platform_error_description`. Treat these as optional.\n\nAlso note: on error redirects, internal markers `headless`, `adsConnect`, `adsScope` are removed, and Zernio params overwrite any of yours with the same name.\n\n`POST /v1/connect/facebook/select-page` now returns `adsAccountId` when completing an ads connect (metaads) selection, and its `409` response is now the standard `ErrorResponse` with `code=reconnect_account_mismatch` (previously `code=RECONNECT_ACCOUNT_MISMATCH` in a custom body).","platforms":["facebook","meta-ads","ads"],"url":"https://docs.zernio.com/changelog#1790332286237"},{"id":"1790330505313","date":"2026-09-25T10:01:45.313Z","type":"breaking_change","message":"Targeting updates on `PUT /v1/ads/{adId}` are now treated as **partial updates** for TikTok and LinkedIn.\n\nFields you omit in `targeting` are preserved; fields you send replace the platform counterpart, and empty/null values clear only that field.\n\nUse `targeting` to update specific facets (e.g., `countries`, `locations`, `industries`) without resending the full targeting object.","platforms":["tiktok","linkedin","tiktok-ads","linkedin-ads","ads"],"url":"https://docs.zernio.com/changelog#1790330505313"},{"id":"1790329004659","date":"2026-09-25T09:36:44.659Z","type":"improvement","message":"Canceling an iMessage sender can now return a conflict when the sender is still being set up.\n\nIf the sender status is still ordering/activating, `DELETE /v1/imessage/senders/{senderId}` will return `409` and cannot be canceled yet.\n\nHandle:\n• `409` - The sender is still being set up and cannot be canceled yet (`code: imessage_sender_setting_up`)","platforms":["imessage"],"url":"https://docs.zernio.com/changelog#1790329004659"},{"id":"1790328267431","date":"2026-09-25T09:24:27.431Z","type":"improvement","message":"Meta ad targeting updates via `PUT /v1/ads/{adId}` are now defined as a **partial update merged over the live ad set targeting**.\n\nAny targeting setting you don’t send is preserved; any field you do send replaces its Meta counterpart. You can also clear a single targeting field by sending an empty/null value.\n\nKey behavior:\n• Preserve by omission: `targeting` fields you omit stay unchanged\n• Replace per-field: send `targeting.&lt;field&gt;` to replace just that field\n• Clear per-field: e.g. `targeting.audienceInclude: []` or `targeting.gender: \"all\"`\n• Locations: any location field (e.g. `targeting.countries`/`regions`/`cities`/`zips`) replaces the entire included location set","platforms":["meta-ads","ads"],"url":"https://docs.zernio.com/changelog#1790328267431"},{"id":"1790325129174","date":"2026-09-25T08:32:09.174Z","type":"improvement","message":"Webhooks and post targets now include **platform-native error details** via `platformError`.\n\nWhen a publish attempt fails and the platform provides its own error (currently Instagram/Facebook), Zernio will include the raw platform message for diagnostics/support tickets (tokens redacted).\n\nNew object: `platformError` with:\n• `message`\n• `code`\n• `subcode`\n\nAvailable on:\n• `WebhookPayloadPost.post.platforms[].platformError`\n• `WebhookPayloadPostPlatform.post.platforms[].platformError`\n• `PlatformTarget.platformError`","platforms":["instagram","publishing","webhooks"],"url":"https://docs.zernio.com/changelog#1790325129174"},{"id":"1790323613076","date":"2026-09-25T08:06:53.076Z","type":"breaking_change","message":"GET `/v1/analytics` (single post lookup with `postId`) now returns more accurate in-flight publishing states.\n\nWhen a post is still publishing on any platform, the endpoint may return `202` and the response `status` will reflect the post’s current state (instead of forcing `partial`/`failed` early).\n\nUpdated `AnalyticsSinglePostResponse.status` enum:\n• `published` • `failed` • `partial` • `scheduled` • `publishing` • `draft` • `cancelled`\n\nNote: a post with one failed platform can return `202` (not `424`) until the remaining platforms finish publishing.","platforms":["publishing","analytics"],"url":"https://docs.zernio.com/changelog#1790323613076"},{"id":"1790323139720","date":"2026-09-25T07:58:59.720Z","type":"improvement","message":"OAuth connect redirects can now include `missing_scopes` when Google permissions are incomplete.\n\nWhen a user unchecks permissions on Google’s consent screen (YouTube / Google Business), the redirect includes `error=missing_google_permissions` with `is_user_fixable=true`, and may also include `missing_scopes` (comma-separated list of requested Google scopes that were not granted).\n\nHandle these optional params on your `redirect_url` callback:\n• `error=missing_google_permissions`\n• `missing_scopes` (optional)\n• `is_user_fixable=true`","platforms":["youtube","google-business"],"url":"https://docs.zernio.com/changelog#1790323139720"},{"id":"1790322666735","date":"2026-09-25T07:51:06.735Z","type":"breaking_change","message":"POST /v1/posts now supports **strong idempotency** via `Idempotency-Key` (recommended) with a 24-hour retry window.\n\nThis makes safe retries more reliable (including after timeouts/5xx), and changes how duplicates and in-flight retries are handled.\n\nUse:\n• `Idempotency-Key` - UUID per logical post (max 255 chars). Retries within 24h return `200` with the original post in `post`.\n• If the original request is still processing, retries return `409` with `code` `idempotency_conflict` and a `Retry-After` header.\n• `x-request-id` behavior changed: it now only replays when the content fingerprint matches (same account/content/media URLs) and is checked after `Idempotency-Key`.\n\niMessage conversation behavior changed on `POST /v1/inbox/conversations` / `POST /v1/inbox/conversations/{conversationId}/messages`: a thread opened to a new contact now allows up to `2` follow-ups (each at least `24h` apart) before requiring a reply; earlier follow-ups return `429` `new_contact_limit`, and a 4th message returns `409` `recipient_must_message_first`.","platforms":["imessage","publishing","inbox"],"url":"https://docs.zernio.com/changelog#1790322666735"},{"id":"1790285972521","date":"2026-09-24T21:39:32.521Z","type":"improvement","message":"GET `/v1/accounts/{accountId}/health` now reports WhatsApp inbound webhook subscription status via `platformConnection.inboundWebhookSubscribed`.\n\nThis helps detect cases where the number is still connected and can send, but inbound messages aren’t delivered to Zernio because Meta reports the message webhook isn’t subscribed.\n\nNew field (WhatsApp accounts only): `platformConnection.inboundWebhookSubscribed` = `true` | `false` | `null`\n• `false`: Meta says Zernio isn’t subscribed to the message webhook (reconnect to re-subscribe; if it persists, the number may be routed to a different WABA)\n• `true`: no issue reported by Meta\n• `null`: not reported / read failed (not evidence either way)\n\nWhen `inboundWebhookSubscribed` is `false`, an entry is added to `issues` and overall `status` is at least `warning`.","platforms":["whatsapp","webhooks"],"url":"https://docs.zernio.com/changelog#1790285972521"},{"id":"1790262899813","date":"2026-09-24T15:14:59.813Z","type":"improvement","message":"Instagram/Facebook message attachments can now include `type` `unsupported_type`, and webhook attachments may include `mimeType`.\n\nThis covers Meta “shared” media (reels/posts/ads) that arrives without a reliable type. Zernio will usually resolve it to `image`/`video`/`audio`/`file` with `originalType` `unsupported_type`, but may temporarily return `type` `unsupported_type` when classification isn’t ready.\n\nKey details:\n• Attachment `type` enum now includes: `image`, `video`, `audio`, `file`, `sticker`, `share`, `template`, `unsupported_type`\n• New field: `attachments[].mimeType` (set when known; especially for `originalType` `unsupported_type`)\n• For `originalType` `unsupported_type`, `refreshUrl` may not be re-mintable later (can 404 after expiry), so download media when the `message.received` webhook arrives.","platforms":["instagram","facebook","inbox","webhooks"],"url":"https://docs.zernio.com/changelog#1790262899813"},{"id":"1790257431650","date":"2026-09-24T13:43:51.650Z","type":"improvement","message":"`GET /v1/inbox/conversations/{conversationId}/messages` now returns richer Instagram/Messenger context for messages.\n\nYou can now reliably detect Instagram story replies (with the story media) and distinguish Meta-withheld messages that have no renderable content.\n\nNew/updated fields:\n• `storyReply` is now an object (Instagram only) with `storyId` and `storyUrl`\n• `isStoryMention` is now a boolean (Instagram only)\n• `noRenderableContent` boolean (Instagram + Facebook Messenger only) — when Meta marks a message as unsupported and both `message` and `attachments` are empty","platforms":["instagram","facebook","inbox"],"url":"https://docs.zernio.com/changelog#1790257431650"},{"id":"1790249569738","date":"2026-09-24T11:32:49.738Z","type":"new_feature","message":"POST `/v1/comment-automations` now supports starting a workflow when the recipient taps a DM button (including product-card buttons) via a `postback` payload.\n\nThis lets you send the initial comment-to-DM, then continue into a specific workflow only after the user taps.\n\nUse a button like:\n`{\"type\":\"postback\",\"title\":\"Send it\",\"payload\":\"zernio:workflow:&lt;workflowId&gt;\"}`\n\nNotes:\n• The workflow must be active and belong to the same `accountId` + `profileId`\n• The workflow starts on tap (the initial DM alone does not start it)\n• Invalid/stale targets do nothing","platforms":["inbox"],"url":"https://docs.zernio.com/changelog#1790249569738"},{"id":"1790244541171","date":"2026-09-24T10:09:01.171Z","type":"improvement","message":"Instagram connect can now return a new callback error: `instagram_login_method_mismatch`.\n\nThis happens when an `instagram_login` authorization lands on a profile whose Instagram account is connected via `loginMethod=facebook_login`; nothing is changed and the existing connection keeps working.\n\nTo refresh the connection, call `GET /v1/connect/instagram` with `loginMethod=facebook_login`. To switch to `instagram_login`, disconnect the account first.","platforms":["instagram"],"url":"https://docs.zernio.com/changelog#1790244541171"},{"id":"1790242670562","date":"2026-09-24T09:37:50.562Z","type":"new_feature","message":"Webhooks can now be scoped to specific connected accounts using **account allowlists**.\n\nThis lets you route events for only certain accounts (and optionally also certain profiles) to a given endpoint. If you set both lists, an event must match _both_.\n\nUse:\n• `POST /v1/webhooks/settings`: `accountIds: string[]`\n• `PUT /v1/webhooks/settings`: `accountIds: string[]` (empty array clears)\n• `GET /v1/webhooks/settings` now returns `accountIds` (and `profileIds` in examples)\n\nNotes:\n• Invalid ids return `404 account_not_found` (and `404 profile_not_found` for `profileIds`).\n• `POST /v1/webhooks/test` `404` is now just “Webhook not found”.","platforms":["webhooks"],"url":"https://docs.zernio.com/changelog#1790242670562"},{"id":"1790237451865","date":"2026-09-24T08:10:51.865Z","type":"new_feature","message":"Webhooks can now be scoped to specific profiles using `profileIds` on `POST /v1/webhooks/settings` and `PUT /v1/webhooks/settings`.\n\nThis lets you pin a webhook subscription to a set of profiles so only events attributable to those profiles are delivered (useful for routing test profiles to a staging endpoint).\n\nUse `profileIds`:\n• Omit or send `[]` to receive events for every profile\n• Send up to `50` profile IDs to scope delivery\n• If any ID is outside your team, the request fails with `404 profile_not_found`\n\nNote: events with no profile behind them (e.g. `verification.*`, `phone_number.*`) are not delivered to a scoped subscription. `POST /v1/webhooks/test` may also return `404 profile_not_found` for scoped subscriptions.","platforms":["telephony","webhooks"],"url":"https://docs.zernio.com/changelog#1790237451865"},{"id":"1790237083341","date":"2026-09-24T08:04:43.341Z","type":"improvement","message":"`POST /v1/ads/boost` and `POST /v1/ads/create` can now return `429` (`MetaAdAccountWriteLimited`).\n\nThis indicates Meta has temporarily limited writes on the ad account; clients should handle it as a retryable condition (backoff) rather than a permanent validation error.\n\nNew response:\n• `429` - `MetaAdAccountWriteLimited`","platforms":["meta-ads","ads"],"url":"https://docs.zernio.com/changelog#1790237083341"},{"id":"1790236494656","date":"2026-09-24T07:54:54.656Z","type":"improvement","message":"Meta Ads scoping on `GET /v1/connect/{platform}/ads` now also controls which ad accounts Zernio subscribes to for ad-account webhooks.\n\nWhen you pass `adAccountId` or `adAccountIds`, only those scoped Meta ad accounts will be synced _and_ subscribed for webhooks (instead of every reachable ad account).\n\nKey params:\n• `adAccountId` (single)\n• `adAccountIds` (repeat or comma-separated)\n\nThis scope is now kept even when the endpoint returns an `authUrl`, so a newly created Meta Ads account after OAuth starts scoped from the beginning.","platforms":["meta-ads","ads","webhooks"],"url":"https://docs.zernio.com/changelog#1790236494656"},{"id":"1790232028702","date":"2026-09-24T06:40:28.702Z","type":"new_feature","message":"New endpoint `GET /v1/ads/tiktok-smart-plus-materials` is available for TikTok Smart+ ads.\n\nIt returns per-creative (material) performance for a Smart+ ad or ad group, with one row per Spark post/video/image from TikTok’s Smart+ material report.\n\nRequired params: `accountId`, `adAccountId`, `startDate`, `endDate`\n\nKey params:\n• `level`: `ad` | `adGroup` (default `ad`)\n• `smartPlusAdIds`: comma-separated (up to 100)\n• `adGroupIds`: comma-separated (up to 100)\n• Pagination: `page`, `pageSize`\n\nResponse rows include `materialId`, `materialType`, `tiktokItemId` (Spark posts), and `metrics` (e.g. `spend`, `impressions`, `clicks`, `purchases`, `purchaseValue`, `roas`).","platforms":["tiktok","tiktok-ads","ads","analytics"],"url":"https://docs.zernio.com/changelog#1790232028702"},{"id":"1790201134840","date":"2026-09-23T22:05:34.840Z","type":"new_feature","message":"**iMessage is now supported** on `POST /v1/inbox/conversations`.\n\nYou can start iMessage threads by passing the recipient as `participantId` (phone in international format with `+`, or an iMessage email). First messages to new contacts are restricted (plain-text only) and may be reviewed before sending.\n\nKey request fields:\n• `participantId` - iMessage phone (`+15551234567`) or email\n\nNew iMessage-specific errors to handle:\n• `400` `invalid_content` - first message to a new contact violates content rules (nothing sent)\n• `409` `recipient_must_message_first` - after the opening message, no further sends until the contact replies\n• `409` `recipient_opted_out` - contact opted out\n• `429` `new_contact_limit` - sender hit new-contact pacing/warm-up limits (retry later)\n\nAlso note: `POST /v1/inbox/conversations/{conversationId}/messages` now documents iMessage pacing (2 minutes between sends to contacts who haven’t written in the last 24h) and a 10,000 character text limit.","platforms":["imessage","inbox","telephony"],"url":"https://docs.zernio.com/changelog#1790201134840"},{"id":"1790168479526","date":"2026-09-23T13:01:19.526Z","type":"improvement","message":"LinkedIn attach mode on `POST /v1/ads/create` now lets you omit `goal` when using `adSetId` (LinkedIn Campaign ID).\n\nWhen `adSetId` is set for LinkedIn, the ad now inherits the Campaign objective automatically, and the created ad’s `targeting` will echo the Campaign’s audience.\n\nUse:\n• `adSetId` (LinkedIn Campaign ID)\n• `goal` can be omitted (inherited from the Campaign)\n\nNote: LinkedIn attach mode still requires omitting Campaign-level fields like `budget`/`schedule`/`targeting`/`bidding` on the request.","platforms":["linkedin","linkedin-ads","ads"],"url":"https://docs.zernio.com/changelog#1790168479526"},{"id":"1790159556083","date":"2026-09-23T10:32:36.083Z","type":"improvement","message":"Phone number purchase can now fail with a new `409` error code: `NO_WHATSAPP_ELIGIBLE_NUMBER` on `POST /v1/phone-numbers/purchase` (and the deprecated alias `POST /v1/whatsapp/phone-numbers/purchase`).\n\nThis happens when every number the carrier offers is rejected by WhatsApp at buy-time; you can mitigate by specifying an `areaCode` or retrying later to get a fresh batch.\n\nHandle `409` `code` values:\n• `PURCHASE_VELOCITY`, `AREA_CODE_UNAVAILABLE`, `PHONE_NUMBER_UNAVAILABLE`, `COUNTRY_OUT_OF_STOCK`, `NO_WHATSAPP_ELIGIBLE_NUMBER`","platforms":["whatsapp","telephony"],"url":"https://docs.zernio.com/changelog#1790159556083"},{"id":"1790152200931","date":"2026-09-23T08:30:00.931Z","type":"new_feature","message":"Meta ad creation now supports combining **Multiple Text Options** with **placement asset customization** on `POST /v1/ads/create`.\n\nYou can pin media per placement via `placementAssets` while letting Meta rotate copy variations via `bodies` + `headlines` (and optionally `descriptions`).\n\nUse:\n• `placementAssets` + `bodies` (1-5) + `headlines` (1-5)\n\nNote: `descriptions` variations can’t be used together with `placementAssets` (Meta rejects it).","platforms":["meta-ads","ads"],"url":"https://docs.zernio.com/changelog#1790152200931"},{"id":"1790150899744","date":"2026-09-23T08:08:19.744Z","type":"new_feature","message":"New endpoint added to remove Meta Lead Gen test leads: `DELETE /v1/ads/lead-forms/{formId}/test-leads`.\n\nThis lets you delete the single test lead Meta allows per form so you can submit a new one (real leads can’t be deleted via this flow).\n\nKey params:\n• `formId` (path, required)\n• `accountId` (query, required) — the Facebook/Meta Ads account whose Page owns the form\n• `leadId` (query, optional) — omit to delete the current test lead on the form\n\nResponse: `200` returns `status` and `deleted` (array of removed test lead IDs). `404` if the form has no test lead.","platforms":["facebook","meta-ads","ads"],"url":"https://docs.zernio.com/changelog#1790150899744"},{"id":"1790150326117","date":"2026-09-23T07:58:46.117Z","type":"new_feature","message":"POST `/v1/ads/boost` now supports additional Meta-only controls: campaign-level budgeting (CBO), attribution windows, and multiple primary-text variants for the boosted post.\n\nUse `budgetLevel` to choose where budget is applied:\n• `adset` (default)\n• `campaign` (Advantage campaign budget / CBO)\n\nSet Meta attribution windows via `attributionSpec` (not allowed with `adSetId`):\n• `eventType`: `CLICK_THROUGH` | `VIEW_THROUGH` | `ENGAGED_VIDEO_VIEW`\n• `windowDays`: `1` | `7` | `28`\n\nProvide extra text options Meta can rotate with `bodies` (1–5 strings). For `traffic`/`conversions`, include `linkUrl` + `callToAction` so Meta has a website URL for these variants.","platforms":["meta-ads","ads"],"url":"https://docs.zernio.com/changelog#1790150326117"},{"id":"1790138798913","date":"2026-09-23T04:46:38.913Z","type":"new_feature","message":"POST `/v1/ads/boost` now supports **conversion-optimized boosts** on Meta as well as TikTok via `promotedObject`.\n\nThis lets you boost an existing organic post while optimizing delivery for conversions (instead of only engagement/traffic). On Meta, the values are applied to the ad set’s `promoted_object`; with `adSetId`, the existing ad set already carries it.\n\nUse `promotedObject`:\n• `pixelId`\n• `customEventType`\n• `customEventStr` (Meta only; requires `customEventType`=`OTHER`)\n• `customConversionId` (Meta only; alternative to `pixelId` + `customEventType`)\n\nWhen `goal`=`conversions`, `promotedObject` is required (TikTok requires both `pixelId` and `customEventType`).","platforms":["tiktok","meta-ads","tiktok-ads","ads"],"url":"https://docs.zernio.com/changelog#1790138798913"},{"id":"1790104929659","date":"2026-09-22T19:22:09.659Z","type":"deprecation","message":"Google Ads endpoints now accept `adAccountId` as the preferred way to select the platform ad account (Google customer ID, digits only).\n\nThis standardizes account selection across Ads APIs; existing calls using `customerId` will continue to work for now but are now deprecated.\n\nUse `adAccountId` (and treat `customerId` as deprecated) on endpoints including:\n• `GET /v1/ads/search-terms`\n• `GET|POST|PATCH /v1/ads/bid-strategies`\n• `GET /v1/ads/local-services/leads` and `GET /v1/ads/local-services/leads/{leadId}/conversations`\n• `GET /v1/ads/campaigns/{campaignId}/bidding`\n• Campaign/ad-group assets: `GET|POST|PUT|DELETE /v1/ads/campaigns/{campaignId}/assets`, `GET|POST|PUT|DELETE /v1/ads/ad-sets/{adSetId}/assets`\n• Keyword Planner: `POST /v1/ads/keywords/ideas`, `POST /v1/ads/keywords/historical-metrics`\n• GAQL insights: `GET /v1/ads/insights`\n• Negative keyword lists + keywords: `/v1/ads/accounts/negative-keyword-lists*`\n• Account assets: `/v1/ads/accounts/callouts`, `/v1/ads/accounts/sitelinks`, `/v1/ads/accounts/structured-snippets`\n• Conversions: `GET|POST /v1/ads/conversions/actions`\n\nTikTok pixels also standardize on `adAccountId` (TikTok advertiser id); `advertiserId` is now deprecated on `GET /v1/ads/pixels`.","platforms":["tiktok","google-ads","tiktok-ads","ads","analytics"],"url":"https://docs.zernio.com/changelog#1790104929659"},{"id":"1790104801663","date":"2026-09-22T19:20:01.663Z","type":"new_feature","message":"`POST /v1/ads/messaging` now supports multi-app messaging ads via `destinations`.\n\nYou can put 2–3 messaging apps on a single ad set (like Ads Manager “all messaging apps”), and Meta will choose the app per viewer.\n\nUse either:\n• `destination` = `whatsapp` | `messenger` | `instagram_direct`\nOR\n• `destinations` = array (2–3 unique items) from `whatsapp`, `messenger`, `instagram_direct` (set this OR `destination`, not both).","platforms":["instagram","facebook","whatsapp","meta-ads","ads"],"url":"https://docs.zernio.com/changelog#1790104801663"},{"id":"1790104112822","date":"2026-09-22T19:08:32.822Z","type":"improvement","message":"Several analytics endpoints now use `fromDate` / `toDate` for date ranges.\n\nThis standardizes date parameters across platforms. Existing parameters are still accepted as deprecated aliases, so current integrations won’t break, but you should migrate to the new names.\n\nUpdated endpoints:\n• `GET /v1/analytics/instagram/account-insights`: `fromDate`, `toDate` (aliases: `since`, `until`)\n• `GET /v1/analytics/instagram/follower-history`: `fromDate`, `toDate` (aliases: `since`, `until`)\n• `GET /v1/analytics/tiktok/account-insights`: `fromDate`, `toDate` (aliases: `since`, `until`)\n• `GET /v1/analytics/facebook/page-insights`: `fromDate`, `toDate` (aliases: `since`, `until`)\n• `GET /v1/analytics/linkedin/org-aggregate-analytics`: `fromDate`, `toDate` (aliases: `since`, `until`)\n• `GET /v1/analytics/youtube/channel-insights`: `fromDate`, `toDate` (aliases: `since`, `until`)\n• `GET /v1/analytics/youtube/daily-views`: `fromDate`, `toDate` (aliases: `startDate`, `endDate`)\n• `GET /v1/analytics/youtube/video-retention`: `fromDate`, `toDate` (aliases: `startDate`, `endDate`)\n• `GET /v1/analytics/youtube/demographics`: `fromDate`, `toDate` (aliases: `startDate`, `endDate`)\n• `GET /v1/analytics/googlebusiness/performance`: `fromDate`, `toDate` (aliases: `startDate`, `endDate`)\n• `GET /v1/accounts/{accountId}/linkedin-aggregate-analytics`: `fromDate`, `toDate` (aliases: `startDate`, `endDate`)","platforms":["instagram","facebook","tiktok","linkedin","youtube","google-business","analytics"],"url":"https://docs.zernio.com/changelog#1790104112822"},{"id":"1790103688547","date":"2026-09-22T19:01:28.547Z","type":"deprecation","message":"POST `/v1/ads/boost` now supports top-level scheduling fields `startDate` and `endDate`.\n\nThis aligns boost scheduling with `POST /v1/ads/create` and makes it easier to set ad-set start/end times without nesting.\n\nUse:\n• `startDate` (ISO 8601 date-time)\n• `endDate` (ISO 8601 date-time; required for `budgetType`=`lifetime`)\n\nThe nested `schedule` object is now **deprecated**. If you send both `startDate`/`endDate` and `schedule` with different values, the request returns `400`.","platforms":["ads"],"url":"https://docs.zernio.com/changelog#1790103688547"},{"id":"1790102999038","date":"2026-09-22T18:49:59.038Z","type":"deprecation","message":"POST `/v1/ads/boost` now supports flat budget fields `budgetAmount` + `budgetType` (preferred), and the existing `budget` object is now _deprecated_.\n\nThis aligns boost requests with the same budget shape as `POST /v1/ads/create`, while keeping backward compatibility for existing callers.\n\nUse:\n• `budgetAmount` (number)\n• `budgetType`: `daily` | `lifetime` (lifetime requires `schedule.endDate`)\n\nThe legacy `budget`: `{ amount, type }` still works for now but is marked deprecated.","platforms":["ads"],"url":"https://docs.zernio.com/changelog#1790102999038"},{"id":"1790102462409","date":"2026-09-22T18:41:02.409Z","type":"deprecation","message":"Connect endpoints now accept snake_case redirect parameters, with the previous camelCase fields marked deprecated.\n\nUse `redirect_url` on:\n• `POST /v1/connect/bluesky/credentials`\n• `POST /v1/connect/openai-ads/credentials`\n• `POST /v1/connect/whatsapp/embedded-signup`\n\nThe older aliases are still supported but deprecated:\n• `redirectUri` (Bluesky, OpenAI Ads)\n• `redirectUrl` (WhatsApp embedded signup)\n\nNotes:\n• Bluesky: if the `state` already includes a redirect (minted by `GET /v1/connect/bluesky` with `redirect_url`), that redirect takes precedence.\n• OpenAI Ads: `redirect_url` is echoed back as `redirectUrl` in the response.\n• WhatsApp embedded signup: send `redirect_url` to receive `redirectUrl` in the response (including error responses).","platforms":["bluesky","whatsapp","ads"],"url":"https://docs.zernio.com/changelog#1790102462409"},{"id":"1790101999584","date":"2026-09-22T18:33:19.584Z","type":"deprecation","message":"`GET /v1/logs` now supports camelCase query params, with the previous snake_case params kept as _deprecated_ aliases.\n\nThis makes filtering consistent with other endpoints while remaining backward-compatible for existing callers.\n\nUse:\n• `accountId` (alias: `account_id`)\n• `requestId` (alias: `request_id`)\n• `statusCode` (alias: `status_code`)\n• `apiKeyId` (alias: `api_key_id`)\n• `includeReadReceipts` (alias: `include_read_receipts`)\n\nAlso: the endpoint can now return `400` for invalid filter values.","platforms":["general"],"url":"https://docs.zernio.com/changelog#1790101999584"},{"id":"1790101486173","date":"2026-09-22T18:24:46.173Z","type":"deprecation","message":"`GET /v1/ads/search-terms` now supports filtering by `adSetId` (Google ad group), and `adGroupId` is now **deprecated**.\n\nUse `adSetId` to filter search terms by ad set/ad group. `adGroupId` remains as an alias for existing callers.\n\nParameters:\n• `adSetId` - platform ad set ID (Google ad group)\n• `adGroupId` - deprecated alias of `adSetId`","platforms":["google-ads","ads"],"url":"https://docs.zernio.com/changelog#1790101486173"},{"id":"1790100745820","date":"2026-09-22T18:12:25.820Z","type":"breaking_change","message":"`GET /v1/posts` now supports offset-based pagination via `offset`.\n\nYou can paginate by row offset (takes precedence over `page` when both are sent); `pagination.page` is derived from the offset.\n\nNew/updated query params:\n• `offset` (integer, min 0)\n• `fromDate`, `toDate` (new)\n• `dateFrom`, `dateTo` are now _deprecated_ aliases of `fromDate`/`toDate`\n\nBehavior change: any unknown query parameter on `GET /v1/posts` now returns `400` (to prevent silently unfiltered results).","platforms":["general"],"url":"https://docs.zernio.com/changelog#1790100745820"},{"id":"1790100245993","date":"2026-09-22T18:04:05.993Z","type":"breaking_change","message":"Webhook settings endpoints now use a consistent webhook identifier name: `webhookId`.\n\nThis aligns `PUT /v1/webhooks/settings` and `DELETE /v1/webhooks/settings` with other /v1/webhooks operations (logs/redeliver/test). Deprecated aliases are still accepted for existing callers.\n\nUse:\n• `PUT /v1/webhooks/settings`: send `webhookId` (deprecated: `_id`)\n• `DELETE /v1/webhooks/settings`: query `webhookId` (deprecated: `id`)\n\nNote: `DELETE /v1/webhooks/settings` now returns `400` if you include any unknown query parameter (nothing is silently ignored).","platforms":["webhooks"],"url":"https://docs.zernio.com/changelog#1790100245993"},{"id":"1790098660198","date":"2026-09-22T17:37:40.198Z","type":"improvement","message":"Messaging/CTWA ad creation now prefers `platformPostId` for reusing an existing post/reel (replacing `existingPostId`).\n\nThis makes existing-post reuse consistent with boost-style inputs, while keeping `existingPostId` as a deprecated alias for back-compat.\n\nUse `platformPostId` (top-level or per `creatives[]`) and do not combine it with `objectStoryId`.\n\nNotes:\n• `existingPostId` is now _deprecated_ (alias of `platformPostId`)\n• Sending both `platformPostId` and `existingPostId` with different values returns `400`","platforms":["meta-ads","ads","publishing"],"url":"https://docs.zernio.com/changelog#1790098660198"},{"id":"1790098307243","date":"2026-09-22T17:31:47.243Z","type":"deprecation","message":"`GET /v1/twitter/tweet` now uses `tweetId` as the canonical query parameter (matching other X/Twitter endpoints), and `id` is now deprecated.\n\nExisting callers can keep using `id` for now (it’s an alias), but new integrations should send `tweetId`.\n\nUse:\n• `accountId` (required)\n• `tweetId` (required)\n• `id` (deprecated alias of `tweetId`)","platforms":["x"],"url":"https://docs.zernio.com/changelog#1790098307243"},{"id":"1790096510559","date":"2026-09-22T17:01:50.559Z","type":"breaking_change","message":"Available phone number search now uses canonical filters `numberType` and `areaCode` on `GET /v1/phone-numbers/available` (and the deprecated alias `GET /v1/whatsapp/phone-numbers/available`).\n\nThis standardizes naming across purchase/availability/KYC and keeps backward compatibility via deprecated aliases.\n\nUse:\n• `numberType`: `local` | `mobile` | `national` | `toll_free`\n• `areaCode`: area code / national dialing code prefix (e.g. `415`, `91`)\n\nDeprecated (still accepted for existing callers):\n• `type` → alias of `numberType`\n• `prefix` → alias of `areaCode`\n\nNote: `400` responses now cover invalid `numberType` and unknown query parameters (nothing is silently ignored).","platforms":["whatsapp","telephony"],"url":"https://docs.zernio.com/changelog#1790096510559"},{"id":"1790091193948","date":"2026-09-22T15:33:13.948Z","type":"improvement","message":"CTWA ad creation now lets you set exact names for the newly provisioned campaign and ad set via `campaignName` and `adSetName` (in `CtwaAdRequestBody`).\n\nThis helps keep Ads Manager naming consistent when Zernio creates the campaign/ad set automatically; if omitted, the defaults remain `&lt;name&gt; - Campaign` and `&lt;name&gt; - Ad Set`.\n\nUse:\n• `campaignName` - exact campaign name (ignored when `adSetId` is set)\n• `adSetName` - exact ad set name (ignored when `adSetId` is set)","platforms":["meta-ads","tiktok-ads","ads"],"url":"https://docs.zernio.com/changelog#1790091193948"},{"id":"1790086904434","date":"2026-09-22T14:21:44.434Z","type":"breaking_change","message":"POST `/v1/ads/boost` now supports using a Meta **business-login** connection as `accountId`.\n\nBusiness-login connections don’t have a posting account, so you must pass the post via `platformPostId` (Facebook `pageId_postId` or an Instagram media id). Sending a Zernio `postId` with a business-login `accountId` now returns `400`.\n\nUse:\n• `accountId` = Meta business-login connection id\n• `platformPostId` = platform post identifier\nAvoid:\n• `postId` (returns `400` on business-login connections)","platforms":["instagram","facebook","meta-ads","ads"],"url":"https://docs.zernio.com/changelog#1790086904434"},{"id":"1790086251791","date":"2026-09-22T14:10:51.791Z","type":"improvement","message":"WhatsApp hosted flows now support Brazilian Portuguese via `language=pt-BR`.\n\nYou can localize the Zernio-hosted WhatsApp signup page and hosted KYC page in `en`, `es`, or `pt-BR`.\n\nApplies to:\n• `GET /v1/connect/{platform}` (WhatsApp only) with `signup=hosted` and `language`\n• `GET /v1/connect/whatsapp/sdk-config` response `branding.language`\n• `POST /v1/phone-numbers/kyc/share` request `language` (also on deprecated alias `POST /v1/whatsapp/phone-numbers/kyc/share`)","platforms":["whatsapp","telephony"],"url":"https://docs.zernio.com/changelog#1790086251791"},{"id":"1790085919402","date":"2026-09-22T14:05:19.402Z","type":"improvement","message":"POST `/v1/ads/messaging` now accepts `pageId` to explicitly choose which Facebook Page the messaging ad runs as.\n\nThis helps when the provided `accountId` connection was granted access to multiple Pages, avoiding ambiguous Page resolution.\n\nUse `pageId`: `\"{facebookPageId}\"` (optional; defaults to the Page bound to the connection).","platforms":["facebook","meta-ads","ads"],"url":"https://docs.zernio.com/changelog#1790085919402"},{"id":"1790083910294","date":"2026-09-22T13:31:50.295Z","type":"improvement","message":"GET `/v1/inbox/reviews` now returns `recommendationType` on review items for Facebook recommendations.\n\nThis lets you distinguish Facebook “recommends / doesn’t recommend” independently of the numeric `rating`.\n\nNew field: `recommendationType` = `positive` | `negative` | `null` (absent for non-Facebook platforms).","platforms":["inbox"],"url":"https://docs.zernio.com/changelog#1790083910294"},{"id":"1790076752260","date":"2026-09-22T11:32:32.260Z","type":"new_feature","message":"POST `/v1/ads/{adId}/duplicate` now supports **reusing the source creative** via `reuseSourceCreative`.\n\nWhen `reuseSourceCreative=true`, the duplicated ad points to the same creative object as the source, preserving the same Facebook post / Instagram media and existing likes, comments, and shares (similar to Ads Manager “show existing reactions, comments and shares”).\n\nNew parameter: `reuseSourceCreative` (boolean, default `false`)\n• Requires `adSetId` to be in the source ad’s ad account\n• Returns `400` if the source ad has no creative yet","platforms":["instagram","meta-ads","ads"],"url":"https://docs.zernio.com/changelog#1790076752260"},{"id":"1790072759813","date":"2026-09-22T10:25:59.813Z","type":"improvement","message":"`GET /v1/api-keys` can now return team-wide keys for the team owner, and each key may include `createdBy`.\n\nThis lets owners identify which team member created each API key; non-owners still only see their own keys.\n\nNew field on `ApiKey`:\n• `createdBy` = `{ id, email }` (or `null`)\n\nRelated behavior clarification: `DELETE /v1/api-keys/{keyId}` — owners can revoke any team member’s key; non-owners can only revoke their own.","platforms":["general"],"url":"https://docs.zernio.com/changelog#1790072759813"},{"id":"1790070508179","date":"2026-09-22T09:48:28.179Z","type":"improvement","message":"`POST /v1/ads/{adId}/duplicate` now falls back to rebuilding the ad when Meta’s native copy fails with its capability error (code `3`).\n\nIf Meta refuses `POST /{ad-id}/copies` (common for some creatives built by other tools), Zernio will create a new creative from the source returned spec and create a new ad in the target ad set, preserving the source name, `statusOption`, rename options, and tracking specs.\n\nKey request fields remain the same: `adSetId`, `statusOption` (`ACTIVE`/`PAUSED`/`INHERITED_FROM_SOURCE`), `renameStrategy` (`DEEP_RENAME`/`ONLY_TOP_LEVEL_RENAME`/`NO_RENAME`), `renamePrefix`, `renameSuffix`, `syncAfter`.","platforms":["meta-ads","ads"],"url":"https://docs.zernio.com/changelog#1790070508179"},{"id":"1790067958062","date":"2026-09-22T09:05:58.062Z","type":"improvement","message":"GET `/v1/ads/targeting/search` now returns `countryCode` on each result (when the ad platform reports it).\n\nThis lets you tell which country a sub-country geo option (e.g. city/region/zip/metro) belongs to before creating an ad, which is useful for EU DSA disclosure checks.\n\nNew field:\n• `results[].countryCode` - ISO-3166 alpha-2 country code (present when available; Meta provides it)","platforms":["meta-ads","ads"],"url":"https://docs.zernio.com/changelog#1790067958062"},{"id":"1790032559871","date":"2026-09-21T23:15:59.871Z","type":"new_feature","message":"Meta ads created via `POST /v1/ads/create` now support selecting the Facebook Page to run the ad as using `pageId`.\n\nThis lets you override the default Page bound to the connection when a business has multiple granted Pages. When `pageId` is set, the Instagram identity is re-resolved for that Page unless `instagramAccountId` is also provided.\n\nSet `pageId` (Meta only):\n• `pageId` - Facebook Page ID used for `object_story_spec.page_id`","platforms":["instagram","facebook","meta-ads","ads"],"url":"https://docs.zernio.com/changelog#1790032559871"},{"id":"1790009745409","date":"2026-09-21T16:55:45.409Z","type":"breaking_change","message":"For usage-based billing accounts, hitting the X spend cap now pauses **all billable X API activity** (including publishing), not just analytics/inbox sync.\n\nWhen `spend.xSpendCents` reaches `spend.xSpendLimitCents`, activity is paused until the billing period resets or the cap is raised.\n\nKey fields:\n• `spend.xSpendCents`\n• `spend.xSpendLimitCents` (`integer` | `null`)\n\nApplies when `billingSystem` is `metronome`.","platforms":["x","publishing"],"url":"https://docs.zernio.com/changelog#1790009745409"},{"id":"1790003103579","date":"2026-09-21T15:05:03.579Z","type":"improvement","message":"iMessage sender setup now returns more specific billing-related error codes on registration and ordering.\n\nWhen calling `POST /v1/imessage/senders` or `POST /v1/imessage/senders/order`, you may now see:\n• `402` with `code: payment_method_required`\n• `409` with `code: billing_setup_incomplete` (support must finish billing setup)\n\nExisting conflict behavior remains: `409` with `code: imessage_sender_conflict` when a sender/profile conflict exists.","platforms":["imessage"],"url":"https://docs.zernio.com/changelog#1790003103579"},{"id":"1789995611509","date":"2026-09-21T13:00:11.509Z","type":"improvement","message":"GET `/v1/ads/accounts` now returns Meta billing funding info when available.\n\nThis lets you surface which payment instrument an ad account is using (when the connected token has billing visibility), which helps diagnose billing-related delivery/setup issues.\n\nNew Meta-only fields on each item in `accounts[]`:\n• `fundingSource`\n• `fundingSourceDetails` (`id`, `displayString`, `type`)\n\nNote: these keys are _absent_ when the token can’t see billing; don’t treat a missing key as “no payment method configured”.","platforms":["meta-ads","ads"],"url":"https://docs.zernio.com/changelog#1789995611509"},{"id":"1789991477421","date":"2026-09-21T11:51:17.421Z","type":"new_feature","message":"New endpoint: `GET /v1/ads/instagram-posts` lists Instagram media available to boost, so you can create ads from an existing IG post without separately connecting the Instagram account.\n\nUse `posts[].id` as `platformPostId` when creating the ad.\n\nKey params:\n• `accountId` (required)\n• `adAccountId` (optional, `act_*`)\n• `igUserId` (optional; rejected if not reachable)\n• `limit` (1–100, default 25)\n• `after` (cursor)\n\nResponse highlights:\n• `igUserId`, `username`\n• `posts[]`: `id`, `mediaType` (e.g. `IMAGE`, `VIDEO`, `CAROUSEL_ALBUM`), `timestamp`, plus optional `caption`/`mediaUrl`/`thumbnailUrl`/`permalink`\n• `paging.after`\n\nNotable errors:\n• `403` `reconnect_required` when the connection predates Instagram access\n• `422` `instagram_business_account_unresolved` when the Page has no linked IG professional account (or `linked_account_required` when no Page is selected and no `adAccountId` is provided)","platforms":["instagram","facebook","meta-ads","ads"],"url":"https://docs.zernio.com/changelog#1789991477421"},{"id":"1789984571274","date":"2026-09-21T09:56:11.274Z","type":"improvement","message":"Post analytics now includes TikTok Business viewer country breakdown via `audienceCountries`.\n\nThis lets you read per-post (and per-day timeline) viewer-country shares, with the tail grouped into `other`.\n\nNew field:\n• `metrics.audienceCountries` (top 20 ISO-3166 alpha-2 country codes + `other`, values are fractions 0..1)\n\nAvailable in:\n• `GET /v1/analytics/post-timeline` (per-day rows)\n• `GET /v1/analytics/delta` (delta feed entries)\n• `PostAnalytics.audienceCountries` (post metrics schema)\n\nFor non-TikTok-Business (and other platforms), `audienceCountries` is an empty object.","platforms":["tiktok","tiktok-ads","analytics"],"url":"https://docs.zernio.com/changelog#1789984571274"},{"id":"1789981953528","date":"2026-09-21T09:12:33.528Z","type":"improvement","message":"iMessage available-number listing now returns provider-reported area info via `location` on each number.\n\nThis helps you pick an instantly-activating number with a known area (when available) before ordering with `availableNumberId`.\n\nKey fields:\n• `GET /v1/imessage/senders/available-numbers` → `numbers[].location` (e.g. a US state)\n• Order instantly with `POST /v1/imessage/senders/order` + `availableNumberId`\n\nAlso note: `GET /v1/imessage/senders/available-numbers` may now return `502` if the provider can’t list numbers (ordering without `availableNumberId` still works).","platforms":["imessage","telephony"],"url":"https://docs.zernio.com/changelog#1789981953528"},{"id":"1789977702780","date":"2026-09-21T08:01:42.780Z","type":"improvement","message":"GET `/v1/ads/ad-sets` now returns richer LinkedIn ad set details via new fields `budget`, `schedule`, and `targeting`.\n\nThis makes it possible to read LinkedIn flight dates and targeting (including exclusions) directly from the ad set list, and to see both daily + lifetime budgets where LinkedIn supports both.\n\nNew/expanded fields:\n• `budget.daily`, `budget.lifetime`, `budget.pacing` (LinkedIn only; `budget.type` enum: `daily`, `lifetime`)\n• `schedule.startDate`, `schedule.endDate` (endDate may be absent when it runs until stopped)\n• `targeting.include`, `targeting.exclude`, `targeting.audienceExpansionEnabled`, `targeting.offsiteDeliveryEnabled` (LinkedIn only)\n\nPOST `/v1/ads/targeting/reach-estimate` adds `active` (LinkedIn only): LinkedIn’s `audienceCounts.active` alongside `lower`/`upper`.","platforms":["linkedin","linkedin-ads","ads"],"url":"https://docs.zernio.com/changelog#1789977702780"},{"id":"1789822353273","date":"2026-09-19T12:52:33.273Z","type":"improvement","message":"Inbox webhooks can now deliver attachments with `type` = `share` in `InboxWebhookMessage.attachments[].type`.\n\nThis lets a single message represent shared content (for example posts/stories) as an attachment, so webhook consumers can render or route these messages differently from files/media.\n\nHandle `attachments[].type` values including: `image`, `video`, `file`, `sticker`, `audio`, `share`.","platforms":["inbox","webhooks"],"url":"https://docs.zernio.com/changelog#1789822353273"},{"id":"1789753454927","date":"2026-09-18T17:44:14.927Z","type":"improvement","message":"WhatsApp connect flows can now return a new user-fixable error: `whatsapp_coexistence_not_registered` / `WHATSAPP_COEXISTENCE_NOT_REGISTERED`.\n\nThis happens when connecting a number from the WhatsApp Business app (coexistence) but the phone hasn’t registered the number on Cloud API yet (Meta may report `platform_type=ON_PREMISE` and refuse contacts/history sync with error `133010`). No account is created in this case.\n\nYou may see it in:\n• Redirect/hosted signup callback `GET /v1/connect/whatsapp` as `error=whatsapp_coexistence_not_registered` (with `is_user_fixable=true`)\n• `POST /v1/connect/whatsapp/select-phone-number` as a `409` conflict\n• `POST /v1/connect/whatsapp/credentials` as a `409` conflict\n\nRecommended handling: prompt the user to open/update WhatsApp Business on the phone, keep it open, then rerun the same Embedded Signup flow (typically succeeds on the second attempt).","platforms":["whatsapp","telephony"],"url":"https://docs.zernio.com/changelog#1789753454927"},{"id":"1789747668950","date":"2026-09-18T16:07:48.950Z","type":"improvement","message":"`GET /v1/auth/verify` now returns optional owner identity fields `name` and `email` in the `200` response.\n\nThis lets clients display who a credential belongs to immediately after verification, without calling another user/profile endpoint.\n\nNew fields:\n• `name` (string|null)\n• `email` (string|null)\n\nExisting `authType` enum remains: `api_key`, `oauth`, `session`.","platforms":["general"],"url":"https://docs.zernio.com/changelog#1789747668950"},{"id":"1789728389123","date":"2026-09-18T10:46:29.123Z","type":"improvement","message":"Phone number purchase can now fail with a new `409` error code: `COUNTRY_OUT_OF_STOCK` on `POST /v1/phone-numbers/purchase` (and the deprecated alias `POST /v1/whatsapp/phone-numbers/purchase`).\n\nThis indicates the selected country (and number type) has no orderable inventory right now, so you should prompt the user to pick another country/type and retry.\n\nHandle `409` `code` values:\n• `PURCHASE_VELOCITY`\n• `AREA_CODE_UNAVAILABLE`\n• `PHONE_NUMBER_UNAVAILABLE`\n• `COUNTRY_OUT_OF_STOCK`","platforms":["whatsapp","telephony"],"url":"https://docs.zernio.com/changelog#1789728389123"},{"id":"1789726297608","date":"2026-09-18T10:11:37.608Z","type":"breaking_change","message":"`CtwaAdRequestBody.welcomeMessage` now supports **quick reply chips** via `quickReplies`, and `prefillText` is no longer required.\n\nThis lets CTWA/messaging ads show tappable options under the greeting instead of pre-filling the user’s message.\n\nUse `welcomeMessage` with:\n• `text` (required)\n• Exactly one of `prefillText` or `quickReplies`\n\n`quickReplies` is an array (1–13 items) of:\n• `title` (max 20 chars)\n• `payload` (max 1000 chars)","platforms":["ads"],"url":"https://docs.zernio.com/changelog#1789726297608"},{"id":"1789726110318","date":"2026-09-18T10:08:30.318Z","type":"improvement","message":"GET `/v1/accounts/{accountId}/health` now returns `integrationLane` for TikTok accounts.\n\nThis lets you tell which TikTok integration the account is connected through, which can help with routing logic and debugging connection/permission issues.\n\nNew field:\n• `integrationLane`: `business` | `developer` (TikTok only; absent on other platforms)","platforms":["tiktok"],"url":"https://docs.zernio.com/changelog#1789726110318"},{"id":"1789723115159","date":"2026-09-18T09:18:35.159Z","type":"breaking_change","message":"`GET /v1/ads/library`: `since` and `until` are now **Meta-only**.\n\nLinkedIn Ad Library searches do not support date filtering via query params; sending `since` or `until` with `platform=linkedin` (or a LinkedIn `accountId`) will return `400`.\n\nUse `since`/`until` only for `platform=meta`. For LinkedIn, filter results client-side using `details.adStatistics.firstImpressionAt` / `details.adStatistics.latestImpressionAt` (EU-delivered ads only).","platforms":["linkedin","meta-ads","linkedin-ads","ads","analytics"],"url":"https://docs.zernio.com/changelog#1789723115159"},{"id":"1789704395226","date":"2026-09-18T04:06:35.226Z","type":"new_feature","message":"TikTok boosts now support choosing the video-views optimization via `optimizationGoal` on `POST /v1/ads/boost`.\n\nFor `goal` = `video_views`, you can select whether TikTok optimizes for 6-second or 15-second engaged views (both billed per view).\n\nSet `optimizationGoal` to:\n• `ENGAGED_VIEW` - 6-second Focused View (default)\n• `ENGAGED_VIEW_FIFTEEN` - 15-second views\n\nAny other `optimizationGoal` value on TikTok video view boosts returns 400.","platforms":["tiktok","tiktok-ads","ads"],"url":"https://docs.zernio.com/changelog#1789704395226"},{"id":"1789693501030","date":"2026-09-18T01:05:01.030Z","type":"new_feature","message":"TikTok ads now support selecting the **ad identity** (the profile shown on the ad).\n\nUse the new endpoint `GET /v1/ads/tiktok-identities` to list identities available on a TikTok advertiser, then pass the chosen identity when creating/boosting.\n\nNew endpoint params:\n• `accountId`\n• `adAccountId`\n\nNew request fields:\n• `POST /v1/ads/boost`: `identityId`, `identityType` (`TT_USER` | `BC_AUTH_TT` | `CUSTOMIZED_USER`)\n• `POST /v1/ads/create`: `identityId`, `identityType` (`TT_USER` | `BC_AUTH_TT` | `CUSTOMIZED_USER`)\n\nIf omitted, TikTok defaults to the connected account’s own identity; the identity must be authorized on the advertiser.","platforms":["tiktok","tiktok-ads","ads"],"url":"https://docs.zernio.com/changelog#1789693501030"},{"id":"1789687102203","date":"2026-09-17T23:18:22.203Z","type":"new_feature","message":"**TikTok is now supported** on `POST /v1/ads/ad-sets/{adSetId}/duplicate`.\n\nFor TikTok, the API recreates the ad group under the target campaign with targeting/bidding/budget/schedule (start resets to now). Set `deepCopy` to also recreate ads (TikTok default is `false`).\n\nTikTok-specific rules:\n• `startTime`, `endTime`, `renameStrategy` are ignored\n• `statusOption` must be `PAUSED` (or omitted)\n\nMeta behavior remains the same (Meta default `deepCopy: true`, optional `syncAfter`).","platforms":["tiktok","meta-ads","tiktok-ads","ads"],"url":"https://docs.zernio.com/changelog#1789687102203"},{"id":"1789679753961","date":"2026-09-17T21:15:53.961Z","type":"new_feature","message":"TikTok boosts now support creating the ad group + Spark ad under an **existing TikTok campaign** via `existingCampaignId` on `POST /v1/ads/boost`.\n\nThis lets you reuse an existing campaign (keeping its status/objective) while Zernio creates the ad group and ad beneath it; the campaign objective must be compatible with your `goal`.\n\nUse `existingCampaignId` (TikTok only). Constraints:\n• Cannot be combined with `adSetId` or `smartPlus`\n• On Meta, use `POST /v1/ads/create` for existing-campaign flows","platforms":["tiktok","tiktok-ads","ads"],"url":"https://docs.zernio.com/changelog#1789679753961"},{"id":"1789667277477","date":"2026-09-17T17:47:57.477Z","type":"improvement","message":"PUT `/v1/ads/{adId}` now supports renaming **TikTok** ads via `name`.\n\nYou can update the ad’s display name on TikTok in the same call you use for status/budget/targeting/creative updates.\n\nSet `name` (string, max 255) in the request body.","platforms":["tiktok","tiktok-ads","ads"],"url":"https://docs.zernio.com/changelog#1789667277477"},{"id":"1789665705051","date":"2026-09-17T17:21:45.051Z","type":"breaking_change","message":"WhatsApp catalog management and Meta product catalog CRUD are now available.\n\nYou can link/unlink Meta Commerce catalogs to a WhatsApp number (used by product/product_list/catalog_message interactive messages) and control commerce settings like catalog visibility and cart.\n\nWhatsApp endpoints:\n• List linked catalogs: `GET /v1/whatsapp/catalogs` with `accountId`\n• Link a catalog: `POST /v1/whatsapp/catalogs` with `accountId`, `catalogId`, optional `catalogAccountId`\n• Unlink a catalog: `DELETE /v1/whatsapp/catalogs` with `accountId`, `catalogId`\n• Get/update commerce settings: `GET /v1/whatsapp/commerce-settings`, `PUT /v1/whatsapp/commerce-settings` with `isCatalogVisible`, `isCartEnabled`\n\nProduct Catalogs endpoints (Meta Commerce catalogs):\n• Create/get/delete catalog: `POST /v1/ads/catalogs`, `GET /v1/ads/catalogs/{catalogId}`, `DELETE /v1/ads/catalogs/{catalogId}`\n• Products: `GET/POST /v1/ads/catalogs/{catalogId}/products`, `GET/PUT/DELETE /v1/ads/catalogs/{catalogId}/products/{productId}`\n• Bulk product ops: `POST /v1/ads/catalogs/{catalogId}/products/batch` (methods `CREATE`/`UPDATE`/`DELETE`) + poll `GET /v1/ads/catalogs/{catalogId}/batches/{handle}`\n• Feeds: `GET/POST /v1/ads/catalogs/{catalogId}/feeds`, `GET/POST /v1/ads/catalogs/{catalogId}/feeds/{feedId}/uploads`\n• Product sets: `POST /v1/ads/catalogs/{catalogId}/product-sets`, `PUT/DELETE /v1/ads/catalogs/{catalogId}/product-sets/{productSetId}`\n\nBreaking-ish change to catalog listing:\n`GET /v1/ads/catalogs` no longer requires `adAccountId` and now supports `catalogAccountId`, optional `adAccountId`, optional `businessId`. Response now includes `businessId` and returns catalogs as `MetaProductCatalog`.\n\nAlso updated: `GET /v1/ads/catalogs/{catalogId}/product-sets` now uses `CatalogAccountId`/`CatalogTokenAccountId` parameters and returns `404 NotFound` (instead of `AccountUnavailable` in some cases).","platforms":["whatsapp","commerce"],"url":"https://docs.zernio.com/changelog#1789665705051"},{"id":"1789661139358","date":"2026-09-17T16:05:39.358Z","type":"new_feature","message":"LinkedIn posts now support **audience targeting** for organization pages via `platformSpecificData.linkedin.audience`.\n\nThis lets you target distribution (OR within a facet, AND across facets). LinkedIn rejects targeting under 300 members, and Zernio rejects `audience` on personal profiles.\n\nUse `audience` with:\n• `countries` (ISO 3166-1 alpha-2)\n• `geoLocations` (geo URNs/ids)\n• `interfaceLocales` ({ `language`, `country` })\n• `industries`, `jobFunctions`, `seniorities` (URN or id)\n• `staffCountRanges`: `SIZE_1`, `SIZE_2_TO_10`, `SIZE_11_TO_50`, `SIZE_51_TO_200`, `SIZE_201_TO_500`, `SIZE_501_TO_1000`, `SIZE_1001_TO_5000`, `SIZE_5001_TO_10000`, `SIZE_10001_OR_MORE`\n• `degrees`, `fieldsOfStudy`, `organizations` (URN or id)","platforms":["linkedin","ads"],"url":"https://docs.zernio.com/changelog#1789661139358"},{"id":"1789653458386","date":"2026-09-17T13:57:38.386Z","type":"new_feature","message":"TikTok Smart+ Web Conversions boosts now support promo codes/offers via `promoCodes` on `POST /v1/ads/boost`.\n\nThis lets you attach Ads Manager-style “Add promo code or offer” details to Smart+ conversion boosts.\n\nUse `promoCodes` (1-10 items) when `smartPlus: true` and `goal: conversions`. Each item supports:\n• `discountType`: `PERCENTAGE` | `CASH`\n• `discountValue`\n• `promoCode` (optional; omit for an automatic offer)\n• `discountCurrency` (required for `CASH`)\n• `minimumPurchaseType`: `QUANTITY` | `SUBTOTAL` (optional)\n• `minimumPurchaseValue` (+ `minimumPurchaseCurrency` required for `SUBTOTAL`)\n\nRejected with 400 on non-TikTok platforms and on Smart+ Lead Generation campaigns.","platforms":["tiktok","tiktok-ads","ads"],"url":"https://docs.zernio.com/changelog#1789653458386"},{"id":"1789652373234","date":"2026-09-17T13:39:33.234Z","type":"new_feature","message":"TikTok Smart+ boosts now support **multiple Spark posts in a single boost** via `sparkPosts` on `POST /v1/ads/boost`.\n\nThis lets you create one Smart+ ad with 1–50 Spark post creatives (including mixing creators), instead of boosting only a single `platformPostId` + `sparkAuthCode`.\n\nUse `smartPlus: true` and send `sparkPosts` (array, `minItems: 1`, `maxItems: 50`), each item:\n• `platformPostId`\n• `sparkAuthCode`\n\n`sparkPosts` replaces `platformPostId` + `sparkAuthCode` for this path. With `adSetId`, it creates one new ad in that Smart+ ad group; without `adSetId`, it creates campaign + ad group + one ad carrying all posts.","platforms":["tiktok","tiktok-ads","ads"],"url":"https://docs.zernio.com/changelog#1789652373234"},{"id":"1789648263964","date":"2026-09-17T12:31:03.964Z","type":"breaking_change","message":"TikTok Smart+ boosts via `POST /v1/ads/boost` now reject `callToAction` with a 400 when `smartPlus: true`.\n\nSmart+ Spark ads use TikTok’s dynamic CTA portfolio instead of a named CTA; Zernio creates/reuses the portfolio per ad account.\n\nIf you use `smartPlus: true`, omit `callToAction` (previously it could be sent/forwarded).","platforms":["tiktok","tiktok-ads","ads"],"url":"https://docs.zernio.com/changelog#1789648263964"},{"id":"1789646026411","date":"2026-09-17T11:53:46.411Z","type":"improvement","message":"TikTok Smart+ boosts now only send `callToAction` when you explicitly provide it (for `POST /v1/ads/boost` with `smartPlus: true`).\n\nThis lets a Smart+ Spark ad run with the boosted post’s own CTA when `callToAction` is omitted, instead of forcing a CTA value.\n\nIf you want to override the post CTA, include `callToAction`; otherwise omit `callToAction` to keep the original post behavior.","platforms":["tiktok","tiktok-ads","ads"],"url":"https://docs.zernio.com/changelog#1789646026411"},{"id":"1789643195430","date":"2026-09-17T11:06:35.430Z","type":"new_feature","message":"WordPress is now supported in the Blogs API, with new connect flows for both WordPress.com OAuth and self-hosted WordPress application passwords.\n\nConnect WordPress.com by generating an OAuth URL via `GET /v1/connect/wordpress` (requires `profileId`, optional `redirect_url`). Connect self-hosted WordPress via `POST /v1/connect/wordpress/token` with `profileId`, `siteUrl` (must be `https://`), `username`, `applicationPassword`.\n\nBlogs + articles endpoints now support `platform` enum `shopify` | `wordpress`:\n• `GET /v1/accounts/{accountId}/blogs` returns 1 blog for WordPress with `nextCursor: null`\n• Articles endpoints (`GET/POST/PATCH/DELETE /v1/accounts/{accountId}/blogs/{blogId}/articles`) work for WordPress; responses may include WordPress-only `status` (`publish` | `future` | `draft` | `pending` | `private`) and `publishDate` (only when `status=future`)\n\nNotes for WordPress:\n• WordPress rejects `seo` on article create/update\n• Blog create/update/delete on WordPress returns `405` (site settings aren’t writable via this API)\n• `SocialAccount.platform` now includes `shopify` and `wordpress`","platforms":["publishing"],"url":"https://docs.zernio.com/changelog#1789643195430"},{"id":"1789642552923","date":"2026-09-17T10:55:52.923Z","type":"improvement","message":"Inbox message attachments can now include **template** content in `GET /v1/inbox/conversations/{conversationId}/messages`.\n\nThis lets clients render rich/template-style messages by reading structured data from the attachment.\n\nUpdates:\n• `attachments[].type` enum now includes `template`\n• When `attachments[].type` is `template`, `attachments[].payload` is present (template content like title/subtitle/image/buttons)","platforms":["inbox"],"url":"https://docs.zernio.com/changelog#1789642552923"},{"id":"1789639730045","date":"2026-09-17T10:08:50.045Z","type":"improvement","message":"GET `/v1/analytics` now provides more detail when a single-post lookup returns `202` (analytics sync pending).\n\nWhen `syncStatus` is `pending`, any platforms whose account failed its last analytics sync will include the failure in `platformAnalytics[].errorMessage`, and the top-level `message` will indicate that.\n\nKey fields:\n• `platformAnalytics[].syncStatus`: `synced` | `pending` | `unavailable`\n• `platformAnalytics[].errorMessage`: now also used for pending entries to show the most recent account sync error (cleared after the next successful sync)","platforms":["analytics"],"url":"https://docs.zernio.com/changelog#1789639730045"},{"id":"1789574797275","date":"2026-09-16T16:06:37.275Z","type":"new_feature","message":"`POST /v1/ads/boost` now supports attaching TikTok Smart+ boosts to an existing ad group via `adSetId`.\n\nThis lets you add a Spark ad (boosted post) into an existing Smart+ ad group (up to 30 Spark ads per ad group), inheriting the Smart+ campaign’s goal and budget.\n\nUse `smartPlus: true` and set `adSetId` (TikTok Smart+ ad group id). A non-Smart+ ad group id is rejected with `400`.\n\n`adSetId` is now: `Meta`, or `TikTok` with `smartPlus: true`.","platforms":["tiktok","tiktok-ads","ads"],"url":"https://docs.zernio.com/changelog#1789574797275"},{"id":"1789574317632","date":"2026-09-16T15:58:37.632Z","type":"new_feature","message":"Shopify accounts now support **Products** via new endpoints: `GET /v1/accounts/{accountId}/products`, `GET /v1/accounts/{accountId}/products/{productId}`, `PATCH /v1/accounts/{accountId}/products/{productId}`.\n\nYou can list products (cursor pagination), fetch a single product, and partially update product fields and variant pricing.\n\nList products supports `limit` (1-50), `cursor`, `status` (`active`/`draft`/`archived`), and `query` (Shopify product search syntax). Response includes `nextCursor`.\n\nUpdate product (`PATCH`) accepts any subset of `title`, `descriptionHtml`, `handle`, `vendor`, `productType`, `tags` (replaces full list), `status`, `seo`, and `variants` (updates only listed variant ids; supports `price` and `compareAtPrice`).\n\nShopify connect now requests scopes `read_content`, `write_content`, `read_products`, `write_products` (also applies to `POST /v1/connect/shopify/token`). Stores connected before product access was added may receive `403` (`insufficient_permissions`) until they reconnect via `GET /v1/connect/shopify`.","platforms":["commerce"],"url":"https://docs.zernio.com/changelog#1789574317632"},{"id":"1789570933413","date":"2026-09-16T15:02:13.413Z","type":"improvement","message":"Meta business login on `GET /v1/connect/{platform}/ads` now supports choosing the system-user permission level via `permissionLevel`.\n\nThis lets you request reduced access for owners who don’t want to grant full control, while still enabling campaign creation and reporting.\n\nUse `permissionLevel` (business login only):\n• `full` (default) – Full control (MANAGE)\n• `advertise` – Manage campaigns (ADVERTISE)\n\nApplies when `loginMode=business` for `platform` `facebook` or `instagram`. If the `advertise` configuration isn’t set up, the endpoint returns `503`.","platforms":["instagram","meta-ads","ads"],"url":"https://docs.zernio.com/changelog#1789570933413"},{"id":"1789559593486","date":"2026-09-16T11:53:13.486Z","type":"improvement","message":"Phone number purchase can now return a retryable carrier error via `503` on `POST /v1/phone-numbers/purchase` (and the deprecated alias `POST /v1/whatsapp/phone-numbers/purchase`).\n\nIf the carrier can’t accept the order at the moment, the API responds with `code` `CARRIER_UNAVAILABLE`; the number is not taken, so you can safely retry the same request shortly after.\n\nHandle:\n• HTTP `503`\n• Response `code`: `CARRIER_UNAVAILABLE`","platforms":["whatsapp","telephony"],"url":"https://docs.zernio.com/changelog#1789559593486"},{"id":"1789559479116","date":"2026-09-16T11:51:19.116Z","type":"new_feature","message":"TikTok boosts via `POST /v1/ads/boost` now support running Spark posts in a **Smart+** campaign using `smartPlus`.\n\nThis lets you create Smart+ Web Conversions (for `goal=conversions`) or Smart+ Lead Generation (for `goal=lead_generation`) instead of a regular TikTok campaign.\n\nSet `smartPlus` to `true` (TikTok only). Requirements:\n• `sparkAuthCode` is required\n• `promotedObject.pixelId` and `promotedObject.customEventType` are required\n\nNotes:\n• `app_promotion` isn’t available on a Spark post with Smart+\n• Sending `smartPlus` on non-TikTok platforms returns `400`","platforms":["tiktok","tiktok-ads","ads"],"url":"https://docs.zernio.com/changelog#1789559479116"},{"id":"1789557462445","date":"2026-09-16T11:17:42.445Z","type":"improvement","message":"POST `/v1/ads/boost` now lets you explicitly name the entities created by a boost via `campaignName` and `adSetName`.\n\nThis makes it easier to keep consistent naming across platforms instead of relying on the default `&lt;name&gt; - Campaign` / `&lt;name&gt; - Ad Group`.\n\nNew request fields:\n• `campaignName` (max 255) — exact campaign name (LinkedIn: campaign group)\n• `adSetName` (max 255) — exact ad-group level name (Meta: ad set; TikTok/Pinterest/Google: ad group; X: line item; LinkedIn: campaign)\n\nNote: both are ignored when using Meta attach mode via `adSetId`.","platforms":["tiktok","x","linkedin","pinterest","meta-ads","google-ads","tiktok-ads","linkedin-ads","pinterest-ads","x-ads","ads"],"url":"https://docs.zernio.com/changelog#1789557462445"},{"id":"1789556097417","date":"2026-09-16T10:54:57.417Z","type":"improvement","message":"Analytics endpoints now include additional TikTok business-lane metrics and breakdowns.\n\nYou can now read and store:\n• `websiteClicks` (also included in `clicks`)\n• `impressionSources` (object of surface shares)\n• `audienceTypes` (object of viewer-type shares)\n\nChanges by endpoint:\n• `GET /v1/analytics`: `sortBy` adds `website_clicks`\n• `GET /v1/analytics/delta`: `metrics` now includes `websiteClicks`, `impressionSources`, `audienceTypes`\n• `GET /v1/analytics/post-timeline`: timeline rows now include `follows`, `completionRate`, `profileViews`, `websiteClicks`, `impressionSources`, `audienceTypes`\n\nNote: in `GET /v1/analytics/delta`, these new `metrics` fields are required in the schema; update any strict validators/typed models accordingly.","platforms":["tiktok","tiktok-ads","analytics"],"url":"https://docs.zernio.com/changelog#1789556097417"},{"id":"1789548044575","date":"2026-09-16T08:40:44.575Z","type":"improvement","message":"`POST /v1/sms/registrations` can now return `409` when the carrier brand already exists and failed identity verification (e.g., a previously rejected registration with the same EIN or legal name).\n\nThis helps prevent accidentally filing (and being billed for) a duplicate brand/registration when the fix should be applied to the existing rejected brand.\n\nHandle the new response:\n• `409` - brand already exists at carrier + failed identity verification","platforms":["telephony"],"url":"https://docs.zernio.com/changelog#1789548044575"},{"id":"1789547303231","date":"2026-09-16T08:28:23.231Z","type":"improvement","message":"Meta ad creation now supports AI media disclosure via `aiDisclosure` on `POST /v1/ads/create` and `POST /v1/ads/creatives`.\n\nThis lets you explicitly set Meta’s “Ad includes media created or edited with AI” self-disclosure on the creative (and therefore the ad), instead of relying on Meta’s default.\n\nSet `aiDisclosure` to:\n• `OPT_IN` - disclose AI-created/edited media\n• `OPT_OUT` - explicitly declare no AI media\n\nApplies to Meta only (facebook/instagram). Omit the field to keep Meta’s default behavior.","platforms":["instagram","meta-ads","ads"],"url":"https://docs.zernio.com/changelog#1789547303231"},{"id":"1789496025449","date":"2026-09-15T18:13:45.449Z","type":"improvement","message":"Google Ads 429 errors now include a new `details.budgetScope` to distinguish Zernio’s own Google Ads operations-budget limits from upstream Google quota issues.\n\nUse this to decide whether to reschedule your own workload vs wait for a platform-wide reset.\n\nNew field:\n• `details.budgetScope`: `user` | `platform`\n\nNotes:\n• Present only on Zernio Google Ads operations-budget 429s\n• Never set alongside `details.quotaExhausted` (which indicates upstream Google quota)","platforms":["google-ads","ads"],"url":"https://docs.zernio.com/changelog#1789496025449"},{"id":"1789487653211","date":"2026-09-15T15:54:13.211Z","type":"improvement","message":"Comment webhooks now populate `comment.author.isOwnAccount` for `platform=tiktok` (inferred).\n\nThis lets you reliably detect and filter comments authored by the connected TikTok account, including comments created through the API.\n\nUse `comment.author.isOwnAccount` (boolean; may be absent when not evaluated). Applies to `event=comment.received`.","platforms":["tiktok","inbox","webhooks"],"url":"https://docs.zernio.com/changelog#1789487653211"},{"id":"1789486552447","date":"2026-09-15T15:35:52.447Z","type":"breaking_change","message":"Country results from `GET /v1/ads/targeting/search` are now normalized across platforms: when `dimension=geo` and a result has `type=country`, its `id` is the ISO 3166-1 alpha-2 code (e.g. `NL`) instead of a platform-specific opaque id.\n\nThis makes country targeting consistent with `targeting.countries`, but clients that stored/expected platform country ids may need to update.\n\nKey behavior:\n• `dimension=geo` + `type=country` ⇒ `id` is ISO code for `targeting.countries`\n• Non-country geo results keep platform ids for `regions`/`cities`/`zips`/`metros`\n• LinkedIn geo results now return `type=country` (ISO id) for countries, otherwise `type=region` with `urn:li:geo:*`\n• TikTok geo results now return country ids as ISO codes; other levels remain numeric ids\n• Google geo results now return country ids as ISO codes; other levels remain numeric criterion ids","platforms":["tiktok","linkedin","google-ads","tiktok-ads","linkedin-ads","ads"],"url":"https://docs.zernio.com/changelog#1789486552447"},{"id":"1789484229754","date":"2026-09-15T14:57:09.754Z","type":"improvement","message":"KYC document submissions now accept an optional `issuedAt` date when referencing an uploaded document.\n\nThis helps pre-review and submission handle regulator “document age” windows (e.g., proof of address) by trusting the provided date over PDF parsing.\n\nYou can now include `issuedAt` (format: `YYYY-MM-DD`) in:\n• `POST /v1/phone-numbers/kyc` → `documents[]` (when using `documentId`)\n• `POST /v1/phone-numbers/kyc/review-packet` → `docs[]`\n• `POST /v1/phone-numbers/{id}/remediate/respond` → `documents[]`","platforms":["telephony"],"url":"https://docs.zernio.com/changelog#1789484229754"},{"id":"1789471600747","date":"2026-09-15T11:26:40.747Z","type":"improvement","message":"Phone number availability now returns `soldOutAreas` on `GET /v1/phone-numbers/availability` (and the deprecated alias `GET /v1/whatsapp/phone-numbers/availability`).\n\nThis lets you show recently-sold-out areas and either pre-order a specific area or watch for it to come back.\n\nNew response field:\n• `soldOutAreas[]` with `ndc`, `name`, `preOrderable`\n\nKYC can now pre-order a specific sold-out area:\n• `POST /v1/phone-numbers/kyc` (and `/v1/whatsapp/phone-numbers/kyc`) adds `preOrder` (boolean)\n• Use with `areaCode` to pre-order an area listed in `soldOutAreas` when it has no stock\n\nStock watches can now target an area:\n• `POST /v1/phone-numbers/stock-watches` adds `areaCode` (requires `numberType`)\n• `PhoneNumberStockWatch` now includes `areaCode`\n• `phone_number.stock_available` webhook payload may include `stock.areaCode` and `stock.areaName` when an area watch fires","platforms":["whatsapp","telephony","webhooks"],"url":"https://docs.zernio.com/changelog#1789471600747"},{"id":"1789471481850","date":"2026-09-15T11:24:41.850Z","type":"improvement","message":"POST `/v1/inbox/reviews/{reviewId}/reply` can now return `400` when Facebook refuses a reply for a review.\n\nThis is a permanent failure for that review (typically Meta error 12; the review may already have a Page reply). Do not retry.\n\nHandle:\n• `400` with `code=platform_api_error` and `platform=facebook` (details echoed in `platformError`)","platforms":["facebook","inbox"],"url":"https://docs.zernio.com/changelog#1789471481850"},{"id":"1789469984216","date":"2026-09-15T10:59:44.216Z","type":"improvement","message":"Error responses for Google Ads `429` can now include quota context in `ErrorResponse.details`.\n\nThis lets you detect when the failure is Google Ads quota exhaustion (not a Zernio rate limit) so you can keep calling other platforms instead of backing off globally.\n\nNew fields:\n• `details.quotaExhausted` (`true` when Google Ads quota is spent)\n• `details.quotaScope` = `DEVELOPER` | `ACCOUNT` (when Google provides the scope)","platforms":["google-ads","ads"],"url":"https://docs.zernio.com/changelog#1789469984216"},{"id":"1789466943494","date":"2026-09-15T10:09:03.494Z","type":"new_feature","message":"TikTok now supports searching and attaching **location tags**.\n\nUse `GET /v1/accounts/{accountId}/tiktok/locations` with required `query` to fetch the closest matching places, then include the selected tag when creating a post.\n\nPost fields added to `TikTokPlatformData`:\n• `locationId` + `locationName` (both required together)\n• `isAdsOnly` (publish as “Only show in ads” / Spark Ad-only)\n\nNotes: `locationId`/`isAdsOnly` apply to TikTok for Business–connected accounts and video posts only, and are ignored on drafts (`draft: true`).","platforms":["tiktok","tiktok-ads","ads","publishing"],"url":"https://docs.zernio.com/changelog#1789466943494"},{"id":"1789461819371","date":"2026-09-15T08:43:39.371Z","type":"new_feature","message":"Google Ads now supports updating radius (proximity) targeting on existing ads via `PUT /v1/ads/{adId}`.\n\nYou can replace the campaign’s radius set independently from place-based geo targeting, and you can remove radius targeting entirely.\n\nUse `targeting.customLocations` (Google only):\n• Send a list to replace all radius targets\n• Send `[]` to drop radius targeting entirely\n\nNote: `customLocations` is its own set (separate from `targeting.locations`/`targeting.countries` etc.).","platforms":["google-ads","ads"],"url":"https://docs.zernio.com/changelog#1789461819371"},{"id":"1789407830077","date":"2026-09-14T17:43:50.077Z","type":"new_feature","message":"Google Ads campaigns now support reading and replacing **ad schedules (dayparting)**.\n\nUse this to fetch the current schedule (including whether the campaign serves 24/7) and to fully replace the schedule windows (send an empty array to clear dayparting).\n\nNew endpoints:\n• `GET /v1/ads/campaigns/{campaignId}/ad-schedule` (optional `includePerformance`, `windowDays`, `fromDate`, `toDate`; optional `platform=google`)\n• `PUT /v1/ads/campaigns/{campaignId}/ad-schedule` (body requires `schedule`; `schedule: []` clears)\n\nSchedule windows use `dayOfWeek` = `MONDAY`/`TUESDAY`/`WEDNESDAY`/`THURSDAY`/`FRIDAY`/`SATURDAY`/`SUNDAY`, with `startHour`/`startMinute` and `endHour`/`endMinute` (quarter-hours only; `endHour=24` means midnight). Optional `bidModifier` is `0.1`-`10.0` (or `null` to use the campaign bid).\n\nThe read response includes `servesAroundTheClock`; when `includePerformance=true` it also returns delivery splits in `performance.byDayOfWeek` and `performance.byHour`.","platforms":["google-ads","ads","analytics"],"url":"https://docs.zernio.com/changelog#1789407830077"},{"id":"1789407655081","date":"2026-09-14T17:40:55.081Z","type":"breaking_change","message":"TikTok Commercial Music trending results now return a publishable sound id, and `musicSoundInfo.musicSoundId` must use it.\n\nIn `GET /v1/accounts/{accountId}/tiktok/commercial-music`, the track `id` is now the value to send as `musicSoundId` (a song clip id). TikTok rejects the `commercialMusicId` at publish time.\n\nKey fields:\n• Track publish id: `tracks[].id`\n• Reference only: `tracks[].commercialMusicId`\n• Optional clip id (also accepted): `tracks[].clip.id`\n\nAlso updated: `TikTokPlatformData.musicSoundInfo.musicSoundId` now expects the track `id` from this endpoint (not the commercial music id).","platforms":["tiktok","tiktok-ads","publishing"],"url":"https://docs.zernio.com/changelog#1789407655081"},{"id":"1789403491505","date":"2026-09-14T16:31:31.505Z","type":"new_feature","message":"TikTok now supports selecting Commercial Music Library tracks for Business-connected accounts.\n\nYou can fetch the current trending CML tracks with `GET /v1/accounts/{accountId}/tiktok/commercial-music` (optionally filter by `countryCode`) and use the returned track `id` when creating a post.\n\nAttach a track on post creation via `platformSpecificData.tiktokSettings.musicSoundInfo`:\n• `musicSoundId`\n• `musicSoundVolume` (0–100, default 50)\n• `musicSoundStart` / `musicSoundEnd` (ms)\n\nIf you attach a commercial track, you can also set `videoOriginalSoundVolume` (0–100).\n\nNote: `musicSoundInfo` applies to accounts connected through the TikTok for Business app; it’s ignored on drafts (`draft: true`).","platforms":["tiktok","publishing"],"url":"https://docs.zernio.com/changelog#1789403491505"},{"id":"1789398451128","date":"2026-09-14T15:07:31.128Z","type":"improvement","message":"Comment automation logs now include more detail for failed DMs via `platformError` and `privateReplyConsumed` on:\n• `GET /v1/comment-automations/{automationId}` (embedded `logs`)\n• `GET /v1/comment-automations/{automationId}/logs`\n\nThis helps you distinguish platform-level failure reasons and detect when an Instagram comment’s single private reply was already spent.\n\nNew fields:\n• `platformError.code`, `platformError.subcode` (present when the platform provides them)\n• `privateReplyConsumed` = `true` when the failed send consumed the comment’s private reply (Instagram only; absent for direct DMs/Facebook/older rows).","platforms":["instagram","inbox"],"url":"https://docs.zernio.com/changelog#1789398451128"},{"id":"1789393870739","date":"2026-09-14T13:51:10.739Z","type":"new_feature","message":"Meta targeting now supports **country groups** (continents/trade blocs) for both inclusion and exclusion.\n\nThis lets you target or exclude broad regions (e.g. EEA, Europe) without listing every country.\n\nUse `countryGroups` (Meta only) with values like `europe`, `north_america`, `eea`, `nafta`, `apec`.\n\nTo exclude, set `targeting.excludedLocations.countryGroups` (same enum values).","platforms":["meta-ads","ads"],"url":"https://docs.zernio.com/changelog#1789393870739"},{"id":"1789391626070","date":"2026-09-14T13:13:46.070Z","type":"new_feature","message":"`GET /v1/ads/targeting/search` now supports `geoType=country_group` when `dimension=geo`.\n\nThis lets you discover Meta continent/trade-bloc targeting groups and use them in targeting specs.\n\nUse:\n• `dimension=geo`\n• `geoType` = `all`, `country`, `country_group`, `region`, `city`, `subcity`, `neighborhood`, `place`, `zip`, `metro_area`, `geo_market`\n\nNew targeting field (Meta only): `countryGroups` (also accepted under `excludedLocations`).","platforms":["meta-ads","ads"],"url":"https://docs.zernio.com/changelog#1789391626070"},{"id":"1789380441115","date":"2026-09-14T10:07:21.115Z","type":"improvement","message":"Webhook payloads for **message.failed** now include richer error info in `error`.\n\nThis makes it easier to debug delivery failures (especially WhatsApp) without guessing from `code`/`message` alone.\n\nNew fields:\n• `error.details` - platform-provided extended detail (WhatsApp: Meta `error_data.details`; absent on SMS)\n• `error.href` - platform documentation URL for the `code` (when provided)\n\nApplies to `WebhookPayloadMessageDeliveryStatus` events: `message.delivered`, `message.read`, `message.failed` (fields populated only on `message.failed`).","platforms":["whatsapp","webhooks"],"url":"https://docs.zernio.com/changelog#1789380441115"},{"id":"1789309058598","date":"2026-09-13T14:17:38.598Z","type":"new_feature","message":"You can now create YouTube playlists via `POST /v1/accounts/{accountId}/youtube-playlists`.\n\nThis 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.\n\nRequest body:\n• `title` (required)\n• `description` (optional)\n• `privacy`: `private` | `public` | `unlisted` (default `private`)\n\nNotes: requires YouTube OAuth scope `youtube` or `youtube.force-ssl`; costs 50 YouTube quota units; not idempotent (retrying can create duplicate playlists).","platforms":["youtube","publishing"],"url":"https://docs.zernio.com/changelog#1789309058598"},{"id":"1789152689952","date":"2026-09-11T18:51:29.952Z","type":"new_feature","message":"New endpoint **GET /v1/ads/campaigns/{campaignId}** returns a single Meta campaign live, so you no longer need to page `GET /v1/ads/campaigns` just to find one campaign.\n\nCall it with `campaignId` (Meta `platformCampaignId`) and `accountId`. Optionally override the Graph projection with `fields` (passed through to Meta; unknown fields return Meta’s `400`).\n\nKey params:\n• `campaignId` (path)\n• `accountId` (query, required)\n• `fields` (query, optional; e.g. `id,name,status,daily_budget`)\n\nNote: if the resolved connection can’t see the campaign, Meta returns `400` (not `404`).\n\nAlso: **PUT /v1/ads/{adId}** may now return `429` when Meta rate-limits writes to metered objects (notably ad creatives). Retry after ~30 seconds.","platforms":["meta-ads","ads"],"url":"https://docs.zernio.com/changelog#1789152689952"},{"id":"1789151592342","date":"2026-09-11T18:33:12.342Z","type":"breaking_change","message":"GET `/v1/ads/lead-forms/{formId}` now returns the full Meta form (including `thank_you_page`) and can be customized with a Meta Graph field selection.\n\nUse `fields` (Meta only) to override the default projection so newly-added Meta fields are reachable without an API change (e.g. `name,thank_you_page{title,body,button_type,website_url}`). Invalid selections are rejected with `400`.\n\nMeta lead forms are now strictly scoped to the Page managed by `accountId`: reading or archiving a form on any other Page returns `404`.\n\nAlso applies to DELETE `/v1/ads/lead-forms/{formId}` (archive): you may now receive `404` when the form isn’t on the managed Page.","platforms":["facebook","meta-ads","ads"],"url":"https://docs.zernio.com/changelog#1789151592342"},{"id":"1789147891767","date":"2026-09-11T17:31:31.767Z","type":"breaking_change","message":"TikTok connect behavior changed on `GET /v1/connect/{platform}`: TikTok connections now go through the **TikTok for Business** app, and each profile supports **one TikTok account**.\n\nConnecting 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.\n\nHandle the new redirect error `missing_tiktok_permissions` (user-fixable): the authorization omitted required permissions, so nothing changes and the existing connection keeps working. Re-run the flow and accept all permissions.\n\nKey fields:\n• Path: `platform=tiktok`\n• Query: `profileId`, `redirect_url`, optional `headless`\n• Redirect error enum now includes: `missing_tiktok_permissions`","platforms":["tiktok","inbox","analytics"],"url":"https://docs.zernio.com/changelog#1789147891767"},{"id":"1789147179299","date":"2026-09-11T17:19:39.299Z","type":"breaking_change","message":"Google Ads behavior changed for campaign status + targeting.\n\n**Campaign status on Google now cascades on resume** via `PUT /v1/ads/campaigns/{campaignId}/status`.\nSetting `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.\n\n**Campaign targeting reads now return real device bid adjustments** via `GET /v1/ads/campaigns/{campaignId}/targeting`.\n`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).\n\n**Device targeting edits are stricter on Google** via `PUT /v1/ads/campaigns/{campaignId}/targeting`.\nWhen 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`.","platforms":["google-ads","ads"],"url":"https://docs.zernio.com/changelog#1789147179299"},{"id":"1789146580620","date":"2026-09-11T17:09:40.620Z","type":"new_feature","message":"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 `leadgen` is missing and lead ads stop arriving.\n\nRead current subscription:\n• `GET /v1/accounts/{accountId}/webhook-subscription`\nReturns `appSubscribed`, `leadgen`, `subscribedFields`, and `warning` (only when `leadgen` is missing).\n\nRe-subscribe (re-sends full field set to Meta and reads back what was actually granted):\n• `POST /v1/accounts/{accountId}/webhook-subscription`\nReturns `resubscribed`, plus `appSubscribed`, `leadgen`, `subscribedFields`, and optional `warning`.\n\nBoth endpoints can return `409` when the connection has no selected Page; the POST can return `502` if Meta rejects the subscription.","platforms":["facebook","ads","webhooks"],"url":"https://docs.zernio.com/changelog#1789146580620"},{"id":"1789146419137","date":"2026-09-11T17:06:59.137Z","type":"breaking_change","message":"Meta **Promotion** is no longer supported in the Ads API.\n\nMeta 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.\n\nChanges:\n• `POST /v1/ads/create`: `promotion` is rejected (400) on all shapes\n• `POST /v1/ads/creatives`: `promotion` is rejected (400)\n• `PUT /v1/ads/{adId}`: `creative.promotion` is not supported; any object is rejected (400)\n• `GET /v1/ads/{adId}`: removed query param `refreshPromotion`\n\nUse `creativeFeatures` (values `OPT_IN` | `OPT_OUT`) for Advantage+ enhancements; set Promotions directly in Meta Ads Manager instead.","platforms":["meta-ads","ads"],"url":"https://docs.zernio.com/changelog#1789146419137"},{"id":"1789145249699","date":"2026-09-11T16:47:29.699Z","type":"improvement","message":"Analytics post items now include **ad detection** via `isAd` in `AnalyticsListResponse.posts[]`.\n\nUse 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).\n\nNew field:\n• `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).","platforms":["tiktok","linkedin","tiktok-ads","linkedin-ads","ads","analytics"],"url":"https://docs.zernio.com/changelog#1789145249699"},{"id":"1789145033219","date":"2026-09-11T16:43:53.219Z","type":"improvement","message":"TikTok posting behavior is now clarified for TikTok for Business accounts when using `videoCoverImageUrl` and `videoMadeWithAi`.\n\nFor 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.\n\nAlso, `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.\n\nKey fields:\n• `videoCoverImageUrl`\n• `videoMadeWithAi`\n• `draft`","platforms":["tiktok","tiktok-ads","publishing"],"url":"https://docs.zernio.com/changelog#1789145033219"},{"id":"1789143486326","date":"2026-09-11T16:18:06.326Z","type":"new_feature","message":"PUT `/v1/ads/{adId}` now supports updating **Google Ads creatives** beyond Search: you can update **Display** responsive ads and **Performance Max** asset groups.\n\nGoogle creative updates are now dispatched by the ad’s `advertisingChannelType`, and unsupported creative fields for a channel return `422`.\n\nKey request fields:\n• Google Search + Display: `headlines`, `descriptions`, `finalUrls`\n• Google Display (additional under `creative`): `creative.longHeadline`, `creative.businessName`, `creative.imageUrl`, `creative.squareImageUrl`\n• Google Performance Max: `assetGroup` (schema `GooglePmaxAssetGroupUpdate`) with fields like `finalUrl`, `headlines`, `descriptions`, `businessName`, `images.landscape`/`images.square`/`images.logo`, `youtubeVideoIds`\n\nChannel validation highlights:\n• Display rejects pinned positions (no `pinnedField`)\n• `assetGroup` returns `422` on non-Performance-Max ads; other Google-only creative fields return `422` when the channel can’t carry them","platforms":["youtube","google-ads","ads"],"url":"https://docs.zernio.com/changelog#1789143486326"},{"id":"1789142022276","date":"2026-09-11T15:53:42.276Z","type":"new_feature","message":"Google Ads ad updates now support editing **campaign-level** geo + language targeting via `PUT /v1/ads/{adId}`.\n\nYou 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.\n\nKey fields:\n• `targeting.locations`: country-code array or { `countries`, `regions`, `cities`, `zips`, `metros</code } (full replacement)\n• `targeting.languages`: [\"en\", \"de\", \"zh_CN\", ...] (full replacement)\n\nBehavior/validation:\n• Empty `locations` now returns `400` (omit `locations` to leave targeting unchanged)\n• `customLocations` (radius targeting) returns `422`\n• Don’t mix `targeting.locations` with top-level geo fields (`countries`/`regions`/`cities`/`zips`/`metros`): mixing returns `400`\n• Location/language updates are applied atomically in one Google mutate (no half-applied sets); negative (excluded) locations are left untouched","platforms":["google-ads","ads"],"url":"https://docs.zernio.com/changelog#1789142022276"},{"id":"1789141265119","date":"2026-09-11T15:41:05.119Z","type":"new_feature","message":"LinkedIn B2B targeting facets can now be searched via `GET /v1/ads/targeting/search` using new `dimension` values.\n\nThis lets you resolve LinkedIn facet values to the exact URNs to pass into `TargetingSpec`, so you don’t need to assemble URNs manually.\n\nUse `dimension`:\n• `industry` → feeds `TargetingSpec.industries`\n• `jobFunction` → feeds `TargetingSpec.jobFunctions`\n• `seniority` → feeds `TargetingSpec.seniorities`\n• `companySize` → feeds `TargetingSpec.companySizes`\n\nReturned `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)`).","platforms":["linkedin","linkedin-ads","ads"],"url":"https://docs.zernio.com/changelog#1789141265119"},{"id":"1789126851645","date":"2026-09-11T11:40:51.645Z","type":"breaking_change","message":"`comment.received` webhook payloads now include Threads and no longer include X/Twitter.\n\nYou 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.\n\nUpdate `comment.platform` enum to:\n• `instagram`, `facebook`, `threads`, `youtube`, `linkedin`, `bluesky`, `reddit`, `tiktok`","platforms":["instagram","threads","tiktok","x","linkedin","youtube","reddit","bluesky","inbox","webhooks"],"url":"https://docs.zernio.com/changelog#1789126851645"},{"id":"1789063718394","date":"2026-09-10T18:08:38.394Z","type":"improvement","message":"Meta campaign budget updates via `PUT /v1/ads/campaigns/{campaignId}` now check the **live** campaign budget mode before applying changes.\n\nThis prevents stale local ABO/CBO state from incorrectly blocking updates, and a successful update will also repair local mirrored budget fields.\n\nKey behavior:\n• If the live campaign is CBO, sending `budget` (with `type` = `daily` | `lifetime`) can succeed even if local data previously indicated ABO.\n• A live ABO campaign still returns `409` for campaign-level `budget` edits (use the ad-set budget endpoint instead).","platforms":["meta-ads","ads"],"url":"https://docs.zernio.com/changelog#1789063718394"},{"id":"1789061308981","date":"2026-09-10T17:28:28.981Z","type":"new_feature","message":"Meta **placement asset customization** on `POST /v1/ads/create` now supports per-placement copy overrides.\n\nYou can pin different `headline`, `body`, and `description` per placement rule while keeping `linkUrl` and `callToAction` shared.\n\nUse:\n• `placementAssets.defaultImageUrl`\n• `placementAssets.rules[].placements`\n• `placementAssets.rules[].imageUrl`\n• `placementAssets.rules[].headline`\n• `placementAssets.rules[].body`\n• `placementAssets.rules[].description`\n\nAlso updated: `validateOnly: true` now supports validating `placementAssets` for all-image setups (video placement validation still requires uploads).","platforms":["meta-ads","ads","publishing"],"url":"https://docs.zernio.com/changelog#1789061308981"},{"id":"1789056684539","date":"2026-09-10T16:11:24.539Z","type":"new_feature","message":"You can now purchase an **exact phone number** by passing `phoneNumber` to `POST /v1/phone-numbers/purchase` (and the deprecated alias `POST /v1/whatsapp/phone-numbers/purchase`).\n\nPick a number from `GET /v1/phone-numbers/available`, then buy that specific E.164 number instead of getting an auto-assigned one.\n\nNew request field:\n• `phoneNumber` (E.164, e.g. `+14155552671`)\n\nNew purchase failure case:\n• `409` with `code` = `PHONE_NUMBER_UNAVAILABLE` (number no longer available / rejected at buy-time)\n\n`GET /v1/phone-numbers/available` results now include:\n• `numbers[].locality`\n• `numbers[].bestEffort` (true if carrier returned a number outside your requested filters)\n\nNote: regulated countries that return `202` `status` = `kyc_required` do not support buying an exact `phoneNumber`.","platforms":["whatsapp","telephony"],"url":"https://docs.zernio.com/changelog#1789056684539"},{"id":"1789055220191","date":"2026-09-10T15:47:00.191Z","type":"improvement","message":"GET `/v1/analytics` now supports sorting by two new metrics: `completion_rate` and `profile_views`.\n\nThis lets you rank posts by TikTok (TikTok for Business app) completion and profile traffic directly in analytics queries.\n\nUse `sortBy`:\n• `completion_rate`\n• `profile_views`\n\nThe delta feed `GET /v1/analytics/delta` also includes these fields in `metrics`: `completionRate`, `profileViews`.","platforms":["tiktok","tiktok-ads","analytics"],"url":"https://docs.zernio.com/changelog#1789055220191"},{"id":"1789051233590","date":"2026-09-10T14:40:33.590Z","type":"new_feature","message":"POST `/v1/inbox/conversations` now supports WhatsApp **CAROUSEL** template sends via `templateCards`.\n\nThis 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).\n\nUse `templateCards` (max 10), each item:\n• `cardIndex` (0–9)\n• `params` (card body variable values)\n• `headerMedia`: `type` = `image` | `video` | `document` + (`link` or `id`)\n• `buttons` (max 2): `subType` = `quick_reply` | `url`\n\nInvalid carousel card overrides now return `400` with `code` = `INVALID_TEMPLATE_CARD_PARAM`.","platforms":["whatsapp","inbox"],"url":"https://docs.zernio.com/changelog#1789051233590"},{"id":"1789050793523","date":"2026-09-10T14:33:13.523Z","type":"breaking_change","message":"Meta Ads **business login** connection flow now supports an automatic Page picker when multiple Facebook Pages are granted.\n\nIf a business-login connect results in multiple Pages, `GET /v1/connect/meta-ads/callback` may redirect to the Page picker instead of completing immediately.\n\nFacebook Page selection endpoints now support a new business-login grant token:\n• `GET /v1/connect/facebook/select-page` can be called with `selectionToken` (instead of `profileId` + `tempToken`)\n• `POST /v1/connect/facebook/select-page` now accepts `{ selectionToken, pageId }` to complete the business-login Meta Ads connection\n\nNotes:\n• `selectionToken` is encrypted and expires in `10` minutes\n• Business selection requires session/bearer auth for the initiating user; `X-Connect-Token` is **not** accepted for the business `selectionToken` flow","platforms":["facebook","meta-ads","ads"],"url":"https://docs.zernio.com/changelog#1789050793523"},{"id":"1789049932115","date":"2026-09-10T14:18:52.115Z","type":"improvement","message":"`POST /v1/ads/campaigns` and `POST /v1/ads/create` now default Meta `buyingType` to `AUCTION`.\n\nIf you omit `buyingType`, Zernio will treat it as `AUCTION` and explicitly send it on new Meta campaigns (including `validateOnly` requests).\n\nSet `buyingType` to:\n• `AUCTION`\n• `RESERVED` (Reach & Frequency; requires `rfPredictionId` on `/v1/ads/create`)","platforms":["meta-ads","ads"],"url":"https://docs.zernio.com/changelog#1789049932115"},{"id":"1789048789596","date":"2026-09-10T13:59:49.596Z","type":"new_feature","message":"You can now **pin/unpin TikTok comments** (TikTok for Business–connected accounts only).\n\nPinning lets you keep a top-level comment at the top of the post’s comment section.\n\nNew endpoints:\n• `POST /v1/inbox/comments/{postId}/{commentId}/pin` with body `accountId`\n• `DELETE /v1/inbox/comments/{postId}/{commentId}/pin` with query `accountId`\n\nTikTok Inbox support was also expanded:\n• `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.\n• `POST /v1/inbox/comments/{postId}/{commentId}/hide` and `DELETE /v1/inbox/comments/{postId}/{commentId}/hide` now support TikTok (TikTok for Business).\n\nBehavior change to handle:\n• `POST /v1/posts/sync-external` may now return `409` with `code` `ads_connection_required` when the account is inactive/needs reconnection (stop scheduled retries until reconnected).\n\nWebhook schema update:\n• `WebhookPayloadComment.comment.platform` now includes `tiktok` (TikTok events may include only author `id`, without username/picture/owner flag).","platforms":["tiktok","tiktok-ads","inbox","webhooks"],"url":"https://docs.zernio.com/changelog#1789048789596"},{"id":"1789045384728","date":"2026-09-10T13:03:04.728Z","type":"new_feature","message":"New Tools endpoint: `GET /v1/tools/tiktok/download`.\n\nIt lets you fetch a TikTok video download URL or list available formats (uses Tools API access + Tools rate limit).\n\nKey params:\n• `url` (required)\n• `action`: `download` | `formats` (default `download`)\n• `formatId` (optional; from `formats` response)\n\nNotes:\n• Provider-side blocks/gateway failures can return `503`; unavailable video returns `404`.\n\nAlso: 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.","platforms":["tiktok","linkedin"],"url":"https://docs.zernio.com/changelog#1789045384728"},{"id":"1789042662718","date":"2026-09-10T12:17:42.718Z","type":"minor","message":"Added 4 new endpoints:\n\n`GET /v1/ads/pixels` - List TikTok ad pixels\n`GET /v1/ads/partnership-content` - List partnership ad content\n`GET /v1/ads/partnership-permissions` - List partnership permissions\n`POST /v1/ads/partnership-permissions` - Set partnership permission","platforms":["tiktok","tiktok-ads","ads"],"url":"https://docs.zernio.com/changelog#1789042662718"},{"id":"1789036361317","date":"2026-09-10T10:32:41.317Z","type":"new_feature","message":"Meta Ads now supports **Business Login** (system-user tokens) as an opt-in flow.\n\nUse it to connect/reconnect `metaads` without creating/requiring a posting parent account, and to select a granted Facebook Page for creatives/lead forms.\n\nStart the flow via `GET /v1/connect/{platform}/ads` with:\n• `platform`: `facebook` | `instagram`\n• `loginMode`: `business` (default: `classic`)\n• optional `pageId`: `^\\d+$`\n\nComplete the OAuth redirect via the new callback endpoint:\n• `GET /v1/connect/meta-ads/callback` with `state` (required) and `code` (or `error`)\n• On success it redirects (307) with `connected=metaads`, `profileId`, `accountId`\n\nNotable behavior/errors:\n• Business login always returns an `authUrl`; returns `503` if not configured\n• Reconnect grant mismatches can return `409`\n• `GET /v1/ads/accounts` adds Meta fields `businessId` and `businessName`\n• Lead forms/leads endpoints now accept a `metaads` business-login accountId (uses system-user token)","platforms":["instagram","facebook","meta-ads","ads"],"url":"https://docs.zernio.com/changelog#1789036361317"},{"id":"1789035969882","date":"2026-09-10T10:26:09.882Z","type":"new_feature","message":"Google Ads now supports **Performance Max** creation via `POST /v1/ads/create`, and you can list created asset groups via `GET /v1/ads/campaigns/{campaignId}/asset-groups`.\n\nThis 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.\n\nCreate PMax by setting:\n• `campaignType` = `pmax`\n• `assetGroup` (required)\n• `budgetType` = `daily` | `lifetime`\n• `status` = `PAUSED` (PMax rejects ACTIVE)\n\nKey `assetGroup` fields:\n• `finalUrl`\n• `headlines` (3-15)\n• `longHeadline`\n• `descriptions` (2-5)\n• `businessName`\n• `images.landscape`, `images.square`, `images.logo` (logo required)\n• `youtubeVideoId` (optional)\n\nList asset groups:\n• `GET /v1/ads/campaigns/{campaignId}/asset-groups` returns `assetGroups`, `cachedAt`, `stale` (may serve cached data when Google quota is exhausted).","platforms":["youtube","google-ads","ads"],"url":"https://docs.zernio.com/changelog#1789035969882"},{"id":"1789035465352","date":"2026-09-10T10:17:45.352Z","type":"new_feature","message":"New endpoint: **POST /v1/ads/accounts** creates a durable Meta ad account in the user’s business portfolio using their connected Meta Ads token.\n\nUse 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).\n\nRequired body fields: `accountId`, `businessId`, `name`, `currency`, `timezoneId` (Meta numeric timezone ID).\n\nOptional fields include `endAdvertiser`, `mediaAgency`, `partner` (default `NONE`), plus invoicing options like `invoice`, `invoiceGroupId`, `invoicingEmails`, `io`, `poNumber`, `fundingId`.\n\nResponse `201` returns `adAccountId` (e.g. `act_...`), `connectionUpdated`, `paymentMethodRequired`, `adsManagerUrl`, `nextSteps`, `warnings`.\n\nImportant: this operation is **not idempotent**. After a timeout or `502` with `details.creationStatus=unknown`, check Ads Manager before retrying.","platforms":["meta-ads","ads"],"url":"https://docs.zernio.com/changelog#1789035465352"},{"id":"1789032093968","date":"2026-09-10T09:21:33.968Z","type":"new_feature","message":"Phone number availability now indicates when an out-of-stock country/type can be **pre-ordered**.\n\nIf `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.\n\nNew fields:\n• `GET /v1/phone-numbers/availability` (+ deprecated alias `/v1/whatsapp/phone-numbers/availability`): `preOrderable`\n• `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`)\n• `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)\n\nNote: `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.","platforms":["whatsapp","telephony"],"url":"https://docs.zernio.com/changelog#1789032093968"},{"id":"1789030152702","date":"2026-09-10T08:49:12.702Z","type":"improvement","message":"WhatsApp messages can now include **location pins** in the `message.received` webhook via `metadata.location`.\n\nThis lets you read the shared coordinates directly from the webhook (the message `text` is only the preview like `📍 &lt;name&gt;`).\n\nNew field:\n• `metadata.location` with `latitude`, `longitude`, `name`, `address`\n\nAlso, `message.sent` webhook `metadata` now includes WhatsApp context so you can detect non-text sends without a GET:\n• `metadata.location`\n• `metadata.contacts`\n(Existing fields like `metadata.quotedMessageId` and `metadata.threadTs` are unchanged.)","platforms":["whatsapp","inbox","webhooks"],"url":"https://docs.zernio.com/changelog#1789030152702"},{"id":"1788977895300","date":"2026-09-09T18:18:15.300Z","type":"new_feature","message":"Google Ads now supports managing extension assets (sitelinks, callouts, structured snippets) at **account**, **campaign**, and **ad-group** levels, plus richer Google RSA reads/updates.\n\nNew asset endpoints:\n• Campaign assets: `GET /v1/ads/campaigns/{campaignId}/assets`, `PUT`, `DELETE`\n• Ad-group assets: `GET /v1/ads/ad-sets/{adSetId}/assets`, `POST`, `PUT`, `DELETE`\n• Account assets:\n  - Callouts: `GET /v1/ads/accounts/callouts`, `POST`, `PUT`, `DELETE`\n  - Sitelinks: `GET /v1/ads/accounts/sitelinks`, `POST`, `PUT`, `DELETE`\n  - Structured snippets: `GET /v1/ads/accounts/structured-snippets`, `POST`, `PUT`, `DELETE`\n\nKey request params:\n• `accountId` (required) and optional `customerId` (required when the connection has multiple customers)\n\nNotes:\n• List endpoints may return cache metadata: `cachedAt` and `stale`\n• Asset updates are in-place via `updates[]` (max 20) with `assetResourceName`; assets are shared, so edits affect all attachments\n• Removes detach only (assets themselves can’t be deleted)\n\nGoogle RSA changes:\n• `GET /v1/ads/{adId}` may now include top-level `cachedAt` and `stale` for Google RSA enrichment\n• `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`: `HEADLINE_1` | `HEADLINE_2` | `HEADLINE_3` and `DESCRIPTION_1` | `DESCRIPTION_2`.","platforms":["google-ads","ads"],"url":"https://docs.zernio.com/changelog#1788977895300"},{"id":"1788977326645","date":"2026-09-09T18:08:46.645Z","type":"new_feature","message":"TikTok Ads now supports **ad comment moderation** via new endpoints for replying, hiding/unhiding, and deleting comments.\n\nThis lets you moderate first-level TikTok ad comments directly from the Ads API (replies-to-replies are rejected; reply is not idempotent).\n\nNew endpoints:\n• `POST /v1/ads/{adId}/comments/{commentId}/reply` with `text`\n• `POST /v1/ads/{adId}/comments/{commentId}/hide` with `hidden` (`true`/`false`)\n• `DELETE /v1/ads/{adId}/comments/{commentId}`\n\nAll three support TikTok comment lookup window params `since`/`until` (max 30 days; defaults to last 30 days).\n\nAlso 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`.\n\nMeta Ads additions for app-promotion setup:\n• `GET /v1/ads/instagram-accounts` (discover IG ad identities)\n• `GET /v1/ads/advertisable-applications`\n• `GET /v1/ads/ios-fourteen-campaign-limits`\n\nCampaign creation update (Meta): `POST /v1/ads/campaigns` adds `validateOnly`, plus SKAdNetwork/app-promotion fields `isSkadnetworkAttribution`, `promotedObject`, and `buyingType` (`AUCTION`/`RESERVED`).","platforms":["instagram","tiktok","meta-ads","tiktok-ads","ads","inbox"],"url":"https://docs.zernio.com/changelog#1788977326645"},{"id":"1788975244519","date":"2026-09-09T17:34:04.519Z","type":"new_feature","message":"Meta ads now support explicit **Promotions** and Advantage+ **creative enhancements** across create/update flows, with a promotion readback status.\n\nYou 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.\n\nUse:\n• `POST /v1/ads/create`: `promotion`, `creativeFeatures` (also as defaults for `creatives[]`; per-item override supports `promotion: null`)\n• `POST /v1/ads/creatives`: `promotion`, `creativeFeatures` (response adds `promotion`, `promotionStatus`)\n• `PUT /v1/ads/{adId}`: `creative.promotion`, `creative.creativeFeatures` (send `promotion: null` to remove)\n\n`promotionStatus` values: `applied` | `not_returned` | `unavailable`\n\nAlso:\n• `GET /v1/ads/{adId}` adds `refreshPromotion=true|false` to read live Meta promotion metadata and include `promotionStatus`.\n• `POST /v1/ads/{adId}/duplicate` now preserves Meta-returned creative settings (including promotion metadata and creativeFeatures) when a native copy requires a creative rebuild.","platforms":["meta-ads","ads"],"url":"https://docs.zernio.com/changelog#1788975244519"},{"id":"1788973264780","date":"2026-09-09T17:01:04.780Z","type":"breaking_change","message":"Meta messaging boosts via `POST /v1/ads/boost` now require an explicit messaging CTA; `destinationType` no longer auto-selects one.\n\nThis prevents accidental messaging-boost behavior when you only intended to set an ad-set destination, and makes CTA/destination handling more predictable.\n\nKey changes:\n• Messaging boosts must use `goal`=`engagement` with `callToAction` in `WHATSAPP_MESSAGE` | `MESSAGE_PAGE` | `INSTAGRAM_MESSAGE`\n• `destinationType` is now independent for non-messaging CTAs; if you do use a messaging `callToAction`, any explicit `destinationType` must match\n• `whatsappPhoneNumber` now requires `callToAction`=`WHATSAPP_MESSAGE` (not just `destinationType`=`WHATSAPP`)\n\nAlso: `Ad.creative.whatsappPhoneNumber` is now returned (when provided) so you can read back the explicit WhatsApp number used on Meta boosts/messaging ads.","platforms":["instagram","facebook","whatsapp","meta-ads","ads","telephony"],"url":"https://docs.zernio.com/changelog#1788973264780"},{"id":"1788972989848","date":"2026-09-09T16:56:29.848Z","type":"new_feature","message":"Google Ads now supports **shared negative keyword lists** (create/list/get/update/delete), including attaching lists to campaigns and replacing the full keyword set.\n\nUse:\n• `GET /v1/ads/accounts/negative-keyword-lists` (cached ~10 min; may return `stale=true`)\n• `POST /v1/ads/accounts/negative-keyword-lists` (not idempotent)\n• `GET /v1/ads/accounts/negative-keyword-lists/{listId}`\n• `PUT /v1/ads/accounts/negative-keyword-lists/{listId}` (rename)\n• `DELETE /v1/ads/accounts/negative-keyword-lists/{listId}`\n• `PUT /v1/ads/accounts/negative-keyword-lists/{listId}/keywords` (replace full set; `keywords` max 5000)\n• `GET /v1/ads/campaigns/{campaignId}/negative-keyword-lists`\n• `PUT /v1/ads/campaigns/{campaignId}/negative-keyword-lists` (replace associations; `listIds` max 20)\n\nKey inputs:\n• `accountId` (required on account-scoped routes)\n• `customerId` (required when a connection has multiple customers)\n• `platform=google` (other platforms return 501)\n• Keyword `matchType`: `broad` | `phrase` | `exact`\n\nGoogle campaign updates now support **daily budget** via `PUT /v1/ads/campaigns/{campaignId}`. Shared budgets return 409 unless `allowSharedBudgetUpdate=true` is explicitly provided.\n\nCampaign 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.\n\nMeta boosts now support messaging boosts and WhatsApp number selection:\n• `POST /v1/ads/boost` adds `destinationType=INSTAGRAM_DIRECT` and `whatsappPhoneNumber` (E.164), and supports messaging CTAs (`WHATSAPP_MESSAGE` | `MESSAGE_PAGE` | `INSTAGRAM_MESSAGE`) with `goal=engagement`.\n\nMessaging/CTWA ad creation now supports reusing existing posts:\n• `POST /v1/ads/messaging` and `POST /v1/ads/ctwa</code accept `existingPostId` or `objectStoryId` (mutually exclusive with fresh creative fields), plus optional `whatsappPhoneNumber` for WhatsApp destination.","platforms":["instagram","facebook","whatsapp","meta-ads","google-ads","ads","telephony","analytics"],"url":"https://docs.zernio.com/changelog#1788972989848"},{"id":"1788970588734","date":"2026-09-09T16:16:28.734Z","type":"improvement","message":"The bootstrap flow for `GET /v1/analytics/delta` has changed: take the cursor _before_ loading your baseline.\n\nTo 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`.\n\nKey behavior:\n• Bootstrap: omit `cursor` → response has empty `data` + `nextCursor`\n• Resume: pass `cursor=&lt;nextCursor&gt;` verbatim (opaque)\n• Retention: cursors older than ~6 days return `400`; recover by repeating the same bootstrap order","platforms":["analytics"],"url":"https://docs.zernio.com/changelog#1788970588734"},{"id":"1788965331251","date":"2026-09-09T14:48:51.251Z","type":"new_feature","message":"Google Ads now supports the **attach** flow on `POST /v1/ads/create` via `adSetId`.\n\nYou 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.\n\nUse:\n• `adSetId` (Google AD GROUP id)\n• `campaignType`: `search` | `display` (default `display`)\n\nNotes:\n• `goal` is still required even though budget/targeting are inherited\n• In attach mode, `budgetAmount`/`budgetType` and bidding fields (`bidStrategy`, `bidAmount`, `portfolioBidStrategyId`) return `400`\n• For Search ad groups, set `campaignType` to `search` (otherwise it’s treated as Display and requires `images.landscape` + `images.square` + `businessName`)","platforms":["google-ads","ads"],"url":"https://docs.zernio.com/changelog#1788965331251"},{"id":"1788963519827","date":"2026-09-09T14:18:39.827Z","type":"breaking_change","message":"GET `/v1/ads/audiences/{audienceId}` response renamed `metaData` to `platformData`.\n\nThis field still contains fresh data from the underlying ads platform API (when available), but clients must now read `platformData` instead of `metaData`.\n\nAlso 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.","platforms":["ads"],"url":"https://docs.zernio.com/changelog#1788963519827"},{"id":"1788955681831","date":"2026-09-09T12:08:01.831Z","type":"new_feature","message":"WhatsApp BYO numbers now support Meta re-verification via 2 new endpoints: `POST /v1/accounts/{accountId}/whatsapp/request-code` and `POST /v1/accounts/{accountId}/whatsapp/verify-code`.\n\nUse 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).\n\nRequest a code with optional:\n• `method`: `SMS` | `VOICE`\n• `language` (Meta locale, e.g. `en_US`)\n\nVerify with:\n• `code` (6 digits; separators like `749-456` are accepted)\n\nNote: `request-code` is rate-limited to 1 request per 10 minutes per account and may return `429` with `Retry-After`.","platforms":["whatsapp","telephony"],"url":"https://docs.zernio.com/changelog#1788955681831"},{"id":"1788949870130","date":"2026-09-09T10:31:10.130Z","type":"breaking_change","message":"Reddit posting behavior has changed for `forceSelf` and `nativeVideo` in `RedditPlatformData`.\n\n`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`.\n\n`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.","platforms":["reddit","publishing"],"url":"https://docs.zernio.com/changelog#1788949870130"},{"id":"1788941349673","date":"2026-09-09T08:09:09.673Z","type":"new_feature","message":"WhatsApp connect now supports a **hosted Embedded Signup** flow to avoid double-selection when a Facebook login can access multiple WhatsApp numbers.\n\nStart 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).\n\nYou can co-brand the hosted page using:\n• `brandName`\n• `primaryColor` (hex `#RRGGBB`)\n• `language`: `en` | `es`\n\nNew endpoint: `GET /v1/connect/whatsapp/sdk-config` returns the Embedded Signup config (`appId`, `configId`, `branding`). Auth via API key or `X-Connect-Token`.\n\n`POST /v1/connect/whatsapp/embedded-signup` now also supports the hosted flow:\n• Auth via API key or `X-Connect-Token`\n• Request adds `redirectUrl` and `echoConnectToken`\n• Response adds `account` and optional `redirectUrl`\n\nHosted flow adds two WhatsApp error values you may see on redirect: `connection_cancelled`, `session_expired`.","platforms":["whatsapp"],"url":"https://docs.zernio.com/changelog#1788941349673"},{"id":"1788876880158","date":"2026-09-08T14:14:40.158Z","type":"improvement","message":"PUT `/v1/posts/{postId}` can now return `409` when the requested `scheduledFor` collides with another post already occupying that slot in the same queue (`code: queue_slot_conflict`).\n\nThis lets clients distinguish queue slot conflicts from generic validation errors and handle rescheduling explicitly.\n\nIf you hit `409`, you can:\n• choose a different `scheduledFor`\n• omit `scheduledFor` to let the queue pick the next open slot\n• send `queueId: null` to schedule outside the queue","platforms":["publishing"],"url":"https://docs.zernio.com/changelog#1788876880158"},{"id":"1788858461184","date":"2026-09-08T09:07:41.184Z","type":"new_feature","message":"Meta generic templates now support setting the image aspect ratio on Facebook via `imageAspectRatio`.\n\nThis 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.\n\nUse `template.imageAspectRatio` with:\n• `horizontal`\n• `square`\n\nApplies to:\n• `POST /v1/inbox/conversations/{conversationId}/messages` (generic `template`)\n• `CommentAutomationTemplate.imageAspectRatio`","platforms":["instagram","facebook","inbox"],"url":"https://docs.zernio.com/changelog#1788858461184"},{"id":"1788858273141","date":"2026-09-08T09:04:33.141Z","type":"new_feature","message":"WhatsApp templates now support configuring a per-template delivery TTL via `message_send_ttl_seconds`.\n\nThis 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.\n\nSet `message_send_ttl_seconds` on:\n• `POST /v1/whatsapp/templates`\n• `PATCH /v1/whatsapp/templates/{templateName}`\n• `PATCH /v1/whatsapp/templates/id/{templateId}`\n\nRead `message_send_ttl_seconds` from:\n• `GET /v1/whatsapp/templates`\n• `GET /v1/whatsapp/templates/{templateName}`\n• `GET /v1/whatsapp/templates/id/{templateId}`\n\nTTL ranges by category:\n• `AUTHENTICATION`: 30–900\n• `UTILITY`: 30–43200\n• `MARKETING`: 43200–2592000\n\nUse `-1` to restore the category default (for `AUTHENTICATION` and `UTILITY`).","platforms":["whatsapp"],"url":"https://docs.zernio.com/changelog#1788858273141"},{"id":"1788853904683","date":"2026-09-08T07:51:44.683Z","type":"breaking_change","message":"`GET /v1/ads/library` now supports searching Meta’s Ad Library without a connected account.\n\nMeta 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`.\n\nUse:\n• `platform`: `meta` | `linkedin` (required unless `accountId` is provided)\n• `accountId`: required for LinkedIn; optional for Meta (passing a Meta account also selects Meta)\n\nMeta-specific behavior changes:\n• Requests may return `429` due to shared Meta quota (back off ~1 minute)\n• New `503` when Meta Ad Library is unavailable on Zernio’s side (`PLATFORM_DISABLED`)\n\nAlso updated: `403` no longer includes Meta identity confirmation errors; it’s now “Ads access required” (legacy plans may need the Ads add-on).","platforms":["linkedin","meta-ads","linkedin-ads","ads"],"url":"https://docs.zernio.com/changelog#1788853904683"},{"id":"1788818726519","date":"2026-09-07T22:05:26.519Z","type":"improvement","message":"Several Google Ads endpoints now serve cached data (with fallback when Google quota is exhausted), and responses include freshness metadata.\n\nThese reads may no longer be strictly live; when quota is exhausted, the API can return the last-good copy instead of failing.\n\nNew response fields:\n• `cachedAt` (date-time | null) — when the data was fetched from Google\n• `stale` (boolean) — `true` when quota was exhausted and the response is a cached fallback\n\nApplies to:\n• `GET /v1/ads/search-terms`\n• `GET /v1/ads/bid-strategies`\n• `GET /v1/ads/campaigns/{campaignId}/bidding`\n• `GET /v1/ads/campaigns/{campaignId}/negative-keywords`\n• `GET /v1/ads/campaigns/{campaignId}/targeting`\n• `GET /v1/ads/accounts/callouts`\n• `GET /v1/ads/accounts` (Google only)\n• `GET /v1/ads/conversions/actions`\n\nAlso: `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).","platforms":["google-ads","ads"],"url":"https://docs.zernio.com/changelog#1788818726519"},{"id":"1788812399203","date":"2026-09-07T20:19:59.203Z","type":"new_feature","message":"Google Ads now supports **portfolio bid strategies** and live campaign bidding reads.\n\nYou 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.\n\nNew endpoints:\n• `GET /v1/ads/bid-strategies` (`accountId`, optional `customerId`, `fromDate`, `toDate`)\n• `POST /v1/ads/bid-strategies` (`accountId`, `name`, `type`, optional `targetCpa`/`targetRoas`)\n• `PATCH /v1/ads/bid-strategies/{strategyId}` (`accountId`, optional `name`, `type`, `targetCpa`, `targetRoas`)\n• `GET /v1/ads/campaigns/{campaignId}/bidding` (`accountId`, `platform=google`, optional `customerId`)\n\nAttach a portfolio strategy to Google campaigns using `portfolioBidStrategyId` on:\n• `POST /v1/ads/campaigns`\n• `PUT /v1/ads/campaigns/{campaignId}`\n• `POST /v1/ads/create`\n\nPortfolio strategy `type` values:\n• `TARGET_CPA`, `TARGET_ROAS`, `MAXIMIZE_CONVERSIONS`, `MAXIMIZE_CONVERSION_VALUE`\n\nAlso 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).","platforms":["google-ads","ads"],"url":"https://docs.zernio.com/changelog#1788812399203"},{"id":"1788810450236","date":"2026-09-07T19:47:30.236Z","type":"new_feature","message":"Google Ads now supports editing more campaign and account assets via new endpoints.\n\nYou 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).\n\nCampaign targeting:\n• `GET /v1/ads/campaigns/{campaignId}/targeting` (`platform=google`)\n• `PUT /v1/ads/campaigns/{campaignId}/targeting` body: `platform`=`google`, `targeting` with any of `devices`/`locations`/`languages` (each provided field replaces existing)\n• `devices` enum: `MOBILE`, `DESKTOP`, `TABLET`, `CONNECTED_TV`\n\nAd groups (ad sets):\n• `GET /v1/ads/ad-sets` (filters: `accountId`, `campaignId`, `platform`)\n• `POST /v1/ads/ad-sets` (Google-only) with optional `Idempotency-Key`\n\nAccount-level callouts (Google-only):\n• `GET /v1/ads/accounts/callouts`\n• `POST /v1/ads/accounts/callouts` (`callouts`: 1–25 chars, up to 20)\n• `DELETE /v1/ads/accounts/callouts` (remove by `assetId`)\n\nConversion actions (Google-only):\n• `GET /v1/ads/conversions/actions` (optional `customerId`, optional `type`)\n• `POST /v1/ads/conversions/actions` create `type`=`WEBPAGE` and returns `tagSnippets`\n\nAlso updated:\n• `POST /v1/ads/create` now supports Google campaign language targeting via `languages` (e.g. `[\"en\",\"de\"]`; unknown codes return 400)\n• `GET /v1/ads/targeting/search` adds `dimension`=`language` (Google-only) to resolve Google `language_constant` options","platforms":["google-ads","ads"],"url":"https://docs.zernio.com/changelog#1788810450236"},{"id":"1788805694620","date":"2026-09-07T18:28:14.620Z","type":"new_feature","message":"Google Ads Search keyword management is now available via new endpoints.\n\nYou 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.\n\nAdd keywords to an ad group with `POST /v1/ads/keywords` using:\n• `accountId`\n• `adSetId`\n• `keywords` (string = BROAD, or `{ text, matchType }` where `matchType` is `exact`/`phrase`/`broad`)\n• `negative` (boolean) to add ad-group-level negatives\n\nPause/enable a keyword with `PATCH /v1/ads/keywords/{keywordId}`:\n• `status`: `active` or `paused` (note: negative keywords can’t be paused/enabled)\n\nRemove a keyword with `DELETE /v1/ads/keywords/{keywordId}`.\n\nCampaign-level negative keywords:\n• List: `GET /v1/ads/campaigns/{campaignId}/negative-keywords`\n• Replace full set: `PUT /v1/ads/campaigns/{campaignId}/negative-keywords` with `keywords` (send an empty array to clear all)\n\nAlso 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.","platforms":["google-ads","ads"],"url":"https://docs.zernio.com/changelog#1788805694620"},{"id":"1788799606639","date":"2026-09-07T16:46:46.639Z","type":"improvement","message":"`GET /v1/ads/timeline` now returns `allConversions` per day, and clarifies Google customer-level reporting when `adAccountId` is a Google customer id.\n\nThis lets you chart Google’s “all conversions” alongside `conversions` (tracked conversions), matching Google Ads’ `metrics.all_conversions`.\n\nNew field in each `rows[]` item:\n• `allConversions` - all conversions (0 on platforms without this concept)\n\nGoogle note:\n• With `adAccountId` set to a Google customer id, the timeline represents customer-level performance (clicks, cost, impressions, conversions, allConversions per day).","platforms":["google-ads","ads","analytics"],"url":"https://docs.zernio.com/changelog#1788799606639"},{"id":"1788795537458","date":"2026-09-07T15:38:57.458Z","type":"new_feature","message":"New endpoint `GET /v1/ads/library` is available to search the public Ad Library for competitor/market research using your connected account token.\n\nIt 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).\n\nRequired: `accountId`\nCommon query params: `q`, `countries`, `since`, `until`, `limit`, `after`\nMeta-only: `pageIds`, `adType` (`ALL`/`POLITICAL_AND_ISSUE_ADS`/`HOUSING_ADS`/`EMPLOYMENT_ADS`/`FINANCIAL_PRODUCTS_AND_SERVICES_ADS`), `status` (`ACTIVE`/`INACTIVE`/`ALL`), `platforms`, `mediaType` (`ALL`/`IMAGE`/`MEME`/`VIDEO`/`NONE`), `languages`, `searchType` (`KEYWORD_UNORDERED`/`KEYWORD_EXACT_PHRASE`), `fields`\nLinkedIn-only: `advertiser` (and `limit` max is `25`)\n\nNotes: Passing a platform-unsupported param returns `400` naming the param. Meta may return `403 meta_identity_confirmation_required` until the Facebook user behind the connection confirms identity/location.\n\nAlso: `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`).","platforms":["instagram","facebook","linkedin","meta-ads","linkedin-ads","ads","inbox"],"url":"https://docs.zernio.com/changelog#1788795537458"},{"id":"1788774966722","date":"2026-09-07T09:56:06.722Z","type":"breaking_change","message":"`POST /v1/phone-numbers/stock-watches` now supports watching stock by number type via `numberType`.\n\nYou 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).\n\nRequest body:\n• `country`\n• `numberType` (optional): `local` | `mobile` | `national` | `toll_free`\n\nResponse schema `PhoneNumberStockWatch` now includes required `numberType` (nullable; `null` means “all types”).","platforms":["telephony"],"url":"https://docs.zernio.com/changelog#1788774966722"},{"id":"1788523258021","date":"2026-09-04T12:00:58.021Z","type":"improvement","message":"POST `/v1/inbox/conversations/{conversationId}/messages` now includes a documented `500` response when the upstream platform rejects or fails the send.\n\nThis 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.\n\nKey response:\n• `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).","platforms":["inbox"],"url":"https://docs.zernio.com/changelog#1788523258021"},{"id":"1788517374954","date":"2026-09-04T10:22:54.954Z","type":"new_feature","message":"Meta ad sets now support **minimum spend targets** via `platformSpecificData` on `PUT /v1/ads/ad-sets/{adSetId}` and when creating Meta ads via `POST /v1/ads/create`.\n\nThis lets you reserve a share of a CBO (campaign-level) budget for a specific ad set using Meta’s `daily_min_spend_target` / `lifetime_min_spend_target`.\n\nSet one of:\n• `platformSpecificData.dailyMinSpendTarget`\n• `platformSpecificData.lifetimeMinSpendTarget`\n\nNotes:\n• `dailyMinSpendTarget` and `lifetimeMinSpendTarget` are mutually exclusive (400)\n• Requires campaign-level budget (CBO); on an ABO ad set this is rejected (PUT returns 409; create returns 400)\n• Read back via `GET /v1/ads/ad-sets/{adSetId}?fields=daily_min_spend_target` (or `lifetime_min_spend_target`)","platforms":["meta-ads","ads"],"url":"https://docs.zernio.com/changelog#1788517374954"},{"id":"1788512774256","date":"2026-09-04T09:06:14.256Z","type":"improvement","message":"`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.\n\nThis means the contents of `AdTreeAdSet.ads` can change based on `fromDate`/`toDate`; use `adCount` for the true total size of the ad set.\n\nKey fields: `AdTreeAdSet.ads`, `fromDate`, `toDate`, `adCount`","platforms":["ads"],"url":"https://docs.zernio.com/changelog#1788512774256"},{"id":"1788452196402","date":"2026-09-03T16:16:36.402Z","type":"new_feature","message":"WhatsApp Flows now supports managing the **Flows endpoint encryption key** via new endpoints.\n\nYou can check whether a business RSA public key is registered (and retrieve it) and register/replace the key used for endpoint-backed flows (`flow_action: data_exchange`).\n\nNew endpoints:\n• `GET /v1/whatsapp/flows/encryption-key` (query `accountId`) returns `publicKey`, `registered`, `signatureStatus` (`VALID` | `MISMATCH`)\n• `POST /v1/whatsapp/flows/encryption-key` body: `accountId`, `businessPublicKey` (PEM)\n\nNote: 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.","platforms":["whatsapp"],"url":"https://docs.zernio.com/changelog#1788452196402"},{"id":"1788438507328","date":"2026-09-03T12:28:27.328Z","type":"new_feature","message":"Meta ads created via `POST /v1/ads/create` now support **Multiple Text Options** (Advantage+ Flexible Format) using new top-level fields `bodies`, `headlines`, and optional `descriptions`.\n\nThis lets you send 1–5 variations of primary text/headlines (and optionally descriptions) and have Meta optimize delivery across them, without using full `dynamicCreative`.\n\nUse:\n• `bodies` (1–5)\n• `headlines` (1–5)\n• `descriptions` (0–5)\n\nRequires `imageUrl` or `video`, plus `linkUrl` and `callToAction`. When set, top-level `body` and `headline` are used for preview text (`object_story_spec`), and this mode is mutually exclusive with `dynamicCreative`, `placementAssets`, `carouselCards`, and `creatives[]`.","platforms":["meta-ads","ads"],"url":"https://docs.zernio.com/changelog#1788438507328"},{"id":"1788432060785","date":"2026-09-03T10:41:00.785Z","type":"new_feature","message":"Instagram publishing now supports **paid partnership**, **sponsor tagging**, **comment toggling**, and **location tagging** via new fields in `platformSpecificData`.\n\nUse:\n• `isPaidPartnership` (boolean) — adds the “Paid partnership” label (feed/Reels/carousels; Stories return 400). Requires Facebook Login connection.\n• `brandedContentSponsors` (array, max 2) — tag sponsor brands by username (optional @) or numeric IG user ID; implies `isPaidPartnership`.\n• `commentsEnabled` (boolean, default true) — when false, turns off comments after publish (best-effort; Stories ignored).\n• `locationId` (string digits) — tags the post location using a Facebook Page ID with location data (feed/Reels/carousels; Stories rejected with 400).","platforms":["instagram","facebook","publishing"],"url":"https://docs.zernio.com/changelog#1788432060785"},{"id":"1788431286859","date":"2026-09-03T10:28:06.859Z","type":"improvement","message":"Meta ads now support regional regulation identity mapping via `regionalRegulationIdentities` on `POST /v1/ads/create` and `POST /v1/ads/boost`.\n\nWhen you include `regionalRegulatedCategories` (e.g. `BRAZIL_REGULATION`, `SINGAPORE_UNIVERSAL`, `TAIWAN_UNIVERSAL`, `THAILAND_UNIVERSAL`, `AUSTRALIA_FINSERV`, `INDIA_FINSERV`, `TAIWAN_FINSERV`), you can now pass the required Meta-verified beneficiary/payer entity IDs in `regionalRegulationIdentities`.\n\nSet:\n• `regionalRegulatedCategories`: [_..._]\n• `regionalRegulationIdentities`: { `universal_beneficiary`: 123, `universal_payer`: 456, _..._ }\n\nIf omitted, Meta may use Ads Manager defaults when configured.","platforms":["meta-ads","ads"],"url":"https://docs.zernio.com/changelog#1788431286859"},{"id":"1788350251379","date":"2026-09-02T11:57:31.379Z","type":"new_feature","message":"New endpoint `GET /v1/analytics/delta` returns a cursor-based feed of analytics snapshots that changed across all readable accounts.\n\nThis 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.\n\nKey params:\n• `cursor` (opaque; omit to start “from now” and receive `data: []` + `nextCursor`)\n• `limit` (1–100, default 50; out-of-range = 400)\n• `platform` (optional)\n• `profileId` (default `all`)\n\nResponse fields:\n• `data` (oldest-first changed snapshots)\n• `nextCursor` (always present, even on empty pages)\n• `hasMore` (if `true`, call again immediately)\n\nNew 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`.","platforms":["analytics","webhooks"],"url":"https://docs.zernio.com/changelog#1788350251379"},{"id":"1788345066805","date":"2026-09-02T10:31:06.805Z","type":"breaking_change","message":"Instagram comment-to-DM private replies may now fail for non-followers when you include interactive elements.\n\nSince 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.\n\nIf you need to reach non-followers, send plain text using `message` only, then add `buttons` after they reply. (`quickReplies` and `buttons` remain mutually exclusive.)","platforms":["instagram","inbox"],"url":"https://docs.zernio.com/changelog#1788345066805"},{"id":"1788343512671","date":"2026-09-02T10:05:12.671Z","type":"improvement","message":"Meta ads now accept **regional regulated categories** on ad set creation/boosting.\n\nUse 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.\n\nSend `regionalRegulatedCategories` (array of strings) on:\n• `POST /v1/ads/create`\n• `POST /v1/ads/boost`\n\nExample values include `SINGAPORE_UNIVERSAL`, `TAIWAN_UNIVERSAL`, `THAILAND_UNIVERSAL`, `AUSTRALIA_FINSERV`, `INDIA_FINSERV`.","platforms":["meta-ads","ads"],"url":"https://docs.zernio.com/changelog#1788343512671"},{"id":"1788296596949","date":"2026-09-01T21:03:16.949Z","type":"improvement","message":"Several Ads endpoints can now return `403` with code `ads_allowance_exceeded`.\n\nThis 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.\n\nYou may see this on:\n• `PUT /v1/ads/campaigns/{campaignId}/status`\n• `PUT /v1/ads/campaigns/{campaignId}`\n• `POST /v1/ads/campaigns/bulk-status`\n• `POST /v1/ads/campaigns/{campaignId}/duplicate`\n• `POST /v1/ads/ad-sets/{adSetId}/duplicate`\n• `POST /v1/ads/{adId}/duplicate`\n• `PUT /v1/ads/ad-sets/{adSetId}` and `PUT /v1/ads/ad-sets/{adSetId}/status`\n• `PUT /v1/ads/{adId}` and `PUT /v1/ads/{adId}/status`\n• `PATCH /v1/ads/{adId}/tracking-tags`\n• `POST /v1/ads/create`\n• `POST /v1/ads/boost`\n• `POST /v1/ads/messaging`, `POST /v1/ads/call`, `POST /v1/ads/ctwa`\n• `POST /v1/ads/campaigns/{campaignId}/assets`\n\nIf you have retry logic, treat `403 ads_allowance_exceeded` as non-retryable until billing is set up.","platforms":["ads"],"url":"https://docs.zernio.com/changelog#1788296596949"},{"id":"1788296177220","date":"2026-09-01T20:56:17.220Z","type":"improvement","message":"`GET /v1/connect/pending-data` pending OAuth tokens are now **repeatable** and expire **1 hour** after issuance (previously one-time use, 10 minutes).\n\nThis 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).\n\nUse `token` (query) from `pendingDataToken` on the redirect. `selectionType` can be: `organizations`, `pages`, `boards`, `locations`, `profiles`.\n\nScope note: this pending-data flow now covers **Google Business locations** and **Slack channels** in addition to existing large selection lists.","platforms":["slack","google-business"],"url":"https://docs.zernio.com/changelog#1788296177220"},{"id":"1788261358216","date":"2026-09-01T11:15:58.216Z","type":"new_feature","message":"GET `/v1/ads` now supports filtering by Meta ad set via `adSetId`.\n\nThis makes it easier to build adset-centric dashboards by listing only ads within a specific platform ad set.\n\nUse `adSetId` (platform ad set id) alongside existing filters like `campaignId`.\n\nPUT `/v1/ads/{adId}` now supports richer creative updates, especially for **Meta**.\n\nMeta 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.\n\nKey Meta-only options in `creative`:\n• `description` (link description)\n• `videoId` (reuse an already-uploaded video)\n• `existingCreativeId` (repoint to a creative from `GET /v1/ads/creatives`; ignores other creative fields)\n\nGET `/v1/ads/targeting/search` now supports Meta work demographics via `dimension` values `workPosition`, `workEmployer`, `workIndustry`.\n\nThese ids can be used in `TargetingSpec` as:\n• `workPositions`\n• `workEmployers`\n• `workIndustries`\n\nAlso, `TargetingSpec` audience lists are now supported: `audienceInclude` and `audienceExclude` accept platform audience IDs (not supported on OpenAI Ads; returns 400).","platforms":["meta-ads","ads"],"url":"https://docs.zernio.com/changelog#1788261358216"},{"id":"1788203642185","date":"2026-08-31T19:14:02.185Z","type":"breaking_change","message":"WhatsApp template sends via `POST /v1/inbox/conversations` now validate `templateParams` more strictly for positional templates.\n\nIf you provide fewer values than the template’s required header + body + dynamic URL-button slots, the request is rejected with `400` (`INVALID_TEMPLATE_PARAMS`) instead of sending a message with misaligned values.\n\nKey fields:\n• `templateParams` - must cover all required slots for positional templates\n• `templateButtonParams` - can override a button’s value; overridden buttons may not need a value in `templateParams` unless later uncovered dynamic URL buttons follow","platforms":["whatsapp","inbox"],"url":"https://docs.zernio.com/changelog#1788203642185"},{"id":"1788166099256","date":"2026-08-31T08:48:19.256Z","type":"improvement","message":"Creating or retrying posts can now return **403** with `code=ACCOUNT_NOT_ENABLED_FOR_POSTING`.\n\nThis 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.\n\nApplies to:\n• `POST /v1/posts` (403 `code` enum now includes `ACCOUNT_NOT_ENABLED_FOR_POSTING`)\n• `POST /v1/posts/{postId}/retry` (403 may include `ACCOUNT_NOT_ENABLED_FOR_POSTING`, `PROFILE_OVER_LIMIT`, or `insufficient_permissions`)","platforms":["linkedin-ads","pinterest-ads","ads","publishing"],"url":"https://docs.zernio.com/changelog#1788166099256"},{"id":"1788165978106","date":"2026-08-31T08:46:18.106Z","type":"breaking_change","message":"Some endpoints now return additional billing/capability errors you may need to handle.\n\n**Posts listing** may now fail with `403` when X analytics isn’t enabled for the account (code `X_ANALYTICS_NOT_ENABLED`) on `GET /v1/accounts/{accountId}/posts`.\n\n**OpenAI Ads connect** may now return `402` `PaymentRequired` on `POST /v1/connect/openai-ads/credentials`.\n\n**SMS sender ID creation** now has explicit billing-related failures on `POST /v1/sms/sender-ids`:\n• `402` (code `payment_required`) when no payment method is on file\n• `409` (code `billing_setup_incomplete`) when billing setup is incomplete","platforms":["x","ads","telephony","analytics"],"url":"https://docs.zernio.com/changelog#1788165978106"},{"id":"1787999193187","date":"2026-08-29T10:26:33.187Z","type":"breaking_change","message":"`PostAnalytics.follows` can now be `null` in `GET /v1/analytics` responses.\n\nThis 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).","platforms":["instagram","analytics"],"url":"https://docs.zernio.com/changelog#1787999193187"},{"id":"1787941123365","date":"2026-08-28T18:18:43.365Z","type":"breaking_change","message":"WhatsApp templates now support working with a specific Meta template variant via its **Meta template id**.\n\nNew endpoints:\n• `GET /v1/whatsapp/templates/id/{templateId}` (requires `accountId`)\n• `PATCH /v1/whatsapp/templates/id/{templateId}` (body: `accountId`, `components`)\n• `DELETE /v1/whatsapp/templates/id/{templateId}` (requires `accountId`)\n\nTemplate listing and name-based operations are now language-aware (Meta stores templates per `name + language`):\n• `GET /v1/whatsapp/templates` adds filters: `name`, `language`, `status` (`APPROVED`/`REJECTED`/`PENDING`/`PAUSED`/`DISABLED`/`IN_APPEAL`/`PENDING_DELETION`)\n• `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`\n• `PATCH /v1/whatsapp/templates/{templateName}` now takes `language` in the request body (required when multiple variants exist); missing language can return `409` `ambiguous_template`\n\nDelete behavior change:\n• `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`.","platforms":["whatsapp"],"url":"https://docs.zernio.com/changelog#1787941123365"},{"id":"1787932160850","date":"2026-08-28T15:49:20.850Z","type":"improvement","message":"WhatsApp sends via `POST /v1/inbox/conversations/{conversationId}/messages` now document a **per-recipient rate limit**.\n\nIf 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.\n\nIf you see `400` responses with `platformError.code` = `131056`, throttle per recipient and retry later.","platforms":["whatsapp","inbox"],"url":"https://docs.zernio.com/changelog#1787932160850"},{"id":"1787906663019","date":"2026-08-28T08:44:23.019Z","type":"new_feature","message":"Phone number stock watches are now available via new endpoints: `GET /v1/phone-numbers/stock-watches`, `POST /v1/phone-numbers/stock-watches`, `DELETE /v1/phone-numbers/stock-watches/{id}`.\n\nYou 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.\n\nCreate 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.\n\nWebhooks now support the new event `phone_number.stock_available` 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`.","platforms":["telephony","webhooks"],"url":"https://docs.zernio.com/changelog#1787906663019"},{"id":"1787905731267","date":"2026-08-28T08:28:51.267Z","type":"new_feature","message":"`GET /v1/usage` metering mode now supports spend attribution by profile/account, plus optional projection onto a single profile or account.\n\nThis 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.\n\nNew query params (metering mode):\n• `groupBy`: `profile` | `account` (adds `attribution`)\n• `profileId`: project results onto one profile (pair with `range`)\n• `accountId`: project results onto one account (pair with `range`)\n\nNew response fields in `UsageMetering`:\n• `attribution` (with `groupBy`, `groups`, `unattributed`, `totals`, `restricted`)\n• `scope` (present with `profileId`/`accountId`)\n\nNote: when `profileId` or `accountId` is set, `peaks`, `callUsage`, and `tax` return `null` (workspace-level only).","platforms":["general"],"url":"https://docs.zernio.com/changelog#1787905731267"},{"id":"1787904102449","date":"2026-08-28T08:01:42.449Z","type":"new_feature","message":"New endpoint: `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.\n\nSupports 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).\n\nKey params:\n• `accountId` (path)\n• `videoId` (query, required)\n• `language` (query, optional; BCP-47)\n• `format` (query) = `json` | `srt` (default `json`)\n• `refresh` (query, boolean)\n\nResponse highlights: `trackKind` = `asr` | `standard`, `cues` (when `format=json`), `srt` (when `format=srt`), and `availableTracks` for language selection.","platforms":["youtube"],"url":"https://docs.zernio.com/changelog#1787904102449"},{"id":"1787845168645","date":"2026-08-27T15:39:28.645Z","type":"new_feature","message":"New endpoint: `DELETE /v1/ads/ad-sets/{adSetId}` deletes an ad set on the platform and cancels its ads locally (it never deletes the campaign).\n\nProvide the ad set ID via `adSetId` (path).\n\nResponse includes `deleted` and `adCount` (number of local Ad documents marked `status: cancelled`). Returns `501` if deletion isn’t supported on the target platform.","platforms":["ads"],"url":"https://docs.zernio.com/changelog#1787845168645"},{"id":"1787842129992","date":"2026-08-27T14:48:49.992Z","type":"new_feature","message":"Instagram is now supported (limited release) for liking/unliking posts and comments via:\n• `POST /v1/inbox/posts/{postId}/like`, `DELETE /v1/inbox/posts/{postId}/like`\n• `POST /v1/inbox/comments/{postId}/{commentId}/like`, `DELETE /v1/inbox/comments/{postId}/{commentId}/like`\n\nThis enables engagement actions on Instagram feed media (posts/reels/carousels) and their comments/replies, but access is currently restricted by Meta permissions.\n\nInstagram requirements/behavior:\n• Requires permission `instagram_manage_engagement`\n• Only works for accounts connected via Facebook Login; Instagram Login returns `400` with code `instagram_likes_require_facebook_login`\n• Accounts whose token predates the permission return `403` with code `reconnect_required`\n• Limited release: non-allowed accounts return `403` with code `PLATFORM_BETA_RESTRICTED`\n• Burst limit: 50 like/unlike calls per 5s per Instagram account; exceeding can lock likes for 1 hour\n\nAlso note: these endpoints’ `403` may now indicate missing platform permission (not only Inbox add-on).","platforms":["instagram"],"url":"https://docs.zernio.com/changelog#1787842129992"},{"id":"1787821008387","date":"2026-08-27T08:56:48.387Z","type":"breaking_change","message":"`PlatformTarget.platformSpecificData` is now returned as a free-form `object` (with `additionalProperties: true`) instead of a `oneOf` union of `*PlatformData` schemas.\n\nThis 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).","platforms":["general"],"url":"https://docs.zernio.com/changelog#1787821008387"},{"id":"1787763705182","date":"2026-08-26T17:01:45.182Z","type":"new_feature","message":"WhatsApp inbox conversation creation now supports sending values for template buttons that carry their own payload (copy-code / flow tokens), via `templateButtonParams` on `POST /v1/inbox/conversations`.\n\nUse 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).\n\nSet `templateButtonParams` as an array of:\n• `index` (0-9)\n• `subType`: `url` | `copy_code` | `flow`\n• `value`\n\nMessage lineage is now exposed as `sentVia` on `GET /v1/inbox/conversations/{conversationId}/messages` and on the `message.sent` webhook, with values:\n`human` | `api` | `broadcast` | `sequence` | `workflow` | `comment_automation` | `bulk-api` | `null`\n\nNote: existing stored messages are not backfilled; treat `sentVia: null` as “unknown”.","platforms":["whatsapp","inbox","webhooks"],"url":"https://docs.zernio.com/changelog#1787763705182"},{"id":"1787755658835","date":"2026-08-26T14:47:38.835Z","type":"new_feature","message":"`DELETE /v1/ads/campaigns/{campaignId}` now accepts `platform=google`.\n\nYou can delete Google campaigns using the same endpoint and request body shape as Facebook/Instagram.\n\nSet `platform` to one of: `facebook`, `instagram`, `google`\n\nFor empty campaigns (0 ads), include `accountId` to allow deletion: `accountId` (Zernio SocialAccount id).","platforms":["instagram","meta-ads","google-ads","ads"],"url":"https://docs.zernio.com/changelog#1787755658835"},{"id":"1787746887227","date":"2026-08-26T12:21:27.227Z","type":"improvement","message":"GET `/v1/whatsapp/template-library` now supports selecting a specific language variant via an optional `language` query param.\n\nThis 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`.\n\nNew:\n• Query param: `language`\n• Response field: `template.availableLanguages` (array of supported language codes)","platforms":["whatsapp"],"url":"https://docs.zernio.com/changelog#1787746887227"},{"id":"1787746023693","date":"2026-08-26T12:07:03.693Z","type":"improvement","message":"GET `/v1/users` now returns `lastLoginAt` for each user.\n\nThis 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.\n\nNew field:\n• `users[].lastLoginAt`\n\nThe `message.sent` webhook payload now supports more platforms and can include send context in a new `metadata` object.\n\nUpdated enum:\n• `message.platform`: `instagram`, `facebook`, `telegram`, `whatsapp`, `twitter`, `reddit`, `bluesky`, `slack`\n\nNew optional fields:\n• `metadata.quotedMessageId`\n• `metadata.threadTs` (Slack only)","platforms":["instagram","reddit","bluesky","telegram","whatsapp","slack","webhooks"],"url":"https://docs.zernio.com/changelog#1787746023693"},{"id":"1787745285679","date":"2026-08-26T11:54:45.679Z","type":"new_feature","message":"You can now fetch a **single Google Business Profile review** by ID via `GET /v1/accounts/{accountId}/gmb-reviews/{reviewId}`.\n\nThis 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).\n\nKey params:\n• `accountId` (path)\n• `reviewId` (path, ID portion only)\n• `locationId` (query, optional override; otherwise uses the account’s selected location)\n\nResponse includes `review` (`GoogleBusinessReview`).","platforms":["google-business","inbox"],"url":"https://docs.zernio.com/changelog#1787745285679"},{"id":"1787745217500","date":"2026-08-26T11:53:37.500Z","type":"new_feature","message":"Instagram now supports publishing videos **muted** via `muteAudio` in `platformSpecificData`.\n\nWhen `muteAudio=true`, we strip the audio track before sending the video to Instagram, so the published video is permanently silent.\n\nUse:\n• `muteAudio` (boolean, default `false`)\n\nApplies to Reels, Stories, and video carousel slides; ignored for images. Videos above 200MB cannot be muted (mute before uploading).","platforms":["instagram","publishing"],"url":"https://docs.zernio.com/changelog#1787745217500"},{"id":"1787744092256","date":"2026-08-26T11:34:52.256Z","type":"new_feature","message":"Threads posts now support an optional **first comment** via `platformSpecificData.threads.firstComment`.\n\nThis lets you automatically publish a reply immediately after the main post is published; when using `threadItems`, it replies to the root post.\n\nKey field:\n• `firstComment` (string, max 500)\n\nNote: the first comment is a Threads post and counts toward the profile’s 250 posts / 24h limit.","platforms":["threads","publishing"],"url":"https://docs.zernio.com/changelog#1787744092256"},{"id":"1787741790858","date":"2026-08-26T10:56:30.858Z","type":"new_feature","message":"Pinterest posts now support targeting a **board section** and optionally disclosing **AI-modified** content via `PinterestPlatformData`.\n\nUse `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.\n\nSet `isAiGenerated` to `true` to include Pinterest’s AI_MODIFIED disclosure (shows an “AI modified” label); `false` (default) omits the disclosure.","platforms":["pinterest","publishing"],"url":"https://docs.zernio.com/changelog#1787741790858"},{"id":"1787672727640","date":"2026-08-25T15:45:27.640Z","type":"new_feature","message":"Google Search ads created via `POST /v1/ads/create` now support campaign-level **sitelinks**.\n\nYou can create and attach sitelink assets during ad creation (Google reviews them asynchronously; approval is not immediate).\n\nUse `sitelinks` (Google Search only):\n• 2–20 items\n• each item: `text` (max 25) + `linkUrl` (URI)\n• optional: `description1`, `description2` (each max 35)\n\nApplies when `campaignType` is `search`.","platforms":["google-ads","ads"],"url":"https://docs.zernio.com/changelog#1787672727640"},{"id":"1787670104033","date":"2026-08-25T15:01:44.033Z","type":"new_feature","message":"Meta ads now support the **Page Likes** objective via `goal` = `page_likes`.\n\nThis lets you create campaigns/ads optimized for Page Likes (Meta maps this to Engagement/Page Likes) using the same create flows.\n\nUse `goal` with:\n• `POST /v1/ads/campaigns`: `goal` now includes `page_likes`\n• `POST /v1/ads/create`: `goal` now includes `page_likes`\n\nNotes 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).","platforms":["facebook","meta-ads","ads"],"url":"https://docs.zernio.com/changelog#1787670104033"},{"id":"1787660947833","date":"2026-08-25T12:29:07.833Z","type":"improvement","message":"Inbox list endpoints now return **which connected accounts were skipped** during aggregation via `meta.accountsSkipped`.\n\nThis 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.\n\nNew response field:\n• `meta.accountsSkipped`: array of `{ accountId, platform }`\n\nApplies to:\n• `GET /v1/inbox/conversations`\n• `GET /v1/inbox/comments`\n• `GET /v1/inbox/reviews`","platforms":["inbox"],"url":"https://docs.zernio.com/changelog#1787660947833"},{"id":"1787658336153","date":"2026-08-25T11:45:36.153Z","type":"breaking_change","message":"`GET /v1/ads/tree` now supports scoping the tree to multiple ad accounts via `adAccountId`.\n\nYou can pass a comma-separated list to limit results to a subset of connected accounts (useful for agency profiles).\n\nUse `adAccountId=act_1,act_2,act_3` (max `50` per request). Single values keep the old behavior.\n\nNote: the plural aliases `adAccountIds` and `platformAdAccountIds` are now rejected with `400` (to avoid silently returning unfiltered results).","platforms":["meta-ads","ads"],"url":"https://docs.zernio.com/changelog#1787658336153"},{"id":"1787657810019","date":"2026-08-25T11:36:50.019Z","type":"improvement","message":"GET `/v1/ads/videos` now returns a playable video URL via `source` in the default response.\n\nThis lets you preview a video before selecting it (useful when multiple videos share the same first frame).\n\nKey field:\n• `data[].source` — signed CDN MP4 URL (expires; use for preview only and re-list to refresh)","platforms":["ads"],"url":"https://docs.zernio.com/changelog#1787657810019"},{"id":"1787652627039","date":"2026-08-25T10:10:27.039Z","type":"new_feature","message":"WhatsApp interactive messages now support **native address capture** via `interactive.type` = `address_message` on `POST /v1/inbox/conversations/{conversationId}/messages`.\n\nThis 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.\n\nTo send, set:\n• `interactive.type`: `address_message`\n• `interactive.action.name`: `address_message`\n• `interactive.action.parameters.country`: ISO 3166-1 alpha-2 (required)\n\nWebhook 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`.","platforms":["whatsapp","inbox","webhooks"],"url":"https://docs.zernio.com/changelog#1787652627039"},{"id":"1787647700501","date":"2026-08-25T08:48:20.501Z","type":"new_feature","message":"New endpoint `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).\n\nUse it to fetch short-lived asset URLs on demand (don’t cache long-term; re-fetch before serving/downloading).\n\nKey details:\n• Path param: `adId` (Zernio 24-char hex or platform ad id)\n• Response: `platform` (`facebook` | `instagram`), `media[]` items with `type` (`image` | `video`), `url`, and for videos: `thumbnailUrl`, `videoId`, `length`, `index`\n• Errors: `422` (no active Meta connection), `501` (only supported on Meta)","platforms":["instagram","meta-ads","ads"],"url":"https://docs.zernio.com/changelog#1787647700501"},{"id":"1787646894231","date":"2026-08-25T08:34:54.231Z","type":"new_feature","message":"CTWA ad creation now supports a custom chat **welcome message** via `welcomeMessage`.\n\nThis 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.\n\nSet `welcomeMessage` (single-creative) or `creatives[].welcomeMessage` (multi-creative) with:\n• `text`\n• `prefillText`","platforms":["whatsapp","ads"],"url":"https://docs.zernio.com/changelog#1787646894231"},{"id":"1787586927572","date":"2026-08-24T15:55:27.572Z","type":"improvement","message":"`GET /v1/inbox/comments` and `GET /v1/inbox/comments/{postId}` responses are now cached for up to `10 minutes`.\n\nThis means comment counts/threads may lag new comments within that window; for real-time updates, use the `comment.received` webhook instead of polling.\n\nNotes:\n• Cache window: `up to 10 minutes`\n• Recommended real-time mechanism: `comment.received`\n• On `GET /v1/inbox/comments/{postId}`, your own writes (create/reply/delete) refresh the cache immediately.","platforms":["inbox","webhooks"],"url":"https://docs.zernio.com/changelog#1787586927572"},{"id":"1787586035996","date":"2026-08-24T15:40:35.996Z","type":"breaking_change","message":"Meta ad videos can now be managed directly via the API: upload standalone videos and delete them from an ad account’s video library.\n\nUse `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`).\n\nUpload params:\n• `accountId`, `adAccountId`\n• Exactly one of `videoUrl` (https) or `videoBase64`\n• Optional `filename` (only for `videoBase64`)\nReturns `video.id` and optional `video.thumbnailUrl`.\n\nUse `DELETE /v1/ads/videos/{videoId}` with `accountId` + `adAccountId` to remove a video; returns `{ success: true }` on success.\n\nBreaking 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).\n\nAlso added for sorting: `createdTime` on `AdTreeCampaign` and `AdTreeAdSet` (proxy based on earliest ad creation time).","platforms":["meta-ads","ads","publishing"],"url":"https://docs.zernio.com/changelog#1787586035996"},{"id":"1787582581845","date":"2026-08-24T14:43:01.845Z","type":"improvement","message":"CTWA ad creation request (`CtwaAdRequestBody`) now supports reusing existing videos and more control over activation.\n\nYou 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).\n\nVideo fields:\n• `video.url` or `video.id` (either; `id` wins)\n• `video.thumbnailUrl` (optional)\n\nNew status controls:\n• `status`: `ACTIVE` | `PAUSED` (ad-level; default `ACTIVE`)\n• `campaignStatus`: `ACTIVE` | `PAUSED` (campaign-level; default `ACTIVE`; rejected when using `adSetId`)","platforms":["meta-ads","ads"],"url":"https://docs.zernio.com/changelog#1787582581845"},{"id":"1787582393133","date":"2026-08-24T14:39:53.133Z","type":"breaking_change","message":"Connecting WhatsApp via `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.\n\nResponse updates:\n• `webhookNotice` may be returned on success with cutover/unsubscribe guidance\n• `account.phoneNumber` replaces `account.selectedPhoneNumber`\n• `account.verifiedName` and `account.qualityRating` are now included when available","platforms":["whatsapp","telephony","webhooks"],"url":"https://docs.zernio.com/changelog#1787582393133"},{"id":"1787574910274","date":"2026-08-24T12:35:10.274Z","type":"new_feature","message":"WhatsApp Flows now let you set a data exchange endpoint via `endpointUri` when creating or updating a flow.\n\nThis enables configuring the HTTPS callback endpoint directly on DRAFT flows (and it will be used only if your uploaded Flow JSON declares `data_api_version` `\"3.0\"`).\n\nNew field:\n• `POST /v1/whatsapp/flows`: `endpointUri`\n• `PATCH /v1/whatsapp/flows/{flowId}`: `endpointUri`\n\nNotes:\n• `endpointUri` is `uri` format and HTTPS-only\n• Settable only while the flow is in `DRAFT`","platforms":["whatsapp"],"url":"https://docs.zernio.com/changelog#1787574910274"},{"id":"1787558914064","date":"2026-08-24T08:08:34.064Z","type":"improvement","message":"LinkedIn **reshare** behavior is now explicitly defined for `platformSpecificData.reshareUrl` on `POST /v1/posts`.\n\nWhen 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.\n\nNote: 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.\n\nUse: `platformSpecificData.reshareUrl` (mutually exclusive with media).","platforms":["linkedin","publishing"],"url":"https://docs.zernio.com/changelog#1787558914064"},{"id":"1787558009263","date":"2026-08-24T07:53:29.263Z","type":"new_feature","message":"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.\n\nNew endpoints:\n• `GET /v1/phone-numbers/sip-trunks` (returns `trunks`, plus `enabled`)\n• `POST /v1/phone-numbers/sip-trunks` (create; returns `digestPassword` only once)\n• `GET /v1/phone-numbers/sip-trunks/{id}`\n• `DELETE /v1/phone-numbers/sip-trunks/{id}`\n• `POST /v1/phone-numbers/sip-trunks/{id}/rotate-credentials` (returns new `digestPassword` only once)\n• `POST /v1/phone-numbers/{id}/sip-trunk` (attach with `trunkId`)\n• `DELETE /v1/phone-numbers/{id}/sip-trunk` (detach)\n\nCreate trunk request fields: `label`, `sipHost`, optional `sipPort`, `transport` (`tls`/`tcp`/`udp`).\n\nPhone 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).\n\nBehavior change: when a number is attached to a trunk, these endpoints can return `409` (`invalid_resource_state`) and require detaching first:\n• `DELETE /v1/phone-numbers/{id}`\n• `POST /v1/phone-numbers/{id}/voice`\n• `POST /v1/phone-numbers/{id}/whatsapp/calling`","platforms":["whatsapp","telephony"],"url":"https://docs.zernio.com/changelog#1787558009263"},{"id":"1787398447196","date":"2026-08-22T11:34:07.196Z","type":"new_feature","message":"LinkedIn **company_list** audiences can now be updated after creation via `POST /v1/ads/audiences/{audienceId}/companies`.\n\nThis 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.\n\nSend:\n• `audienceId` (path)\n• `companies` (array, `1..300000`) — each row must include at least one of `name`, `domain`, `website`, `linkedinPageUrl`\n\nNotes:\n• `company_list` is filled at creation from `companies` on `POST /v1/ads/audiences`; use the new endpoint for later changes\n• `POST /v1/ads/audiences/{audienceId}/users` is `customer_list`-only and returns `422` for `company_list` (use `/companies` instead)","platforms":["linkedin","linkedin-ads","ads"],"url":"https://docs.zernio.com/changelog#1787398447196"},{"id":"1787397645169","date":"2026-08-22T11:20:45.169Z","type":"new_feature","message":"New endpoint **GET /v1/ads/videos** is available.\n\nIt 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).\n\nCall with:\n• `accountId` (required)\n• `adAccountId` (required, `act_&lt;n&gt;`)\n• `fields` (optional Graph field override)\n• `limit` (optional, default `25`, max `100`)\n• `after` (optional cursor)\n\nUse returned `data[].id` as `video.id` on `POST /v1/ads/create`. A video is only usable once `status.video_status` is `ready`.\n\nNote: this endpoint is Meta-only; other platforms return `501`.","platforms":["meta-ads","ads","publishing"],"url":"https://docs.zernio.com/changelog#1787397645169"},{"id":"1787396552224","date":"2026-08-22T11:02:32.224Z","type":"improvement","message":"POST `/v1/inbox/conversations` now returns more specific **Reddit** error details.\n\nThis improves client-side handling when creating a Reddit DM thread fails (missing user, DM not allowed, or rate limiting).\n\nKey response changes:\n• `404` now includes `code`: `PARTICIPANT_NOT_FOUND` when the `u/username` doesn’t exist\n• `422` now also applies to Reddit when the recipient doesn’t accept private messages (still `code`: `DM_NOT_ALLOWED`)\n• `429` may now be returned for Reddit rate limits with `code`: `rate_limited`","platforms":["reddit","inbox"],"url":"https://docs.zernio.com/changelog#1787396552224"},{"id":"1787392011561","date":"2026-08-22T09:46:51.561Z","type":"new_feature","message":"`POST /v1/posts/{postId}/edit` now supports editing published posts on more platforms and can target a specific account’s copy of a post.\n\nYou can now edit on `platform`: `twitter`, `discord`, `facebook`, `reddit`, `linkedin`, `telegram`, `pinterest`, `googlebusiness`, `youtube`, `slack`.\n\nWhen a post was published to multiple accounts on the same platform, pass `accountId` to choose which one to edit (defaults to the first).\n\nRequest body: `platform`, `content`, optional `accountId`.","platforms":["linkedin","youtube","pinterest","reddit","telegram","discord","slack","google-business"],"url":"https://docs.zernio.com/changelog#1787392011561"},{"id":"1787373658659","date":"2026-08-22T04:40:58.659Z","type":"improvement","message":"External Instagram posts and analytics responses now include additional Instagram-specific metadata fields.\n\nThis helps you distinguish content types like Reels vs Feed, detect AI-generated labels, and see whether a Reel was shared to the main feed.\n\nNew optional fields:\n• `mediaProductType` (Instagram only; e.g. `FEED`, `REELS`, `STORY`, `AD`)\n• `isAiGenerated` (Instagram only)\n• `isSharedToFeed` (Instagram Reels only)\n• `mediaAudioType` (Instagram only; `MUSIC` or `ORIGINAL_SOUND`)\n\nAlso, `GET /v1/whatsapp/account-events` events now include `accountId` in each event item.","platforms":["instagram","whatsapp","analytics"],"url":"https://docs.zernio.com/changelog#1787373658659"},{"id":"1787310952120","date":"2026-08-21T11:15:52.120Z","type":"improvement","message":"Phone number listings now include WhatsApp Business Calling status via `callingEnabled`.\n\nThis lets you detect whether Calling is enabled per number when listing purchased numbers and connected (bring-your-own) numbers.\n\nNew fields:\n• `GET /v1/phone-numbers`\n  - `numbers[].callingEnabled`\n  - `connected[].callingEnabled`\n• `GET /v1/whatsapp/phone-numbers` (deprecated alias)\n  - `numbers[].callingEnabled`\n  - `connected[].callingEnabled`","platforms":["whatsapp","telephony"],"url":"https://docs.zernio.com/changelog#1787310952120"},{"id":"1787308809917","date":"2026-08-21T10:40:09.917Z","type":"new_feature","message":"New endpoint: `GET /v1/whatsapp/account-events`.\n\nYou 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.\n\nRequired query: `accountId`\nOptional query: `limit` (1–200, default `50`)\n\nResponse: `events[]` with `id`, `type` (e.g. `template_approved`, `template_rejected`, `account_restricted`, `account_disconnected`), `severity` (`info`/`success`/`warning`/`critical`), `title`, `detail`, `createdAt`.","platforms":["whatsapp","webhooks"],"url":"https://docs.zernio.com/changelog#1787308809917"},{"id":"1787304038926","date":"2026-08-21T09:20:38.926Z","type":"new_feature","message":"New endpoint: **POST** `/v1/connect/shopify/token` to connect a Shopify store using a merchant-created custom-app Admin token (token-paste alternative to OAuth).\n\nUse 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.\n\nRequest body:\n• `profileId`\n• `shop` (e.g. `your-store.myshopify.com`)\n• `accessToken` (starts with `shpat_`)\n\nResponse includes `account` with `platform` = `shopify` and `username` = store domain.","platforms":["commerce"],"url":"https://docs.zernio.com/changelog#1787304038926"},{"id":"1787221439467","date":"2026-08-20T10:23:59.467Z","type":"improvement","message":"WhatsApp connect endpoints now return a clearer `409` conflict when the selected number is already connected on another profile/workspace.\n\nThis lets you distinguish between “number pinned to another profile” vs “number already connected elsewhere” and handle the conflict programmatically.\n\nOn `POST /v1/connect/whatsapp/credentials` and `POST /v1/connect/whatsapp/select-phone-number`, `409` may include:\n• `WHATSAPP_NUMBER_PINNED_TO_PROFILE` — move it first with `PATCH /v1/whatsapp/phone-numbers/{id}/profile`\n• `WHATSAPP_NUMBER_ALREADY_CONNECTED` — disconnect it from the other profile/workspace first (a number can only be live on one profile)","platforms":["whatsapp","telephony"],"url":"https://docs.zernio.com/changelog#1787221439467"},{"id":"1787188032597","date":"2026-08-20T01:07:12.597Z","type":"improvement","message":"`mediaStatus` on external/synced post media items can now be explicitly returned as `available`.\n\nThis makes media availability explicit instead of relying only on the field being absent, while keeping backward compatibility (older synced items may still omit `mediaStatus`).\n\nUpdated enums:\n• `ExternalPostMediaItem.mediaStatus`: `available` | `unavailable`\n• `AnalyticsSinglePostResponse.mediaItems[].mediaStatus`: `available` | `unavailable`\n\nNotes: `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`.","platforms":["linkedin","analytics"],"url":"https://docs.zernio.com/changelog#1787188032597"},{"id":"1787185938566","date":"2026-08-20T00:32:18.566Z","type":"improvement","message":"In `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`.\n\nThis lets you distinguish a real downloadable media file from a thumbnail/cover-image fallback.\n\nCheck:\n• `url` (may be `null`, or for LinkedIn videos may be a cover image)\n• `mediaStatus`: `unavailable` (present only when the media file could not be retrieved)\n• `unavailableReason`: `platform_withheld`","platforms":["linkedin","webhooks"],"url":"https://docs.zernio.com/changelog#1787185938566"},{"id":"1787159897783","date":"2026-08-19T17:18:17.783Z","type":"new_feature","message":"X (Twitter) posts now support **long-form X Articles** via `platformSpecificData.article` in `POST /v1/posts`.\n\nThis 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.\n\nUse `platforms[].platformSpecificData.article` with:\n• `title`\n• `content_state`\n• `mode`: `publish` | `draft`\n• optional `cover.url` (+ `cover.altText`)\n\nBilling/pricing: `GET /v1/billing/x-pricing` now includes `article_draft` and `article_publish` (both in tier `x_api_010`, $0.010/call).","platforms":["x","publishing"],"url":"https://docs.zernio.com/changelog#1787159897783"},{"id":"1787134650650","date":"2026-08-19T10:17:30.650Z","type":"new_feature","message":"POST `/v1/ads/boost` now accepts a Meta-only CTA value `VIEW_INSTAGRAM_PROFILE` in `callToAction`.\n\nThis lets you boost a post with a CTA that drives clicks to an Instagram profile (typically paired with the profile URL).\n\nUse:\n• `callToAction`: `VIEW_INSTAGRAM_PROFILE`\n• `linkUrl`: (Instagram profile URL)\n\nApplies to Meta boosts only; other platforms will reject Meta-only options.","platforms":["instagram","meta-ads","ads"],"url":"https://docs.zernio.com/changelog#1787134650650"},{"id":"1787050923703","date":"2026-08-18T11:02:03.703Z","type":"improvement","message":"`PlatformTarget.errorCategory` now includes more granular failure types: `platform_rate_limit` and `quota_exhausted`.\n\nUse these to distinguish platform throttling vs. shared daily API quota exhaustion and adjust retry/backoff or alerting logic accordingly.\n\nUpdated `errorCategory` values now include: `auth_expired`, `user_content`, `user_abuse`, `account_issue`, `platform_rejected`, `platform_error`, `platform_rate_limit`, `quota_exhausted`, `system_error`, `unknown`.","platforms":["general"],"url":"https://docs.zernio.com/changelog#1787050923703"},{"id":"1787049879652","date":"2026-08-18T10:44:39.652Z","type":"improvement","message":"WhatsApp messages sent via the inbox endpoints can now disable link previews using `linkPreview`.\n\nThis lets you send WhatsApp text without the URL thumbnail/preview (useful for cleaner utility messages or when previews are undesirable).\n\nSet `linkPreview` (boolean, default `true`):\n• `false` - send without link preview\n• `true` - send with link preview\n\nAvailable on:\n• `POST /v1/inbox/conversations` (WhatsApp Direct Send with `category` = `utility`)\n• `POST /v1/inbox/conversations/{conversationId}/messages` (WhatsApp; ignored on other platforms)","platforms":["whatsapp","inbox"],"url":"https://docs.zernio.com/changelog#1787049879652"},{"id":"1787049414081","date":"2026-08-18T10:36:54.081Z","type":"improvement","message":"Comment webhooks now include `comment.author.isOwnAccount` in `WebhookPayloadComment`.\n\nThis 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.\n\nNew field:\n• `comment.author.isOwnAccount` (boolean) — populated on Instagram/Facebook realtime webhooks only; if absent, it was not evaluated (don’t treat missing as `false`).","platforms":["instagram","inbox","webhooks"],"url":"https://docs.zernio.com/changelog#1787049414081"},{"id":"1787047098317","date":"2026-08-18T09:58:18.317Z","type":"improvement","message":"Webhook `message.received` payload now may include `metadata.noRenderableContent` (Instagram / Facebook Messenger only).\n\nThis 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.\n\nNew field: `metadata.noRenderableContent` (`boolean`)","platforms":["instagram","facebook","inbox","webhooks"],"url":"https://docs.zernio.com/changelog#1787047098317"},{"id":"1786993951499","date":"2026-08-17T19:12:31.499Z","type":"new_feature","message":"Bluesky now supports **default post languages** at the account level, and per-post language tagging for feed-generator filtering.\n\nUse the new endpoints to get/set an account default that applies at publish time when a post omits `platformSpecificData.langs`:\n• `GET /v1/accounts/{accountId}/bluesky-settings` → returns `defaultLangs`\n• `PATCH /v1/accounts/{accountId}/bluesky-settings` with `defaultLangs` = 1–3 BCP-47 codes (or `null` to clear)\n\nOn `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.","platforms":["bluesky","publishing"],"url":"https://docs.zernio.com/changelog#1786993951499"},{"id":"1786979651463","date":"2026-08-17T15:14:11.463Z","type":"improvement","message":"Meta ad creation now supports setting campaign status independently via `campaignStatus` on `POST /v1/ads/create`.\n\nThis 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.\n\nSet `campaignStatus` to `ACTIVE` or `PAUSED` (Meta only). If omitted, it follows `status`.\n\nAds now also return Meta’s creative id as `ad.creative.creativeId`, which you can reuse via `existingCreativeId` on `POST /v1/ads/create`.","platforms":["meta-ads","ads"],"url":"https://docs.zernio.com/changelog#1786979651463"},{"id":"1786939180900","date":"2026-08-17T03:59:40.900Z","type":"improvement","message":"Campaign/ad set status toggles now return clearer, more explicit results and always confirm what switch was written.\n\nFor `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.\n\nResponse additions:\n• `status` (`active` | `paused`)\n• `skippedReasons` (why ads were skipped)\n\nFor 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.\n\nResponse additions/changes:\n• `status` may be absent (see `statusMessage`/`message`)\n• `statusSkippedReasons` / `skippedReasons`\n• `statusMessage` / `message`\n\nAlso 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.","platforms":["linkedin","meta-ads","linkedin-ads","ads"],"url":"https://docs.zernio.com/changelog#1786939180900"},{"id":"1786776199769","date":"2026-08-15T06:43:19.769Z","type":"new_feature","message":"GET `/v1/accounts/{accountId}/health` now includes `platformConnection` for WhatsApp accounts.\n\nThis 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`.\n\nNew WhatsApp-only fields:\n• `platformConnection.status`: `connected` | `disconnected` | `unknown`\n• `platformConnection.checkedAt`\n• `platformConnection.phoneStatus`\n• `platformConnection.metaError` (when `disconnected`: `code`, `subcode`, `message`)","platforms":["whatsapp"],"url":"https://docs.zernio.com/changelog#1786776199769"},{"id":"1786727686192","date":"2026-08-14T17:14:46.192Z","type":"new_feature","message":"Webhooks now support the new WhatsApp event **template category updates** via `whatsapp.template.category_updated`.\n\nThis fires when Meta reclassifies a WhatsApp template’s category on a connected WABA, so you can react to upcoming (`scheduled`) and applied (`applied`) changes.\n\nSubscribe by adding `whatsapp.template.category_updated` to `events` when calling `POST /v1/webhooks/settings` or `PUT /v1/webhooks/settings`.\n\nPayload highlights:\n• `event`: `whatsapp.template.category_updated`\n• `template.changeType`: `scheduled` | `applied`\n• `template.category`: `UTILITY` | `MARKETING` | `AUTHENTICATION`\n• `template.previousCategory` (only when `applied`)\n• `template.scheduledCategory`, `template.effectiveAt` (only when `scheduled`)","platforms":["whatsapp","webhooks"],"url":"https://docs.zernio.com/changelog#1786727686192"},{"id":"1786722288747","date":"2026-08-14T15:44:48.747Z","type":"breaking_change","message":"Meta ad creation changed how the default Instagram identity is chosen when `instagramAccountId` is omitted on `POST /v1/ads/create`.\n\nIf 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.\n\nTo force a specific IG identity, set `instagramAccountId` to the desired Instagram Business Account ID (e.g. `17841...`).","platforms":["instagram","facebook","meta-ads","ads"],"url":"https://docs.zernio.com/changelog#1786722288747"},{"id":"1786717475470","date":"2026-08-14T14:24:35.470Z","type":"breaking_change","message":"Meta conversion setup on `POST /v1/ads/create` is now stricter/clearer around `promotedObject`.\n\nMeta 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.\n\nKey fields:\n• `promotedObject.pixelId` + `promotedObject.customEventType` (required together for Meta pixel-based conversion optimization)\n• `promotedObject.customConversionId` (can be sent alone; if `pixelId` is also sent, `customEventType` is still required)\n• For pixel custom events: `promotedObject.customEventType` = `OTHER` + `promotedObject.customEventStr`","platforms":["meta-ads","ads"],"url":"https://docs.zernio.com/changelog#1786717475470"},{"id":"1786711529710","date":"2026-08-14T12:45:29.710Z","type":"breaking_change","message":"SMS is now supported in inbox webhooks, and SMS sends now explicitly fail for opted-out recipients.\n\nInbound 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).\n\nWhen sending via `POST /v1/sms/messages`:\n• Opted-out recipients (replied STOP) are refused with `409`\n• `409` can also mean the same `Idempotency-Key` is still in flight\n\nWebhook platform enum now includes `sms` (alongside `instagram`, `facebook`, `telegram`, `whatsapp`).","platforms":["instagram","telegram","whatsapp","inbox","telephony","webhooks"],"url":"https://docs.zernio.com/changelog#1786711529710"},{"id":"1786709185200","date":"2026-08-14T12:06:25.200Z","type":"improvement","message":"KYC address payloads now accept an optional `extended_address` field on:\n• `POST /v1/phone-numbers/kyc`\n• `POST /v1/phone-numbers/kyc/validate-address`\n(and the deprecated `/v1/whatsapp/...` aliases).\n\nUse `extended_address` for address complements like apartment/suite/unit (or quadra/lote in some countries). This can help pass address validation without changing `street_address`.\n\nSet `address.extended_address` (KYC submit) or top-level `extended_address` (validate-address).","platforms":["whatsapp","telephony"],"url":"https://docs.zernio.com/changelog#1786709185200"},{"id":"1786706059563","date":"2026-08-14T11:14:19.563Z","type":"new_feature","message":"Webhooks now support **referral click events** via the new `referral.received` event.\n\nThis 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).\n\nTo subscribe, include `referral.received` in `events` when creating/updating a webhook:\n• `POST /v1/webhooks/settings` `events`\n• `PUT /v1/webhooks/settings` `events`\n\nThe webhook payload is `WebhookPayloadReferral` with:\n• `event` = `referral.received`\n• `referral` (Meta object forwarded verbatim; may include `ref`, `source`, `type`, `referer_uri`, `ad_id`, `ads_context_data`)\n• `sender`, `conversation`, `account`, `timestamp`","platforms":["threads","meta-ads","ads","webhooks"],"url":"https://docs.zernio.com/changelog#1786706059563"},{"id":"1786703808365","date":"2026-08-14T10:36:48.365Z","type":"new_feature","message":"You can now attach a Meta **multi-language ad** when adding an ad to an existing ad set via `POST /v1/ads/create`.\n\nIn `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.\n\nUse:\n• `adSetId` (attach mode)\n• `defaultLocale` (e.g. `en`, `pt_BR`)\n• `translations[]` with required fields: `locale`, `headline`, `body`, `description` (optional per-locale: `linkUrl`, `imageUrl`, `videoUrl`, `thumbnailUrl`)\n\nNote: `translations` is mutually exclusive with `dynamicCreative`, `placementAssets`, `carouselCards`, `existingCreativeId` and `creatives[]`.","platforms":["meta-ads","ads"],"url":"https://docs.zernio.com/changelog#1786703808365"},{"id":"1786650777089","date":"2026-08-13T19:52:57.089Z","type":"new_feature","message":"POST `/v1/posts` now supports **LinkedIn plain reposts** (no commentary).\n\nIf 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.\n\nUse `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).","platforms":["linkedin","publishing"],"url":"https://docs.zernio.com/changelog#1786650777089"},{"id":"1786636940914","date":"2026-08-13T16:02:20.914Z","type":"improvement","message":"Workflow nodes now support an optional display label via `WorkflowNode.label`.\n\nThis 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.\n\nUse `label` (string, 1–80 chars) on `WorkflowNode`.","platforms":["general"],"url":"https://docs.zernio.com/changelog#1786636940914"},{"id":"1786632185306","date":"2026-08-13T14:43:05.306Z","type":"improvement","message":"`GET /v1/ads/targeting/search` expands geo targeting with a new `geoType` option `all` and additional geo types.\n\nYou can now search across multiple geo levels in one call (relevance-ranked) instead of picking a single level.\n\nUpdated `geoType` enum:\n• `all`, `country`, `region`, `city`, `subcity`, `neighborhood`, `place`, `zip`, `metro_area`, `geo_market`\n\nPinterest 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.","platforms":["pinterest","pinterest-ads","ads"],"url":"https://docs.zernio.com/changelog#1786632185306"},{"id":"1786628197712","date":"2026-08-13T13:36:37.712Z","type":"improvement","message":"`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).\n\nThis lets you page deeper reply threads without a separate endpoint; on YouTube, `postId` must still be a video id.\n\nUse:\n• `GET /v1/inbox/comments/{postId}?accountId=...` where `{postId}` can be a post ID or (FB/IG only) a comment ID\n• New response signal (Facebook only): `repliesHasMore` (when `true`, fetch remaining replies by calling the same endpoint with that comment’s `id` as `postId`)\n\nAlso updated for `GET /v1/inbox/comments` pagination:\n• `nextCursor` pagination can continue past the first page on `facebook`, `instagram`, `threads`, `linkedin`, `youtube`\n• Cursor paging is only coherent for `sortBy=date` + `sortOrder=desc`; other sort combinations can make page 2 unreliable\n• Treat `nextCursor` as opaque and dedupe results by `id`","platforms":["instagram","threads","linkedin","youtube","inbox"],"url":"https://docs.zernio.com/changelog#1786628197712"},{"id":"1786617033029","date":"2026-08-13T10:30:33.029Z","type":"new_feature","message":"Shopify is now supported as a **connect-only** platform, with a new Blogs API to manage store blogs and articles.\n\nStart 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`.\n\nManage blogs on a connected Shopify account (`accountId`):\n• `GET /v1/accounts/{accountId}/blogs` (`limit` 1-50, `cursor`)\n• `POST /v1/accounts/{accountId}/blogs` (`title`, optional `handle`)\n• `GET/PATCH/DELETE /v1/accounts/{accountId}/blogs/{blogId}` (`blogId` is platform-native numeric)\n\nManage articles within a blog:\n• `GET /v1/accounts/{accountId}/blogs/{blogId}/articles` (`limit` 1-50, `cursor`)\n• `POST /v1/accounts/{accountId}/blogs/{blogId}/articles` (supports `bodyHtml`, `tags`, `image.url`, `seo.title`/`seo.description`, `isPublished`, `publishDate`)\n• `GET/PATCH/DELETE /v1/accounts/{accountId}/blogs/{blogId}/articles/{articleId}` (`articleId` is platform-native numeric)\n\nNotes: a future `publishDate` schedules publication natively on Shopify; `DELETE` on a blog deletes the blog _and all its articles_ permanently.\n\nAlso added: `GET /v1/connect/{platform}` now accepts WhatsApp-only `onboarding` = `api` | `business_app`.","platforms":["whatsapp","publishing","commerce"],"url":"https://docs.zernio.com/changelog#1786617033029"},{"id":"1786553316742","date":"2026-08-12T16:48:36.742Z","type":"improvement","message":"Campaign listing and tree endpoints now support filtering by delivery/spend within a date window.\n\nOn `GET /v1/ads/campaigns`, you can now return only campaigns that actually delivered in `fromDate`–`toDate`, or that reached a spend threshold.\n\nNew query params:\n• `hasDelivery` (boolean) — only campaigns with delivery in the window (spend &gt; 0 or impressions at zero spend)\n• `minSpend` (number, min 0) — only campaigns whose spend in the window reaches this amount (in the campaign’s own `currency`); implies `hasDelivery`\n\nOn `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.","platforms":["ads","analytics"],"url":"https://docs.zernio.com/changelog#1786553316742"},{"id":"1786544875295","date":"2026-08-12T14:27:55.295Z","type":"new_feature","message":"Google Local Services Ads lead insights are now available via two new endpoints.\n\nYou 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.\n\nGet leads with `GET /v1/ads/local-services/leads`\nRequired: `accountId`\nOptional filters: `customerId`, `fromDate`, `toDate`, `leadType` (`PHONE_CALL`/`MESSAGE`/`BOOKING`), `leadStatus` (e.g. `NEW`, `BOOKED`, `WIPED_OUT`), `chargedOnly`, `pageToken`\nKey fields: `id`, `contact` (null when `WIPED_OUT`), `charged`, `creditState`, `createdTime`\n\nGet lead conversations with `GET /v1/ads/local-services/leads/{leadId}/conversations`\nParams: `leadId`, `accountId` (+ optional `customerId`, `pageToken`)\nConversation fields include `channel` (`PHONE_CALL`/`MESSAGE`/`SMS`/`EMAIL`/`WHATSAPP`/`ADS_API`), `participantType` (`ADVERTISER`/`CONSUMER`), plus `phoneCall.callRecordingUrl` / `message.attachmentUrls` when applicable.","platforms":["whatsapp","google-ads","ads","inbox","telephony","analytics"],"url":"https://docs.zernio.com/changelog#1786544875295"},{"id":"1786544104877","date":"2026-08-12T14:15:04.877Z","type":"new_feature","message":"TikTok ads now populate **engagement breakdown** in `AdEngagementCounts` (previously Meta-only).\n\nYou can now read paid engagement interactions for TikTok directly from `engagementBreakdown` (while TikTok follows remain in `actions`).\n\nKey fields:\n• `AdMetrics.engagementBreakdown.reactions` (TikTok paid likes)\n• `AdMetrics.engagementBreakdown.comments`\n• `AdMetrics.engagementBreakdown.shares`\n• `AdMetrics.actions.follow` (TikTok followers objective result)\n\nOther platforms are unchanged (still 0 where not supported).","platforms":["tiktok","tiktok-ads","ads","analytics"],"url":"https://docs.zernio.com/changelog#1786544104877"},{"id":"1786541973985","date":"2026-08-12T13:39:33.985Z","type":"new_feature","message":"POST `/v1/ads/create` now supports TikTok **Smart+** campaigns via `smartPlus`.\n\nWhen `smartPlus: true`, Zernio creates an Upgraded Smart+ campaign where TikTok automates targeting/bidding/delivery (within your provided geo targeting).\n\nKey fields:\n• Enable: `smartPlus` (boolean)\n• Supported `goal`: `conversions`, `lead_generation`, `app_promotion`\n• Web Smart+ requires `promotedObject.pixelId` + `promotedObject.customEventType`\n• App Smart+ requires `promotedObject.applicationId`\n• Budget is campaign-level; `budgetType=lifetime` requires `endDate`\n• Cannot be combined with `adSetId`","platforms":["tiktok","tiktok-ads","ads"],"url":"https://docs.zernio.com/changelog#1786541973985"},{"id":"1786534860016","date":"2026-08-12T11:41:00.016Z","type":"new_feature","message":"LinkedIn posts now support **polls** via `poll` in `LinkedInPlatformData`.\n\nUse 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.\n\nSet `poll` with:\n• `question` (1-140 chars)\n• `options` (2-4 items, 1-30 chars each)\n• `duration` (optional): `ONE_DAY` | `THREE_DAYS` | `SEVEN_DAYS` | `FOURTEEN_DAYS` (default `SEVEN_DAYS`)","platforms":["linkedin","publishing"],"url":"https://docs.zernio.com/changelog#1786534860016"},{"id":"1786529316255","date":"2026-08-12T10:08:36.255Z","type":"improvement","message":"Ad analytics now include additional Meta click + cost breakdown fields in `AdMetrics`.\n\nThis 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.\n\nNew fields:\n• `costPerAction` (object) — derived `spend / actions[type]` per action type\n• `outboundClicks`, `outboundClicksCtr`\n• `inlineLinkClicks`, `inlineLinkClickCtr`\n• `uniqueClicks`, `uniqueCtr`\n\nNote: these click fields are Meta-only; other platforms return `0` (and `costPerAction` may be `{}` when not applicable).","platforms":["meta-ads","ads","analytics"],"url":"https://docs.zernio.com/changelog#1786529316255"},{"id":"1786458672181","date":"2026-08-11T14:31:12.181Z","type":"new_feature","message":"Google Ads now supports **keyword edits** on existing ads via `PUT /v1/ads/{adId}`.\n\nYou 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.\n\nUse:\n• `targeting.keywords` (Google only) — items are `\"string\"` (defaults to broad) or `{ text, matchType }` where `matchType` is `exact` | `phrase` | `broad`\n• `targeting.negativeKeywords` (Google only) — same shape as above\n\nAlso added for Google Search creation via `POST /v1/ads/create`:\n• `negativeKeywords` — broad-match negative keywords on the new ad group (editable later via `PUT /v1/ads/{adId}` `targeting.negativeKeywords`).","platforms":["google-ads","ads"],"url":"https://docs.zernio.com/changelog#1786458672181"},{"id":"1786447018355","date":"2026-08-11T11:16:58.355Z","type":"improvement","message":"TikTok ad reporting now populates additional video metrics in `AdMetrics` (previously documented as Meta-only).\n\nThis lets you use the same video KPI fields across Meta and TikTok when reading ad/campaign metrics.\n\nTikTok sources now apply to:\n• `videoPlayActions`\n• `videoP25WatchedActions`\n• `videoP50WatchedActions`\n• `videoP75WatchedActions`\n• `videoP100WatchedActions`\n• `videoAvgTimeWatchedActions`\n\nMeta-only remains:\n• `video30SecWatchedActions`\n• `videoThruplayWatchedActions`\n• `videoP95WatchedActions`\n• `costPerThruplay`","platforms":["tiktok","meta-ads","tiktok-ads","ads","analytics"],"url":"https://docs.zernio.com/changelog#1786447018355"},{"id":"1786380461777","date":"2026-08-10T16:47:41.777Z","type":"new_feature","message":"Instagram now supports attaching catalog audio to Reels via `platformSpecificData.audioConfiguration`.\n\nYou 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.\n\nSearch params:\n• `audioType`: `music` | `original_sound`\n• `q`: optional keywords (omit for trending)\n\nTo publish with audio, set:\n• `platformSpecificData.audioConfiguration.audioId` (required)\n• `platformSpecificData.audioConfiguration.audioVolume` (0-100)\n• `platformSpecificData.audioConfiguration.videoVolume` (0-100)\n\nRequires an Instagram account connected via **Facebook Login**. Classic Instagram Login accounts will get a 400 `instagram_audio_requires_facebook_login` and must be reconnected using the Facebook option.","platforms":["instagram","publishing"],"url":"https://docs.zernio.com/changelog#1786380461777"},{"id":"1786378567109","date":"2026-08-10T16:16:07.109Z","type":"improvement","message":"`GET /v1/broadcasts/{broadcastId}/recipients` now returns `errorTraceId` for failed recipients.\n\nUse `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).\n\nNew field: `errorTraceId` (`string` | `null`)","platforms":["inbox"],"url":"https://docs.zernio.com/changelog#1786378567109"},{"id":"1786361408875","date":"2026-08-10T11:30:08.875Z","type":"improvement","message":"`GET /v1/accounts/{accountId}/instagram/stories/{storyId}/insights` can now return `502` when Instagram rejects the upstream request.\n\nThis makes upstream failures explicit so clients can distinguish Instagram-side rejection from successful-but-empty expired story results.\n\nHandle these responses:\n• `200` with `data.source` = `live` | `cached` | `unavailable`\n• `502` - Instagram rejected the request","platforms":["instagram","analytics"],"url":"https://docs.zernio.com/changelog#1786361408875"},{"id":"1786360085008","date":"2026-08-10T11:08:05.008Z","type":"new_feature","message":"Meta ads now support **scheduled budget increases** via `POST /v1/ads/high-demand-periods`.\n\nThis lets you pre-schedule temporary budget bumps (e.g. Black Friday) instead of editing budgets manually on the day.\n\nSend:\n• `accountId`\n• exactly one of `campaignId` / `adSetId`\n• `budgetValueType`: `ABSOLUTE` | `MULTIPLIER`\n• `budgetValue`\n• `timeStart`, `timeEnd` (Unix seconds, 15-min boundaries)\n• optional `recurrenceType`: `ONE_TIME` | `WEEKLY` | `MONTHLY`\n\nMeta custom conversions are now manageable via the API:\n\n• List: `GET /v1/accounts/{accountId}/custom-conversions` (requires `adAccountId`)\n• Create or reuse: `POST /v1/accounts/{accountId}/custom-conversions`\n\nCreating 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`.\n\nMeta creatives now accept `multiAdvertiser`: `OPT_IN` | `OPT_OUT` on:\n• `POST /v1/ads/creatives`\n• `POST /v1/ads/create`\n\nUse `OPT_OUT` to disable Meta’s multi-advertiser ads behavior (Meta auto-enrolls by default).","platforms":["meta-ads","ads"],"url":"https://docs.zernio.com/changelog#1786360085008"},{"id":"1786359502519","date":"2026-08-10T10:58:22.519Z","type":"breaking_change","message":"`POST /v1/media/presign` no longer accepts the `permanent` request field.\n\nIf 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.\n\nRemoved parameter: `permanent` (boolean)","platforms":["publishing"],"url":"https://docs.zernio.com/changelog#1786359502519"},{"id":"1786358205264","date":"2026-08-10T10:36:45.264Z","type":"new_feature","message":"POST `/v1/media/presign` now supports `permanent` uploads.\n\nBy 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).\n\nNew request field: `permanent` (boolean, default `false`)\n\nWhen `permanent` is true, the response `publicUrl` and `key` use the `media/` prefix instead of `temp/`.","platforms":["publishing"],"url":"https://docs.zernio.com/changelog#1786358205264"},{"id":"1786303081657","date":"2026-08-09T19:18:01.657Z","type":"new_feature","message":"You can now **like/unlike posts** via the Inbox API.\n\nThis 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.\n\nNew endpoints:\n• `POST /v1/inbox/posts/{postId}/like` with `accountId` (+ optional `reactionType` for LinkedIn: `LIKE`, `PRAISE`, `EMPATHY`, `INTEREST`, `APPRECIATION`, `ENTERTAINMENT`; optional `cid` for Bluesky)\n• `DELETE /v1/inbox/posts/{postId}/like` with `accountId` (+ `likeUri` required for Bluesky)\n\nAlso 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).","platforms":["linkedin","bluesky","inbox"],"url":"https://docs.zernio.com/changelog#1786303081657"},{"id":"1786287232636","date":"2026-08-09T14:53:52.636Z","type":"new_feature","message":"Workflow **AI** nodes now support `openrouter` as an `ai.provider` value.\n\nThis lets you route AI steps through OpenRouter using your BYOK setup (set `provider` + `model`).\n\nSet `ai.provider` to one of: `anthropic`, `openai`, `google`, `mistral`, `groq`, `openrouter`.","platforms":["general"],"url":"https://docs.zernio.com/changelog#1786287232636"},{"id":"1786286662626","date":"2026-08-09T14:44:22.626Z","type":"new_feature","message":"Inbox messages now support **refreshable attachment URLs** for Instagram/Facebook DMs.\n\nMeta DM media URLs expire; attachments now include a stable `refreshUrl` you can store and call later to get a working media URL.\n\nNew endpoint: `GET /v1/inbox/conversations/{conversationId}/messages/{messageId}/attachments/{index}`\nKey params: `accountId` (required), `format` (`redirect` | `json`, default `redirect`)\n\nResponse behavior:\n• Default `format=redirect` returns `302` to the live media URL\n• `format=json` returns `{ url, refreshed }`\n\nAlso added on `GET /v1/inbox/conversations/{conversationId}/messages` attachments:\n• `attachments[].refreshUrl` (Instagram/Facebook only)\n• `attachments[].url` is now documented as expiring on Instagram/Facebook (use immediately; don’t store).","platforms":["instagram","facebook","inbox"],"url":"https://docs.zernio.com/changelog#1786286662626"},{"id":"1786285216483","date":"2026-08-09T14:20:16.483Z","type":"new_feature","message":"Comment-to-DM automations can now also trigger from inbound DMs via `alsoMatchInDms`.\n\nSet `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).\n\nKey details:\n• `alsoMatchInDms` (boolean, default `false`) on `POST /v1/comment-automations` and `PATCH /v1/comment-automations/{automationId}`\n• Requires at least one `keywords` entry (empty keyword lists are rejected when enabling)\n• Not allowed for `trigger=story_reply`\n\nResponses now include:\n• `alsoMatchInDms` on automation objects (list/get/create/update)\n• `source` on log entries: `comment` | `story_reply` | `dm`","platforms":["inbox"],"url":"https://docs.zernio.com/changelog#1786285216483"},{"id":"1786283353179","date":"2026-08-09T13:49:13.179Z","type":"new_feature","message":"Instagram connect now supports **headless mode** for `loginMethod=facebook_login` on `GET /v1/connect/instagram`.\n\nThis 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`.\n\nUse:\n• `GET /v1/connect/instagram?loginMethod=facebook_login&headless=true`\nCallback includes: `platform=instagram`, `step=select_account`, `profileId`, `tempToken`, `connect_token`","platforms":["instagram"],"url":"https://docs.zernio.com/changelog#1786283353179"},{"id":"1786117957370","date":"2026-08-07T15:52:37.370Z","type":"breaking_change","message":"WhatsApp connect now rejects Zernio-provisioned numbers that are pinned to a different profile with a `409` conflict.\n\nThis 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.\n\nNew `409` responses:\n• `GET /v1/connect/whatsapp` (when `profileId` doesn’t match the provisioned number’s profile)\n• `POST /v1/connect/whatsapp/credentials`\n• `POST /v1/connect/whatsapp/select-phone-number`\n\nRemediation:\n• Move the number with `PATCH /v1/whatsapp/phone-numbers/{id}/profile`, then connect again.","platforms":["whatsapp","telephony"],"url":"https://docs.zernio.com/changelog#1786117957370"},{"id":"1786117230634","date":"2026-08-07T15:40:30.634Z","type":"improvement","message":"Google Business location selection/updates now validate the provided Google account resource name and will reject mismatches.\n\nIf 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.\n\nApplies to:\n• `POST /v1/connect/googlebusiness/select-location` with `accountId`\n• `PUT /v1/accounts/{accountId}/gmb-locations` with `googleAccountId` (legacy body field `accountId` still accepted)\n• `POST /v1/accounts/{accountId}/gmb-locations/assign` with `googleAccountId`","platforms":["google-business"],"url":"https://docs.zernio.com/changelog#1786117230634"},{"id":"1786116194020","date":"2026-08-07T15:23:14.020Z","type":"breaking_change","message":"POST `/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`.\n\nThis 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).\n\nKey changes:\n• Meta-only attach: send `adSetId` to attach under an existing ad set\n• When `adSetId` is set, `budget`/`schedule`/`targeting` are owned by the ad set and sending any of them is a `400`\n• `budget` is now required only when `adSetId` is NOT set\n• Meta-only additions: `instagramAccountId`, `destinationType` (`INSTAGRAM_PROFILE`, `WEBSITE`, `ON_AD`, `MESSENGER`, `WHATSAPP`)\n• `linkUrl` is now supported on Meta boosts (still supported on TikTok); `callToAction` must be sent together with `linkUrl` (sending either alone is a `400`)\n\nPOST `/v1/ads/ctwa` (deprecated) also adds an attach flow:\n• Send `adSetId` to add creatives to an existing messaging ad set; then `budgetAmount`/`budgetType`/`endDate`/`objective`/`countries`/`interests`/`audienceId` are rejected with `400`\n\nSchema note (breaking for generated clients): `CtwaAdRequestBody` no longer lists `budgetAmount`/`budgetType` as always-required (they’re required unless `adSetId` is set).","platforms":["instagram","facebook","tiktok","whatsapp","meta-ads","tiktok-ads","ads"],"url":"https://docs.zernio.com/changelog#1786116194020"},{"id":"1786112242866","date":"2026-08-07T14:17:22.866Z","type":"new_feature","message":"New endpoint: `PATCH /v1/whatsapp/phone-numbers/{id}/profile` to move a provisioned number to another profile.\n\nThis re-homes the number and any associated accounts together (so you don’t split the number across profiles by moving only the social account).\n\nSend `{ \"profileId\": \"...\" }` where `id` is the phone number record id from `GET /v1/phone-numbers` (not an account id).\n\nResponse may include `movedPlatforms` with values `phone`, `sms`, `whatsapp`. Conflicts return `409` if the destination profile already has an account on any of those platforms.","platforms":["whatsapp","telephony"],"url":"https://docs.zernio.com/changelog#1786112242866"},{"id":"1786111076430","date":"2026-08-07T13:57:56.430Z","type":"new_feature","message":"`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).\n\nFor Google, you can set campaign bidding via:\n• `platform`: `google`\n• `bidStrategy` (Meta + Google)\n• `bidAmount` (Google only; required for `LOWEST_COST_WITH_BID_CAP` and `COST_CAP`)\n• `roasAverageFloor` (Google only; required for `LOWEST_COST_WITH_MIN_ROAS`)\n\nThe `200` response now echoes back only the fields you sent, plus `updated` (and may include `bidAmount`/`roasAverageFloor` when provided).","platforms":["meta-ads","google-ads","ads"],"url":"https://docs.zernio.com/changelog#1786111076430"},{"id":"1786092850004","date":"2026-08-07T08:54:10.004Z","type":"new_feature","message":"Message reactions are now supported on **Instagram**, **Facebook Messenger**, and **Slack** via the existing reactions endpoints.\n\nYou can add/remove reactions across more inbox channels, and reaction webhooks now cover these platforms too.\n\nUse:\n• `POST /v1/inbox/conversations/{conversationId}/messages/{messageId}/reactions` with `emoji`\n• `DELETE /v1/inbox/conversations/{conversationId}/messages/{messageId}/reactions` with `accountId` (query)\n\nNotes:\n• Slack reactions require a Slack emoji name like `:thumbsup:` (unnamed Unicode emoji returns 400)\n• Instagram/Messenger reactions are subject to Meta’s 24h messaging window\n• `reaction.received` webhook now includes Slack/Instagram/Messenger (sender can be the business platform id; compare `reaction.sender.id` with `conversation.participantId`)\n\nComment-to-DM automations now support sending a Meta generic template (product card) as the first DM.\n\nSet `template` on `POST /v1/comment-automations` / `PATCH /v1/comment-automations/{automationId}`:\n• `template.type`: `generic`\n• `template.elements`: 1–10 cards (each supports `title`, `subtitle`, `imageUrl`, `buttons`)\n\nConstraints:\n• `template` is mutually exclusive with `buttons` (sending both is a 400)\n• For `PATCH`, pass `template: null` to clear the card\n\nAlso: for `POST /v1/inbox/conversations/{conversationId}/messages` on Instagram/Facebook, top-level `buttons` are now explicitly mutually exclusive with `template` (sending both returns 400).","platforms":["instagram","facebook","slack","inbox","webhooks"],"url":"https://docs.zernio.com/changelog#1786092850004"},{"id":"1786090366267","date":"2026-08-07T08:12:46.267Z","type":"new_feature","message":"Instagram follow checks are now available via `GET /v1/accounts/{accountId}/follow-status/{userId}`.\n\nThis 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.\n\nKey fields:\n• `isFollower`: `true` | `null` (null = unknown, never “no”)\n• `unavailableReason`: `consent_required` | `dm_access_disabled` | `not_messageable` | `error` | `null`\n\nComment-to-DM automations now support follower-based targeting + optional follow verification (Instagram only), and delayed sends.\n\nNew request fields on `POST /v1/comment-automations` and `PATCH /v1/comment-automations/{automationId}`:\n• `dmDelaySeconds` (0–86400)\n• `commentReplyDelaySeconds` (0–86400)\n• `audience` (`followerStatus`: `any` | `follower` | `non_follower`; `whenUnknown`: `send` | `skip` | `verify`; optional `minFollowerCount`)\n• `followGate` (`message`, `buttonLabel`, optional `notFollowingMessage`)\n\nAutomation logs (`GET /v1/comment-automations/{automationId}` and `.../logs`) add new `status` values `pending` and `gated`, plus fields like `audienceOutcome` (`passed` | `blocked` | `gate_sent` | `gate_passed` | `gate_failed`) and `nextDueAt` while queued.\n\nWebhook comment payloads now may include Instagram-only `comment.author.instagramProfile` (best-effort) with `isFollower`/`followerCount` when available; absence means “unknown”.","platforms":["instagram","inbox","webhooks"],"url":"https://docs.zernio.com/changelog#1786090366267"},{"id":"1786021438653","date":"2026-08-06T13:03:58.653Z","type":"improvement","message":"`GET /v1/comment-automations/{automationId}/logs` now returns **non-matching comment diagnostics** via a new `misses` object.\n\nUse `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.\n\nNew response fields:\n• `misses.total`\n• `misses.retentionDays`\n• `misses.samples[]`: `commentText`, `commenterName`, `excludedBy`, `at`\n\nRequest validation tightened:\n• `limit` now `1..200`\n• `skip` now `&gt;= 0`\n• Invalid values may return `400 BadRequest`","platforms":["inbox","analytics"],"url":"https://docs.zernio.com/changelog#1786021438653"},{"id":"1786019651268","date":"2026-08-06T12:34:11.268Z","type":"improvement","message":"PATCH `/v1/comment-automations/{automationId}` now accepts `trigger` to control what fires an automation.\n\nYou 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.\n\nSet `trigger` to: • `comment` • `story_reply` (Instagram only)","platforms":["instagram","inbox"],"url":"https://docs.zernio.com/changelog#1786019651268"},{"id":"1786018613918","date":"2026-08-06T12:16:53.918Z","type":"new_feature","message":"Ads analytics now includes **funnel step counts**, an **engagement breakdown**, and **cost per ThruPlay**.\n\nThis 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.\n\nNew fields in `AdMetrics`:\n• `costPerThruplay`\n• `funnel` (ref `AdFunnelCounts`): `landingPageViews`, `contentViews`, `searches`, `wishlistAdds`, `cartAdds`, `checkoutsInitiated`, `paymentInfoAdds`, `purchases`, `leads`, `registrationsCompleted`, `appInstalls`, `messagingConversationsStarted`, `messagingFirstReplies`\n• `engagementBreakdown` (ref `AdEngagementCounts`): `postEngagement`, `pageEngagement`, `reactions`, `comments`, `shares`, `saves`, `pageLikes`, `videoViews`, `linkClicks`\n\nAlso new in `Ad`:\n• `creativeType`: `carousel` | `video` | `document` | `image` | `null`","platforms":["facebook","meta-ads","ads","analytics"],"url":"https://docs.zernio.com/changelog#1786018613918"},{"id":"1786018133044","date":"2026-08-06T12:08:53.044Z","type":"new_feature","message":"Comment-to-DM automations now support more precise keyword matching and exclusions via `matchMode=word`, plus new fields `excludeKeywords` and `typoTolerance`.\n\nThis lets you avoid accidental triggers (e.g., keyword `app` no longer firing on `happy`) and optionally match close misspellings when using word-based matching.\n\nUse `matchMode`:\n• `contains` • `word` • `exact`\n\nNew fields:\n• `excludeKeywords: string[]` - comments matching these never trigger (compared using the same `matchMode`)\n• `typoTolerance: boolean` - only with `matchMode=word`\n\nAvailable on `POST /v1/comment-automations`, `PATCH /v1/comment-automations/{automationId}`, and returned by `GET` endpoints.","platforms":["inbox"],"url":"https://docs.zernio.com/changelog#1786018133044"},{"id":"1786016762094","date":"2026-08-06T11:46:02.094Z","type":"improvement","message":"LinkedIn connected accounts now include additional profile details in `SocialAccount.metadata.profileData` (refreshed on each daily snapshot).\n\nThis lets you read LinkedIn bio/headline and vanity slug info directly from the account object without extra lookups.\n\nKey fields:\n• `metadata.profileData.bio` (nullable)\n• `metadata.profileData.extraData.vanityName` (personal accounts)\n• `metadata.organizationInfo.vanityName` (organization accounts)","platforms":["linkedin"],"url":"https://docs.zernio.com/changelog#1786016762094"},{"id":"1786014808646","date":"2026-08-06T11:13:28.646Z","type":"new_feature","message":"Meta ads creation now supports optimizing for **pixel custom events** via `promotedObject.customEventStr` on `POST /v1/ads/create`.\n\nUse 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).\n\nSet:\n• `promotedObject.customEventType` = `OTHER`\n• `promotedObject.customEventStr` = `&lt;your_event_name&gt;`\n\n`customEventStr` is case-sensitive, and `OTHER` requires `customEventStr` (and vice versa).","platforms":["meta-ads","ads"],"url":"https://docs.zernio.com/changelog#1786014808646"},{"id":"1786014431087","date":"2026-08-06T11:07:11.087Z","type":"improvement","message":"Incoming **message.received** webhooks now include quote-reply context via `metadata.quotedMessageId`.\n\nThis lets you correlate an inbound message that quotes/replies to an earlier message (WhatsApp/Instagram/Facebook Messenger) using the original message’s `platformMessageId`.\n\nNew field:\n• `metadata.quotedMessageId` - platformMessageId of the message being quoted/replied to\n\nAlso clarified `POST /v1/inbox/conversations/{conversationId}/messages` `replyTo` behavior:\n• WhatsApp: pass `wamid`\n• Telegram: pass Telegram message ID\n• Instagram: outbound quote replies are not supported by Meta's Send API; `replyTo` is ignored. Quote context is available on inbound messages only.\n• Slack: threads the reply (uses `thread_ts`)\n• Silently ignored on platforms without reply support (incl. Facebook Messenger)","platforms":["instagram","facebook","telegram","whatsapp","slack","inbox","webhooks"],"url":"https://docs.zernio.com/changelog#1786014431087"},{"id":"1786013524799","date":"2026-08-06T10:52:04.799Z","type":"improvement","message":"Meta multi-language ads now support per-locale destination URLs via `translations[].linkUrl` on `POST /v1/ads/create`.\n\nThis lets you send users to different landing pages per language while keeping a single ad (shared social proof).\n\nSet `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`).","platforms":["meta-ads","ads"],"url":"https://docs.zernio.com/changelog#1786013524799"},{"id":"1786009166442","date":"2026-08-06T09:39:26.442Z","type":"new_feature","message":"Webhooks now support per-subscription event blocking via `disabledResourceGroups` on `POST /v1/webhooks/settings` and `PUT /v1/webhooks/settings`.\n\nThis 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).\n\nUse `disabledResourceGroups` (enum: `publishing`, `engagement`, `messages`, `contacts`, `analytics`, `ads`, `telephony`, `accounts`, `billing`, `webhooks`):\n• Create: include `disabledResourceGroups` to opt out; omit or `[]` to receive everything in `events`\n• Update: sending `disabledResourceGroups` replaces the denylist; send `[]` to clear; omit to leave unchanged\n\nRestricted `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.\n\nNew/expanded `403` behavior (`ResourceGroupForbidden`) was added to:\n• `GET /v1/webhooks/settings`\n• `DELETE /v1/webhooks/settings`\n• `POST /v1/webhooks/test` (note: `webhook.test` is in the `webhooks` group)\n\nNote: after updating a webhook denylist, already-queued events may still arrive for up to ~5 minutes.","platforms":["inbox","analytics","webhooks"],"url":"https://docs.zernio.com/changelog#1786009166442"},{"id":"1785942355864","date":"2026-08-05T15:05:55.864Z","type":"breaking_change","message":"Non-draft updates to `PUT /v1/posts/{postId}` now run the same per-platform validation as post creation.\n\nIf the resulting post violates platform rules (media requirements, platform-specific fields, etc.), the update will return `400` instead of succeeding.\n\nAlso, `PostUpdateResponse` may now include `warnings` (array of strings).","platforms":["publishing"],"url":"https://docs.zernio.com/changelog#1785942355864"},{"id":"1785929615698","date":"2026-08-05T11:33:35.698Z","type":"new_feature","message":"WhatsApp now supports **requesting a user’s contact info** via interactive messages.\n\nYou 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.\n\nSend with `interactive.type`:\n• `request_contact_info`\n\nOn `message.received`, check:\n• `metadata.contacts` (array)\n• `metadata.contactsOrigin`: `contact_request` | `other`","platforms":["whatsapp","inbox"],"url":"https://docs.zernio.com/changelog#1785929615698"},{"id":"1785927544199","date":"2026-08-05T10:59:04.199Z","type":"new_feature","message":"Slack DMs are now supported in the Inbox API.\n\nYou 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`.\n\nKey params:\n• `GET /v1/accounts/{accountId}/slack-members`: `query`, `limit`\n• `POST /v1/inbox/conversations`: `participantId` (Slack member id, e.g. `U01ABCDEF`)\n\nAlso updated: `POST /v1/inbox/conversations` now lists supported platforms as X/Twitter, Bluesky, Reddit, WhatsApp, **SMS**, and **Slack**.","platforms":["x","reddit","bluesky","whatsapp","slack","inbox","telephony"],"url":"https://docs.zernio.com/changelog#1785927544199"},{"id":"1785927394969","date":"2026-08-05T10:56:34.969Z","type":"improvement","message":"OAuth connect now surfaces upstream token-exchange failures via a new `502` response on `POST /v1/connect/{platform}`.\n\nThis helps distinguish platform-side rejections from internal errors; handle `502` separately from `500` (the upstream 4xx is forwarded as `platform_error`).\n\nKey response codes:\n• `502` - platform rejected token exchange (`platform_error`)\n• `500` - internal error while connecting the account\n\nLead 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.\n\nKey response code:\n• `422` - Meta rejected lead form (often requires a Page admin to accept Lead Ads Terms)","platforms":["facebook","meta-ads","ads"],"url":"https://docs.zernio.com/changelog#1785927394969"},{"id":"1785923870082","date":"2026-08-05T09:57:50.082Z","type":"improvement","message":"Contacts listing now supports filtering by SocialAccount via `accountId` on `GET /v1/contacts`.\n\nUse `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`).\n\nNew query param: `accountId`\n\nAlso, `POST /v1/contacts` `409` is now explicitly documented as: `Duplicate channel. The platformIdentifier is already bound to a channel on this accountId.`","platforms":["inbox"],"url":"https://docs.zernio.com/changelog#1785923870082"},{"id":"1785921042493","date":"2026-08-05T09:10:42.493Z","type":"new_feature","message":"New endpoint `GET /v1/auth/verify` was added to validate a bearer credential (API key or OAuth access token) without calling a data endpoint.\n\nUse it to confirm a credential is valid before proceeding (e.g., when verifying an incoming token).\n\nOn `200` it returns `valid`, `userId`, `authType` (`api_key` | `oauth` | `session`), and `scope` (space-separated OAuth scopes; `null` for API keys). `401` if invalid.","platforms":["general"],"url":"https://docs.zernio.com/changelog#1785921042493"},{"id":"1785920153542","date":"2026-08-05T08:55:53.542Z","type":"improvement","message":"POST `/v1/ads/boost` now supports **Meta-only optimization goal overrides** via `optimizationGoal`.\n\nThis 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.\n\nSet `optimizationGoal` to a Meta-compatible value for the objective derived from your `goal` (e.g. boost `goal` `engagement` maps to Meta objective `OUTCOME_AWARENESS`, so values like `REACH`, `IMPRESSIONS`, `AD_RECALL_LIFT`, or THRUPLAY-class are accepted; `POST_ENGAGEMENT` is rejected under that objective).","platforms":["meta-ads","ads"],"url":"https://docs.zernio.com/changelog#1785920153542"},{"id":"1785919433990","date":"2026-08-05T08:43:53.990Z","type":"new_feature","message":"Meta Ads now supports **Value Rule Sets** (bid adjustments by audience segment) via new endpoints under `/v1/ads/value-rule-sets`.\n\nYou 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).\n\nNew endpoints:\n• `GET /v1/ads/value-rule-sets`\n• `POST /v1/ads/value-rule-sets`\n• `GET /v1/ads/value-rule-sets/{valueRuleSetId}`\n• `PUT /v1/ads/value-rule-sets/{valueRuleSetId}` (full replace, not patch)\n• `DELETE /v1/ads/value-rule-sets/{valueRuleSetId}`\n\nAttach/detach on ad sets (Meta only) via `PUT /v1/ads/ad-sets/{adSetId}`:\n• Attach/replace: send `valueRuleSetId` (optionally `valueRulesApplied: true`)\n• Detach: send `valueRulesApplied: false` and omit `valueRuleSetId`\n• Sending `valueRulesApplied: false` with `valueRuleSetId` returns 400 `mutually_exclusive_fields`\n\nAlso supported on create (Meta only) via `POST /v1/ads/create`:\n• Attach to the new ad set with `valueRuleSetId` (optional `valueRulesApplied: true`)\n• `valueRulesApplied: false` is rejected on create\n\nEligibility (Meta enforced): only ad sets on `LOWEST_COST_WITHOUT_CAP` or `COST_CAP`.","platforms":["meta-ads","ads"],"url":"https://docs.zernio.com/changelog#1785919433990"},{"id":"1785853932965","date":"2026-08-04T14:32:12.965Z","type":"new_feature","message":"WhatsApp template creation now supports **NAMED** variables via `parameter_format` on `POST /v1/whatsapp/templates`.\n\nThis lets you use {{customer_name}}-style tokens (in addition to the existing positional {{1}} style) and provide per-variable examples for Meta review.\n\nSet `parameter_format` to `POSITIONAL` | `NAMED` (also accepts `positional` | `named`).\n\nFor NAMED templates, include examples in:\n• `components[].example.body_text_named_params`\n• `components[].example.header_text_named_params`\n\nEach named param example uses: `{ param_name, example }`.","platforms":["whatsapp"],"url":"https://docs.zernio.com/changelog#1785853932965"},{"id":"1785852576949","date":"2026-08-04T14:09:36.949Z","type":"breaking_change","message":"`Ad.creative.thumbnailUrl` can now be `null` (was always a string).\n\nThis affects ad preview/rendering: don’t assume a thumbnail URL is always available; fall back to other creative fields when it’s missing.\n\nKey change: `creative.thumbnailUrl: string | null`","platforms":["ads"],"url":"https://docs.zernio.com/changelog#1785852576949"},{"id":"1785844073739","date":"2026-08-04T11:47:53.739Z","type":"improvement","message":"Connect flow redirects now include the created social account id in the success `redirect_url` for these endpoints:\n• `POST /v1/connect/facebook/select-page`\n• `POST /v1/connect/googlebusiness/select-location`\n• `POST /v1/connect/pinterest/select-board`\n• `POST /v1/connect/snapchat/select-profile`\n\nThis lets you reliably capture the connected account immediately after OAuth without extra lookup.\n\nLook for `accountId` in the returned `redirect_url` query params (e.g. `...&accountId=...`).","platforms":["facebook","pinterest","snapchat","google-business","pinterest-ads"],"url":"https://docs.zernio.com/changelog#1785844073739"},{"id":"1785783739836","date":"2026-08-03T19:02:19.836Z","type":"breaking_change","message":"PUT `/v1/posts/{postId}` now requires `platforms` when sending root-level `tiktokSettings` or `facebookSettings`.\n\nIf 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.","platforms":["tiktok","tiktok-ads"],"url":"https://docs.zernio.com/changelog#1785783739836"},{"id":"1785776429721","date":"2026-08-03T17:00:29.721Z","type":"breaking_change","message":"In `PUT /v1/posts/{postId}`, `platforms[].platformSpecificData` update behavior is now explicitly defined.\n\nA `<platform>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).\n\nKey field: `platforms[].platformSpecificData`","platforms":["tiktok","tiktok-ads"],"url":"https://docs.zernio.com/changelog#1785776429721"},{"id":"1785764602022","date":"2026-08-03T13:43:22.022Z","type":"new_feature","message":"`GET /v1/inbox/conversations` now supports filtering by `platform=whatsapp`, and conversation objects can include new ad attribution data via `metadata`.\n\nThis 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.\n\nUse `platform` enum values: `facebook`, `instagram`, `twitter`, `bluesky`, `reddit`, `telegram`, `whatsapp`\n\nNew field: `metadata` (nullable). Keys are optional and may include:\n• WhatsApp CTWA: `ctwa_clid`, `ctwa_source_id`, `ctwa_source_type`, `ctwa_source_url`, `ctwa_headline`, `ctwa_captured_at`\n• IG/FB ads: `meta_ad_id`, `meta_ad_source`, `meta_ad_type`, `meta_ad_ref`, `meta_ad_title`, `meta_ad_photo_url`, `meta_ad_video_url`, `meta_ad_post_id`, `meta_ad_product_id`, `meta_ad_flow_id`, `meta_ad_captured_at`\n\n`GET /v1/inbox/conversations/{conversationId}` also returns `metadata</code; it currently includes the `meta_ad_*` family there.","platforms":["instagram","reddit","bluesky","telegram","whatsapp","meta-ads","ads","inbox"],"url":"https://docs.zernio.com/changelog#1785764602022"},{"id":"1785748602934","date":"2026-08-03T09:16:42.934Z","type":"breaking_change","message":"`POST /v1/sms/sender-ids` no longer enforces global uniqueness for alphanumeric sender IDs.\n\nThe 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.\n\nChanges:\n• Removed `409` (`sender_id_taken`) conflict response\n• Increased active sender ID limit in `403` from 5 to `1,000` (`sender_id_limit_reached`)\n\nRequest remains: `senderId`","platforms":["telephony"],"url":"https://docs.zernio.com/changelog#1785748602934"},{"id":"1785747854501","date":"2026-08-03T09:04:14.501Z","type":"new_feature","message":"WhatsApp now supports **Meta Direct Send** utility messages via `category` on inbox messaging endpoints.\n\nEligible 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.\n\nUse `category`:\n• `POST /v1/inbox/conversations`: set `category`=`utility` with `message` and no `templateName` (WhatsApp only)\n• `POST /v1/inbox/conversations/{conversationId}/messages`: set `category`=`utility` to send as a business-initiated utility message (WhatsApp only)\n\nNotes:\n• `category` enum: `utility`\n• `category` cannot be combined with `templateName` (create-conversation) or `template` (send-message)\n• `category` is accepted on `application/json` only (not `multipart/form-data`)","platforms":["whatsapp","inbox"],"url":"https://docs.zernio.com/changelog#1785747854501"},{"id":"1785742035443","date":"2026-08-03T07:27:15.444Z","type":"improvement","message":"Sending inbox messages now supports **idempotent retries** via `Idempotency-Key` on `POST /v1/inbox/conversations/{conversationId}/messages`.\n\nThis 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.\n\nUse:\n• `Idempotency-Key` (header)\n\nNew error responses:\n• `409` - Idempotency key still in flight\n• `422` - Idempotency key reused with a different body","platforms":["inbox"],"url":"https://docs.zernio.com/changelog#1785742035443"},{"id":"1785663423124","date":"2026-08-02T09:37:03.124Z","type":"improvement","message":"`PostAnalytics` now includes `videoDurationSeconds` (nullable integer).\n\nThis exposes the video length (currently Instagram Reels only) so you can estimate retention when combined with `igReelsAvgWatchTime` (ms).\n\nNew field:\n• `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).","platforms":["instagram","analytics"],"url":"https://docs.zernio.com/changelog#1785663423124"},{"id":"1785624225977","date":"2026-08-01T22:43:45.977Z","type":"new_feature","message":"LinkedIn ads now return delivery diagnostics and effective bidding details via new fields on `Ad` and `GET /v1/ads/tree` nodes.\n\nThis lets you distinguish “configured active” vs “actually serving” and understand why a LinkedIn campaign/creative is not delivering (holds, review, budget, etc.).\n\nNew fields:\n• `Ad.costType` (LinkedIn only): `CPM` | `CPC` | `CPV`\n• `Ad.servingStatuses` (LinkedIn only): array of serving/hold reasons (e.g. `RUNNABLE`, `ACCOUNT_TOTAL_BUDGET_HOLD`)\n• `Ad.creative.isServing` (LinkedIn only): `true` | `false` | `null`\n• `Ad.creative.servingHoldReasons` (LinkedIn only): creative-level hold reasons (e.g. `UNDER_REVIEW`, `REJECTED`)\n\nAlso added to tree responses:\n• `AdTreeAdSet.costType`, `AdTreeAdSet.servingStatuses`\n\nNote: for LinkedIn, `bidAmount` represents the campaign’s effective `unitCost` and can be `0` (a real, delivery-stopping value), independent of `bidStrategy`.","platforms":["linkedin","linkedin-ads","ads","analytics"],"url":"https://docs.zernio.com/changelog#1785624225977"},{"id":"1785597502383","date":"2026-08-01T15:18:22.383Z","type":"improvement","message":"GET `/v1/contacts` now supports filtering by multiple tags via a new `tags` query parameter.\n\nUse `tags` as a comma-separated list to match contacts that have _any_ of the provided tags.\n\nNew parameter: `tags` (comma-separated, OR match). Example: `tags=vip,lead,partner`","platforms":["inbox"],"url":"https://docs.zernio.com/changelog#1785597502383"},{"id":"1785585928396","date":"2026-08-01T12:05:28.396Z","type":"improvement","message":"GET `/v1/ads/accounts` now returns additional platform status details for ad accounts.\n\nThis 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`.\n\nNew fields in `accounts[]`:\n• `accountStatus` (platform status; type varies by platform)\n• `disableReason` (Meta only; integer code when `accountStatus` indicates disabled)\n• `approvalStatus` (X only; observed: `ACCEPTED`, `PENDING`, `REJECTED`)\n\nNote: LinkedIn continues to use `status` (filtered to active accounts).","platforms":["x","linkedin","meta-ads","linkedin-ads","x-ads","ads"],"url":"https://docs.zernio.com/changelog#1785585928396"},{"id":"1785584959708","date":"2026-08-01T11:49:19.708Z","type":"improvement","message":"`POST /v1/contacts/bulk` now treats missing `platformIdentifier` as a per-contact failure when importing in channel mode.\n\nIf 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).\n\nKey behavior:\n• Channel mode: `accountId` set + contact missing `platformIdentifier` → HTTP `200` with `errors[]`\n• 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 to","platforms":["inbox"],"url":"https://docs.zernio.com/changelog#1785584959708"},{"id":"1785525449190","date":"2026-07-31T19:17:29.190Z","type":"improvement","message":"GET `/v1/ads/campaigns` now supports listing Meta **empty campaigns** (campaigns with `adCount: 0`), which were previously invisible because campaigns are aggregated from ad documents.\n\nUse `includeEmpty=true` to include these campaigns (metrics will be zeroed). Requires `accountId` and `adAccountId` when `includeEmpty=true`.\n\nPUT `/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.","platforms":["meta-ads","ads","analytics"],"url":"https://docs.zernio.com/changelog#1785525449190"},{"id":"1785523318397","date":"2026-07-31T18:41:58.397Z","type":"improvement","message":"Meta ads campaign create/delete behavior has been tightened and now supports deleting “empty” campaign shells.\n\nOn `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 `LOWEST_COST_WITH_BID_CAP`/`COST_CAP`) are rejected up front.\n\nOn `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.\n\nOn `POST /v1/ads/create`, `budgetAmount` is also clarified as WHOLE currency units (not cents) to avoid Meta minor-unit mixups.","platforms":["instagram","meta-ads","ads"],"url":"https://docs.zernio.com/changelog#1785523318397"},{"id":"1785514233155","date":"2026-07-31T16:10:33.155Z","type":"breaking_change","message":"`POST /v1/contacts/bulk` request requirements and account/platform resolution have changed.\n\nYou 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.\n\nKey changes:\n• Top-level required fields are now `profileId` and `contacts` (no longer always requiring `accountId`/`platform`)\n• `accountId` is required when contacts include `platformIdentifier` (or a row-level `accountId`); otherwise the request returns `400`\n• `platform` is ignored when `accountId` is set; the platform is derived from the resolved account, and a mismatch can `404` as account not found","platforms":["general"],"url":"https://docs.zernio.com/changelog#1785514233155"},{"id":"1785507700393","date":"2026-07-31T14:21:40.393Z","type":"breaking_change","message":"Facebook post options have been reorganized: **draft/carousel/colored-background text settings** are now grouped under `facebookSettings`.\n\nThis makes Facebook-specific options consistent and clarifies which fields belong under `platformSpecificData` vs nested settings.\n\nMove these fields under `facebookSettings` (root-level or per-platform):\n• `draft`\n• `carouselCards`\n• `carouselLink`\n• `textFormatPresetId`\n\nApplies to `POST /v1/posts` and `PUT /v1/posts/{postId}`. Also note: `firstComment` is skipped when `facebookSettings.draft` is true.","platforms":["publishing"],"url":"https://docs.zernio.com/changelog#1785507700393"},{"id":"1785504766446","date":"2026-07-31T13:32:46.446Z","type":"improvement","message":"GET `/v1/accounts/{accountId}/linkedin-mentions` now returns `403` when LinkedIn blocks person-mention lookup due to missing company page posting privileges.\n\nThis lets you distinguish “insufficient LinkedIn org/page role” from invalid input (`400`) or not found (`404`), and show a clearer remediation message.\n\nNew response:\n• `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)","platforms":["linkedin"],"url":"https://docs.zernio.com/changelog#1785504766446"},{"id":"1785497809042","date":"2026-07-31T11:36:49.042Z","type":"breaking_change","message":"Create/duplicate ads endpoints now support safe retries via an `Idempotency-Key` header.\n\nThis prevents accidental duplicate campaigns/ad sets/ads when your client retries after timeouts or network errors.\n\nNow supported on:\n• `POST /v1/ads/campaigns`\n• `POST /v1/ads/campaigns/{campaignId}/duplicate`\n• `POST /v1/ads/ad-sets/{adSetId}/duplicate`\n• `POST /v1/ads/{adId}/duplicate`\n\nBehavior:\n• same `Idempotency-Key` + same body → replay original 2xx response\n• same key + different body → `422`\n• key still processing → `409`\n\nAlso: metrics `conversions` is now a `number` (can be fractional) in `GET /v1/ads/timeline` and the `AdMetrics` schema (attribution splitting + Google modeled conversions).","platforms":["meta-ads","ads","analytics"],"url":"https://docs.zernio.com/changelog#1785497809042"},{"id":"1785487366479","date":"2026-07-31T08:42:46.479Z","type":"new_feature","message":"You can now manage OAuth **connected apps** for the authenticated user: list authorized clients and revoke their access.\n\nList connected apps with `GET /v1/me/connected-apps`. Each item includes `clientId`, `clientName`, `redirectHost`, `scopes`, `authorizedAt`, `lastUsedAt`, `tokenCount`.\n\nRevoke an app with `DELETE /v1/me/connected-apps/{clientId}` (idempotent). Response includes `revoked`, `revokedTokens`, `invalidatedCodes`.\n\nAuth note: requires a session or full-scope API key; profile-scoped API keys and OAuth access tokens get `403` (`insufficient_permissions`).","platforms":["general"],"url":"https://docs.zernio.com/changelog#1785487366479"},{"id":"1785483415295","date":"2026-07-31T07:36:55.295Z","type":"new_feature","message":"WhatsApp now supports re-registering a connected number on Meta Cloud API, and the credentials connect flow can include a 2-step verification PIN.\n\nIf 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.\n\nUse:\n• `POST /v1/connect/whatsapp/credentials` with optional `pin` (6 digits, `^\\d{6}$`)\n• New: `POST /v1/accounts/{accountId}/whatsapp/register` with optional `pin`\n\nConnect response may include `registrationWarning` when the account was created but Cloud API registration was rejected (number can’t send until resolved).\n\nInbox 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}`.","platforms":["whatsapp","inbox","telephony"],"url":"https://docs.zernio.com/changelog#1785483415295"},{"id":"1785428860493","date":"2026-07-30T16:27:40.493Z","type":"improvement","message":"Facebook **text background posts** via `textFormatPresetId` no longer reject long captions.\n\nPreviously, requests were rejected with `400` when `content` exceeded ~130 characters. Now, length is not rejected; Facebook may publish and return a warning instead.\n\nUse `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.","platforms":["facebook","publishing"],"url":"https://docs.zernio.com/changelog#1785428860493"},{"id":"1785413243979","date":"2026-07-30T12:07:23.979Z","type":"new_feature","message":"WhatsApp Calling config now supports a call duration cap and forwarded caller ID control on calling endpoints.\n\nYou 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.\n\nNew fields:\n• `maxCallDurationSeconds` (request/response) — `30..14400` or `null` (no cap / clear cap)\n• `forwardCallerId` (request/response) — `business` | `caller`\n\nAvailable on:\n• `GET /v1/whatsapp/calling`\n• `GET /v1/phone-numbers/{id}/whatsapp/calling`\n• `POST`/`PATCH` `/v1/phone-numbers/{id}/whatsapp/calling` (and the deprecated alias `/v1/whatsapp/phone-numbers/{id}/calling`)","platforms":["whatsapp","telephony"],"url":"https://docs.zernio.com/changelog#1785413243979"},{"id":"1785412065004","date":"2026-07-30T11:47:45.004Z","type":"breaking_change","message":"Bulk contact import (`POST /v1/contacts/bulk`) now validates and normalizes phone identifiers for phone platforms.\n\nWhen `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).\n\nCheck your handling of `errors[]` in the `200` response (now returned as strings describing per-contact failures).","platforms":["whatsapp","inbox","telephony"],"url":"https://docs.zernio.com/changelog#1785412065004"},{"id":"1785410872172","date":"2026-07-30T11:27:52.172Z","type":"new_feature","message":"WhatsApp Calling now supports **caller-ID verification** for customer-brought (BYO) phone numbers, so `tel:` call forwards can present the business number as caller ID.\n\nStart (or resend) a verification code via `POST /v1/phone-numbers/{id}/whatsapp/caller-id-verification` with optional `method`: `sms` | `call`.\n\nConfirm the code via `POST /v1/phone-numbers/{id}/whatsapp/caller-id-verification/verify` with required `code`.\n\n`GET /v1/whatsapp/calling` now returns:\n• `callerIdMode`: `business` | `platform`\n• `callerIdVerified`: `true` | `false`","platforms":["whatsapp","telephony"],"url":"https://docs.zernio.com/changelog#1785410872172"},{"id":"1785410800868","date":"2026-07-30T11:26:40.868Z","type":"improvement","message":"WhatsApp Calling endpoints now return caller ID details for tel: forwards via `callerIdMode` (and verification status on GET).\n\nThis helps you understand whether the PSTN forward-leg will present the business number or a Zernio platform number when caller ID isn’t verified.\n\nNew response fields:\n• `POST /v1/phone-numbers/{id}/whatsapp/calling` and `POST /v1/whatsapp/phone-numbers/{id}/calling`: `callerIdMode` = `business` | `platform`\n• `GET /v1/phone-numbers/{id}/whatsapp/calling`: `callerIdMode` = `business` | `platform`, `callerIdVerified` (boolean)\n\nAlso clarified: `400` can be returned if `forwardTo` is set to the number itself.","platforms":["whatsapp","telephony"],"url":"https://docs.zernio.com/changelog#1785410800868"},{"id":"1785406208519","date":"2026-07-30T10:10:08.519Z","type":"new_feature","message":"Facebook posts now support large-text background presets via `textFormatPresetId` in `FacebookPlatformData`.\n\nThis lets you render a text-only feed post as large text on a colored background using Meta’s `text_format_preset_id`.\n\nUse `textFormatPresetId` (numeric string, pattern `^\\d+$`). Constraints:\n• Text-only feed posts only: request is rejected if `mediaItems` or `carouselCards` are present\n• Not allowed with `contentType` `story` or `reel`\n• `content` must be non-empty and ≤ 130 chars (otherwise rejected)\n• If a URL is present in `content`, no link preview is attached while a preset is set","platforms":["publishing"],"url":"https://docs.zernio.com/changelog#1785406208519"},{"id":"1785405750710","date":"2026-07-30T10:02:30.710Z","type":"improvement","message":"Rate limit guidance has been updated: the per-account velocity limit referenced in `429` responses is now `25 posts/hour per account` (was `15`) for:\n• `POST /v1/posts`\n• `POST /v1/posts/{postId}/retry`\n\nIf you throttle based on the error text for `429`, update your assumptions to match the new `25 posts/hour` limit.","platforms":["publishing"],"url":"https://docs.zernio.com/changelog#1785405750710"},{"id":"1785405540179","date":"2026-07-30T09:59:00.179Z","type":"improvement","message":"Meta ad URL tags now preserve Meta dynamic macros in tracking parameters.\n\nWhen 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.\n\nApplies to:\n• `PATCH /v1/ads/{adId}/tracking-tags` via `urlTags`\n• `POST /v1/ads/boost` via `tracking.urlTags`\n• `POST /v1/ads/create` via `tracking.urlTags`","platforms":["meta-ads","ads"],"url":"https://docs.zernio.com/changelog#1785405540179"},{"id":"1785400494329","date":"2026-07-30T08:34:54.329Z","type":"improvement","message":"Meta ads reporting now supports filtering by Facebook Page via `pageId` on ads, campaigns, and the campaign tree.\n\nThis 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).\n\nNew query param:\n• `pageId` (Meta only)\n\nAvailable on:\n• `GET /v1/ads`\n• `GET /v1/ads/campaigns`\n• `GET /v1/ads/tree`\n\nMatches against `ad.creative.pageId` (may be `null` for non-Meta ads and rare IG-only creatives).","platforms":["instagram","facebook","meta-ads","ads","analytics"],"url":"https://docs.zernio.com/changelog#1785400494329"},{"id":"1785399648846","date":"2026-07-30T08:20:48.846Z","type":"new_feature","message":"Facebook analytics now supports **per-post monetization earnings** via `GET /v1/analytics/facebook/post-earnings`.\n\nThis returns _lifetime cumulative_ earnings for a single post, read live from Meta on each request (no date range supported).\n\nRequired params:\n• `accountId`\n• `postId` (e.g. `{pageId}_{postId}` or bare Reels video ID)\n\nOptional:\n• `metrics` = `content_monetization_earnings`, `monetization_approximate_earnings` (defaults to both)\n\nNotes:\n• `since`/`until`/`period`/`metricType`/`breakdown` are rejected with 400 (lifetime only).\n• Money metrics include `unit` (`micro_amount` | `unspecified`) and `currency` (string | null), and are never rescaled.\n• Metrics Meta can’t serve are omitted from `metrics` and listed in `unavailableMetrics` (HTTP 200).\n\nAlso: `GET /v1/analytics/facebook/page-insights` now accepts monetization metrics (`content_monetization_earnings`, `monetization_approximate_earnings`) and may return `unavailableMetrics` with the same omit-vs-zero behavior.","platforms":["facebook","analytics"],"url":"https://docs.zernio.com/changelog#1785399648846"},{"id":"1785352435273","date":"2026-07-29T19:13:55.273Z","type":"new_feature","message":"`POST /v1/media/presign` now supports generating presigned upload URLs for **audio files**.\n\nYou can upload audio by setting `contentType` to one of: `audio/mpeg`, `audio/mp4`, `audio/aac`, `audio/ogg`, `audio/wav`, `audio/webm`, `audio/x-m4a`.","platforms":["publishing"],"url":"https://docs.zernio.com/changelog#1785352435273"},{"id":"1785347172837","date":"2026-07-29T17:46:12.837Z","type":"improvement","message":"Phone number purchases can now be auto-assigned to a different profile when the requested `profileId` already has a number.\n\nSend `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.\n\nCheck the assigned profile in:\n• `phoneNumber.profileId` (when a number is provisioned)\n• `profileId` in the `{ status: \"already_purchased\" }` response when using `purchaseIntentId`","platforms":["telephony"],"url":"https://docs.zernio.com/changelog#1785347172837"},{"id":"1785326345373","date":"2026-07-29T11:59:05.373Z","type":"breaking_change","message":"Instagram **user tagging** behavior has changed for `platformSpecificData.instagram.userTags`.\n\n`userTags` items now require only `username` (previously `username` + `x` + `y` were required). Tag handling now depends on media type:\n• Photos: `x`/`y` are required; photo tags without valid coordinates are skipped\n• Reels/videos: username-only tags; any `x`/`y` provided are ignored\n• Stories: `x`/`y` are optional\n\nCarousel targeting still uses `mediaIndex` (defaults to 0); out-of-range indices are ignored.","platforms":["instagram"],"url":"https://docs.zernio.com/changelog#1785326345373"},{"id":"1785326098198","date":"2026-07-29T11:54:58.198Z","type":"new_feature","message":"`GET /v1/inbox/conversations/search` now supports filtering by Slack via `platform=slack`.\n\nThis lets you search conversations and restrict results to Slack accounts the same way as other stored-message platforms.\n\nUse `platform` = `facebook` | `instagram` | `telegram` | `whatsapp` | `sms` | `slack`\n\nThe `conversation.started` webhook payload also adds `conversation.platform=slack` in `WebhookPayloadConversationStarted`.","platforms":["instagram","telegram","whatsapp","slack","inbox","webhooks"],"url":"https://docs.zernio.com/changelog#1785326098198"},{"id":"1785324735197","date":"2026-07-29T11:32:15.197Z","type":"new_feature","message":"`POST /v1/posts/sync-external` now supports **LinkedIn personal profiles**.\n\nBecause 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).\n\nUse `accountId` + `url` (required for LinkedIn personal), where `url` can be:\n• `linkedin.com/posts/…`\n• `linkedin.com/feed/update/urn:li:activity:…`\n• `urn:li:share:…` / `urn:li:ugcPost:…`\n\nImported LinkedIn personal posts return analytics (impressions, reach, reactions, comments, reshares, saves) but no content/media.","platforms":["linkedin","analytics"],"url":"https://docs.zernio.com/changelog#1785324735197"},{"id":"1785321407691","date":"2026-07-29T10:36:47.691Z","type":"new_feature","message":"Slack accounts now support reading and updating per-channel default message identity via new endpoints `GET /v1/accounts/{accountId}/slack-settings` and `PATCH /v1/accounts/{accountId}/slack-settings`.\n\nThis lets you set default author name/avatar for Slack messages on an account (per-post `platformSpecificData.username`/`platformSpecificData.iconUrl` can still override).\n\nUpdate with `defaultUsername` (max 80) and `defaultIconUrl`. Send an empty string to clear either field.\n\nThe settings response includes channel details like `channelId`, `channelName`, `channelType` (`public` or `private`), plus `teamId`/`teamName`.","platforms":["slack"],"url":"https://docs.zernio.com/changelog#1785321407691"},{"id":"1785320554740","date":"2026-07-29T10:22:34.740Z","type":"improvement","message":"POST `/v1/inbox/conversations/{conversationId}/messages` now returns more detailed send failures in `400` responses.\n\nYou can distinguish missing-recipient cases and, for Instagram/Facebook, get Meta’s diagnostic fields to debug rejected sends.\n\nNew `400` fields:\n• `code`: `PLATFORM_LIMITATION` | `MISSING_PARTICIPANT`\n• `platformError` (Instagram/Facebook only): `code`, `subcode`, `fbtraceId`, `type`","platforms":["instagram","facebook","inbox"],"url":"https://docs.zernio.com/changelog#1785320554740"},{"id":"1785320289490","date":"2026-07-29T10:18:09.490Z","type":"new_feature","message":"Slack is now supported as a posting platform via `POST /v1/posts` and `POST /v1/tools/validate/post`, and can be connected via `GET /v1/connect/{platform}`.\n\nYou can schedule/publish Slack messages to the channel tied to the connected Slack `accountId` (one connected account per channel).\n\nUse `platform=slack` and Slack options in `platformSpecificData`:\n• `threadTs`\n• `unfurlLinks`\n• `unfurlMedia`\n• `username`\n• `iconUrl`\n\nSlack is also available for filtering in `GET /v1/accounts/health` via `platform=slack`.","platforms":["slack","publishing"],"url":"https://docs.zernio.com/changelog#1785320289490"},{"id":"1785319163680","date":"2026-07-29T09:59:23.680Z","type":"new_feature","message":"New Google Ads keyword research endpoints are available, and `GET /v1/ads/insights` now supports Google Ads queries.\n\nYou can now pull Keyword Planner data via:\n• `POST /v1/ads/keywords/ideas` (generate ideas from `seedKeywords` and/or `seedUrl`)\n• `POST /v1/ads/keywords/historical-metrics` (metrics for up to `keywords` = 1000)\n\nKey request fields:\n• `accountId` (required)\n• `customerId` (optional; needed if the connection has several Google Ads accounts)\n• `countries` (optional; omitted = worldwide)\n• `languageConstantId` (default `1000`)\n• `network`: `GOOGLE_SEARCH` | `GOOGLE_SEARCH_AND_PARTNERS`\n\n`GET /v1/ads/insights` now routes by platform:\n• Meta: use `objectId` (+ `fields`/`breakdowns`/`filtering` as before)\n• Google Ads: use `query` (GAQL SELECT), with paging via `pageToken` and `paging.nextPageToken` (fixed 10,000 rows/page)\n\nNote: Google responses return raw rows (camelCase) with int64 counters encoded as strings; bid/CPC values are in micros.","platforms":["google-ads","ads","analytics"],"url":"https://docs.zernio.com/changelog#1785319163680"},{"id":"1785318226731","date":"2026-07-29T09:43:46.731Z","type":"improvement","message":"Instagram and Facebook Inbox now include **pre-connect DM history replay** in `GET /v1/inbox/conversations` and `GET /v1/inbox/conversations/{conversationId}/messages`.\n\nWhen 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.\n\nKey behavior:\n• `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)\n• Replayed history emits `no webhooks` and is stored as `read` (does not affect `unreadCount`)\n• `GET /v1/inbox/conversations/{conversationId}/messages`: replay includes the `500` most recent messages per conversation; older messages beyond that are not retrievable","platforms":["instagram","inbox","webhooks"],"url":"https://docs.zernio.com/changelog#1785318226731"},{"id":"1785314984188","date":"2026-07-29T08:49:44.188Z","type":"breaking_change","message":"`POST /v1/sms/registrations` no longer requires `phoneNumbers` in the request body.\n\nFor 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.\n\nKey request fields:\n• `registrationType`: `standard_10dlc` | `sole_prop_10dlc` | `toll_free`\n• `phoneNumbers`: optional (new defaulting behavior for 10DLC)","platforms":["telephony"],"url":"https://docs.zernio.com/changelog#1785314984188"},{"id":"1785311924487","date":"2026-07-29T07:58:44.487Z","type":"new_feature","message":"Webhooks now support `post.platform.deleted` in `events` for `POST /v1/webhooks/settings` and `PUT /v1/webhooks/settings`.\n\nThis 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).\n\nSubscribe by including `post.platform.deleted` in `events`.\n\nIn `WebhookPayloadPostPlatform`:\n• `event`: `post.platform.published` | `post.platform.failed` | `post.platform.deleted` | `post.tiktok.url_resolved`\n• `platform.status`: `published` | `failed` | `deleted`\n• New field on delete events: `platform.deletedAt` (ISO 8601)","platforms":["tiktok","webhooks"],"url":"https://docs.zernio.com/changelog#1785311924487"},{"id":"1785260231723","date":"2026-07-28T17:37:11.723Z","type":"new_feature","message":"**Instagram** is now supported for typing indicators via `POST /v1/inbox/conversations/{conversationId}/typing`.\n\nCalling 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).\n\nRequest: `{ \"accountId\": \"...\" }`\nResponse: `{ \"success\": true|false }` where `success` indicates whether the typing indicator was actually sent (false on unsupported platforms or if the platform call failed).\n\nThe endpoint may now also return `400` (`BadRequest`) in addition to existing responses.","platforms":["instagram","inbox"],"url":"https://docs.zernio.com/changelog#1785260231723"},{"id":"1785256016938","date":"2026-07-28T16:26:56.938Z","type":"new_feature","message":"Meta conversion goals on `POST /v1/ads/create` now support optimizing against a **Custom Conversion**.\n\nThis lets you run `goal` `conversions` or `lead_conversion` without providing a standard event type, by pointing the ad set at an existing Custom Conversion.\n\nUse `promotedObject.customConversionId` (instead of `promotedObject.customEventType`) for:\n• `goal=conversions`\n• `goal=lead_conversion`","platforms":["meta-ads","ads"],"url":"https://docs.zernio.com/changelog#1785256016938"},{"id":"1785253519724","date":"2026-07-28T15:45:19.724Z","type":"new_feature","message":"SMS carrier registrations now support a **preflight** dry-run and **change-request replies**.\n\nUse `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.\n\nRequest: `registrationType` (`standard_10dlc` | `sole_prop_10dlc`), `brand`, `campaign`, optional `phoneNumbers`, optional `messagingBrandName`\nResponse: `composed`, `advisories` (severity `block` | `warn`), `verdict` (`pass` | `warn` | `fail` | `unreviewed`), `aiUnavailable`\n\nUse `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`.\n\nAlso added to `POST /v1/sms/registrations`: `messagingBrandName` (2–60 chars) to brand samples/auto-replies when it differs from the legal `brand.displayName`.","platforms":["telephony"],"url":"https://docs.zernio.com/changelog#1785253519724"},{"id":"1785252805759","date":"2026-07-28T15:33:25.759Z","type":"improvement","message":"Webhook limits have increased: you can now configure up to **50 webhooks per user** (was 10).\n\nThis lets you split event subscriptions across more endpoints/environments without hitting the previous cap.\n\nApplies to:\n• `GET /v1/webhooks/settings` (list up to 50)\n• `POST /v1/webhooks/settings` (create up to 50; `400` if max reached)","platforms":["webhooks"],"url":"https://docs.zernio.com/changelog#1785252805759"},{"id":"1785249410757","date":"2026-07-28T14:36:50.757Z","type":"new_feature","message":"New endpoint `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.\n\nUse it with:\n• `accountId` (required)\n• `query` (required, 1–512 chars)\n• `limit` (10–100, default 10)\n• `cursor` (pagination)\n• `sinceId` / `untilId` (numeric tweet IDs)\n• `startTime` / `endTime` (ISO 8601 UTC, within last 7 days)\n• `sortOrder`: `recency` | `relevancy` (default `recency`)\n\nTo reply to a found tweet, set `platformSpecificData.replyToTweetId` to the returned `tweets[].id` when creating a post.\n\nRate limit: 300 requests per 15 minutes per connected account.","platforms":["x","publishing"],"url":"https://docs.zernio.com/changelog#1785249410757"},{"id":"1785177727677","date":"2026-07-27T18:42:07.677Z","type":"improvement","message":"KYC submission now enforces stricter validation when requesting multiple numbers with an `areaCode`.\n\nIf you pass `areaCode` and request `quantity` above that area’s live inventory, the request is rejected with `400` (instead of best-effort skipping).\n\nApplies to:\n• `POST /v1/phone-numbers/kyc`\n• `POST /v1/whatsapp/phone-numbers/kyc`\n\nKey params: `areaCode`, `quantity` (1–5).","platforms":["whatsapp","telephony"],"url":"https://docs.zernio.com/changelog#1785177727677"},{"id":"1785175381838","date":"2026-07-27T18:03:01.838Z","type":"breaking_change","message":"`GET /v1/accounts` now enforces stricter pagination and query validation.\n\nIf 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).\n\nKey params:\n• `page` (1-based) + `limit` (1–100) must be supplied together\n• `profileId` must be a valid ObjectId","platforms":["general"],"url":"https://docs.zernio.com/changelog#1785175381838"},{"id":"1785162080086","date":"2026-07-27T14:21:20.086Z","type":"improvement","message":"`POST /v1/tools/validate/post` now accepts `accountId` per platform to validate against account-specific limits.\n\nFor `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.\n\nUse:\n• `platforms[].platform` (enum: `twitter`, `instagram`, `tiktok`, `youtube`, `facebook`, `linkedin`, `bluesky`, `threads`, `reddit`, `pinterest`, `telegram`, `snapchat`, `googlebusiness`, `discord`)\n• `platforms[].accountId`","platforms":["instagram","threads","tiktok","x","linkedin","youtube","pinterest","reddit","bluesky","telegram","snapchat","discord","google-business"],"url":"https://docs.zernio.com/changelog#1785162080086"},{"id":"1785144728805","date":"2026-07-27T09:32:08.805Z","type":"new_feature","message":"New endpoint `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).\n\nKeywords are populated by the periodic ads discovery sweep (~every 3 hours per account). Campaign-level negative keywords aren’t included (ad-group-level only).\n\nFilter with:\n• `accountId`, `adAccountId` (Google customer ID), `profileId`\n• `campaignId`, `adSetId` (Google ad group)\n• `status`: `active` | `paused`\n• `matchType`: `exact` | `phrase` | `broad` | `unknown`\n• `negative`: `true` (negative only) | `false` (positive only)\n• `search` (substring match), plus pagination via `page`/`limit` (1–500)\n\nResponse returns `keywords` with fields like `keyword`, `matchType`, `status`, `negative`, and `syncedAt`, plus `pagination`.","platforms":["google-ads","ads"],"url":"https://docs.zernio.com/changelog#1785144728805"},{"id":"1784911110226","date":"2026-07-24T16:38:30.226Z","type":"improvement","message":"WhatsApp template endpoints can now return `502` when Meta rejects the request or is unreachable.\n\nThis makes Meta-side failures explicit so you can distinguish platform outages/rejections from Zernio validation/auth errors.\n\nApplies to:\n• `GET /v1/whatsapp/templates/{templateName}`\n• `PATCH /v1/whatsapp/templates/{templateName}`\n• `DELETE /v1/whatsapp/templates/{templateName}`\n\nAlso added ad review state support via `reviewStatus` (schema `AdReviewStatus`: `in_review`, `approved`, `rejected`, `with_issues`) and Meta-only `configuredStatus` on `Ad` to separate delivery status from the ad’s on/off toggle.","platforms":["whatsapp","ads"],"url":"https://docs.zernio.com/changelog#1784911110226"},{"id":"1784887688728","date":"2026-07-24T10:08:08.728Z","type":"improvement","message":"`message.sent` webhook payload now includes `message.source` for WhatsApp.\n\nThis 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).\n\nNew field: `message.source` = `whatsapp_business_app` | `cloud_api`\n\nOnly present for WhatsApp; absent on non-WhatsApp platforms.","platforms":["whatsapp","inbox","webhooks"],"url":"https://docs.zernio.com/changelog#1784887688728"},{"id":"1784827263926","date":"2026-07-23T17:21:03.926Z","type":"new_feature","message":"New endpoint: `POST /v1/phone-numbers/{id}/remediate/respond` lets you respond to a regulatory reviewer for a phone number awaiting remediation.\n\nYou 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.\n\nRequest body supports:\n• `message` (max 2000)\n• `documents`[] keyed by `requirementId` (use `base64` or `documentId`)\n• `address` (corrected address record)\n• `entityType`: `individual` | `business` | `null`\n• `attachments`[] (max 5; each `filename` + `base64`)\n\nResponse includes `status`: `resubmitted` | `replied`, plus `posted`, `phoneNumber`, and `siblingsResubmitted`.","platforms":["publishing","telephony"],"url":"https://docs.zernio.com/changelog#1784827263926"},{"id":"1784812964069","date":"2026-07-23T13:22:44.069Z","type":"improvement","message":"`GET /v1/profiles` now supports exact-name filtering and pagination, and the default sort order is now default-first (then creation date).\n\nUse `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`.\n\nNew query params: `name`, `limit` (1..1000), `skip` (>=0)\n\n`POST /v1/profiles` now supports idempotent retries via `Idempotency-Key` (header).\n\nWith `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`.","platforms":["general"],"url":"https://docs.zernio.com/changelog#1784812964069"},{"id":"1784805847712","date":"2026-07-23T11:24:07.712Z","type":"new_feature","message":"New endpoint: `POST /v1/phone-numbers/{id}/remediate/reply` lets you reply to a regulatory reviewer on a phone number awaiting remediation, including optional file attachments.\n\nUse 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.\n\nRequest body:\n• `text` (string, max 2000)\n• `attachments` (array, max 5) items: `{ filename, base64 }` (PDF/JPG/PNG/WEBP, max 10MB each)\n\nResponse `200`:\n• `posted` (boolean)\n• `attachments` (integer uploaded)\n\nErrors: `400`, `401`, `404`, `502` (retry).","platforms":["publishing","telephony"],"url":"https://docs.zernio.com/changelog#1784805847712"},{"id":"1784803964461","date":"2026-07-23T10:52:44.461Z","type":"new_feature","message":"You can now view KYC documents on file via `GET /v1/phone-numbers/kyc/document/{documentId}`.\n\nThis 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).\n\nKYC spec responses now include `documentId` on document rows:\n• `GET /v1/phone-numbers/kyc` → `reusable.options[].details[].documentId`\n• `GET /v1/whatsapp/phone-numbers/kyc` → `reusable.options[].details[].documentId`\n\nUse that `documentId` as the `{documentId}` path parameter when fetching the PDF.","platforms":["whatsapp","telephony"],"url":"https://docs.zernio.com/changelog#1784803964461"},{"id":"1784802548813","date":"2026-07-23T10:29:08.813Z","type":"improvement","message":"KYC reuse selection now supports a stable option identifier via `reusable.options[].id`, and KYC reuse options now include `instant` to indicate activation speed.\n\nThis 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.\n\nUse on submit:\n• `POST /v1/phone-numbers/kyc`: pass `reuseOptionId` (preferred)\n• `reuseFrom` is now a legacy fallback and may be ambiguous\n\nReturned on form spec:\n• `GET /v1/phone-numbers/kyc` (and the deprecated WhatsApp alias): `reusable.options[]` now includes `id` and `instant`","platforms":["whatsapp","telephony"],"url":"https://docs.zernio.com/changelog#1784802548813"},{"id":"1784801314376","date":"2026-07-23T10:08:34.376Z","type":"new_feature","message":"`PUT /v1/ads/ad-sets/{adSetId}` now supports updating **bid strategy** for `platform=openai`.\n\nYou can set OpenAI ad group bidding via `bidStrategy` + `bidAmount`; `bidAmount` is converted to micros (× 1,000,000).\n\nUse `bidStrategy`:\n• `LOWEST_COST_WITH_BID_CAP` (requires `bidAmount`)\n• `COST_CAP` (requires `bidAmount`)\n\n`LOWEST_COST_WITH_MIN_ROAS` is not supported on OpenAI and will return `422`.","platforms":["ads"],"url":"https://docs.zernio.com/changelog#1784801314376"},{"id":"1784797901355","date":"2026-07-23T09:11:41.355Z","type":"new_feature","message":"Zernio Ads now supports **OpenAI Ads (ChatGPT Ads)**, including connect, create/manage, reporting, and conversions.\n\nConnect an OpenAI Ads account via `POST /v1/connect/openai-ads/credentials` with `apiKey` and `profileId` (requires Ads add-on).\n\nAds endpoints now accept `platform=openai`:\n• `GET /v1/ads`, `GET /v1/ads/campaigns`, `GET /v1/ads/tree`, `GET /v1/ads/timeline`\n• 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}/status`\n\nCreating ads: `POST /v1/ads/create` supports OpenAI Ads with:\n• `goal`: `traffic` | `awareness` | `conversions`\n• Required creative fields: `headline`, `body`, `imageUrl`, `linkUrl`\n• Budget: `budgetType` must be `lifetime` (sending `daily` returns 422) and requires `endDate`\n\nConversions: `POST /v1/ads/conversions` now supports `accountId` platform `openaiads`.\n• Use `GET /v1/accounts/{accountId}/conversion-destinations` to get the OpenAI pixel `destinationId` (pixel wire id)\n• 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)\n\nNote: `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).","platforms":["ads","analytics"],"url":"https://docs.zernio.com/changelog#1784797901355"},{"id":"1784795496828","date":"2026-07-23T08:31:36.828Z","type":"improvement","message":"YouTube analytics endpoints now support returning **provisional (not-yet-finalized) days** when you set an explicit `endDate` up to today.\n\nBy 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.\n\nApplies to:\n• `GET /v1/analytics/youtube/daily-views`\n• `GET /v1/analytics/youtube/video-retention`\n• `GET /v1/analytics/youtube/demographics`\n\nNew response field:\n• `provisionalSince` — first date in the returned range whose numbers are provisional and may be revised by YouTube.","platforms":["youtube","analytics"],"url":"https://docs.zernio.com/changelog#1784795496828"},{"id":"1784732291211","date":"2026-07-22T14:58:11.211Z","type":"new_feature","message":"WhatsApp template creation now supports **carousel** and **limited time offer** components in `components` on `POST /v1/whatsapp/templates`.\n\nThis lets you build richer MARKETING templates (multi-card carousels and offer labels/countdowns) using the same template creation endpoint.\n\nNew component types:\n• `type=carousel` with `cards` (2–10), each card `components` can include `type=header`, `type=body`, `type=buttons`\n• `type=limited_time_offer` with `limited_time_offer.text` (<=16 chars) and optional `limited_time_offer.has_expiration`\n\nPhone 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.","platforms":["whatsapp","telephony"],"url":"https://docs.zernio.com/changelog#1784732291211"},{"id":"1784722326195","date":"2026-07-22T12:12:06.195Z","type":"improvement","message":"Profile create/update now return **409 Conflict** when the profile name is already taken.\n\nThis lets clients distinguish name collisions from generic validation errors and handle retries/renames cleanly.\n\nNew response:\n• `POST /v1/profiles` → `409` (code: `profile_name_conflict`)\n• `PUT /v1/profiles/{profileId}` → `409` (code: `profile_name_conflict`)\n\nSeveral Ads read endpoints now also document **400 Bad Request** for invalid query input:\n• `GET /v1/ads` → `400`\n• `GET /v1/ads/catalogs` → `400`\n• `GET /v1/ads/catalogs/{catalogId}/product-sets` → `400`\n• `GET /v1/ads/conversions/quality` → `400`","platforms":["pinterest-ads","ads"],"url":"https://docs.zernio.com/changelog#1784722326195"},{"id":"1784721561903","date":"2026-07-22T11:59:21.903Z","type":"improvement","message":"Meta ads now support additional promoted-object fields, and some payloads are validated more strictly.\n\nIn `POST /v1/ads/create`, `promotedObject` adds:\n• `offlineConversionDataSetId`\n• `whatsappPhoneNumber`\n\nIn `PUT /v1/ads/ad-sets/{adSetId}`, `platformSpecificData.promotedObject` adds the same fields and now rejects unknown keys (`additionalProperties: false`), returning 400.\n\nIn `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).\n\n`GET /v1/ads/audiences` and `POST /v1/ads/audiences` add a new audience `type` value: `meta_engagement`.","platforms":["whatsapp","meta-ads","ads","telephony"],"url":"https://docs.zernio.com/changelog#1784721561903"},{"id":"1784720912072","date":"2026-07-22T11:48:32.072Z","type":"breaking_change","message":"Discord: new member lookup + search endpoints, and guild member listing now fails fast when the Server Members Intent is missing.\n\nYou can now fetch a single member or prefix-search members without the privileged intent (useful when full listing is blocked).\n\nNew endpoints:\n• `GET /v1/discord/guilds/{guildId}/members/{userId}` (`accountId`)\n• `GET /v1/discord/guilds/{guildId}/members/search` (`accountId`, `query`, optional `limit`) — `query` is prefix-only\n\nBreaking change:\n• `GET /v1/discord/guilds/{guildId}/members` now returns `403` if the Server Members Intent is not enabled (previously Discord could return an empty array).\n\nSMS: alphanumeric Sender IDs are now supported.\n\nCreate 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.\n\nNew endpoints:\n• `GET /v1/sms/sender-ids` (returns `senderIds` + `budget` with `cap`/`usedToday`/`level`/`pendingRequest`)\n• `POST /v1/sms/sender-ids` (`senderId`)\n• `DELETE /v1/sms/sender-ids/{id}`\n• `POST /v1/sms/sender-ids/limit-request` (`requestedCap`, `reason`)\n\nAds (Meta): new campaign/creative utilities + validate-only dry runs.\n\nYou can now create campaign shells, manage the creative library, duplicate ad sets/ads, and run a Meta validation pass without creating anything.\n\nNew endpoints:\n• `POST /v1/ads/campaigns` (standalone campaign)\n• `POST /v1/ads/creatives`, `GET /v1/ads/creatives`, `GET /v1/ads/creatives/{creativeId}`, `PUT /v1/ads/creatives/{creativeId}`, `DELETE /v1/ads/creatives/{creativeId}`\n• `GET /v1/ads/images`, `GET /v1/ads/labels`, `GET /v1/ads/businesses`, `GET /v1/ads/high-demand-periods`\n• `POST /v1/ads/ad-sets/{adSetId}/duplicate`, `POST /v1/ads/{adId}/duplicate`\n\nNew option on `POST /v1/ads/create`:\n• `validateOnly: true` (Meta only; single standalone shape only) — returns `200` with validation results instead of creating entities.","platforms":["discord","meta-ads","ads","telephony"],"url":"https://docs.zernio.com/changelog#1784720912072"},{"id":"1784712068387","date":"2026-07-22T09:21:08.387Z","type":"breaking_change","message":"WhatsApp inbound attachments can now be downloaded via `GET /v1/whatsapp/media/{mediaId}`.\n\nThis streams the binary for a WhatsApp attachment referenced by `attachments[].url` in `message.received` webhooks and List messages.\n\nCall it with:\n• `mediaId` (path) from `attachments[].payload.id`\n• `accountId` (query) = the WhatsApp account that received the media\n• `Authorization: Bearer &lt;your API key&gt;` (required; otherwise `401`)\n\nImportant behavior:\n• Download on receipt: WhatsApp media expires on Meta’s side (typically ~7 days). After expiry this endpoint returns `400` permanently (do not retry).\n\nRemoved endpoints:\n• `GET /v1/sms/sender-ids`, `POST /v1/sms/sender-ids`, `POST /v1/sms/sender-ids/limit-request`, `DELETE /v1/sms/sender-ids/{id}`\n• `POST /v1/ads/campaigns`\n• `POST /v1/ads/ad-sets/{adSetId}/duplicate`\n• `POST /v1/ads/{adId}/duplicate`\n• `GET /v1/ads/labels`\n• `GET /v1/ads/high-demand-periods`\n• `GET/POST/GET/PUT/DELETE /v1/ads/creatives` (and `/v1/ads/creatives/{creativeId}`)\n• `GET /v1/ads/images`\n\nBreaking change (SMS): `POST /v1/sms/messages` no longer supports alphanumeric sender IDs in `from` (now requires an SMS-enabled number).","platforms":["whatsapp","meta-ads","ads","inbox","telephony","webhooks"],"url":"https://docs.zernio.com/changelog#1784712068387"},{"id":"1784707849223","date":"2026-07-22T08:10:49.223Z","type":"new_feature","message":"Meta Ads: new endpoints for campaign/creative management, duplication, and library access.\n\nYou 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.\n\nNew endpoints:\n• Create campaign (ODAX shell): `POST /v1/ads/campaigns` with `goal` (`engagement`, `traffic`, `awareness`, `video_views`, `lead_generation`, `lead_conversion`, `job_applicants`, `conversions`, `app_promotion`, `catalog_sales`), optional CBO via `budgetAmount` + `budgetType` (`daily`|`lifetime`), `status` (`ACTIVE`|`PAUSED`)\n• Duplicate ad set: `POST /v1/ads/ad-sets/{adSetId}/duplicate` with `deepCopy`, `campaignId`, `statusOption` (`ACTIVE`|`PAUSED`|`INHERITED_FROM_SOURCE`), `syncAfter`\n• Duplicate ad: `POST /v1/ads/{adId}/duplicate` with `adSetId`, `statusOption` (`ACTIVE`|`PAUSED`|`INHERITED_FROM_SOURCE`), `syncAfter`\n• 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}`\n• Libraries/metadata: `GET /v1/ads/images`, `GET /v1/ads/labels`\n• Budget schedules: `GET /v1/ads/high-demand-periods` (exactly one of `campaignId` or `adSetId`)\n\nAudience updates expanded:\n`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`.","platforms":["meta-ads","ads"],"url":"https://docs.zernio.com/changelog#1784707849223"},{"id":"1784645307930","date":"2026-07-21T14:48:27.930Z","type":"new_feature","message":"SMS now supports **alphanumeric sender IDs** (e.g. `ZERNIO`) for one-way international texting.\n\nCreate/manage sender IDs via:\n• `GET /v1/sms/sender-ids` (includes workspace daily budget: `budget.cap`, `budget.usedToday`, `budget.level`, `budget.pendingRequest`)\n• `POST /v1/sms/sender-ids` with `senderId` (3–11 chars; letters/digits/spaces; at least one letter)\n• `DELETE /v1/sms/sender-ids/{id}`\n• `POST /v1/sms/sender-ids/limit-request` with `requestedCap` + `reason`\n\nSending: `POST /v1/sms/messages` now accepts `from` as either an SMS-enabled number or an approved sender ID.\n\nNotes for sender IDs: text-only (no MMS), recipients can’t reply, and sends to US/CA/PR are blocked.\n\nNew responses to handle on `POST /v1/sms/messages`:\n• `403` (gate: registration/MMS not supported/alpha to US-CA-PR)\n• `429` (alphanumeric daily cap reached; code `alpha_daily_limit_reached`; resets midnight UTC)\n• `404` now covers “no SMS-enabled number _or sender ID_ matches `from`”.","platforms":["telephony"],"url":"https://docs.zernio.com/changelog#1784645307930"},{"id":"1784620100313","date":"2026-07-21T07:48:20.313Z","type":"new_feature","message":"Meta ads now support **Reach & Frequency (RESERVED)** buying via new R&F prediction + reservation endpoints.\n\nYou can request a quote (no spend, no entities created), reserve it to lock price/inventory, then create an ad using the reserved prediction.\n\nNew endpoints:\n• `POST /v1/ads/rf-predictions` (create quote; provide date range + exactly one of `budgetAmount` or `reach`)\n• `GET /v1/ads/rf-predictions/{predictionId}`\n• `POST /v1/ads/rf-predictions/{predictionId}/reserve` (returns a NEW RESERVED `predictionId` + `expiresAt`)\n• `DELETE /v1/ads/rf-predictions/{predictionId}` (cancel reservation)\n\n`POST /v1/ads/create` adds:\n• `buyingType`: `AUCTION` | `RESERVED`\n• `rfPredictionId` (required when `buyingType=RESERVED`)\n\nNotes: RESERVED uses the single-ad shape only (no `creatives[]`, `adSetId`, `existingCampaignId`, or `dynamicCreative`). Budget/schedule/pricing come from the reservation; bid fields are ignored.","platforms":["meta-ads","ads"],"url":"https://docs.zernio.com/changelog#1784620100313"},{"id":"1784618264101","date":"2026-07-21T07:17:44.101Z","type":"new_feature","message":"New Meta Ads endpoints are available for live reads, auditing, studies, finances, and asset upload.\n\nYou can now:\n• Read a live Meta ad set (incl. learning phase) via `GET /v1/ads/ad-sets/{adSetId}` with `accountId` and optional `fields` (default includes `learning_stage_info`).\n• 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`.\n• List A/B tests & lift studies via `GET /v1/ads/studies` with `accountId`, `adAccountId`, optional `fields`, and paging `limit`/`after`.\n• Read ad account finances via `GET /v1/ads/accounts/finance` (`balance`, `amountSpent`, `spendCap`, `fundingSource`).\n• Upload ad images from base64 via `POST /v1/ads/images` with `accountId`, `adAccountId`, `imageBase64` (optional `filename`); returns `image.hash` and `image.url`.\n\nAlso updated:\n• `specialAdCategories` now includes `FINANCIAL_PRODUCTS_SERVICES` and `ONLINE_GAMBLING_AND_GAMING` on `POST /v1/ads/boost` and `POST /v1/ads/create`.\n• `callToAction` enums expanded on `POST /v1/ads/create` (and Meta `creatives[].callToAction`) to include values like `ADD_TO_CART`, `APPLY_NOW`, `BOOK_NOW`, `ORDER_NOW`, `INSTALL_MOBILE_APP`, `USE_APP` (plus others).\n• `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.","platforms":["meta-ads","ads","publishing","analytics"],"url":"https://docs.zernio.com/changelog#1784618264101"},{"id":"1784557101261","date":"2026-07-20T14:18:21.261Z","type":"deprecation","message":"New Meta Ads capabilities are available: ad previews, click-to-message ads, and click-to-call ads.\n\nYou 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.\n\nClick-to-message ads are now unified under `POST /v1/ads/messaging` with `destination`:\n• `whatsapp`\n• `messenger`\n• `instagram_direct`\n\nClick-to-call ads are supported via `POST /v1/ads/call` using `phoneNumber` (E.164) and `linkUrl`.\n\n`POST /v1/ads/ctwa` is now **deprecated**; use `POST /v1/ads/messaging` with `destination: whatsapp` (the old endpoint remains for back-compat).","platforms":["instagram","facebook","whatsapp","meta-ads","ads","telephony"],"url":"https://docs.zernio.com/changelog#1784557101261"},{"id":"1784542088487","date":"2026-07-20T10:08:08.487Z","type":"new_feature","message":"Meta insights now supports action-level segmentation and attribution controls on both the sync and async insights APIs.\n\nYou can segment `actions[]` and control attribution windows/reporting time without changing endpoints.\n\nNew on `GET /v1/ads/insights`:\n• `actionBreakdowns`\n• `actionAttributionWindows`\n• `actionReportTime`\n• `useUnifiedAttributionSetting`\n\nNew on `POST /v1/ads/insights/reports`:\n• `actionBreakdowns`\n• `actionAttributionWindows` (array of strings)\n• `actionReportTime`\n• `useUnifiedAttributionSetting`\n\nMeta ad creation also adds hand-built carousels via `carouselCards` on `POST /v1/ads/create` (2–10 cards, deterministic order).","platforms":["meta-ads","ads","analytics"],"url":"https://docs.zernio.com/changelog#1784542088487"},{"id":"1784536715588","date":"2026-07-20T08:38:35.588Z","type":"improvement","message":"**Google Business batch reviews** now supports server-side sorting via `orderBy` on `POST /v1/accounts/{accountId}/gmb-reviews/batch`.\n\nThis lets you fetch newest-updated reviews first (default) and stop paginating earlier when polling for recent changes.\n\nUse `orderBy`:\n• `updateTime desc` (default)\n• `rating`\n• `rating desc`\n\nAlso note: `locationNames` is now capped at `maxItems: 50` per request (chunk larger sets).\n\nResponses 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).\n\n**Inbox reviews** (`GET /v1/inbox/reviews`) now includes Google Business location attribution fields: `locationId` (bare id) and `locationName` (display name).","platforms":["google-business","inbox"],"url":"https://docs.zernio.com/changelog#1784536715588"},{"id":"1784535587325","date":"2026-07-20T08:19:47.325Z","type":"new_feature","message":"POST `/v1/inbox/comments/{postId}` now supports attaching an image when replying on **Facebook** via `attachmentUrl`.\n\nThis lets you publish a photo comment alongside the text reply. The image URL must be publicly accessible so Meta can fetch it.\n\nNew request field:\n• `attachmentUrl` (uri) — Facebook only\n\nIf `attachmentUrl` is sent for non-Facebook platforms, the API now returns `400` (e.g. code `PLATFORM_NOT_SUPPORTED`).","platforms":["publishing","inbox"],"url":"https://docs.zernio.com/changelog#1784535587325"},{"id":"1784481264807","date":"2026-07-19T17:14:24.807Z","type":"improvement","message":"POST `/v1/ads/create` now supports more TikTok controls and targeting.\n\nYou can create TikTok ads/campaigns in a paused state and use additional geo targeting fields for TikTok.\n\nKey changes:\n• `status` now applies to **Meta and TikTok**: `ACTIVE` | `PAUSED` (TikTok pauses campaign &gt; ad group &gt; ad)\n• Geo targeting fields now support **TikTok**: `cities`, `regions`, `metros`\n\nGET `/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.","platforms":["tiktok","meta-ads","tiktok-ads","ads"],"url":"https://docs.zernio.com/changelog#1784481264807"},{"id":"1784380732740","date":"2026-07-18T13:18:52.740Z","type":"improvement","message":"TikTok conversion ads created via `POST /v1/ads/create` now accept **TikTok Events Manager event names** in `promotedObject.customEventType` (auto-resolved to the underlying optimization_event code).\n\nThis makes it easier to configure `goal=conversions` without manually mapping to TikTok’s internal codes; note that `ON_WEB_ORDER` is now deprecated.\n\nUse `promotedObject.customEventType` with one of:\n• `SHOPPING`, `ON_WEB_CART`, `INITIATE_ORDER`, `FORM`, `ON_WEB_REGISTER`, `ON_WEB_DETAIL`\n\nApplies to TikTok only; `promotedObject.pixelId` is still required for `goal=conversions`.","platforms":["tiktok","tiktok-ads","ads"],"url":"https://docs.zernio.com/changelog#1784380732740"},{"id":"1784298927530","date":"2026-07-17T14:35:27.530Z","type":"new_feature","message":"New Meta Ads insights endpoints are available: `GET /v1/ads/insights`, `POST /v1/ads/insights/reports`, `GET /v1/ads/insights/reports/{reportRunId}`.\n\nYou 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.\n\nSync query: `GET /v1/ads/insights` with `accountId`, `objectId`, optional `level` (`ad`/`adset`/`campaign`/`account`), `fields`, `breakdowns`, `filtering`, `datePreset` or `fromDate`+`toDate`, `timeIncrement`, `limit`, `after`.\n\nAsync flow:\n• 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`\n• Poll: `GET /v1/ads/insights/reports/{reportRunId}` with `accountId`, `limit`, `after` → returns `status` and `percentCompletion`; when `status` is `Job Completed`, includes `data` + paging.\n\nMeta only (facebook/instagram). Invalid field/breakdown combos return `400` with Meta’s error message.","platforms":["instagram","meta-ads","ads","analytics"],"url":"https://docs.zernio.com/changelog#1784298927530"},{"id":"1784289588985","date":"2026-07-17T11:59:48.986Z","type":"new_feature","message":"Google Business Profiles: you can now assign a GBP location to a different profile by reusing an existing OAuth grant (no browser re-auth).\n\nUse `POST /v1/accounts/{accountId}/gmb-locations/assign` with:\n• `profileId` (target profile)\n• `selectedLocationId` (e.g. `locations/123`)\n• `googleAccountId` (optional, recommended for accounts with many locations)\nReturns `409` if the target profile already has a Google Business connection.\n\nPhone number purchasing now supports selecting a number type.\n\nOn `POST /v1/phone-numbers/purchase` you can pass `numberType`: `local` | `mobile` | `national` | `toll_free`. Availability/KYC can vary by (country, type).\n\nCountry/type discovery updates:\n• `GET /v1/phone-numbers/countries` now includes `inStock` and `types[]` (per-type `monthlyCents`, `needsKyc`, `whatsappAvailable`, `smsAvailable`, `callsAvailable`, `inStock`)\n• `GET /v1/phone-numbers/availability` adds optional `numberType`\n• `GET /v1/phone-numbers/kyc` adds optional `numberType`\n\nFacebook connect flow: `POST /v1/connect/facebook/select-page` can now return `409` with code `RECONNECT_ACCOUNT_MISMATCH` when a `force=true` token-recovery re-auth lands on a different user/page than the existing connection.","platforms":["facebook","whatsapp","google-business","telephony"],"url":"https://docs.zernio.com/changelog#1784289588985"},{"id":"1784230295730","date":"2026-07-16T19:31:35.730Z","type":"new_feature","message":"Google Business reviews now include attached photos in review responses.\n\nYou can display/download reviewer-uploaded images and know how many photos are attached per review.\n\nAdded fields:\n• `GET /v1/accounts/{accountId}/gmb-reviews` → `reviews[].photoCount`, `reviews[].photos[]` (each item: `url`)\n• `GET /v1/inbox/reviews` → `data[].photos[]` (Google Business only; each item: `url`)\n\nNotes: `photoCount` counts photos only (videos not counted). For non-Google platforms, `photos` is always an empty array.","platforms":["google-business","inbox"],"url":"https://docs.zernio.com/changelog#1784230295730"},{"id":"1784222995177","date":"2026-07-16T17:29:55.177Z","type":"new_feature","message":"Meta campaign updates now support a lifetime spend cap via `platformSpecificData.spendCap` on `PUT /v1/ads/campaigns/{campaignId}`.\n\nThis lets you set or remove a campaign-level spend cap without recreating the campaign.\n\nUse:\n• `platformSpecificData.spendCap` = `number` (sets cap)\n• `platformSpecificData.spendCap` = `null` (removes cap; Meta rejects `0`)\n\nMeta ad set updates now accept post-launch delivery settings via `platformSpecificData` on `PUT /v1/ads/ad-sets/{adSetId}`.\n\nSet any of:\n• `platformSpecificData.optimizationGoal`\n• `platformSpecificData.billingEvent`\n• `platformSpecificData.startDate`, `platformSpecificData.endDate`\n• `platformSpecificData.promotedObject` (e.g. `pixelId`, `customEventType`, `pageId`, `applicationId`, `objectStoreUrl`, `customConversionId`, `productCatalogId`, `productSetId`)\n\nAlso added: `billingEvent` (Meta only) on `POST /v1/ads/create` to explicitly set the ad set’s billing event (defaults to `IMPRESSIONS`).","platforms":["meta-ads","ads","commerce"],"url":"https://docs.zernio.com/changelog#1784222995177"},{"id":"1784216352059","date":"2026-07-16T15:39:12.059Z","type":"breaking_change","message":"**Queue schedules**: `DELETE /v1/queue/slots` now supports deleting _all_ queues for a profile by omitting `queueId`.\n\nThis lets you clear a profile’s entire queue schedule in one call (previously `queueId` was required).\n\nUse:\n• `profileId` (required)\n• `queueId` (optional) — omit to delete all queues\n\n**Sequences**: `POST /v1/sequences/{sequenceId}/enroll` now returns per-contact outcomes.\n\nResponse now includes `failed` and `results` (array of `{ contactId, success, error }`) in addition to `enrolled`.\n\nAlso: `GET /v1/queue/slots` expands `all` to `true` | `false`.","platforms":["general"],"url":"https://docs.zernio.com/changelog#1784216352059"},{"id":"1784215202846","date":"2026-07-16T15:20:02.846Z","type":"improvement","message":"GET `/v1/accounts/{accountId}/gmb-locations` now supports a **limit** query parameter.\n\nYou 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.\n\nUse `limit`:\n• `limit` (integer, default `100`, min `1`, max `500`)\n\nInvalid values now return `400` (e.g. `limit` out of range).","platforms":["google-business"],"url":"https://docs.zernio.com/changelog#1784215202846"},{"id":"1784202310306","date":"2026-07-16T11:45:10.306Z","type":"new_feature","message":"YouTube demographics analytics now supports **per-video** audience breakdowns via `videoId` on `GET /v1/analytics/youtube/demographics`.\n\nPass `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.\n\nUse:\n• `videoId` - YouTube video ID (scoped to the connected channel; otherwise 404)\n• `startDate` - defaults to the video publish date (lifetime) when `videoId` is provided\n\nResponse adds (video mode only): `videoId`, `title`, `publishedAt`.\n\nAlso: 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.","platforms":["youtube","meta-ads","ads","analytics"],"url":"https://docs.zernio.com/changelog#1784202310306"},{"id":"1784194396338","date":"2026-07-16T09:33:16.338Z","type":"improvement","message":"GET `/v1/connect/{platform}/ads` now supports `force` to trigger a fresh OAuth flow even when an ads account is already connected.\n\nThis lets you proactively refresh stored tokens (the endpoint normally returns `alreadyConnected: true` based on account active state, not token liveness).\n\nUse `force=true` to always receive `authUrl`; completing OAuth refreshes the stored token on the existing posting + ads accounts in place.\n\nNew param: `force` (`true`/`false`, default `false`)","platforms":["ads"],"url":"https://docs.zernio.com/changelog#1784194396338"},{"id":"1784129629470","date":"2026-07-15T15:33:49.470Z","type":"breaking_change","message":"SMS carrier registration `status` now includes additional lifecycle states in registration APIs.\n\nYou 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.\n\nUpdated `status` enum:\n• `pending`, `approved`, `rejected`, `requested`, `changes_requested`, `deactivated`\n\nApplies to:\n• `GET /v1/sms/registrations`\n• `GET /v1/sms/registrations/{id}`\n• `POST /v1/phone-numbers/{id}/sms/reuse-registration`\n\nAlso added to `POST /v1/sms/registrations` request body:\n• `resubmitRequestId` (resubmit an existing registration returned for changes)\n• `wizardValues` (optional raw wizard answers; can be omitted by API integrators)","platforms":["telephony"],"url":"https://docs.zernio.com/changelog#1784129629470"},{"id":"1784128922066","date":"2026-07-15T15:22:02.066Z","type":"improvement","message":"`conversions` in ads metrics now includes non-Meta platforms.\n\nYou can now rely on `conversions` for more platforms when charting or aggregating performance (instead of always getting 0 outside Meta).\n\nApplies to:\n• `GET /v1/ads/timeline` (`rows[].conversions`)\n• `AdMetrics.conversions`\n\nNew platform behavior:\n• Meta: events matching the campaign optimization goal\n• Google: tracked conversions\n• X / LinkedIn: reported website/lead conversions (added 2026-07)","platforms":["x","linkedin","meta-ads","google-ads","linkedin-ads","x-ads","ads","analytics"],"url":"https://docs.zernio.com/changelog#1784128922066"},{"id":"1784113951346","date":"2026-07-15T11:12:31.346Z","type":"improvement","message":"GET `/v1/sms/registrations` now supports `includeDeactivated` (boolean).\n\nDeactivated (terminated) registrations are hidden by default; pass `includeDeactivated=true` to include them in the results.\n\nNew query param:\n• `includeDeactivated` - `true` to include deactivated registrations (default: hidden)","platforms":["general"],"url":"https://docs.zernio.com/changelog#1784113951346"},{"id":"1784113685641","date":"2026-07-15T11:08:05.641Z","type":"new_feature","message":"New endpoint: `DELETE /v1/sms/registrations/{id}` to deactivate an SMS brand/campaign registration.\n\nThis 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.\n\nKey parameter: `id` (path, string)\n\nResponse: `200` returns `status` = `deactivated` (enum: `deactivated`)","platforms":["telephony"],"url":"https://docs.zernio.com/changelog#1784113685641"},{"id":"1784111720106","date":"2026-07-15T10:35:20.106Z","type":"new_feature","message":"LinkedIn now supports **Thought Leader ads** on `POST /v1/ads/create` via `platformSpecificData.thoughtLeader`.\n\nThis 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.\n\nSet:\n• `platformSpecificData.thoughtLeader.postUrn` (pattern: `urn:li:share:\\d+` or `urn:li:ugcPost:\\d+`)\n\nNotes:\n• Mutually exclusive with other LinkedIn creative sources in `platformSpecificData` (e.g. `carousel`, `document`, `conversation`, etc.)\n• When using `thoughtLeader`, you don’t need `headline`/`body`/`imageUrl`/`organizationId` for the creative (the referenced post provides them).","platforms":["linkedin","linkedin-ads","ads"],"url":"https://docs.zernio.com/changelog#1784111720106"},{"id":"1784107622595","date":"2026-07-15T09:27:02.595Z","type":"new_feature","message":"New LinkedIn-only Ads helpers are available:\n\n• **Suggested bid & budget bounds** via `POST /v1/ads/targeting/bid-pricing`\nReturns suggested bid + allowed bid limits and daily budget bounds for a targeting spec (use before `POST /v1/ads/create`).\nKey request fields: `accountId`, `adAccountId`, `spec`, optional `campaignType` (`TEXT_AD`/`SPONSORED_UPDATES`/`SPONSORED_INMAILS`), `bidType` (`CPM`/`CPC`/`CPV`), `matchType` (`EXACT`/`AUDIENCE_EXPANDED`), `dailyBudget`.\nResponse: `available` and `pricing` (may be null).\n\n• **Impressions/clicks/spend forecast** via `POST /v1/ads/targeting/supply-forecast`\nForecasts metrics for a targeting spec over a future time range.\nKey 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`.\nResponse: `available` and `forecast` series with `metricType` and `granularity` (`DAILY`/`SEVEN_DAY`/`THIRTY_DAY`/`CUSTOM`).\n\nFor non-LinkedIn accounts, both endpoints return `available: false` (not an error).","platforms":["linkedin","linkedin-ads","ads","analytics"],"url":"https://docs.zernio.com/changelog#1784107622595"},{"id":"1784107272818","date":"2026-07-15T09:21:12.818Z","type":"new_feature","message":"New endpoint `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.\n\nSend `multipart/form-data` with `file` (PNG/JPG/WebP/GIF/PDF, max 4MB).\n\nResponse: `{ \"url\": \"...\" }`\n\nErrors: `422` unsupported type/too large, `401` unauthorized. Alias: `/v1/sms/registrations/{id}/opt-in-proof`.","platforms":["ads","publishing","telephony"],"url":"https://docs.zernio.com/changelog#1784107272818"},{"id":"1784106833569","date":"2026-07-15T09:13:53.569Z","type":"new_feature","message":"POST `/v1/sms/messages` now supports scheduling via `sendAt`.\n\nSet `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.\n\nNew field: `sendAt`","platforms":["telephony","webhooks"],"url":"https://docs.zernio.com/changelog#1784106833569"},{"id":"1784105982942","date":"2026-07-15T08:59:42.942Z","type":"improvement","message":"SMS carrier registration now accepts optional policy URLs on 10DLC campaigns via `POST /v1/sms/registrations`.\n\nYou can include privacy/terms links in the `campaign` object to help carrier reviewers validate compliance.\n\nNew `campaign` properties:\n• `privacyPolicyLink`\n• `termsAndConditionsLink`\n\nAlso, `embeddedLink` and `embeddedPhone` now document auto-derivation from your sample messages when omitted.","platforms":["telephony"],"url":"https://docs.zernio.com/changelog#1784105982942"},{"id":"1784104360230","date":"2026-07-15T08:32:40.230Z","type":"new_feature","message":"New endpoint: `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.\n\nUse it to host proof of consent when submitting `POST /v1/sms/registrations/{id}/appeal` (add the returned URL to your `messageFlow`).\n\nRequest: `multipart/form-data` with `file` (PNG/JPG/WebP/GIF/PDF, max 4MB)\nResponse: `{ \"url\": \"...\" }`\nErrors: `404` registration not found, `422` unsupported type/too large","platforms":["publishing","telephony"],"url":"https://docs.zernio.com/changelog#1784104360230"},{"id":"1784103095424","date":"2026-07-15T08:11:35.424Z","type":"new_feature","message":"New endpoint: `POST /v1/accounts/{accountId}/pinterest-boards` to **create Pinterest boards** on a connected Pinterest account.\n\nThe returned `board.id` can be used immediately as `platformSpecificData.boardId` when creating a Pinterest post.\n\nRequest body:\n• `name` (required)\n• `description`\n• `privacy`: `PUBLIC` | `PROTECTED` | `SECRET` (default `PUBLIC`)\n\nResponse (201): `board` with `id`, `name`, `description`, `privacy`, `url`.","platforms":["pinterest"],"url":"https://docs.zernio.com/changelog#1784103095424"},{"id":"1784096637720","date":"2026-07-15T06:23:57.720Z","type":"new_feature","message":"**Website retargeting audiences** are now supported via a new audience type: `website_retargeting` (LinkedIn only).\n\nYou can now create and list these audiences using `type=website_retargeting` on `POST /v1/ads/audiences` and filter them via `GET /v1/ads/audiences`.\n\nWhen creating, set:\n• `type`: `website_retargeting`\n• `matchRules`: array of URL patterns (`matchType`: `EXACT` | `STARTS_WITH` | `CONTAINS` | `ENDS_WITH`, plus `matchValue`)","platforms":["linkedin","linkedin-ads","ads"],"url":"https://docs.zernio.com/changelog#1784096637720"},{"id":"1784047042489","date":"2026-07-14T16:37:22.489Z","type":"new_feature","message":"Custom audiences now support new types: `company_list` and `engagement` on `/v1/ads/audiences`.\n\nYou can list and filter these via `GET /v1/ads/audiences` using `type` = `customer_list` | `company_list` | `engagement` | `website` | `lookalike` | `saved_targeting`.\n\nTo create them via `POST /v1/ads/audiences`:\n• `type=engagement` (LinkedIn): requires `sourceType` (`VIDEO_ADS` | `LEAD_GEN_FORMS` | `ORGANIZATION_PAGES` | `EVENT_PAGES` | `SINGLE_IMAGE_ADS`), plus `trigger`, `lookbackDays` (`30` | `60` | `90` | `180` | `365`), and `engagementSources`.\n• `type=company_list` (LinkedIn): requires `companies` (array of company rows like `name`, `domain`, `website`, `linkedinPageUrl`).","platforms":["linkedin","linkedin-ads","ads"],"url":"https://docs.zernio.com/changelog#1784047042489"},{"id":"1784040992325","date":"2026-07-14T14:56:32.325Z","type":"new_feature","message":"**LinkedIn firmographic breakdowns** are now supported in analytics via the `breakdowns` query param on `GET /v1/ads/{adId}/analytics` and `GET /v1/ads/campaigns/{campaignId}/analytics`.\n\nThis 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 &lt;3 events are omitted).\n\nUse `breakdowns` with LinkedIn dimensions:\n• `job_title`, `job_function`, `seniority`, `industry`\n• `company`, `company_size`\n• `country`, `region`\n\nLinkedIn breakdown rows include the raw pivot `value` plus a resolved `name`.","platforms":["linkedin","linkedin-ads","ads","analytics"],"url":"https://docs.zernio.com/changelog#1784040992325"},{"id":"1784039626973","date":"2026-07-14T14:33:46.973Z","type":"new_feature","message":"POST /v1/ads/create now supports LinkedIn **Jobs ads** via a new goal `job_applicants`.\n\nThis lets you create Dynamic Jobs Ads that promote open roles (requires a Company Page with active job postings).\n\nUse:\n• `goal`: `job_applicants`\n• `platformSpecificData.jobs` with `headline` and `buttonLabel` (each takes exactly one of `preApproved` or `custom`)\n\nAlso added for LinkedIn-only creatives on `platformSpecificData`:\n• `conversation` (Conversation Ads) with `subject`, `firstMessageId`, and `messages` (buttons use exactly one of `nextMessageId` or `url`).","platforms":["linkedin","linkedin-ads","ads","inbox"],"url":"https://docs.zernio.com/changelog#1784039626973"},{"id":"1784033945042","date":"2026-07-14T12:59:05.042Z","type":"new_feature","message":"POST `/v1/ads/create` now supports more **LinkedIn ad formats** via `platformSpecificData`.\n\nYou can create LinkedIn-only creatives (beyond single image/video) by setting one of these mutually-exclusive options:\n• `platformSpecificData.carousel` (2–10 cards)\n• `platformSpecificData.document` (PDF/PPT/DOC)\n• `platformSpecificData.spotlight` (dynamic spotlight)\n• `platformSpecificData.follower` (dynamic follower)\n• `platformSpecificData.textAd` (right-rail text ad)\n• `platformSpecificData.event` (promote an existing event)\n\nNotes:\n• Carousel cards use `cards[].imageUrl` and optionally `cards[].headline` / `cards[].landingUrl` (falls back to ad-level `linkUrl`).\n• Document ads use `document.url` + `document.title`.\n• Spotlight ads use `spotlight.headline` + `spotlight.callToAction` + `spotlight.landingUrl`.\n• Follower ads use `follower.callToAction` enum: `VISIT_ORGANIZATION_COMPANY_PAGE`, `VISIT_ORGANIZATION_LIFE_PAGE`, `VISIT_ORGANIZATION_JOBS_PAGE`, `VISIT_ORGANIZATION_CAREERS_PAGE`.\n\nAlso clarified: on LinkedIn, `imageUrl` + `linkUrl` publishes an ARTICLE-content creative (with `longHeadline` as the description).","platforms":["linkedin","linkedin-ads","ads"],"url":"https://docs.zernio.com/changelog#1784033945042"},{"id":"1784033424762","date":"2026-07-14T12:50:24.762Z","type":"new_feature","message":"Sole-prop 10DLC registrations can now **re-send the OTP** via `POST /v1/sms/registrations/{id}/resend-otp`.\n\nUse 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.\n\nRequest: `id` (path)\nResponse: `{ sent: boolean }`\nErrors: `400` not awaiting OTP, `404` not found, `429` wait 1 minute\n\nPort-in updates:\n• `POST /v1/phone-numbers/port-in`: `endUser.accountNumber` is now required (and `endUser.countryCode` is restricted to `US`/`CA`)\n• `POST /v1/phone-numbers/port-in/check`: response adds `lineType` (when known) to indicate if a transfer PIN is required at submit\n\n10DLC campaign submissions:\n• `POST /v1/sms/registrations`: `campaign.optinMessage`, `campaign.optoutMessage`, and `campaign.helpMessage` are now optional; when omitted, compliant templates are generated.","platforms":["telephony"],"url":"https://docs.zernio.com/changelog#1784033424762"},{"id":"1784022043515","date":"2026-07-14T09:40:43.515Z","type":"new_feature","message":"LinkedIn ads now support campaign bidding & delivery controls via `platformSpecificData` on `POST /v1/ads/create` and `POST /v1/ads/boost`.\n\nThis 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).\n\nUse `platformSpecificData` (LinkedIn only):\n• `costType`: `CPM` | `CPC` | `CPV`\n• `unitCost` (manual bid; requires `costType`)\n• `optimizationTargetType` (pass-through)\n• `creativeSelection`: `OPTIMIZED` | `ROUND_ROBIN`\n• `audienceExpansionEnabled`, `offsiteDeliveryEnabled`, `connectedTelevisionOnly`","platforms":["linkedin","linkedin-ads","ads"],"url":"https://docs.zernio.com/changelog#1784022043515"},{"id":"1784014107870","date":"2026-07-14T07:28:27.870Z","type":"new_feature","message":"Comment-to-DM automations now support **message variations** for random rotation of DM text and public comment replies.\n\nThis lets you provide multiple alternate texts so repeat triggers don’t always send identical messages.\n\nNew optional fields:\n• Create/Update: `dmMessageVariations` (up to 5), `commentReplyVariations` (up to 5)\n• Returned on list/detail/update responses: `dmMessageVariations`, `commentReplyVariations`\n\nRotation behavior:\n• DM text is picked at random from `[dmMessage, ...dmMessageVariations]`\n• Public reply is picked at random from `[commentReply, ...commentReplyVariations]`\n\nApplies to: `POST /v1/comment-automations`, `PATCH /v1/comment-automations/{automationId}`, `GET /v1/comment-automations`, `GET /v1/comment-automations/{automationId}`.","platforms":["inbox"],"url":"https://docs.zernio.com/changelog#1784014107870"},{"id":"1783967787147","date":"2026-07-13T18:36:27.147Z","type":"improvement","message":"Analytics responses now include media accessibility alt text via `mediaItems[].altText`.\n\nUse this to display or audit the alt text set on each media item when available.\n\nNew field:\n• `mediaItems[].altText` (string)\n\nIncluded in:\n• `AnalyticsSinglePostResponse`\n• `AnalyticsListResponse`","platforms":["analytics"],"url":"https://docs.zernio.com/changelog#1783967787147"},{"id":"1783952985646","date":"2026-07-13T14:29:45.646Z","type":"improvement","message":"GET `/v1/inbox/reviews` now returns Google Business review photo info via `hasPhotos` and `photoCount`.\n\nThis lets you detect and display reviews with attached photos (Google Business only; other platforms return `hasPhotos=false` and `photoCount=0`).\n\nNew fields:\n• `hasPhotos`\n• `photoCount`\n\nGET `/v1/ads/accounts` now includes Meta’s minimum daily budget via `minimumDailyBudget`.\n\nUse this to validate budget inputs against account-level minimums (field may be absent if the connected token can’t read it).\n\nNew field:\n• `minimumDailyBudget`","platforms":["google-business","meta-ads","ads","inbox"],"url":"https://docs.zernio.com/changelog#1783952985646"},{"id":"1783950235698","date":"2026-07-13T13:43:55.698Z","type":"new_feature","message":"Meta ads now support setting **EU DSA (Article 26) defaults** at the ad-account level, so EU-targeted calls can omit per-ad `dsaBeneficiary`/`dsaPayor` when defaults are configured.\n\nSet defaults once via `PATCH /v1/ads/accounts` with `accountId`, `adAccountId`, `defaultDsaBeneficiary`, optional `defaultDsaPayor` (if omitted, payor defaults to beneficiary).\n\nRead current defaults via `GET /v1/ads/dsa-defaults` (`accountId`, `adAccountId`), and fetch Meta-provided suggestions via `GET /v1/ads/dsa-recommendations` (`accountId`, `adAccountId`).\n\n`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).","platforms":["meta-ads","ads"],"url":"https://docs.zernio.com/changelog#1783950235698"},{"id":"1783936313626","date":"2026-07-13T09:51:53.626Z","type":"improvement","message":"POST `/v1/phone-numbers/port-in` now supports `portType` to specify whether you’re porting the entire losing account or only some numbers.\n\nUse `portType` to avoid ambiguity on ports where the losing account keeps some numbers.\n\nNew/updated request behavior:\n• `portType`: `full` | `partial` (default: `full`)\n• `endUser.billingPhoneNumber`: now documented as the number on the losing carrier’s bill; defaults to the ported number itself on single-number orders\n• `focDatetimeRequested`: when omitted, defaults to ~1 week out (shifted off weekends)","platforms":["telephony"],"url":"https://docs.zernio.com/changelog#1783936313626"},{"id":"1783771180666","date":"2026-07-11T11:59:40.666Z","type":"new_feature","message":"New endpoint: `PUT /v1/ads/{adId}/status` to pause/resume a single ad.\n\nThis is ad-scoped only (does not affect the parent ad set/campaign), and is a thin wrapper around updating the ad `status`.\n\nSet `status` to `active` or `paused`. `{adId}` accepts Zernio `_id`, Meta `platformAdId`, or the creative’s effective story/media IDs.\n\nResponse includes `updated` (1/0) and `skipped` (1/0); `message` is returned when the change is skipped (terminal status or already in target state).","platforms":["meta-ads","ads"],"url":"https://docs.zernio.com/changelog#1783771180666"},{"id":"1783767577047","date":"2026-07-11T10:59:37.047Z","type":"new_feature","message":"`conversation.started` webhooks now include SMS conversations via `conversation.platform`.\n\nThis lets you receive “new conversation started” events for SMS in the same platform-agnostic subscription.\n\nNew enum value:\n• `conversation.platform`: `sms` (in addition to existing platforms)","platforms":["inbox","telephony","webhooks"],"url":"https://docs.zernio.com/changelog#1783767577047"},{"id":"1783766128876","date":"2026-07-11T10:35:28.876Z","type":"new_feature","message":"WhatsApp template sends via `POST /v1/inbox/conversations` now support overriding a media-header template asset per message using `headerMedia`.\n\nThis 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.\n\nSet `headerMedia` (JSON body only):\n• `type`: `image` | `video` | `document` (must match the template header type)\n• Provide exactly one of `link` (public URL) or `id` (Meta media id)\n• Optional: `filename` (document only)","platforms":["whatsapp","inbox"],"url":"https://docs.zernio.com/changelog#1783766128876"},{"id":"1783704887339","date":"2026-07-10T17:34:47.339Z","type":"improvement","message":"GET `/v1/ads/accounts` now returns Meta ad account usability flags: `selectable` and `unusableReason`.\n\nThis lets you filter out ad accounts that can’t currently create/run ads and show a user-friendly reason in your UI.\n\nNew fields in `accounts[]` (Meta only):\n• `selectable` (boolean) — whether the account can create/run ads now\n• `unusableReason` (string|null) — reason when `selectable` is false; null when selectable\n\nOn non-Meta platforms these fields are absent (treat `selectable` as true).","platforms":["meta-ads","ads"],"url":"https://docs.zernio.com/changelog#1783704887339"},{"id":"1783703054267","date":"2026-07-10T17:04:14.267Z","type":"improvement","message":"`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).\n\nContact-identity matches are case-insensitive substrings; they return `matchCount=0` and `matches=[]`. Note: identity matching is not applied when `direction` is set (`incoming` | `outgoing`).\n\nAlso updated:\n• Searchable platforms now include Twitter/X and Reddit; Bluesky is live-fetched and appears in `meta.accountsSkipped`\n• Response adds `conversation.lastMessage` (most recent message preview)","platforms":["x","reddit","bluesky","inbox"],"url":"https://docs.zernio.com/changelog#1783703054267"},{"id":"1783690683092","date":"2026-07-10T13:38:03.092Z","type":"improvement","message":"Meta placements now support delivering ads on **WhatsApp** by allowing `whatsapp` in `placements.publisherPlatforms`.\n\nThis lets you explicitly include WhatsApp inventory when using manual placements (instead of relying on automatic placements).\n\nSet `placements.publisherPlatforms` to include:\n• `whatsapp` (alongside `facebook`, `instagram`, `threads`, `messenger`, `audience_network`)\n\nApplies to:\n• `POST /v1/ads/create`\n• `POST /v1/ads/ctwa`","platforms":["instagram","facebook","threads","whatsapp","meta-ads","ads"],"url":"https://docs.zernio.com/changelog#1783690683092"},{"id":"1783683060260","date":"2026-07-10T11:31:00.260Z","type":"improvement","message":"POST `/v1/ads/ctwa` now supports manual ad placements via `placements`.\n\nThis 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).\n\nUse `placements` with:\n• `publisherPlatforms`: `facebook` | `instagram` | `threads` | `messenger` | `audience_network`\n• `facebookPositions`, `instagramPositions`, `messengerPositions`, `audienceNetworkPositions`, `threadsPositions`, `whatsappPositions`\n• `devicePlatforms`: `mobile` | `desktop`","platforms":["instagram","facebook","threads","whatsapp","meta-ads","ads"],"url":"https://docs.zernio.com/changelog#1783683060260"},{"id":"1783681912471","date":"2026-07-10T11:11:52.471Z","type":"improvement","message":"`GET /v1/usage` now includes estimated tax via a new `tax` field on `UsageMetering`.\n\nThis 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.\n\nNew fields:\n• `tax.taxUsd` — estimated tax in USD (added on top of `totals.total`)\n• `tax.ratePercent` — combined rate percentage (nullable)\n• `tax.jurisdictionLabel` — human label like “ES VAT” (nullable)\n• `tax.reverseCharge` — `true` for EU/UK B2B reverse charge (0 tax added)","platforms":["general"],"url":"https://docs.zernio.com/changelog#1783681912471"},{"id":"1783675186589","date":"2026-07-10T09:19:46.589Z","type":"improvement","message":"Webhooks now support additional event subscriptions for conversations, calls, leads, and WhatsApp.\n\nYou can subscribe to these new `events` via `POST /v1/webhooks/settings` or `PUT /v1/webhooks/settings`:\n• `conversation.started`\n• `call.received`, `call.ended`, `call.failed`, `call.permission_request`\n• `lead.received`\n• `whatsapp.automatic_event`\n\nSMS carrier registrations (10DLC) now accept `campaign.subUsecases` on `POST /v1/sms/registrations` (2–5 values): `2FA`, `ACCOUNT_NOTIFICATION`, `CUSTOMER_CARE`, `DELIVERY_NOTIFICATION`, `FRAUD_ALERT`, `HIGHER_EDUCATION`, `MARKETING`, `POLLING_VOTING`, `PUBLIC_SERVICE_ANNOUNCEMENT`, `SECURITY_ALERT`.\n\nFor 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`.","platforms":["whatsapp","telephony","webhooks"],"url":"https://docs.zernio.com/changelog#1783675186589"},{"id":"1783606570099","date":"2026-07-09T14:16:10.099Z","type":"new_feature","message":"New **billing snapshot** endpoint: `GET /v1/billing`.\n\nIt 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.\n\nKey response blocks:\n• `plan` (includes `isUsageBased`)\n• `period` (`start`/`end` may be `null` on Stripe)\n• `balance` (`null` for Stripe; populated for Metronome)\n• `caps` (e.g. `xSpendUsedCents`, `xSpendLimitCents`)\n• `status` (e.g. `hasAccess`, `suspended`, `openInvoiceUrl`, `declineReason`)\n\nAlso: `GET /v1/usage` is now dual-mode.\n• Default (no metering params): same snapshot as before\n• Metering mode (billed spend by product) when passing any of: `range`, `granularity`, `from`, `to`\n\nMetering params:\n• `range`: `cycle` | `prev-cycle` | `7d` | `14d` | `30d` | `3mo` | `12mo` | `custom`\n• `granularity`: `day` | `month` | `total`\n• `from`/`to` required when `range=custom`\n\nStripe accounts calling metering mode receive `{ \"supported\": false }`.","platforms":["general"],"url":"https://docs.zernio.com/changelog#1783606570099"},{"id":"1783601843471","date":"2026-07-09T12:57:23.471Z","type":"new_feature","message":"POST `/v1/whatsapp/business-profile/photo` now accepts an image via URL in addition to multipart upload.\n\nYou 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.\n\nNew JSON body option:\n• `accountId`\n• `url` (public https image URL; must resolve directly without redirects)\n\nNew response to handle:\n• `422` when the profile photo is locked for WhatsApp coexistence numbers (must be managed in the WhatsApp Business app).","platforms":["whatsapp","publishing"],"url":"https://docs.zernio.com/changelog#1783601843471"},{"id":"1783598063608","date":"2026-07-09T11:54:23.608Z","type":"breaking_change","message":"POST `/v1/sms/registrations` now enforces additional required fields for 10DLC registrations.\n\nThis can break existing integrations that submit `brand`/`campaign` without the newly-required fields.\n\nFor `brand`, these fields are now required: `website`, `street`, `city`, `state`, `postalCode` (in addition to existing required fields).\n\nFor `campaign`, `sample2` is now required and must be distinct from `sample1`.\n\nWebhook post events now echo back your post’s free-form `metadata` (when provided at create time) in:\n• `WebhookPayloadPost.post.metadata`\n• `WebhookPayloadPostPlatform.post.metadata`","platforms":["telephony","webhooks"],"url":"https://docs.zernio.com/changelog#1783598063608"},{"id":"1783583424358","date":"2026-07-09T07:50:24.358Z","type":"breaking_change","message":"Google Business Profile posts: `offer.offerType` has been removed from `GoogleBusinessPlatformData`.\n\nIf you were sending `offerType` (previously `OFFER` or `BUY_ONE_GET_ONE`), remove it from your payloads and use the remaining offer fields instead:\n• `redeemOnlineUrl`\n• `termsConditions`\n• `couponCode`","platforms":["google-business"],"url":"https://docs.zernio.com/changelog#1783583424358"},{"id":"1783511395169","date":"2026-07-08T11:49:55.169Z","type":"breaking_change","message":"The supported `breakdowns` values were reduced on analytics endpoints.\n\nIf you request breakdowns that are no longer supported, your call may fail (400) or no longer return those breakdowns.\n\nAffected endpoints:\n• `GET /v1/ads/{adId}/analytics`: Meta breakdowns now limited to `age`, `gender`, `country`, `publisher_platform`, `device_platform`, `region` (removed: `platform_position`, `impression_device`, `video_asset`, `image_asset`, `body_asset`, `title_asset`; also removed aliases `placement` and singular `breakdown`)\n• `GET /v1/ads/campaigns/{campaignId}/analytics`: Meta breakdowns now limited to `age`, `gender`, `country`, `publisher_platform`, `device_platform`, `region`, `platform_position`, `impression_device`, `video_asset`, `image_asset`, `body_asset`, `title_asset` (removed aliases `placement` and singular `breakdown`)","platforms":["meta-ads","ads","analytics"],"url":"https://docs.zernio.com/changelog#1783511395169"},{"id":"1783508462155","date":"2026-07-08T11:01:02.155Z","type":"new_feature","message":"`GET /v1/analytics` now supports a new metric: `follows`.\n\nYou 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).\n\nYou can also sort analytics results by `sortBy` = `follows` (enum now includes: `date`, `engagement`, `impressions`, `reach`, `likes`, `comments`, `shares`, `saves`, `clicks`, `views`, `follows`).","platforms":["instagram","analytics"],"url":"https://docs.zernio.com/changelog#1783508462155"},{"id":"1783507164686","date":"2026-07-08T10:39:24.686Z","type":"breaking_change","message":"`GET /v1/ads/{adId}/analytics` and `GET /v1/ads/campaigns/{campaignId}/analytics` now validate breakdown dimensions and will return `400` for unknown values.\n\nThis prevents silent/partial breakdown results and makes invalid `breakdowns` requests fail fast with a supported-values list.\n\nUpdates:\n• `breakdowns`: unknown dimensions now return `400` (instead of being ignored)\n• `placement` is accepted as an alias for `publisher_platform`\n• `breakdown` (singular) is also accepted\n\nNew Meta breakdown options include: `platform_position`, `impression_device`, `video_asset`, `image_asset`, `body_asset`, `title_asset`.","platforms":["meta-ads","ads","analytics"],"url":"https://docs.zernio.com/changelog#1783507164686"},{"id":"1783504017183","date":"2026-07-08T09:46:57.183Z","type":"new_feature","message":"You can now update **saved_targeting** audiences via `PUT /v1/ads/audiences/{audienceId}`.\n\nThis lets you change an audience’s `name`, `description`, or fully replace its `spec` (no merge). Uploaded/derived audiences aren’t updatable and return `422`.\n\nRequest body (send at least one): `name`, `description`, `spec`\n\nAlso: `GET /v1/ads/audiences` now returns `accountId` for `type=saved_targeting` items, and `DELETE /v1/ads/audiences/{audienceId}` clarifies that `saved_targeting` deletes only the local record (it exists only on Zernio).","platforms":["ads"],"url":"https://docs.zernio.com/changelog#1783504017183"},{"id":"1783435059706","date":"2026-07-07T14:37:39.706Z","type":"improvement","message":"Ads reporting endpoints may now return `202` when part of the requested date range isn’t ingested yet.\n\nWhen 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.\n\nApplies to:\n• `GET /v1/ads`\n• `GET /v1/ads/tree`\n• `GET /v1/ads/timeline`\n• `GET /v1/ads/campaigns/{campaignId}/analytics`\n• `GET /v1/ads/{adId}/analytics`\n\nAlso, `POST /v1/posts` `403` responses now include a stable `code` to distinguish causes:\n• `ACCOUNT_DISCONNECTED`\n• `PROFILE_OVER_LIMIT`","platforms":["pinterest-ads","ads","analytics"],"url":"https://docs.zernio.com/changelog#1783435059706"},{"id":"1783424382130","date":"2026-07-07T11:39:42.130Z","type":"new_feature","message":"WhatsApp inbox messages now support **commerce interactive messages** (products, product lists, catalog messages, and carousels) via `interactive` on `POST /v1/inbox/conversations/{conversationId}/messages`.\n\nThis lets you send catalog-driven messages and receive cart submissions / product inquiries back in webhooks.\n\nNew `interactive.type` values:\n• `product`, `product_list`, `catalog_message`, `carousel`\n\nWebhook `message.received` metadata adds:\n• `metadata.order` (cart submitted from commerce messages)\n• `metadata.referredProduct` (product inquiry context)\n\nNote: interactive messages are session messages (must be sent within WhatsApp’s 24-hour window).","platforms":["whatsapp","inbox","commerce","webhooks"],"url":"https://docs.zernio.com/changelog#1783424382130"},{"id":"1783422743813","date":"2026-07-07T11:12:23.813Z","type":"improvement","message":"GET `/v1/inbox/mentions` now returns additional LinkedIn author details in each mention.\n\nYou can display richer mention attribution (when available) without extra profile resolution on your side.\n\nNew fields:\n• `authorName` - display name resolved from `authorUrn` (can be `null`)\n• `authorUsername` - LinkedIn vanity name (can be `null`)\n• `authorPicture` - author profile picture URL (can be `null`; CDN URLs may expire, so fetch promptly)","platforms":["linkedin","inbox"],"url":"https://docs.zernio.com/changelog#1783422743813"},{"id":"1783357001796","date":"2026-07-06T16:56:41.796Z","type":"new_feature","message":"Meta ad metrics now include **video performance fields** in `AdMetrics` (and therefore `AdDailyMetrics`).\n\nYou 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).\n\nNew fields:\n• `videoPlayActions`\n• `video30SecWatchedActions`\n• `videoThruplayWatchedActions`\n• `videoP25WatchedActions`, `videoP50WatchedActions`, `videoP75WatchedActions`, `videoP95WatchedActions`, `videoP100WatchedActions`\n• `videoAvgTimeWatchedActions`\n\nNote: in `AdDailyMetrics`, `videoAvgTimeWatchedActions` is recomputed per day; don’t sum or plain-average it across days (the range value is play-weighted).","platforms":["meta-ads","ads","analytics"],"url":"https://docs.zernio.com/changelog#1783357001796"},{"id":"1783339249936","date":"2026-07-06T12:00:49.936Z","type":"new_feature","message":"New endpoint: `GET /v1/inbox/conversations/search`.\n\nYou 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).\n\nKey params:\n• `query` (required, 2–200 chars)\n• `direction`: `incoming` | `outgoing`\n• `platform`: `facebook` | `instagram` | `telegram` | `whatsapp` | `sms`\n• `profileId`, `accountId`\n• `limit` (1–50, default 20), `cursor`\n\nResponse 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.\n\nRequires Inbox addon (`403` if missing).","platforms":["instagram","telegram","whatsapp","inbox"],"url":"https://docs.zernio.com/changelog#1783339249936"},{"id":"1783335999956","date":"2026-07-06T11:06:39.956Z","type":"improvement","message":"Inbox webhook **account context** now includes optional `profileId` in `InboxWebhookAccount`.\n\nUse `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.","platforms":["whatsapp","webhooks"],"url":"https://docs.zernio.com/changelog#1783335999956"},{"id":"1783334609922","date":"2026-07-06T10:43:29.922Z","type":"new_feature","message":"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.\n\nUsage snapshot + breakdowns:\n• `GET /v1/usage` (replaces `GET /v1/usage-stats`; same contract)\n• `GET /v1/usage/calls` with optional `groupBy`: `day` | `number` | `channel` and filters `since`, `until`, `channel` (`whatsapp` | `pstn`), `number`\n• `GET /v1/usage/sms` with optional `groupBy`: `day` | `number` and filters `since`, `until`, `number`\n\nUnified call history (WhatsApp + PSTN) + recordings:\n• `GET /v1/calls` filters: `channel` (`whatsapp` | `pstn`), `status` (`ringing` | `answered` | `ended` | `failed`), `direction` (`inbound` | `outbound`), `number`, `search`, cursor pagination via `before` + `nextCursor`\n• `GET /v1/calls/{id}`\n• `GET /v1/calls/{id}/recording` (default `302`; `as=json` returns `{ url }`)\n\nWhatsApp call history improvements:\n• `GET /v1/whatsapp/calls` now supports cursor pagination via `before` and returns `nextCursor`\n• `POST /v1/whatsapp/calls` now supports idempotent retries via `Idempotency-Key`\n\nPSTN Voice calling (new):\n• `POST /v1/phone-numbers/{id}/voice` configure inbound routing (`forwardTo`), recording/transcription, voicemail, business hours, IVR, blocklist\n• `DELETE /v1/phone-numbers/{id}/voice`\n• 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`\n• Browser softphone handshake: `POST /v1/voice/calls/web` then `POST /v1/voice/calls/web/dial`\n\nSMS (new):\n• `POST /v1/sms/messages` (supports `Idempotency-Key`; MMS via `mediaUrls`)\n• `GET /v1/sms/lookup` (carrier + line type `mobile` | `landline` | `voip` | `toll-free` | `unknown`)\n• Opt-outs export: `GET /v1/sms/opt-outs` with `format`: `json` | `csv`\n• 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/share`\n\nPhone Numbers (new canonical resource):\n• `GET /v1/phone-numbers`, `GET /v1/phone-numbers/{id}`, `DELETE /v1/phone-numbers/{id}`\n• Purchase + inventory: `POST /v1/phone-numbers/purchase`, `GET /v1/phone-numbers/countries`, `GET /v1/phone-numbers/available`, `GET /v1/phone-numbers/availability`\n• 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`\n• 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}`\n\nWhatsApp Calling config is now number-keyed:\n• New canonical sub-resource: `GET/POST/PATCH/DELETE /v1/phone-numbers/{id}/whatsapp/calling`\n• Existing `/v1/whatsapp/phone-numbers/{id}/calling` endpoints are deprecated aliases\n\nDeprecated aliases (no immediate break, but new integrations should switch):\n• `GET /v1/usage-stats` → `GET /v1/usage`\n• Many `/v1/whatsapp/phone-numbers/*` paths → corresponding `/v1/phone-numbers/*` paths","platforms":["whatsapp","telephony","analytics"],"url":"https://docs.zernio.com/changelog#1783334609922"},{"id":"1783172970596","date":"2026-07-04T13:49:30.596Z","type":"improvement","message":"Webhooks now support **TikTok URL backfill** via the new event `post.tiktok.url_resolved`.\n\nThis 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`).\n\nSubscribe by including `post.tiktok.url_resolved` in `events` when calling `POST /v1/webhooks/settings` or `PUT /v1/webhooks/settings`.\n\nAlso 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.","platforms":["tiktok","publishing","webhooks"],"url":"https://docs.zernio.com/changelog#1783172970596"},{"id":"1783105078652","date":"2026-07-03T18:57:58.652Z","type":"improvement","message":"Webhook payloads now include `account.accountId` (same value as `account.id`) as a canonical field for filtering/routing events.\n\nThis 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.\n\nNew field:\n• `account.accountId`\n\nAdded to these webhook payloads:\n• `comment.received`\n• `lead.received`\n• `review.new`\n• `review.updated`\n• `post.external.created`, `post.external.updated`, `post.external.deleted`\n• inbox webhook payloads via `InboxWebhookAccount`","platforms":["inbox","webhooks"],"url":"https://docs.zernio.com/changelog#1783105078652"},{"id":"1783104210049","date":"2026-07-03T18:43:30.049Z","type":"improvement","message":"Webhook post events now include the connected social account per platform target via `post.platforms[].accountId`.\n\nThis lets you route/attribute webhook deliveries by the specific SocialAccount used for each platform publish (useful when a single post spans multiple accounts).\n\nNew field:\n• `WebhookPayloadPost.post.platforms[].accountId`\n• `WebhookPayloadPostPlatform.post.platforms[].accountId` (on `post.platform.published` / `post.platform.failed`; see also the top-level `account` block there)","platforms":["webhooks"],"url":"https://docs.zernio.com/changelog#1783104210049"},{"id":"1783103357709","date":"2026-07-03T18:29:17.709Z","type":"improvement","message":"WhatsApp KYC form spec now returns an optional **audience** per requirement in `GET /v1/whatsapp/phone-numbers/kyc`.\n\nThis lets you show/collect only the fields that apply to the end-user type (business vs individual) and avoid submitting both sets of requirements.\n\nNew field: `fields[].audience` = `business` | `individual` | `null` (applies to both).","platforms":["whatsapp","telephony"],"url":"https://docs.zernio.com/changelog#1783103357709"},{"id":"1783091174274","date":"2026-07-03T15:06:14.274Z","type":"improvement","message":"`GET /v1/connect/pending-data` now supports Pinterest board selection in headless OAuth flows.\n\nFor Pinterest (in addition to LinkedIn orgs and Snapchat profiles), the OAuth redirect returns `pendingDataToken` and you can fetch the full selection payload via `token`.\n\nUse `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).","platforms":["linkedin","pinterest","snapchat","linkedin-ads","pinterest-ads"],"url":"https://docs.zernio.com/changelog#1783091174274"},{"id":"1783088685789","date":"2026-07-03T14:24:45.789Z","type":"improvement","message":"Workflow **send_message** nodes now allow omitting `interactive.type`; it will be inferred from the `interactive` payload shape.\n\nThis makes interactive message configs simpler, but payloads with neither `type` nor an inferable shape are now rejected.\n\nApplies to `send_message.config` with `messageType` = `interactive` (WhatsApp-only).","platforms":["whatsapp","inbox"],"url":"https://docs.zernio.com/changelog#1783088685789"},{"id":"1783068088463","date":"2026-07-03T08:41:28.463Z","type":"new_feature","message":"`POST /v1/invite/tokens` now supports inviting users with the new **admin** role via `role`.\n\nUse `role=admin` to grant team-management permissions (invite/remove members, change roles and access) without billing, ownership transfer, or account deletion permissions.\n\n`role` enum is now: `admin`, `member`, `billing_admin`, `viewer`.\n\nNote: `readOnly` (deprecated) can’t be combined with `role=billing_admin` or `role=admin`.","platforms":["general"],"url":"https://docs.zernio.com/changelog#1783068088463"},{"id":"1782907037094","date":"2026-07-01T11:57:17.094Z","type":"improvement","message":"WhatsApp document sends via URL now support setting the displayed filename using `attachmentName` on `POST /v1/inbox/conversations/{conversationId}/messages`.\n\nThis lets you control the recipient-visible file name for `attachmentUrl` document messages (otherwise WhatsApp derives it from the URL and may show “Untitled”).\n\nUse:\n• `attachmentType`: `file`\n• `attachmentUrl`: (public URL)\n• `attachmentName`: e.g. `Report.pdf`\n\nApplies to WhatsApp only; ignored for `image`/`video`/`audio` and for multipart binary uploads.","platforms":["whatsapp","inbox"],"url":"https://docs.zernio.com/changelog#1782907037094"},{"id":"1782902610923","date":"2026-07-01T10:43:30.923Z","type":"new_feature","message":"New endpoint `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.\n\nUse it to confirm a user-submitted post exists by sending `accountId` plus either `url` or `postId`.\n\nRequest body:\n• `accountId` (required)\n• `url` (optional)\n• `postId` (optional)\n\nResponse:\n• `synced.postsFound`, `synced.postsSynced`, `synced.skipped`\n• If `url`/`postId` provided: `found` + `post` (or null)\n• If no locator provided: `posts` (recent external posts)\n\nReturned posts use `ExternalPostSummary` (includes `platform`, `platformPostId`, `platformPostUrl`, `publishedAt`, and basic `analytics` like `likes`/`comments`).","platforms":["analytics"],"url":"https://docs.zernio.com/changelog#1782902610923"},{"id":"1782838756625","date":"2026-06-30T16:59:16.625Z","type":"new_feature","message":"WhatsApp Business Profile now supports managing business usernames via new endpoints.\n\nYou can fetch the current username + approval state, claim/transfer a username, release it, and retrieve available suggestions.\n\nNew endpoints:\n• `GET /v1/whatsapp/business-profile/username` (`accountId` query) → returns `username` (nullable) and `status` (`approved`/`reserved`/`none`)\n• `POST /v1/whatsapp/business-profile/username` body: `accountId`, `username`, optional `transferAction` (`none`/`force_transfer`)\n• `DELETE /v1/whatsapp/business-profile/username` body: `accountId`\n• `GET /v1/whatsapp/business-profile/username/suggestions` (`accountId` query) → returns `suggestions`[]\n\nOn username claim failures, expect error codes like `whatsapp_username_unavailable`, `whatsapp_username_ineligible`, or `whatsapp_username_transfer_required`.","platforms":["whatsapp"],"url":"https://docs.zernio.com/changelog#1782838756625"},{"id":"1782813988124","date":"2026-06-30T10:06:28.124Z","type":"new_feature","message":"New endpoint `GET /v1/inbox/mentions` is available to list mentions of your connected organization accounts delivered via platform webhooks.\n\nCurrently supports LinkedIn organization mentions and requires the Inbox addon.\n\nKey query params:\n• `accountId` (filter by social account ID)\n• `profileId` (filter by profile ID)\n• `sortOrder` (`asc` | `desc`, default `desc`)\n• `limit` (1–100, default 25)\n• `cursor` (pagination)\n\nResponse includes `data` items with `id`, `platform` (`linkedin`), `content`, `permalink`, `authorUrn`, `organizationalEntity`, `publishedAt`, plus `pagination` (`hasMore`, `cursor`).","platforms":["linkedin","inbox","webhooks"],"url":"https://docs.zernio.com/changelog#1782813988124"},{"id":"1782810767580","date":"2026-06-30T09:12:47.580Z","type":"new_feature","message":"New endpoint `GET /v1/ads/campaigns/{campaignId}/analytics` returns campaign-level analytics in one call (summary metrics, `daily[]` timeline, and optional Meta demographic breakdowns).\n\nUse 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`, `publisher_platform`, `device_platform`, `region`, `platform_position`, `impression_device`, `video_asset`, `image_asset`, `body_asset`, `title_asset`.\n\n`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`.","platforms":["meta-ads","ads","analytics"],"url":"https://docs.zernio.com/changelog#1782810767580"},{"id":"1782749165045","date":"2026-06-29T16:06:05.045Z","type":"new_feature","message":"**Instagram Trial Reels** are now identifiable per platform target in post responses.\n\nWhen an Instagram reel is created as a Trial (via `platformSpecificData.trialParams`), the corresponding `PlatformTarget` may include:\n• `isTrialReel` (boolean) — present and true only for trial reels\n• `trialGraduationStrategy` — `MANUAL` | `SS_PERFORMANCE` (present only when `isTrialReel` is true)\n\nUse these fields to segment trial reels in analytics; note this reflects creation-time intent (Instagram doesn’t expose a readable trial flag).","platforms":["instagram","analytics"],"url":"https://docs.zernio.com/changelog#1782749165045"},{"id":"1782490117972","date":"2026-06-26T16:08:37.972Z","type":"breaking_change","message":"Removed endpoint `GET /v1/accounts/{accountId}/posts`.\n\nThis 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).\n\nIf you were using YouTube filtering, note that `excludeUnlisted` is no longer available via this route.","platforms":["instagram","threads","tiktok","x","linkedin","youtube","pinterest","reddit","bluesky"],"url":"https://docs.zernio.com/changelog#1782490117972"},{"id":"1782488382208","date":"2026-06-26T15:39:42.208Z","type":"breaking_change","message":"New endpoint `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).\n\nYou can optionally filter YouTube results to avoid returning internal/unlisted content with `excludeUnlisted`.\n\nUse:\n• `accountId` (path)\n• `excludeUnlisted` (query, boolean; YouTube only)\n\nAlso note a targeting spec change: `TargetingSpec.cities[].distanceUnit` is now `TargetingSpec.cities[].distance_unit` (with `radius` still supported). `excludedLocations` now supports excluded `cities` with optional `radius` + `distance_unit`, plus excluded `places`, `neighborhoods`, and excluded `customLocations`.","platforms":["instagram","threads","tiktok","x","linkedin","youtube","pinterest","reddit","bluesky","ads"],"url":"https://docs.zernio.com/changelog#1782488382208"},{"id":"1782234016825","date":"2026-06-23T17:00:16.825Z","type":"new_feature","message":"Instagram posts now support AI-generated media self-disclosure via `isAiGenerated` in `platformSpecificData`.\n\nWhen `isAiGenerated` is `true`, Instagram labels the post as containing AI-generated media (this is for media, not AI-written captions).\n\nSet:\n• `isAiGenerated` (boolean, default `false`)\n\nApplies to feed posts, Reels, Stories, and carousels.","platforms":["instagram","publishing"],"url":"https://docs.zernio.com/changelog#1782234016825"},{"id":"1782213896092","date":"2026-06-23T11:24:56.092Z","type":"new_feature","message":"`POST /v1/whatsapp/phone-numbers/kyc` now supports provisioning multiple same-country numbers in a single KYC submission via `quantity`.\n\nSet `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).\n\nResponse now includes `numbers` (array) with all provisioned numbers. `phoneNumber` remains and represents the first/primary number for backward compatibility.\n\n`status` remains: `kyc_submitted` | `kyc_reused` | `kyc_already_submitted`","platforms":["whatsapp","telephony"],"url":"https://docs.zernio.com/changelog#1782213896092"},{"id":"1782206671797","date":"2026-06-23T09:24:31.797Z","type":"new_feature","message":"WhatsApp phone number provisioning now supports creating a **hosted KYC link** via `POST /v1/whatsapp/phone-numbers/kyc/share`.\n\nThis 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).\n\nRequest fields:\n• `profileId` (required)\n• `country` (required, ISO-2; non-KYC countries return `400`)\n• `branding` (optional: `companyName`, `logoUrl`, `brandColor`)\n• `redirect_url` (optional; on success appends `kyc=submitted` and `country=&lt;ISO-2&gt;`)\n\nResponse fields:\n• `url`\n• `token`\n• `expiresAt`\n\nTo react when the form is completed, subscribe webhooks to the new event: `whatsapp.number.kyc_submitted` (available in `POST`/`PUT /v1/webhooks/settings`).","platforms":["whatsapp","telephony","webhooks"],"url":"https://docs.zernio.com/changelog#1782206671797"},{"id":"1782201740821","date":"2026-06-23T08:02:20.821Z","type":"improvement","message":"Comment webhooks (`comment.received`) now include an optional `comment.attachment` object for **Facebook** graphic-only comments (sticker, GIF, photo).\n\nThis lets you detect and render comments that have no text content; note Meta URLs may be ephemeral, so fetch promptly.\n\nKey fields:\n• `comment.attachment.type` (required)\n• `comment.attachment.imageUrl`\n• `comment.attachment.url`","platforms":["inbox","webhooks"],"url":"https://docs.zernio.com/changelog#1782201740821"},{"id":"1782142981327","date":"2026-06-22T15:43:01.327Z","type":"new_feature","message":"Meta boosts via `POST /v1/ads/boost` now support **raw Meta-native targeting** with `rawTargeting`.\n\nUse `rawTargeting` to pass a Meta targeting spec verbatim (snake_case like `geo_locations`, `custom_audiences`, `excluded_custom_audiences`, `flexible_spec`, `targeting_automation`) to target specific custom/lookalike audiences or clone targeting exactly.\n\nKey rule: `rawTargeting` is mutually exclusive with `targeting` (sending both returns `422`).","platforms":["meta-ads","ads"],"url":"https://docs.zernio.com/changelog#1782142981327"},{"id":"1782127569078","date":"2026-06-22T11:26:09.079Z","type":"new_feature","message":"POST `/v1/ads/create` now supports additional Meta geo targeting controls.\n\nYou can target more precise location types and explicitly exclude locations when creating Meta ads.\n\nNew fields:\n• `places` (Meta geoType `place`)\n• `neighborhoods` (Meta geoType `neighborhood`)\n• `excludedLocations` with `countries`, `regions`, `cities`, `zips`\n\nPOST `/v1/ads/lead-forms` adds richer Meta Instant Form configuration.\n\nNew fields:\n• `formType`: `MORE_VOLUME` | `HIGHER_INTENT` | `RICH_CREATIVE`\n• `blockDisplayForNonTargetedViewer`\n• `allowOrganicLeadGen`\n• `questionPageCustomHeadline`\n• `contextCard` (`title`, `content`, `style`: `LIST_STYLE` | `PARAGRAPH_STYLE`, `buttonText`, `coverPhoto`)\n\nGET `/v1/ads/targeting/search` geo search now supports `geoType=all` and returns coordinates.\n\nUse `dimension=geo` with `geoType=all` to search across all geo types in one call, and read `latitude` / `longitude` from results (when available).","platforms":["meta-ads","ads"],"url":"https://docs.zernio.com/changelog#1782127569078"},{"id":"1782120825162","date":"2026-06-22T09:33:45.162Z","type":"improvement","message":"Moving an account to another profile via `PATCH /v1/accounts/{accountId}` can now return `409` when the target profile already has an account on the same platform.\n\nThis enforces the rule that a profile can hold only one account per platform.\n\nHandle the new response:\n• `409` - target profile already has an account of the same platform (`profile_platform_conflict`)","platforms":["general"],"url":"https://docs.zernio.com/changelog#1782120825162"},{"id":"1781876147254","date":"2026-06-19T13:35:47.254Z","type":"improvement","message":"GET `/v1/ads/lead-forms/{formId}` now returns a fully detailed `form` object (enough to duplicate the form via `POST /v1/ads/lead-forms`).\n\nKey fields include `form.status` (`ACTIVE`/`ARCHIVED`), `questions`, `thank_you_page`, and `context_card`.\n\nGeo targeting search via `GET /v1/ads/targeting/search` now supports additional `geoType` values when `dimension=geo`: `subcity`, `neighborhood`, `place`, `metro_area`, `geo_market` (in addition to `country`, `region`, `city`, `zip`).","platforms":["linkedin-ads","ads"],"url":"https://docs.zernio.com/changelog#1781876147254"},{"id":"1781866039506","date":"2026-06-19T10:47:19.506Z","type":"improvement","message":"Inbox conversations now include **ad-click attribution** via a new `metadata` object in responses from `GET /v1/inbox/conversations` and `GET /v1/inbox/conversations/{conversationId}`.\n\nThis lets you identify when a conversation started from a click-to-message ad (vs organic) and store the associated ad identifiers/details.\n\nNew field:\n• `metadata` (object, nullable) — only present for ad-originated conversations; absent for organic\n\nCommon keys you may see in `metadata`:\n• WhatsApp CTWA: `ctwa_clid`, `ctwa_source_id`, `ctwa_source_url`, `ctwa_headline`, `ctwa_source_type`, `ctwa_captured_at`\n• Meta CTM/CTD: `meta_ad_id`, `meta_ad_title`, `meta_ad_source`, `meta_ad_type`, `meta_ad_ref`, `meta_ad_captured_at`, `meta_ad_photo_url`, `meta_ad_video_url`, `meta_ad_post_id`, `meta_ad_product_id`, `meta_ad_flow_id`","platforms":["whatsapp","meta-ads","ads","inbox"],"url":"https://docs.zernio.com/changelog#1781866039506"},{"id":"1781861337468","date":"2026-06-19T09:28:57.468Z","type":"improvement","message":"Analytics post items now include `isAd` in `AnalyticsListResponse.posts[]`.\n\nThis lets you distinguish ad creatives from organic posts in analytics results (currently only available for LinkedIn posts; false when unavailable).\n\nNew field:\n• `isAd` (boolean) — true for ad creatives, otherwise false","platforms":["linkedin","linkedin-ads","ads","analytics"],"url":"https://docs.zernio.com/changelog#1781861337468"},{"id":"1781796516526","date":"2026-06-18T15:28:36.526Z","type":"improvement","message":"Meta ads created via `POST /v1/ads/create` now allow combining manual `placements` with `rawTargeting`.\n\nThis lets you clone an ad set’s full Meta-native targeting spec via `rawTargeting` while still overriding placements using the structured `placements` object.\n\nWhen both are set, `placements` is converted to Meta snake_case and merged into `rawTargeting` before sending to Meta.\n\nKey fields:\n• `rawTargeting` (Meta TargetingSpec, snake_case)\n• `placements.publisherPlatforms`: `facebook`, `instagram`, `threads`, `messenger`, `audience_network`\n• `placements.facebookPositions`, `placements.instagramPositions`, `placements.messengerPositions`, `placements.audienceNetworkPositions`, `placements.threadsPositions`, `placements.whatsappPositions`, `placements.devicePlatforms`: `mobile`/`desktop`","platforms":["instagram","facebook","threads","whatsapp","meta-ads","ads"],"url":"https://docs.zernio.com/changelog#1781796516526"},{"id":"1781790558403","date":"2026-06-18T13:49:18.403Z","type":"improvement","message":"POST `/v1/ads/create` now supports more control over Meta publish state and creative reuse.\n\nYou 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.\n\nNew/updated fields:\n• `status` (`ACTIVE` | `PAUSED`) now explicitly controls desired publish state; in attach mode (`adSetId`) it affects only the new ad\n• `campaignStatus` (`ACTIVE` | `PAUSED`) lets you set campaign state independently on the legacy create shape (ignored when `existingCampaignId` is set)\n• `existingCreativeId` can now be used with `adSetId` to attach an ad that reuses an existing Meta creative (copy/media fields can be omitted)","platforms":["meta-ads","ads","publishing"],"url":"https://docs.zernio.com/changelog#1781790558403"},{"id":"1781688222361","date":"2026-06-17T09:23:42.361Z","type":"new_feature","message":"POST `/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**.\n\nThis enables safer launches (no immediate spend) and easier multi-ad-set testing while keeping creatives consistent.\n\nNew request fields:\n• `status`: `ACTIVE` | `PAUSED`\n• `existingCampaignId` (Meta only; mutually exclusive with `adSetId` and `creatives[]`)\n• `existingCreativeId` (Meta only; mutually exclusive with `creatives[]`, `dynamicCreative`, `placementAssets`)","platforms":["meta-ads","ads"],"url":"https://docs.zernio.com/changelog#1781688222361"},{"id":"1781684073557","date":"2026-06-17T08:14:33.557Z","type":"improvement","message":"LinkedIn posts now support **quote-reshare** via `reshareUrl` in `LinkedInPlatformData`.\n\nSet `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.\n\nNotes:\n• `reshareUrl` is mutually exclusive with media\n• Works on personal profiles and organization pages\n\nError responses are now more structured for programmatic handling: `ErrorResponse` adds `type`, `code`, `param`, `platform`, and `platformError` (raw upstream payload when `type` is `platform_error`).\n\n`type` can be: `invalid_request_error`, `authentication_error`, `permission_error`, `not_found`, `rate_limit_error`, `platform_error`, `api_error`.","platforms":["linkedin","publishing"],"url":"https://docs.zernio.com/changelog#1781684073557"},{"id":"1781510828051","date":"2026-06-15T08:07:08.051Z","type":"new_feature","message":"`POST /v1/ads/conversions` now supports **TikTok** conversions (platform `tiktokads`).\n\nYou 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.\n\nKey details:\n• `accountId` now accepts: `metaads`, `googleads`, `linkedinads`, `tiktokads`\n• Response `platform` enum now includes `tiktokads`\n\n`GET /v1/accounts/{accountId}/conversion-destinations` also now supports `tiktokads` (platform enum includes `tiktokads`).\n\n`GET /v1/ads/tree` adds `campaignId` (platform campaign id) to restrict the tree to a single campaign, independent of pagination.","platforms":["tiktok","linkedin","google-ads","tiktok-ads","linkedin-ads","ads"],"url":"https://docs.zernio.com/changelog#1781510828051"},{"id":"1781354724834","date":"2026-06-13T12:45:24.834Z","type":"improvement","message":"Google Ads conversion destination creation via `POST /v1/accounts/{accountId}/conversion-destinations` is now effectively idempotent by `name`.\n\nIf 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`.\n\nKey fields:\n• `name`\n• `type`\n\nNew response:\n• `409` (Google Ads only) — `IDEMPOTENCY_CONFLICT`","platforms":["google-ads","ads"],"url":"https://docs.zernio.com/changelog#1781354724834"},{"id":"1781351112003","date":"2026-06-13T11:45:12.003Z","type":"new_feature","message":"`POST /v1/accounts/{accountId}/conversion-destinations` now supports creating conversion destinations for **Google Ads** in addition to LinkedIn.\n\nYou 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.\n\nKey Google Ads fields:\n• `countingType`: `MANY_PER_CLICK` | `ONE_PER_CLICK`\n• `primaryForGoal`: `true` | `false` (default `false`)\n\nNotes:\n• `accountId` can now be `linkedinads` or `googleads`\n• Creation is **not idempotent** — retries can create duplicates\n• 201 response `platform` now includes `linkedinads` | `googleads`","platforms":["linkedin","google-ads","linkedin-ads","ads"],"url":"https://docs.zernio.com/changelog#1781351112003"},{"id":"1781287430205","date":"2026-06-12T18:03:50.205Z","type":"improvement","message":"WhatsApp regulated-number KYC reuse now supports choosing which prior approved verification to reuse.\n\n`GET /v1/whatsapp/phone-numbers/kyc` adds `reusable.options` (newest first), listing all approved verifications (each with `fromPhoneNumber` + `details`).\n\nWhen 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`.","platforms":["whatsapp","telephony"],"url":"https://docs.zernio.com/changelog#1781287430205"},{"id":"1781286115313","date":"2026-06-12T17:41:55.313Z","type":"improvement","message":"GET `/v1/whatsapp/phone-numbers/kyc` now returns a human-readable summary when a reusable KYC verification is available.\n\nWhen `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.\n\nNew field: `reusable.details` (array of `{ label, value }`) alongside `reusable.fromPhoneNumber`.","platforms":["whatsapp","telephony"],"url":"https://docs.zernio.com/changelog#1781286115313"},{"id":"1781267620212","date":"2026-06-12T12:33:40.212Z","type":"improvement","message":"`GET /v1/connect/{platform}/ads` now supports ad sync scoping via `adAccountId`/`adAccountIds` across more ads platforms (not just Meta).\n\nYou 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.\n\nUse:\n• `adAccountId` - single ad account id (platform-specific format)\n• `adAccountIds` - multiple ids (repeat param or comma-separated)\n\nNotes:\n• `tiktok` ignores these params (advertisers are scoped during OAuth)\n• `pinterest` ignores these params (no ads discovery)\n• Meta ids (`act_&lt;digits&gt;`) are validated against the connected token; unreachable ids return `400`\n• Omitting both `adAccountId` and `adAccountIds` keeps the previously persisted scope unchanged","platforms":["instagram","tiktok","x","linkedin","pinterest","meta-ads","google-ads","tiktok-ads","linkedin-ads","pinterest-ads","x-ads","ads"],"url":"https://docs.zernio.com/changelog#1781267620212"},{"id":"1781265972399","date":"2026-06-12T12:06:12.399Z","type":"improvement","message":"Broadcast recipient results now include a plain-language failure reason via `errorExplanation` on `GET /v1/broadcasts/{broadcastId}/recipients`.\n\nThis helps you interpret WhatsApp `errorCode` values (e.g. opt-out / re-engagement required) without maintaining your own mapping.\n\nUse:\n• `recipients[].errorCode` (integer, nullable)\n• `recipients[].errorExplanation` (string, nullable; null if unmapped — fall back to `error`)\n\nWebhooks for delivery status now also include `error.explanation` in `WebhookPayloadMessageDeliveryStatus` (populated on `event`=`message.failed`).","platforms":["whatsapp","inbox","webhooks"],"url":"https://docs.zernio.com/changelog#1781265972399"},{"id":"1781264773334","date":"2026-06-12T11:46:13.334Z","type":"improvement","message":"Google campaign objects now include `advertisingChannelType` in `AdCampaign` and `AdTreeCampaign`.\n\nThis exposes the raw Google Ads `campaign.advertising_channel_type` so you can distinguish campaign serving surfaces (e.g. Search vs Performance Max) when syncing/reporting.\n\nNew field:\n• `advertisingChannelType` (string, nullable) — Google-only (e.g. `SEARCH`, `PERFORMANCE_MAX`, `VIDEO`, `DEMAND_GEN`, `DISPLAY`, `SHOPPING`)","platforms":["google-ads","ads"],"url":"https://docs.zernio.com/changelog#1781264773334"},{"id":"1781263891032","date":"2026-06-12T11:31:31.032Z","type":"new_feature","message":"WhatsApp interactive messages now support **voice call** buttons when sending inbox messages via `POST /v1/inbox/conversations/{conversationId}/messages`.\n\nThis 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.\n\nUse `interactive.type` = `voice_call` and `interactive.action.name` = `voice_call`. Optional `interactive.action.parameters`:\n• `display_text`\n• `ttl_minutes`\n• `payload`\n\nAlso, `GET /v1/whatsapp/calling` now returns `callDeepLink` (nullable) for the public `https://wa.me/call/&lt;number&gt;` deep link when calling is enabled.","platforms":["whatsapp","inbox","telephony","webhooks"],"url":"https://docs.zernio.com/changelog#1781263891032"},{"id":"1781262141391","date":"2026-06-12T11:02:21.391Z","type":"new_feature","message":"YouTube analytics now supports **video retention curves** via `GET /v1/analytics/youtube/video-retention`.\n\nThis 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.\n\nCall with:\n• `videoId`\n• `accountId`\n• optional `startDate`, `endDate`\n\nResponse highlights:\n• `retentionCurve[]` points with `elapsedVideoTimeRatio`, `audienceWatchRatio` (can be &gt; 1), `relativeRetentionPerformance`\n• empty `retentionCurve` for low-view / not-yet-processed videos (2–3 day delay)\n\nRequires `yt-analytics.readonly`; if missing you may get `412` with `code`=`youtube_analytics_scope_missing`.\n\nAlso added to `GET /v1/analytics/youtube/daily-views` responses:\n• top-level `durationSeconds`\n• per-day `averageViewPercentage` (can exceed 100; don’t clamp client-side)","platforms":["youtube","analytics"],"url":"https://docs.zernio.com/changelog#1781262141391"},{"id":"1781262002743","date":"2026-06-12T11:00:02.743Z","type":"new_feature","message":"GET `/v1/analytics/daily-metrics` now supports **engagement attribution** via a new query param `attribution`.\n\nThis 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).\n\nUse `attribution`:\n• `publish` (default) — attribute each post’s lifetime total to its publish date\n• `received` — attribute per-day engagement increases to the day they occurred","platforms":["analytics"],"url":"https://docs.zernio.com/changelog#1781262002743"},{"id":"1781256601095","date":"2026-06-12T09:30:01.095Z","type":"new_feature","message":"WhatsApp adds **blocked-status lookup** for a single contact via `GET /v1/whatsapp/block-users/status`.\n\nThis 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).\n\nRequired query params:\n• `accountId`\n• `user` (consumer wa_id or E.164 phone; leading `+` optional)\n\nResponse `200`:\n• `blocked` (boolean)","platforms":["whatsapp","telephony"],"url":"https://docs.zernio.com/changelog#1781256601095"},{"id":"1781245092719","date":"2026-06-12T06:18:12.719Z","type":"new_feature","message":"POST `/v1/whatsapp/calls` now supports sending the WhatsApp call-consent prompt via a new optional `action` parameter.\n\nUse 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).\n\nSet:\n• `action`: `send_call_permission_request` (omit `action` to place a call)\n• `bodyText`: optional text shown with the consent prompt (max 1024 chars)","platforms":["whatsapp","telephony"],"url":"https://docs.zernio.com/changelog#1781245092719"},{"id":"1781189723393","date":"2026-06-11T14:55:23.393Z","type":"new_feature","message":"WhatsApp now supports managing your blocklist via `/v1/whatsapp/block-users`.\n\nYou 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).\n\nNew endpoints:\n• `GET /v1/whatsapp/block-users` with `accountId`, optional `limit`, `after` (cursor pagination via `nextCursor`)\n• `POST /v1/whatsapp/block-users` body: `accountId`, `users` (1..1000)\n• `DELETE /v1/whatsapp/block-users` body: `accountId`, `users` (1..1000)\n\nWhatsApp interactive messages now also support location requests in `POST /v1/inbox/conversations/{conversationId}/messages` via `interactive.type` = `location_request_message` (and `action` can be omitted; defaults to `{\"name\":\"send_location\"}`).","platforms":["whatsapp","inbox"],"url":"https://docs.zernio.com/changelog#1781189723393"},{"id":"1781174119287","date":"2026-06-11T10:35:19.287Z","type":"new_feature","message":"Meta catalog ads are now supported via `goal` = `catalog_sales` on `POST /v1/ads/create`.\n\nThis 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).\n\nDiscover inputs with:\n• `GET /v1/ads/catalogs` (`accountId`, `adAccountId`)\n• `GET /v1/ads/catalogs/{catalogId}/product-sets` (`catalogId`, `accountId`)\n\nCreate the ad with:\n• `goal`: `catalog_sales`\n• `promotedObject.productSetId`\n• `promotedObject.pixelId`\n• `promotedObject.customEventType`\n\nNotes: `catalog_sales` is single-shape only (no `creatives[]`, `adSetId`, `dynamicCreative`, or `placementAssets`).","platforms":["meta-ads","ads","commerce"],"url":"https://docs.zernio.com/changelog#1781174119287"},{"id":"1781167562451","date":"2026-06-11T08:46:02.451Z","type":"improvement","message":"GET `/v1/whatsapp/phone-numbers` now also returns **connected (bring-your-own) WhatsApp numbers** in a separate `connected` array.\n\nThis 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.\n\nNew response field: `connected[]` with:\n• `accountId`\n• `phoneNumber`\n• `displayName`\n• `profileId`\n• `connectedAt`\n\nIncluded only on the default view and when `status=active`.","platforms":["whatsapp","telephony"],"url":"https://docs.zernio.com/changelog#1781167562451"},{"id":"1781110986789","date":"2026-06-10T17:03:06.789Z","type":"improvement","message":"WhatsApp phone number purchase now supports idempotent retries and duplicate-purchase protection on `POST /v1/whatsapp/phone-numbers/purchase`.\n\nUse `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.\n\nA 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`.","platforms":["whatsapp","telephony"],"url":"https://docs.zernio.com/changelog#1781110986789"},{"id":"1781105401441","date":"2026-06-10T15:30:01.441Z","type":"new_feature","message":"New endpoint: `GET /v1/accounts/{accountId}/gmb-attribute-metadata`.\n\nYou can now fetch Google Business Profile attribute metadata (valid attribute names, value types, and allowed enum values) before reading/writing via `gmb-attributes`.\n\nQuery modes (mutually exclusive):\n• Location mode: `locationId` (or omit to use the account’s stored `selectedLocationId`)\n• Category mode: `categoryName` (must start with `categories/`) + `regionCode` (optional `languageCode`)\n\nPagination: `pageSize` (1–200) and `pageToken`.\n\nResponse 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.","platforms":["google-business"],"url":"https://docs.zernio.com/changelog#1781105401441"},{"id":"1781087943581","date":"2026-06-10T10:39:03.581Z","type":"improvement","message":"Meta video ads created via `POST /v1/ads/create` no longer require a thumbnail.\n\nYou 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.\n\nApplies to:\n• Top-level `video` (legacy + attach shapes): `video.url` required, `video.thumbnailUrl` optional\n• `creatives[].video` (Meta multi-creative): `video.url` required, `video.thumbnailUrl` optional","platforms":["meta-ads","ads"],"url":"https://docs.zernio.com/changelog#1781087943581"},{"id":"1781084280239","date":"2026-06-10T09:38:00.239Z","type":"new_feature","message":"POST `/v1/ads/create` now supports Meta link descriptions via a new `description` field.\n\nThis lets you set the secondary link text on Facebook/Instagram ads instead of relying on Meta scraping OpenGraph data.\n\nUse:\n• `description` (Meta only) — applies to legacy + attach + `placementAssets`\n• `creatives[].description` (Meta multi-creative) — per-ad override; falls back to top-level `description`\n\nAlso clarified for Meta lead ads: when using `adSetId` (attach shape) into a lead ad set, you must pass `leadGenFormId` on every attached ad.","platforms":["instagram","meta-ads","ads"],"url":"https://docs.zernio.com/changelog#1781084280239"},{"id":"1781081785482","date":"2026-06-10T08:56:25.482Z","type":"deprecation","message":"Invite creation now supports a dedicated read-only role via `role` on `POST /v1/invite/tokens`.\n\nUse `role` to control access level:\n• `member`\n• `billing_admin`\n• `viewer` - read-only (no publish/edit/delete/connect)\n\nThe `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`).","platforms":["general"],"url":"https://docs.zernio.com/changelog#1781081785482"},{"id":"1781079612095","date":"2026-06-10T08:20:12.095Z","type":"new_feature","message":"Webhooks now support a new WhatsApp number event: `whatsapp.number.action_required`.\n\nSubscribe to this event to be notified when a WhatsApp number needs additional action to proceed.\n\nAdd it to `events` when creating/updating a webhook via `POST /v1/webhooks/settings` or `PUT /v1/webhooks/settings`:\n• `events`: `[\"whatsapp.number.action_required\", ...]`","platforms":["whatsapp","telephony","webhooks"],"url":"https://docs.zernio.com/changelog#1781079612095"},{"id":"1781023198400","date":"2026-06-09T16:39:58.400Z","type":"improvement","message":"Invite token creation now supports assigning an org role and issuing read-only invites via `POST /v1/invite/tokens`.\n\nUse `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`).","platforms":["general"],"url":"https://docs.zernio.com/changelog#1781023198400"},{"id":"1781013544205","date":"2026-06-09T13:59:04.205Z","type":"breaking_change","message":"WhatsApp template buttons updated: `WhatsAppTemplateButton` now supports a new button `type` value `copy_code`, and `text` is no longer required for all button types.\n\nThis enables WhatsApp marketing “copy code” buttons where the label is fixed by WhatsApp, and the coupon code is provided via `example`.\n\nKey changes:\n• `type` enum now includes: `quick_reply`, `url`, `phone_number`, `otp`, `copy_code`, `flow`, `mpm`, `catalog`\n• `text`: required for all types except `copy_code`\n• `example` is now `string` or `string[]` (URL buttons: `string[]`; `copy_code`: coupon code as `string`)","platforms":["whatsapp","telephony"],"url":"https://docs.zernio.com/changelog#1781013544205"},{"id":"1781010114625","date":"2026-06-09T13:01:54.625Z","type":"new_feature","message":"Meta Lead Gen ads created via `POST /v1/ads/create` now support **Dynamic Creative** using `dynamicCreative` together with `goal`=`lead_generation`.\n\nYou 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.\n\nUse:\n• `goal`=`lead_generation`\n• `leadGenFormId`=`&lt;form_id&gt;`\n• `dynamicCreative.imageUrls` (send 1+; send a single entry if you only want multi-text)\n• Optional variations: `dynamicCreative.titles`, `dynamicCreative.bodies`, `dynamicCreative.descriptions`, `dynamicCreative.linkUrls`\n• Required: `dynamicCreative.callToActionTypes` (e.g. `LEARN_MORE`, `SIGN_UP`, `DOWNLOAD`, `REGISTER`, `BUY_NOW`)","platforms":["meta-ads","ads"],"url":"https://docs.zernio.com/changelog#1781010114625"},{"id":"1780993750465","date":"2026-06-09T08:29:10.465Z","type":"new_feature","message":"X (Twitter) posts now support additional labeling and media safety controls via `platformSpecificData`.\n\nYou can label posts as paid promotions or AI-generated media, and mark attached media with sensitive-content warnings.\n\nNew fields in `TwitterPlatformData`:\n• `paidPartnership` (boolean)\n• `madeWithAi` (boolean)\n• `sensitiveMedia` with `adultContent`, `graphicViolence`, `other` (booleans; requires media)\n\nReddit posts now support additional post options via `platformSpecificData`.\n\nNew fields in `RedditPlatformData`:\n• `flairText` (string; ignored if `flairId` is set)\n• `nsfw` (boolean)\n• `spoiler` (boolean)\n• `sendreplies` (boolean)","platforms":["x","reddit","x-ads","publishing"],"url":"https://docs.zernio.com/changelog#1780993750465"},{"id":"1780925277408","date":"2026-06-08T13:27:57.408Z","type":"improvement","message":"GET `/v1/accounts` now supports filtering by connection status via the new `status` query param.\n\nThis 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`.\n\nUse `status`:\n• `connected`\n• `disconnected`","platforms":["general"],"url":"https://docs.zernio.com/changelog#1780925277408"},{"id":"1780913490875","date":"2026-06-08T10:11:30.875Z","type":"improvement","message":"Meta Lead Gen ads created via `POST /v1/ads/create` now support **per-placement creative** using `placementAssets`.\n\nYou can pin different image/video assets to placement groups (e.g. Stories/Reels vs Feed) while still attaching a Lead Gen form via `leadGenFormId`.\n\nUse:\n• `goal=lead_generation`\n• `leadGenFormId`\n• `placementAssets` with `defaultImageUrl` or `defaultVideoUrl` + `rules[].placements`\n\nNote: `dynamicCreative` is still not compatible with `goal=lead_generation`.","platforms":["instagram","meta-ads","ads"],"url":"https://docs.zernio.com/changelog#1780913490875"},{"id":"1780910102669","date":"2026-06-08T09:15:02.669Z","type":"improvement","message":"Meta ads tracking-tag updates are now simpler on `PATCH /v1/ads/{adId}/tracking-tags`: you can send `urlTags` alone and Zernio will preserve the existing creative verbatim while rebuilding/repointing the ad.\n\nThis removes the previous requirement to provide the full Meta `creative` payload just to add/update click-URL tags. For creatives where Meta strips `object_story_spec` (e.g. share/page-post/dark/asset_feed), you may still get `422` and need to include `creative` explicitly.\n\nKey fields:\n• `urlTags` (Meta)\n• `creative` (Meta, optional; required only when preservation isn’t possible)\n• `trackingUrlTemplate`, `finalUrlSuffix` (Google)\n• `dynamicValueParameters`, `customValueParameters` (LinkedIn)","platforms":["linkedin","meta-ads","google-ads","linkedin-ads","ads"],"url":"https://docs.zernio.com/changelog#1780910102669"},{"id":"1780906823336","date":"2026-06-08T08:20:23.336Z","type":"new_feature","message":"Meta ads created via `POST /v1/ads/create` now support explicitly setting the ad set optimization goal via `optimizationGoal`.\n\nThis lets you override the default optimization derived from `goal` (e.g. `traffic` defaulting to LINK_CLICKS) and pass a Meta-native `optimization_goal` value directly; Meta will validate and reject incompatible combinations.\n\nUse `optimizationGoal` (Meta only), e.g. `LANDING_PAGE_VIEWS`, `LINK_CLICKS`, `REACH`, `IMPRESSIONS`, `OFFSITE_CONVERSIONS`, `THRUPLAY`, `LEAD_GENERATION`.","platforms":["meta-ads","ads"],"url":"https://docs.zernio.com/changelog#1780906823336"},{"id":"1780668010768","date":"2026-06-05T14:00:10.768Z","type":"new_feature","message":"Google Ads now supports **conversion adjustments** via `POST /v1/ads/conversions/adjustments`.\n\nYou can retract, restate value, or enhance an already-uploaded conversion (Google only; other platforms return `405`). Partial failure is supported (check `adjustmentsFailed` / `failures[]`).\n\nSend:\n• `accountId` (must be a `googleads` account)\n• `destinationId` (conversion action resource name)\n• `adjustments[]` with `adjustmentType` = `RETRACTION` | `RESTATEMENT` | `ENHANCEMENT`\n\nPer adjustment:\n• Identify the original conversion with `orderId` (required for `ENHANCEMENT`) or `gclid` + `conversionTime`\n• `adjustmentTime` (unix seconds)\n• `restatementValue` + `currency` (for `RESTATEMENT`)\n• `user.email`/`user.phone` (+ optional `userAgent`) for `ENHANCEMENT`\n\nAlso: Meta standalone ad creation adds `goal` = `lead_conversion` on `POST /v1/ads/create` (and `Ad.goal`) for website pixel lead optimization (distinct from `lead_generation` instant forms).","platforms":["meta-ads","google-ads","ads"],"url":"https://docs.zernio.com/changelog#1780668010768"},{"id":"1780655795910","date":"2026-06-05T10:36:35.910Z","type":"improvement","message":"`GET /v1/comment-automations` now returns `stats.trackedSends` for each automation.\n\nUse `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.\n\nNew field:\n• `stats.trackedSends` - DMs sent with a trackable (wrapped) link","platforms":["inbox","analytics"],"url":"https://docs.zernio.com/changelog#1780655795910"},{"id":"1780650782642","date":"2026-06-05T09:13:02.642Z","type":"new_feature","message":"Webhooks now support **external post** events for posts created/updated/deleted directly on the platform (not published via Zernio).\n\nSubscribe by including these in `events` when calling `POST /v1/webhooks/settings` or `PUT /v1/webhooks/settings`:\n• `post.external.created`\n• `post.external.updated`\n• `post.external.deleted`\n\nPayload 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.","platforms":["webhooks"],"url":"https://docs.zernio.com/changelog#1780650782642"},{"id":"1780650092724","date":"2026-06-05T09:01:32.724Z","type":"new_feature","message":"POST `/v1/ads/create` now supports Meta-specific **single-ad naming** and **ad-level tracking**.\n\nYou can explicitly set the created ad’s name (instead of inheriting `name`), and attach pixel measurement + URL tags regardless of optimization goal.\n\nNew fields:\n• `adName` (Meta only) — overrides the default ad name (default was `name`)\n• `tracking.pixelId` (Meta only) — attaches pixel measurement via ad `tracking_specs`\n• `tracking.urlTags` (Meta only) — sets ad `url_tags` as `[{ key, value }]`","platforms":["meta-ads","ads","analytics"],"url":"https://docs.zernio.com/changelog#1780650092724"},{"id":"1780644705349","date":"2026-06-05T07:31:45.349Z","type":"new_feature","message":"New **Inbox Analytics** endpoints are available to query messaging volume, response times, and conversation-level stats (max date range: `365` days).\n\nNew endpoints:\n• `GET /v1/analytics/inbox/volume` — volume + KPIs + per-platform split (key params: `fromDate`, `toDate`, `profileId`, `platform`, `accountId`, `source`)\n• `GET /v1/analytics/inbox/heatmap` — day-of-week × hour heatmap (key params: `fromDate`, `toDate`, `action`=`message.received`|`message.sent`|`message.read`|`all`)\n• `GET /v1/analytics/inbox/source-breakdown` — breakdown by `metadata.source`\n• `GET /v1/analytics/inbox/response-time` — time-to-first-response summary + histogram\n• `GET /v1/analytics/inbox/top-accounts` — top accounts by inbox volume (key param: `limit` 1–50)\n• `GET /v1/analytics/inbox/conversations` — paginated conversation analytics (key params: `limit`, `page`, `sortBy`=`lastMessageAt`|`firstMessageAt`|`totalMessages`|`received`|`sent`|`read`|`failed`, `order`=`asc`|`desc`)\n• `GET /v1/analytics/inbox/conversations/{conversationId}` — single-conversation analytics (`{conversationId}` accepts Mongo `_id` or `platformConversationId`)\n\nWebhook event enums were expanded in `POST /v1/webhooks/settings`, `PUT /v1/webhooks/settings`, and the `Webhook` schema to include:\n• `whatsapp.number.suspended`\n• `whatsapp.number.reactivated`\n• `whatsapp.number.released`","platforms":["whatsapp","inbox","analytics","webhooks"],"url":"https://docs.zernio.com/changelog#1780644705349"},{"id":"1780587235448","date":"2026-06-04T15:33:55.448Z","type":"new_feature","message":"You can now **rename Meta ads objects** via update endpoints.\n\nThis adds name updates without recreating entities (Meta only; other platforms return `501`).\n\n• `PUT /v1/ads/campaigns/{campaignId}` now accepts `name` (max 255)\n• `PUT /v1/ads/ad-sets/{adSetId}` now accepts `name` (max 255)\n• `PUT /v1/ads/{adId}` now supports `name` (max 255) and propagates it to Meta; non-Meta returns `501`\n\nAd creation on Meta also adds more naming control:\n• `POST /v1/ads/create` now accepts `campaignName` and `adSetName` (max 255)\n• In Meta multi-creative mode, `creatives[].name` sets the per-ad name (max 255)","platforms":["meta-ads","ads"],"url":"https://docs.zernio.com/changelog#1780587235448"},{"id":"1780565882304","date":"2026-06-04T09:38:02.304Z","type":"new_feature","message":"Meta ads created via `POST /v1/ads/create` now support **raw Meta-native targeting** via `rawTargeting`.\n\nThis 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.\n\nUse `rawTargeting` (Meta only) and do not combine it with:\n• camelCase targeting fields (e.g. `countries`, `regions`, `cities`, `interests`, `ageMin`, ...)\n• `audienceId`\n• `savedTargetingId`\n\nSending both will return `422`. If targeting EU users, also provide `dsaBeneficiary` / `dsaPayor` separately (not inside `rawTargeting`).","platforms":["meta-ads","ads"],"url":"https://docs.zernio.com/changelog#1780565882304"},{"id":"1780560906260","date":"2026-06-04T08:15:06.260Z","type":"improvement","message":"POST `/v1/ads/create` now supports safe retries via an optional `Idempotency-Key` header.\n\nThis 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.\n\nUse `Idempotency-Key` (e.g. a UUID):\n• Same key + same body → replays the original response (response header `Idempotent-Replayed: true`)\n• Same key + different body → `422`\n• Key still processing → `409` (retry after backoff)\n\nKeys are scoped to your credential and expire after 24h.","platforms":["ads"],"url":"https://docs.zernio.com/changelog#1780560906260"},{"id":"1780498059505","date":"2026-06-03T14:47:39.505Z","type":"improvement","message":"Google Business Profile location listing now supports server-side narrowing for large accounts via `search` and advanced `filter`.\n\nThis helps users find a specific location without loading the full list, and responses can now indicate when the list is bounded.\n\nChanges:\n• `GET /v1/connect/googlebusiness/locations`: new `search`, `filter`; response adds `hasMore` and per-location `storeCode`\n• `GET /v1/accounts/{accountId}/gmb-locations`: new `search`, `filter`; response adds `hasMore` and per-location `storeCode`\n• `POST /v1/connect/googlebusiness/select-location`: request body adds optional (recommended) `accountId` to resolve the location directly for large accounts\n• `PUT /v1/accounts/{accountId}/gmb-locations`: request body adds optional (recommended) `accountId` when switching locations","platforms":["google-business"],"url":"https://docs.zernio.com/changelog#1780498059505"},{"id":"1780483941828","date":"2026-06-03T10:52:21.828Z","type":"improvement","message":"Meta **placement asset customization** in `POST /v1/ads/create` now supports pinning **video** per placement group (not just images).\n\nYou 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).\n\nUse `placementAssets` in one of these modes:\n• Image mode: `defaultImageUrl` + `rules[].imageUrl`\n• Video mode: `defaultVideoUrl` + `rules[].videoUrl` (+ optional `defaultThumbnailUrl` / `rules[].thumbnailUrl`)\n\n`placementAssets` still can’t be combined with `dynamicCreative`, `creatives[]`, or `adSetId`.","platforms":["meta-ads","ads"],"url":"https://docs.zernio.com/changelog#1780483941828"},{"id":"1780479491530","date":"2026-06-03T09:38:11.530Z","type":"new_feature","message":"Ads now support unified read/update of click-URL tracking tags via `GET /v1/ads/{adId}/tracking-tags` and `PATCH /v1/ads/{adId}/tracking-tags`.\n\nThis lets you audit and set platform-native tracking params across Meta/Google/LinkedIn (platforms without a click-URL tracking surface return `405`).\n\nRead returns `platform`, `level` (`creative` | `campaign`) plus platform fields:\n• Meta: `urlTags`, `templateUrlSpec`\n• Google: `trackingUrlTemplate`, `finalUrlSuffix`\n• LinkedIn: `dynamicValueParameters`, `customValueParameters`\n\nUpdate expects only the platform fields:\n• Meta: `urlTags` (array of `{key,value}`) + full `creative` (`headline`, `body`, `callToAction`, `linkUrl`, `imageUrl`); may return `422` if the creative can’t be rebuilt\n• Google: `trackingUrlTemplate` and/or `finalUrlSuffix`\n• LinkedIn: `dynamicValueParameters` and/or `customValueParameters`\n\nMeta-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`).\n\nAlso added Meta advanced matching fields to `ConversionEvent.user`: `city`, `state`, `zip`, `dob`, `gender` (Meta-only; hashed server-side).","platforms":["linkedin","meta-ads","google-ads","linkedin-ads","ads","analytics"],"url":"https://docs.zernio.com/changelog#1780479491530"},{"id":"1780411092864","date":"2026-06-02T14:38:12.864Z","type":"new_feature","message":"POST `/v1/ads/create` now supports Meta **placement asset customization** via `placementAssets`.\n\nThis 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 `asset_feed_spec` + `asset_customization_rules` behavior.\n\nUse `placementAssets`:\n• `defaultImageUrl` (required)\n• `rules[]` (1-10), each with `imageUrl` + `placements`\n\n`placements` supports: `publisherPlatforms`, `facebookPositions`, `instagramPositions`, `messengerPositions`, `audienceNetworkPositions`, `threadsPositions`, `whatsappPositions`, `devicePlatforms`.\n\nConstraints: Meta-only; cannot be combined with `dynamicCreative`, `creatives[]`, or `adSetId`.","platforms":["instagram","facebook","threads","whatsapp","meta-ads","ads"],"url":"https://docs.zernio.com/changelog#1780411092864"},{"id":"1780394602926","date":"2026-06-02T10:03:22.926Z","type":"new_feature","message":"Comment-to-DM automations now support **Instagram story reply** triggers via a new `trigger` field.\n\nYou 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).\n\nUse `trigger`:\n• `comment`\n• `story_reply`\n\nApplies to:\n• `POST /v1/comment-automations` (new request field `trigger`, default `comment`)\n• `GET /v1/comment-automations` and `GET /v1/comment-automations/{automationId}` (responses now include `trigger`)","platforms":["instagram","inbox"],"url":"https://docs.zernio.com/changelog#1780394602926"},{"id":"1780393777123","date":"2026-06-02T09:49:37.123Z","type":"improvement","message":"GET `/v1/comment-automations` now returns additional automation stats: `stats.delivered` and `stats.read`.\n\nThis lets you report delivery/read performance for comment-to-DM automations (where supported) without extra calls.\n\nNew fields:\n• `stats.delivered` - DMs confirmed delivered (Messenger; IG emits no delivery receipt)\n• `stats.read` - DMs confirmed read (IG messaging_seen / Messenger message_reads)","platforms":["instagram","facebook","inbox","analytics"],"url":"https://docs.zernio.com/changelog#1780393777123"},{"id":"1780393310318","date":"2026-06-02T09:41:50.318Z","type":"new_feature","message":"Comment-to-DM automations now support tagging contacts who click tracked links via `clickTag`.\n\nWhen `linkTracking` is enabled, you can set `clickTag` so anyone who clicks a tracked link button in the DM gets tagged for segmentation (broadcasts/sequences).\n\nUse:\n• `POST /v1/comment-automations`: `clickTag` (optional; requires `linkTracking`)\n• `PATCH /v1/comment-automations/{automationId}`: `clickTag` (empty string clears it)\n\nReturned in:\n• `GET /v1/comment-automations`: `clickTag`\n• `GET /v1/comment-automations/{automationId}`: `clickTag`","platforms":["inbox"],"url":"https://docs.zernio.com/changelog#1780393310318"},{"id":"1780392263233","date":"2026-06-02T09:24:23.233Z","type":"new_feature","message":"Comment-to-DM automations now support **link tracking** for DM button URLs, and the list endpoint includes click metrics.\n\nWhen enabled, link buttons are wrapped in a tracked redirect so you can measure clicks and CTR.\n\nNew fields:\n• Create/update: `linkTracking` (POST default: `true`; set `false` to send links untouched)\n• List: `automations[].linkTracking`\n• List stats: `automations[].stats.linkClicks`, `automations[].stats.uniqueClicks`\n• Details/create responses: `automation.linkTracking`","platforms":["inbox","analytics"],"url":"https://docs.zernio.com/changelog#1780392263233"},{"id":"1780303415925","date":"2026-06-01T08:43:35.925Z","type":"new_feature","message":"X (Twitter) posts now support **quote reposting** via `quoteTweetId` in `TwitterPlatformData`.\n\nSet `quoteTweetId` to a tweet ID (or full status URL) to publish a quote tweet. For threads, it applies to the first tweet only.\n\nKey property:\n• `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).","platforms":["x","publishing"],"url":"https://docs.zernio.com/changelog#1780303415925"},{"id":"1780073798314","date":"2026-05-29T16:56:38.314Z","type":"improvement","message":"WhatsApp call billing now includes `billableCostUSD` in call history, cost estimates, and `call.ended` webhooks.\n\nThis makes it explicit what Zernio bills you (Telnyx + recording) vs what Meta bills directly to your WABA (display-only).\n\nNew/updated billing fields:\n• `billing.billableCostUSD` - amount Zernio bills (excludes Meta)\n• `billing.metaCostUSD` - Meta per-minute charge (billed by Meta directly; display only)\n• `billing.totalCostUSD` - full cost including Meta (display only)\n\nApplies to:\n• `GET /v1/whatsapp/calls`\n• `GET /v1/whatsapp/calls/estimate` (in `breakdown`)\n• Webhook schema `WebhookPayloadCallEnded` (in `call.billing`)","platforms":["whatsapp","webhooks"],"url":"https://docs.zernio.com/changelog#1780073798314"},{"id":"1780071729215","date":"2026-05-29T16:22:09.215Z","type":"improvement","message":"GET `/v1/contacts/{contactId}` now returns additional messaging activity fields on the contact and channels.\n\nYou can use these to show engagement stats and recent activity without querying message history.\n\nNew fields:\n• Contact: `messagesSentCount`, `messagesReceivedCount`, `lastMessageSentAt` (nullable), `lastMessageReceivedAt` (nullable)\n• Channel: `lastActiveAt` (nullable)","platforms":["inbox","analytics"],"url":"https://docs.zernio.com/changelog#1780071729215"},{"id":"1780050620166","date":"2026-05-29T10:30:20.166Z","type":"new_feature","message":"New **Workflows** API endpoints are available to build and run branching conversation workflows (node/edge graphs), including activation/pausing and execution tracking.\n\nManage workflows:\n• `GET /v1/workflows` (filters: `profileId`, `status` = `draft`|`active`|`paused`, `limit`, `skip`)\n• `POST /v1/workflows` (create draft; required: `profileId`, `accountId`, `name`; optional: `platform` = `whatsapp`|`instagram`|`facebook`|`telegram`|`twitter`|`bluesky`|`reddit`, `nodes`, `edges`, `entryNodeId`)\n• `GET /v1/workflows/{workflowId}`\n• `PATCH /v1/workflows/{workflowId}` (graph edits only when `draft` or `paused`)\n• `DELETE /v1/workflows/{workflowId}`\n\nControl lifecycle:\n• `POST /v1/workflows/{workflowId}/activate` (validates runnable graph; idempotent)\n• `POST /v1/workflows/{workflowId}/pause` (stops matching new inbound messages; idempotent)\n\nTrack and test runs:\n• `GET /v1/workflows/{workflowId}/executions` (filter `status` = `running`|`waiting`|`completed`|`exited`|`failed`; `limit`, `skip`)\n• `POST /v1/workflows/{workflowId}/executions` (manual start; provide `conversationId` or WhatsApp-only `to`; optional `text` seeds `lastMessage`)","platforms":["instagram","reddit","bluesky","telegram","whatsapp","inbox"],"url":"https://docs.zernio.com/changelog#1780050620166"},{"id":"1780046288227","date":"2026-05-29T09:18:08.227Z","type":"new_feature","message":"WhatsApp now supports **Sandbox session management** via new endpoints: `GET /v1/whatsapp/sandbox/sessions`, `POST /v1/whatsapp/sandbox/sessions`, `DELETE /v1/whatsapp/sandbox/sessions/{sessionId}`.\n\nThis 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.\n\nKey fields:\n• Create/refresh with `phone` (server normalizes to E.164)\n• Session object `WhatsAppSandboxSession`: `id`, `phoneE164`, `status` (`pending` | `active`), `expiresAt`, `activatedAt`\n• Responses include `sandboxNumber`\n\nAlso, `GET /v1/whatsapp/phone-numbers` now includes an optional `sandbox` object (or `null`) with `phoneNumber`, `accountId`, and allowed `template` (`name`, `language`), plus `isSandbox` = `true`.","platforms":["whatsapp","telephony"],"url":"https://docs.zernio.com/changelog#1780046288227"},{"id":"1779986592128","date":"2026-05-28T16:43:12.128Z","type":"improvement","message":"WhatsApp conversion events no longer require a Facebook Page ID on the account when calling `POST /v1/whatsapp/conversions`.\n\nThe endpoint now only requires `metaCapiDatasetId` (provision via `POST /v1/whatsapp/dataset`) and forwards the WABA ID as `user_data.whatsapp_business_account_id` for Meta attribution.\n\nIf you were setting account metadata with `connectedFacebookPageId`, you can stop—missing it will no longer cause `422`. Missing `metaCapiDatasetId` can still return `422`.","platforms":["facebook","whatsapp","meta-ads","ads"],"url":"https://docs.zernio.com/changelog#1779986592128"},{"id":"1779980377479","date":"2026-05-28T14:59:37.479Z","type":"improvement","message":"Inbox webhooks now include `contactId` on `conversation` (via `InboxWebhookConversation`).\n\nThis 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.\n\nNew field:\n• `conversation.contactId` - Zernio CRM Contact ID for the participant (omitted when no match or `participantId` is absent)\n\nApplies to:\n• `message.received`\n• `message.sent`\n• `reaction.received`\n• `conversation.started`","platforms":["inbox","webhooks"],"url":"https://docs.zernio.com/changelog#1779980377479"},{"id":"1779976162162","date":"2026-05-28T13:49:22.162Z","type":"improvement","message":"**CTWA ad creation** now supports more precise geo targeting in `POST /v1/ads/ctwa`.\n\nYou can target by city/region/ZIP/metro or point-radius locations (useful for local WhatsApp campaigns) instead of relying only on country targeting.\n\nNew optional geo fields:\n• `cities[]` (with optional `radius` + `distance_unit`: `mile` | `kilometer`)\n• `regions[]`\n• `zips[]`\n• `metros[]`\n• `customLocations[]` (requires `latitude`, `longitude`, `radius`, `distanceUnit`: `mile` | `kilometer`)\n\nNote: `countries` now defaults to `[\"US\"]` only when no other geo targeting (`cities`/`regions`/`zips`/`metros`/`customLocations`) is provided.","platforms":["whatsapp","ads"],"url":"https://docs.zernio.com/changelog#1779976162162"},{"id":"1779964250156","date":"2026-05-28T10:30:50.156Z","type":"new_feature","message":"New webhook event **conversation.started** is available via `WebhookPayloadConversationStarted`.\n\nIt fires once when a new DM conversation begins (first message exchange) and is platform-agnostic across `instagram`, `facebook`, `telegram`, `whatsapp`, `twitter`, `reddit`, `bluesky`.\n\nKey fields:\n• `event`=`conversation.started`\n• `conversation.id`, `conversation.platform`, `conversation.platformConversationId`\n• `conversation.status`: `active` | `archived`\n• `account`\n• `startedAt`, `timestamp`\n• `id` (stable webhook event ID)","platforms":["instagram","x","reddit","bluesky","telegram","whatsapp","inbox","webhooks"],"url":"https://docs.zernio.com/changelog#1779964250156"},{"id":"1779958695423","date":"2026-05-28T08:58:15.423Z","type":"new_feature","message":"POST `/v1/inbox/conversations/{conversationId}/messages` now supports WhatsApp voice messages (PTT) via `voiceNote` in the JSON body.\n\nWhen 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.\n\nKey params:\n• `attachmentType`: `audio`\n• `attachmentUrl`: public URL to the audio file\n• `voiceNote`: `true` (WhatsApp only)\n\nNote: the audio must be `.ogg` encoded with OPUS (mono); other formats will be rejected by WhatsApp.","platforms":["whatsapp","publishing","inbox"],"url":"https://docs.zernio.com/changelog#1779958695423"},{"id":"1779958305348","date":"2026-05-28T08:51:45.348Z","type":"new_feature","message":"New endpoint: `GET /v1/whatsapp/conversions`.\n\nYou 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.\n\nRequired query: `accountId`\nOptional query: `limit` (1–200, default `50`)\n\nResponse includes per-event fields like `timestamp`, `eventName` (`LeadSubmitted`, `Purchase`, `AddToCart`, `InitiateCheckout`, `ViewContent`), `conversationId`, `eventsReceived`, `eventsFailed`, `traceId`, `durationMs`.","platforms":["whatsapp","ads","analytics"],"url":"https://docs.zernio.com/changelog#1779958305348"},{"id":"1779958121672","date":"2026-05-28T08:48:41.672Z","type":"improvement","message":"Inbox webhooks now include a sender `contactId` when the sender matches a Zernio CRM Contact.\n\nThis 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).\n\nNew field:\n• `message.sender.contactId` in `message.received` and `message.sent`\n• `reaction.sender.contactId` in `reaction.received`\n• `InboxWebhookMessage.sender.contactId`","platforms":["inbox","webhooks"],"url":"https://docs.zernio.com/changelog#1779958121672"},{"id":"1779956549144","date":"2026-05-28T08:22:29.144Z","type":"new_feature","message":"WhatsApp now supports managing the Meta **CTWA conversions dataset** via two new endpoints.\n\nUse these to detect whether Conversions API reporting is configured for a WhatsApp account and to provision the required Meta dataset (idempotent).\n\nCheck current dataset: `GET /v1/whatsapp/dataset` with `accountId` (query)\nReturns `datasetId` (nullable).\n\nProvision (or fetch existing) dataset: `POST /v1/whatsapp/dataset` with body `accountId`\nReturns `datasetId` and `created` (boolean).\n\nIf the account token is missing `whatsapp_business_manage_events`, provisioning returns `422` (reconnect required).","platforms":["whatsapp","meta-ads","ads","analytics"],"url":"https://docs.zernio.com/changelog#1779956549144"},{"id":"1779893444536","date":"2026-05-27T14:50:44.536Z","type":"new_feature","message":"New endpoint: `GET /v1/whatsapp/template-library`.\n\nYou 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).\n\nRequired query params:\n• `accountId` (WhatsApp social account ID)\n• `name` (exact library template name)\n\nResponse (`200`): `template` (object or `null`) with key fields like `body_params` and `buttons` where `buttons[].type` can be `QUICK_REPLY`, `URL`, `PHONE_NUMBER`, `OTP`, `FLOW`, ...","platforms":["whatsapp","telephony"],"url":"https://docs.zernio.com/changelog#1779893444536"},{"id":"1779890638485","date":"2026-05-27T14:03:58.485Z","type":"new_feature","message":"WhatsApp Flows now supports listing submitted flow responses via `GET /v1/whatsapp/flow-responses`.\n\nThis lets you fetch customer responses parsed from `nfm_reply` webhook messages (newest first), and optionally scope them to a specific flow.\n\nUse:\n• `accountId` (required)\n• `flowId` (optional)\n• `limit` (optional, max `200`, default `50`)\n\nResponse items include `id`, `receivedAt`, `from`, `senderName`, `conversationId`, `flowToken`, `data`, `raw`.\n\nAlso updated: when sending flows via `POST /v1/whatsapp/flows/send`, if `flow_token` is omitted it is now auto-generated as `&lt;flowId&gt;:&lt;uuid&gt;` so responses can be attributed to that flow in the Flow Responses view.","platforms":["whatsapp","webhooks"],"url":"https://docs.zernio.com/changelog#1779890638485"},{"id":"1779889199933","date":"2026-05-27T13:39:59.933Z","type":"new_feature","message":"WhatsApp Flows now support **flow previews** and **version history**.\n\nYou can fetch Meta’s embeddable web preview URL for a flow (drafts included), and list Zernio-tracked versions for a flow’s clone lineage.\n\nNew endpoints:\n• `GET /v1/whatsapp/flows/{flowId}/preview` with `accountId` and optional `invalidate` (mint a fresh link)\n• `GET /v1/whatsapp/flows/{flowId}/versions` with `accountId`\n\nFlow versioning metadata is now returned:\n• `GET /v1/whatsapp/flows` adds `version` and `lineageId` per flow\n• `POST /v1/whatsapp/flows` response adds `version` and `lineageId`\n\nCloning can now stay in the same lineage:\n• `POST /v1/whatsapp/flows` adds `asVersion` (only when using `cloneFlowId`)","platforms":["whatsapp"],"url":"https://docs.zernio.com/changelog#1779889199933"},{"id":"1779814407456","date":"2026-05-26T16:53:27.456Z","type":"new_feature","message":"Inbox messages now include **platform-specific metadata** on each message via `messages[].metadata`.\n\nThis lets you read extra context like quoted replies and WhatsApp interactive/tap details without relying only on webhooks.\n\nNew field:\n• `messages[].metadata` (free-form object; commonly includes `quotedMessageId`, `waInteractive`, and inbound tap fields like `interactiveType` / `interactiveId`)\n\nWhatsApp multipart sends now support **voice note uploads** via `voiceNote`.\n\nSet `voiceNote` to `true` when uploading an in-browser recorded audio so it’s transcoded to WhatsApp-native ogg/Opus.\n\nNew multipart field:\n• `voiceNote`: `true` (WhatsApp-only)","platforms":["whatsapp","publishing","inbox","webhooks"],"url":"https://docs.zernio.com/changelog#1779814407456"},{"id":"1779814239158","date":"2026-05-26T16:50:39.158Z","type":"new_feature","message":"Inbox messages now include **emoji reactions** on supported platforms (WhatsApp / Telegram) via `reactions` on each message.\n\nThis lets you display who reacted and when, alongside the message timeline.\n\nNew per-message field:\n• `reactions[]`: `{ emoji, fromMe, reactedAt }`\n\nSending messages now supports WhatsApp **location** and **contact cards**.\n\nNew request fields for `POST /v1/inbox/conversations/{conversationId}/messages`:\n• `location`: `{ latitude, longitude, name?, address? }`\n• `contacts[]`: contact card objects (requires `name.formatted_name`; optional `phones[]`, `emails[]`)","platforms":["telegram","whatsapp","inbox"],"url":"https://docs.zernio.com/changelog#1779814239158"},{"id":"1779807306421","date":"2026-05-26T14:55:06.421Z","type":"new_feature","message":"`POST /v1/inbox/conversations` now supports starting conversations on **X/Twitter, Bluesky, Reddit, and WhatsApp** (previously X/Twitter only).\n\nYou can initiate DMs across these platforms using `participantId` or `participantUsername` (platform-dependent).\n\nWhatsApp 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.\n\n`skipDmCheck` remains **X/Twitter only**.","platforms":["x","reddit","bluesky","whatsapp","inbox"],"url":"https://docs.zernio.com/changelog#1779807306421"},{"id":"1779724894114","date":"2026-05-25T16:01:34.114Z","type":"improvement","message":"`PostAnalytics` now includes Instagram Reels watch-time metrics.\n\nYou can retrieve average and total watch time for Reels (values are `0` for non-Reels media and other platforms).\n\nNew fields:\n• `igReelsAvgWatchTime` (ms)\n• `igReelsVideoViewTotalTime` (ms)","platforms":["instagram","analytics"],"url":"https://docs.zernio.com/changelog#1779724894114"},{"id":"1779704331088","date":"2026-05-25T10:18:51.088Z","type":"new_feature","message":"New webhook payload schema added for Meta Lead Gen / Instant Forms: **lead.received** events now use `WebhookPayloadLead`.\n\nThis lets webhook consumers reliably parse incoming leads, including form metadata, ad attribution (when available), and a flattened field map.\n\nKey properties:\n• `event`: `lead.received`\n• `lead.fields`: key → value map (multiple-choice values are option keys, e.g. `k1`)\n• `lead.isOrganic`: boolean\n• `lead.createdAt`: ISO 8601 datetime\n• `account.platform`: `facebook`\n• `lead.formName`, `lead.adId`, `lead.adsetId`, `lead.campaignId`: nullable when not available","platforms":["meta-ads","ads","webhooks"],"url":"https://docs.zernio.com/changelog#1779704331088"},{"id":"1779702352744","date":"2026-05-25T09:45:52.744Z","type":"new_feature","message":"Meta Lead Gen is now supported end-to-end: you can manage Instant Forms and retrieve submitted leads via new Ads endpoints.\n\nNew endpoints:\n• List all leads (cross-form CRM view): `GET /v1/ads/leads` with `formId`, `accountId`, `since`, `limit`, `cursor`\n• Manage forms: `GET /v1/ads/lead-forms`, `POST /v1/ads/lead-forms`, `GET /v1/ads/lead-forms/{formId}`, `DELETE /v1/ads/lead-forms/{formId}`\n• Form leads + testing: `GET /v1/ads/lead-forms/{formId}/leads`, `POST /v1/ads/lead-forms/{formId}/test-leads`\n\nAd 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).","platforms":["meta-ads","ads"],"url":"https://docs.zernio.com/changelog#1779702352744"},{"id":"1779466209251","date":"2026-05-22T16:10:09.251Z","type":"new_feature","message":"New endpoint **POST /v1/ads/targeting/reach-estimate** returns a normalized pre-flight audience reach estimate for a targeting spec before creating a campaign.\n\nSend `accountId` + `spec` (same shape as `TargetingSpec` used by ads create). Optionally pass `optimizationGoal`.\n\nResponse includes `available`, and when available: `lower`, `upper`, optional `daily`, `currency`, and Meta-only `estimateReady`.\n\n**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).\n\n**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? }] }`.\n\n**POST /v1/ads/create** targeting now supports additional fields: `zips`, `metros`, `customLocations`, `behaviors`, `incomeTier` (`top_5` | `top_10` | `top_10_25` | `top_25_50`), `languages`, `savedTargetingId`, `specialAdCategories` (`HOUSING` | `EMPLOYMENT` | `CREDIT` | `ISSUES_ELECTIONS_POLITICS`).\n\nAudiences now support reusable targeting presets: **POST /v1/ads/audiences** accepts `type=saved_targeting` with `spec` (no `adAccountId`), and **GET /v1/ads/audiences** adds `type` filter and returns `spec` when `type` is `saved_targeting`.","platforms":["meta-ads","ads"],"url":"https://docs.zernio.com/changelog#1779466209251"},{"id":"1779454061078","date":"2026-05-22T12:47:41.078Z","type":"improvement","message":"GET `/v1/inbox/comments/{postId}` now returns **(Reddit only)** post metadata alongside comments in a new `post` object.\n\nThis 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.\n\nKey fields in `post`: `id`, `fullname`, `title`, `selftext`, `author`, `subreddit`, `permalink`, `url`, `score`, `numComments`, `createdUtc`, `over18`, `stickied`, `flairText`, `isGallery`.","platforms":["reddit","inbox"],"url":"https://docs.zernio.com/changelog#1779454061078"},{"id":"1779377961244","date":"2026-05-21T15:39:21.244Z","type":"new_feature","message":"Google Business Profile verification is now supported via new **GMB Verifications** endpoints.\n\nYou can fetch verification status/history, discover eligible methods, start a verification (SMS/call/email/postcard), and complete it by submitting the PIN.\n\nNew endpoints:\n• `GET /v1/accounts/{accountId}/gmb-verifications` (optional `locationId`) returns `voiceOfMerchantState.hasVoiceOfMerchant` and `verifications[].state` = `PENDING`/`COMPLETED`/`FAILED`\n• `POST /v1/accounts/{accountId}/gmb-verifications/options` requires `languageCode`; returns `options[].verificationMethod` = `ADDRESS`/`EMAIL`/`PHONE_CALL`/`SMS`/`AUTO`/`VETTED_PARTNER`\n• `POST /v1/accounts/{accountId}/gmb-verifications` starts verification with `method` (+ method-specific fields like `phoneNumber`/`emailAddress`)\n• `POST /v1/accounts/{accountId}/gmb-verifications/{verificationId}/complete` completes with `pin`\n\nNote: service-area businesses may require `context` (service address) when fetching options/starting verification.","platforms":["google-business","telephony"],"url":"https://docs.zernio.com/changelog#1779377961244"},{"id":"1779358335718","date":"2026-05-21T10:12:15.718Z","type":"new_feature","message":"Webhooks now support **message reaction events** via the new `reaction.received` subscription.\n\nSubscribe by including `reaction.received` in `events` when calling `POST /v1/webhooks/settings` or `PUT /v1/webhooks/settings`.\n\nThe `reaction` payload includes `emoji`, `action` (`added` | `removed`), `platformMessageId`, optional `messageId`, `sender`, and `reactedAt`.","platforms":["webhooks"],"url":"https://docs.zernio.com/changelog#1779358335718"},{"id":"1779289260426","date":"2026-05-20T15:01:00.426Z","type":"new_feature","message":"`GET /v1/ads/audiences` now supports fetching custom audiences for **LinkedIn** and **X (Twitter)** ad accounts.\n\nUse the `platform` query param with one of:\n• `facebook`, `instagram`, `googleads`, `tiktok`, `tiktokads`, `pinterest`, `linkedin`, `linkedinads`, `twitter`, `xads`","platforms":["instagram","tiktok","x","linkedin","pinterest","google-ads","tiktok-ads","linkedin-ads","pinterest-ads","x-ads","ads"],"url":"https://docs.zernio.com/changelog#1779289260426"},{"id":"1779123921252","date":"2026-05-18T17:05:21.252Z","type":"improvement","message":"GET `/v1/broadcasts/{broadcastId}/recipients` now returns `errorCode` per recipient.\n\nThis provides the Meta WhatsApp error code to help you classify and handle delivery failures (only populated when `status`=`failed`).\n\nNew field: `errorCode` (integer, nullable) — e.g. `131049` (antispam), `131021` (invalid phone), `131026` (re-engagement required).","platforms":["whatsapp","inbox"],"url":"https://docs.zernio.com/changelog#1779123921252"},{"id":"1779101008126","date":"2026-05-18T10:43:28.126Z","type":"new_feature","message":"Instagram now supports **Stories retrieval + insights**.\n\nYou can list currently-active stories and fetch metrics for a specific story, including a clear indicator of whether insights are live, cached, or unavailable.\n\nNew endpoints:\n• `GET /v1/accounts/{accountId}/instagram/stories`\n  Returns active stories (24h window). Key fields: `id`, `mediaType`, `mediaUrl` (nullable), `permalink`, `thumbnailUrl`, `timestamp`\n• `GET /v1/accounts/{accountId}/instagram/stories/{storyId}/insights`\n  Returns `data.source` = `live` | `cached` | `unavailable` and `data.metrics` including `views`, `reach`, `replies`, `shares`, `navigation`, `tapsForward`, `tapsBack`, `exits`, `swipesForward`, `profileVisits`, `follows`, `reposts`, `totalInteractions`.","platforms":["instagram","analytics"],"url":"https://docs.zernio.com/changelog#1779101008126"},{"id":"1779099287824","date":"2026-05-18T10:14:47.824Z","type":"new_feature","message":"WhatsApp is now supported as a `platform` for connecting accounts and checking account health.\n\nYou can now use `whatsapp` with:\n• `GET /v1/connect/{platform}` (path param `platform` enum now includes `whatsapp`)\n• `GET /v1/accounts/health` (query param `platform` enum now includes `whatsapp`)","platforms":["whatsapp"],"url":"https://docs.zernio.com/changelog#1779099287824"},{"id":"1779004968082","date":"2026-05-17T08:02:48.082Z","type":"improvement","message":"GET `/v1/ads/tree` now supports campaign-level sorting via a new `sort` query parameter.\n\nThis lets you control the order of returned campaigns (by recency or aggregated spend) while keeping pagination at the campaign level.\n\nUse `sort`:\n• `newest` (default)\n• `oldest`\n• `spend_desc`\n• `spend_asc`","platforms":["ads"],"url":"https://docs.zernio.com/changelog#1779004968082"},{"id":"1778870589133","date":"2026-05-15T18:43:09.133Z","type":"new_feature","message":"Facebook now supports **multi-link carousel posts** via `facebookSettings` on `POST /v1/posts`.\n\nThis lets you publish a 2–5 card carousel where each image has its own click-through link (and optional headline/description).\n\nSet `facebookSettings.carouselCards` (2–5 items) with:\n• `link` (required)\n• `name` (optional)\n• `description` (optional)\n\nOptionally set `facebookSettings.carouselLink` for the carousel end-card destination.\n\nConstraints: `mediaItems` must be images only, and must match `carouselCards` length/order. Mutually exclusive with `facebookSettings.contentType` = `story` or `reel`.","platforms":["publishing"],"url":"https://docs.zernio.com/changelog#1778870589133"},{"id":"1778865307231","date":"2026-05-15T17:15:07.231Z","type":"improvement","message":"POST `/v1/posts` now supports request idempotency via `x-request-id`.\n\nIf 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).\n\nUse `x-request-id` (UUID) per logical create call. If you omit it, each request is treated as new.\n\nDuplicate protection still applies separately: identical content to the same `(platform, accountId)` within 24h returns `HTTP 409` with `details.accountId`, `details.platform`, `details.existingPostId`.","platforms":["publishing"],"url":"https://docs.zernio.com/changelog#1778865307231"},{"id":"1778860220175","date":"2026-05-15T15:50:20.175Z","type":"improvement","message":"`{adId}` in `GET /v1/ads/{adId}` and `GET /v1/ads/{adId}/comments` now accepts multiple identifier types.\n\nYou 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 `effective_object_story_id` / `effective_instagram_media_id`. Any of these resolves to the same ad, so you don’t need a translation step.","platforms":["instagram","meta-ads","ads"],"url":"https://docs.zernio.com/changelog#1778860220175"},{"id":"1778858576021","date":"2026-05-15T15:22:56.021Z","type":"new_feature","message":"Webhooks now support per-platform terminal post events: `post.platform.published` and `post.platform.failed`.\n\nThese 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.\n\nSubscribe via `events` on `POST /v1/webhooks/settings` or `PUT /v1/webhooks/settings`:\n• `post.platform.published`\n• `post.platform.failed`\n\nA new webhook event is also available:\n• `whatsapp.template.status_updated`","platforms":["whatsapp","webhooks"],"url":"https://docs.zernio.com/changelog#1778858576021"},{"id":"1778839558435","date":"2026-05-15T10:05:58.435Z","type":"new_feature","message":"`GET /v1/ads/campaigns` now supports filtering aggregated campaign metrics by a date range.\n\nUse `fromDate` and `toDate` (YYYY-MM-DD, inclusive) to control the metrics window returned for each campaign.\n\nNew query params:\n• `fromDate` - start date (defaults to 90 days ago when both date params are omitted)\n• `toDate` - end date (defaults to today; max 730-day range)","platforms":["ads","analytics"],"url":"https://docs.zernio.com/changelog#1778839558435"},{"id":"1778837853975","date":"2026-05-15T09:37:33.975Z","type":"new_feature","message":"Webhooks now support **WhatsApp template status updates** via the new event `whatsapp.template.status_updated`.\n\nThis 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.\n\nSubscribe by adding `whatsapp.template.status_updated` to `Webhook.events`.\n\nPayload: `WebhookPayloadWhatsAppTemplateStatusUpdated` with `template.status` ∈ `APPROVED`, `REJECTED`, `PENDING`, `PAUSED`, `DISABLED`, `IN_APPEAL`, `PENDING_DELETION` and `template.reason` (e.g. `\"NONE\"` on approval).","platforms":["whatsapp","webhooks"],"url":"https://docs.zernio.com/changelog#1778837853975"},{"id":"1778835258086","date":"2026-05-15T08:54:18.086Z","type":"new_feature","message":"Google Business reviews now support owner replies via new endpoints: `POST /v1/accounts/{accountId}/gmb-reviews/{reviewId}/reply` and `DELETE /v1/accounts/{accountId}/gmb-reviews/{reviewId}/reply`.\n\nUse `comment` to post/update a reply (calling POST again overwrites the previous reply). Deleting removes only the reply, not the review.\n\nReply body: `{ \"comment\": \"...\" }`\n\n`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.\n\nNew optional bidding controls on CTWA: `bidStrategy` (`LOWEST_COST_WITHOUT_CAP`, `LOWEST_COST_WITH_BID_CAP`, `COST_CAP`, `LOWEST_COST_WITH_MIN_ROAS`) plus `bidAmount` (required for bid-cap/cost-cap) and `roasAverageFloor` (required for min-ROAS).\n\nCTWA create response is now a tagged union via `adType`: `single` or `multi`.","platforms":["google-business","google-ads","ads"],"url":"https://docs.zernio.com/changelog#1778835258086"},{"id":"1778761581115","date":"2026-05-14T12:26:21.115Z","type":"new_feature","message":"Webhooks now support `ad.status_changed`.\n\nSubscribe to get notified when a Meta ads object (campaign/ad set/ad) changes status or enters `WITH_ISSUES`.\n\nAdd `ad.status_changed` to `events` when creating/updating a webhook via `POST /v1/webhooks/settings` or `PUT /v1/webhooks/settings`.\n\nPayload highlights:\n• `event`: `ad.status_changed`\n• `adObject.level`: `CAMPAIGN` | `AD_SET` | `AD`\n• `status.raw`: platform-native status (e.g. `ACTIVE`, `PAUSED`, `WITH_ISSUES`)\n• `error`: optional diagnostics on most `WITH_ISSUES` events\n\nAlso, `GET /v1/ads/{adId}/analytics` now returns `ad.currency` (ISO 4217) so you can interpret money values in `summary` and `daily`.","platforms":["meta-ads","ads","analytics","webhooks"],"url":"https://docs.zernio.com/changelog#1778761581115"},{"id":"1778683756014","date":"2026-05-13T14:49:16.014Z","type":"improvement","message":"`GET /v1/ads/timeline` now supports filtering by a specific platform ad account via `adAccountId`.\n\nUse 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).\n\nNew query param:\n• `adAccountId` (optional)\n\nNote: rows ingested before `2026-05-13` may not include this dimension yet; the recurring 7-day re-sync will repopulate them over time.","platforms":["tiktok","meta-ads","tiktok-ads","ads","analytics"],"url":"https://docs.zernio.com/changelog#1778683756014"},{"id":"1778682999480","date":"2026-05-13T14:36:39.480Z","type":"new_feature","message":"New endpoint `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.\n\nKey params:\n• `accountId` (required)\n• `fromDate`, `toDate` (YYYY-MM-DD; defaults to last 90 days; max 730 days)\n• `platform` (optional): `facebook`, `instagram`, `tiktok`, `linkedin`, `pinterest`, `google`, `twitter`\n\nResponse: `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.","platforms":["instagram","tiktok","x","linkedin","pinterest","meta-ads","google-ads","tiktok-ads","linkedin-ads","pinterest-ads","x-ads","ads","analytics"],"url":"https://docs.zernio.com/changelog#1778682999480"},{"id":"1778659144027","date":"2026-05-13T07:59:04.027Z","type":"new_feature","message":"New endpoint: `PATCH /v1/accounts/{accountId}` to move a connected account to a different profile owned by the same user.\n\nSend `profileId` in the JSON body to set the target profile.\n\nRequest body:\n• `profileId` (required)\n\nAuth/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`.\n\nResponses: `200` returns `message` and `profileId`; errors include `400`, `401`, `403`, `404`.","platforms":["general"],"url":"https://docs.zernio.com/changelog#1778659144027"},{"id":"1778598504777","date":"2026-05-12T15:08:24.777Z","type":"improvement","message":"TikTok **conversion** ads via `POST /v1/ads/create` now support/require TikTok pixel optimization settings through `promotedObject`.\n\nFor TikTok, `goal=conversions` uses `promotedObject` to configure the ad group’s pixel + optimization event (or inherit them by using `adSetId`).\n\nSet:\n• `goal`: `conversions`\n• `promotedObject.pixelId` (required for TikTok conversions)\n• `promotedObject.customEventType` (optional TikTok `optimization_event` code, e.g. `ON_WEB_ORDER`, `INITIATE_ORDER`, `ON_WEB_CART`, `ON_WEB_REGISTER`, `FORM`, `ON_WEB_DETAIL`)","platforms":["tiktok","tiktok-ads","ads"],"url":"https://docs.zernio.com/changelog#1778598504777"},{"id":"1778596889727","date":"2026-05-12T14:41:29.727Z","type":"new_feature","message":"POST `/v1/ads/create` now supports creating **LinkedIn** standalone ads.\n\nThis 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`).\n\nKey request fields for LinkedIn:\n• `organizationId` (Company Page org ID or `urn:li:organization:N`; required unless it can be inferred)\n• `headline` (now required for LinkedIn; max 400)\n• `body` (used as LinkedIn post commentary)\n• `imageUrl` (required for LinkedIn)\n• `linkUrl` (required for `goal=traffic`)\n• `longHeadline` (optional secondary description on LinkedIn traffic ads)\n\nAlso updated:\n• `callToAction` enum expanded with `REGISTER`, `JOIN`, `ATTEND`, `REQUEST_DEMO`, `VIEW_QUOTE`, `APPLY`, `SEE_MORE`, `BUY_NOW`\n• Meta-only: new `attributionSpec` to control conversion attribution windows (`CLICK_THROUGH`/`VIEW_THROUGH`/`ENGAGED_VIDEO_VIEW` with `windowDays` 1/7/28).","platforms":["linkedin","meta-ads","linkedin-ads","ads"],"url":"https://docs.zernio.com/changelog#1778596889727"},{"id":"1778579261371","date":"2026-05-12T09:47:41.371Z","type":"new_feature","message":"**Meta ads (boosted/dark posts) are now included** in `GET /v1/inbox/comments` for users with the Ads add-on (Metronome plans always qualify).\n\nAd rows are flagged so you can route comment fetching to the ads comments thread.\n\nUse `platform`:\n• `metaads` - ads only\n• `facebook`/`instagram` - organic only\n• omit `platform` - both\n\nNew fields on inbox rows: `isAd` (boolean), `adId` (string). For ad rows, fetch the thread via `GET /v1/ads/{adId}/comments`.\n\n`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 `USE_AD_COMMENTS_ENDPOINT` when `postId` is an ad creative/ad ID.\n\n`GET /v1/ads` adds filters to map Business Manager IDs back to Zernio ads: `platformAdId`, `effectiveObjectStoryId`, `effectiveInstagramMediaId`.","platforms":["instagram","meta-ads","ads","inbox"],"url":"https://docs.zernio.com/changelog#1778579261371"},{"id":"1778575627726","date":"2026-05-12T08:47:07.726Z","type":"improvement","message":"GET `/v1/ads/{adId}/comments` now returns additional Instagram metadata in `meta` for Instagram-placed ads.\n\nThis 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`).\n\nNew `meta` fields (Instagram-only):\n• `instagramUserId`\n• `instagramPermalink`\n• `instagramAccountId`\n\nAlso 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 `ads_connection_required`.","platforms":["instagram","meta-ads","ads","inbox"],"url":"https://docs.zernio.com/changelog#1778575627726"},{"id":"1778516748591","date":"2026-05-11T16:25:48.591Z","type":"new_feature","message":"Meta Ads now supports managing **Tracking Tags (Meta Pixels)** via new endpoints.\n\nYou can list/create/get/update pixels, share/unshare them across ad accounts, and fetch aggregated event stats.\n\nNew endpoints:\n• `GET /v1/accounts/{accountId}/tracking-tags` (optional `adAccountId=act_...`)\n• `POST /v1/accounts/{accountId}/tracking-tags` body: `adAccountId`, `name` (not idempotent)\n• `GET /v1/accounts/{accountId}/tracking-tags/{tagId}` (includes install `code`)\n• `PATCH /v1/accounts/{accountId}/tracking-tags/{tagId}` fields: `name`, `enableAutomaticMatching`, `automaticMatchingFields` (`em`/`ph`/`fn`/`ln`/`ge`/`db`/`ct`/`st`/`zp`/`country`/`external_id`), `firstPartyCookieStatus` (`empty`/`first_party_cookie_disabled`/`first_party_cookie_enabled`), `dataUseSetting` (`advertising_and_analytics`/`analytics_only`/`empty`)\n• `GET|POST|DELETE /v1/accounts/{accountId}/tracking-tags/{tagId}/shared-accounts` (DELETE supports `adAccountId` query)\n• `GET /v1/accounts/{accountId}/tracking-tags/{tagId}/stats` (optional `aggregation`, `startTime`, `endTime`; `aggregation` enum includes `event`, `url`, `device_type`, `browser_type`, etc.)\n\nNotes: 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).","platforms":["meta-ads","ads","analytics"],"url":"https://docs.zernio.com/changelog#1778516748591"},{"id":"1778484275756","date":"2026-05-11T07:24:35.756Z","type":"new_feature","message":"Comment-to-DM automations now support **inline DM buttons** via `buttons`.\n\nYou can attach up to 3 buttons to the auto-DM (URL, postback, or phone on Facebook) to drive clicks or capture postbacks.\n\nUse `buttons` in:\n• `POST /v1/comment-automations`\n• `PATCH /v1/comment-automations/{automationId}` (pass `buttons: []` to clear)\n\nButton schema: `{ type, title, url|payload|phone }` where `type` is `url` | `postback` | `phone` (phone = Facebook-only), and `title` is max 20 chars.\n\nNote: 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).","platforms":["facebook","inbox"],"url":"https://docs.zernio.com/changelog#1778484275756"},{"id":"1778411402753","date":"2026-05-10T11:10:02.753Z","type":"deprecation","message":"X API pricing/usage reporting now includes a new **$0.200 “posts with URL”** operation and tier.\n\nThis lets you attribute X spend more accurately per action; new clients should rely on per-operation counts rather than tier aggregates.\n\nKey changes:\n• New operation in `GET /v1/billing/x-pricing`: `operation=content_create_with_url` with `tier=x_api_200` and `pricePerCallUsd=0.2`\n• `content_create` price moved to `pricePerCallUsd=0.015` (now `tier=x_api_015`)\n• In `GET /v1/usage-stats`, `usage.xApiCalls` is now **deprecated** and excludes `x_api_200` (and future tiers); use `usage.xApiCallsByOperation` as the source of truth","platforms":["x","publishing","analytics"],"url":"https://docs.zernio.com/changelog#1778411402753"},{"id":"1778408721767","date":"2026-05-10T10:25:21.767Z","type":"improvement","message":"**WhatsApp** is now supported on `POST /v1/inbox/conversations/{conversationId}/typing`.\n\nOn 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.\n\nUse: `{ \"accountId\": \"...\" }`","platforms":["whatsapp","inbox"],"url":"https://docs.zernio.com/changelog#1778408721767"},{"id":"1778261020572","date":"2026-05-08T17:23:40.572Z","type":"new_feature","message":"Message webhooks now include expanded Meta ad-click attribution in `metadata.referral` (in addition to WhatsApp CTWA).\n\nThis lets you attribute the first inbound message to a specific Facebook/Instagram ad click for CTM/CTD flows.\n\nNew fields in `metadata.referral`:\n• `ad_id`\n• `ref`\n• `source`\n• `type`\n• `ads_context_data` (object: `ad_title`, `photo_url`, `video_url`, `post_id`, `product_id`, `flow_id`)\n\n`metadata.referral` is now `nullable` and is only populated on the first inbound message after the click.","platforms":["instagram","whatsapp","meta-ads","ads","inbox","analytics","webhooks"],"url":"https://docs.zernio.com/changelog#1778261020572"},{"id":"1778250600000","date":"2026-05-08T15:30:00.000Z","type":"new_feature","message":"**LinkedIn Ads now plugs into Zernio's unified Conversions API.** Stream offline conversion events to LinkedIn's `/rest/conversionEvents` through the same `/v1/ads/conversions` endpoint you already use for Meta and Google.\n\nNew capabilities for `linkedinads` accounts:\n• `POST /v1/ads/conversions` — stream events (BATCH_CREATE, up to 5,000 per call)\n• `GET /v1/accounts/{accountId}/conversion-destinations` — list rules across every sponsored ad account\n• `POST /v1/accounts/{accountId}/conversion-destinations` — create a new rule (with `autoAssociationType=ALL_CAMPAIGNS` by default)\n• `GET|PATCH|DELETE /v1/accounts/{accountId}/conversion-destinations/{destinationId}` — single-rule CRUD (DELETE soft-deletes via `enabled: false`)\n• `GET|POST|DELETE .../associations` — campaign↔conversion mapping\n• `GET .../metrics?startDate=&endDate=&granularity=` — `externalWebsiteConversions`, `conversionValueInLocalCurrency`, `qualifiedLeads`, etc., bucketed by date\n\nSupports the LinkedIn first-party click ID `li_fat_id` (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.\n\n**Reconnect required for LinkedIn accounts connected before this change** — LinkedIn does not silently upgrade existing OAuth grants with the new `rw_conversions` scope. Affected accounts return `403 linkedin_reconnect_required` until the user re-authorises.","platforms":["linkedin","linkedin-ads","ads","analytics"],"url":"https://docs.zernio.com/changelog#1778250600000"},{"id":"1778245675490","date":"2026-05-08T13:07:55.490Z","type":"new_feature","message":"Webhooks now support additional **message** events you can subscribe to when creating/updating webhook settings.\n\nThis lets you receive callbacks for outbound message lifecycle changes (send/edit/delete/delivery/read/failure), not just inbound messages.\n\nUse `events` in `POST /v1/webhooks/settings` or `PUT /v1/webhooks/settings` with any of:\n• `message.sent`, `message.edited`, `message.deleted`, `message.delivered`, `message.read`, `message.failed`\n\nAlso added to `WebhookPayloadMessage.metadata` (Instagram only): `storyReply` and `isStoryMention`.","platforms":["instagram","inbox","webhooks"],"url":"https://docs.zernio.com/changelog#1778245675490"},{"id":"1778074373032","date":"2026-05-06T13:32:53.032Z","type":"improvement","message":"Ads analytics endpoints now support up to a **730-day** metrics date range (previously 90 days).\n\nYou 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.\n\nApplies to:\n• `GET /v1/ads` (max range `730` days)\n• `GET /v1/ads/tree` (max range `730` days)\n• `GET /v1/ads/{adId}/analytics` (max range `730` days)\n\nUse `fromDate` / `toDate`; ranges older than 90 days may require scoping to `accountId` (where noted) to trigger the on-demand fetch.","platforms":["ads","analytics"],"url":"https://docs.zernio.com/changelog#1778074373032"},{"id":"1777965220561","date":"2026-05-05T07:13:40.561Z","type":"new_feature","message":"POST `/v1/inbox/comments/{postId}/{commentId}/private-reply` now supports inline **buttons** on the first private reply.\n\nYou 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.\n\nNew request field: `buttons` (min 1, max 3), mutually exclusive with `quickReplies`.\nButton types:\n• `url`: `{ type, title, url }`\n• `postback`: `{ type, title, payload }`\n• `phone` (Facebook only): `{ type, title, phone }`","platforms":["instagram","facebook","inbox"],"url":"https://docs.zernio.com/changelog#1777965220561"},{"id":"1777923951823","date":"2026-05-04T19:45:51.823Z","type":"new_feature","message":"WhatsApp headless OAuth now supports **multi-phone WABA selection** via a new two-step flow.\n\nWhen the OAuth redirect includes `step=select_phone_number`, first list available numbers with `GET /v1/connect/whatsapp/select-phone-number` using `profileId` + `tempToken` (optional `X-Connect-Token`).\n\nThen bind the chosen number with `POST /v1/connect/whatsapp/select-phone-number`:\n• `profileId`\n• `tempToken`\n• `wabaId`\n• `phoneNumberId`\n\nNote: `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.","platforms":["linkedin","snapchat","whatsapp","linkedin-ads","telephony"],"url":"https://docs.zernio.com/changelog#1777923951823"},{"id":"1777901996701","date":"2026-05-04T13:39:56.701Z","type":"new_feature","message":"New endpoint `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.\n\n`GET /v1/usage-stats` now varies by `billingSystem`: `stripe` | `metronome`.\nFor `metronome` accounts it includes:\n• `usage.connectedAccounts`\n• `usage.xApiCalls` (tier buckets: `x_api_005` | `x_api_010` | `x_api_015`)\n• `usage.xApiCallsByOperation` (keys resolve via `GET /v1/billing/x-pricing`)\n• `spend.currentPeriodCents`, `spend.xSpendCents`, `spend.xSpendLimitCents`\n\n`PUT /v1/accounts/{accountId}` (X only, usage-based) accepts `xCapabilities` to opt into background metered operations:\n• `xCapabilities.analytics`\n• `xCapabilities.inbox`\n\nAlso: `POST /v1/profiles` and `GET /v1/connect/{platform}` may now return `402 PaymentRequired`.","platforms":["x","inbox","analytics"],"url":"https://docs.zernio.com/changelog#1777901996701"},{"id":"1777879516845","date":"2026-05-04T07:25:16.845Z","type":"new_feature","message":"POST `/v1/inbox/comments/{postId}/{commentId}/private-reply` now supports optional **quick reply chips** on the first private reply.\n\nYou can include up to 13 chips to let users pick an intent in-app (Instagram/Messenger apps only).\n\nNew optional field: `quickReplies` (max 13)\n• `quickReplies[].title` (max 20 chars)\n• `quickReplies[].payload`\n• `quickReplies[].imageUrl` (optional)","platforms":["instagram","facebook","inbox"],"url":"https://docs.zernio.com/changelog#1777879516845"},{"id":"1777647538852","date":"2026-05-01T14:58:58.852Z","type":"improvement","message":"Account groups can now include accounts across multiple profiles. For API keys scoped to specific profiles, `GET /v1/account-groups` only returns groups whose `accountIds` are all in allowed profiles.\n\n`GET /v1/account-groups` may now include `createdBy` and legacy `profileId` (only on older groups).\n\n`POST /v1/account-groups` accepts deprecated `profileId` (ignored) for backward compatibility.\n\nMeta ads connect can now be scoped to specific ad accounts via `GET /v1/connect/{platform}/ads` (metaads only): `adAccountId` (format `act_&lt;digits&gt;`) or `adAccountIds` (repeat or comma-separated). Response may include `scopedAdAccountIds`.\n\nWebhook `account.ads.initial_sync_completed` now includes richer correlation + error details:\n• `account.platformAdAccountId`, `account.platformAdAccountIds`\n• `sync.error`, `sync.errorCode`, `sync.errorSubcode`, `sync.errorCategory` = `token_invalid` | `permission_denied` | `no_ad_accounts` | `rate_limited` | `discovery_failed` | `unknown`","platforms":["meta-ads","ads","webhooks"],"url":"https://docs.zernio.com/changelog#1777647538852"},{"id":"1777645718661","date":"2026-05-01T14:28:38.661Z","type":"breaking_change","message":"`POST /v1/account-groups` no longer requires `profileId` in the request body.\n\nYou can now create an account group using only a name and the list of social account IDs.\n\nRequest body now requires:\n• `name`\n• `accountIds`","platforms":["general"],"url":"https://docs.zernio.com/changelog#1777645718661"},{"id":"1777624327413","date":"2026-05-01T08:32:07.413Z","type":"new_feature","message":"`POST /v1/comment-automations` now supports **account-wide (any post)** comment-to-DM automations.\n\nYou 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.\n\nKey request rules:\n• Per-post: set `platformPostId` and include `postId`\n• Account-wide: omit `platformPostId` (and `postId`)\n• `matchMode`: `exact` | `contains`\n\nNote: `409` now applies only to per-post automations (duplicate active `platformPostId`).","platforms":["inbox"],"url":"https://docs.zernio.com/changelog#1777624327413"},{"id":"1777553948287","date":"2026-04-30T12:59:08.287Z","type":"new_feature","message":"New Meta geo targeting lookup endpoint: `GET /v1/ads/targeting/search`.\n\nIt lets you resolve a human-readable location name into Meta’s opaque location `key` for use in ad targeting.\n\nCall with `accountId`, `q`, optional `type` (`country`/`region`/`city`/`subcity`/`neighborhood`/`zip`/`metro_area`/`geo_market`, default `city`), optional `countryCode`, and optional `limit`.\n\n`POST /v1/ads/create` now supports Meta-only geo targeting via `cities[]` and `regions[]`:\n• `cities[]`: `{ key, radius?, distance_unit? }` where `distance_unit` is `mile` or `kilometer`\n• `regions[]`: `{ key }`\n\n`countries[]` is now documented as ISO-2 codes and defaults to `[\"US\"]` when no `cities`/`regions` are provided.","platforms":["meta-ads","ads"],"url":"https://docs.zernio.com/changelog#1777553948287"},{"id":"1777547282111","date":"2026-04-30T11:08:02.111Z","type":"improvement","message":"TikTok ads created via `POST /v1/ads/create` now honor `callToAction` (it’s passed through to the Spark Ad creative’s `call_to_action`).\n\nThis lets you set a CTA on TikTok using the same field you already use for Meta.\n\nSet `callToAction` to one of:\n• `LEARN_MORE`, `SHOP_NOW`, `SIGN_UP`, `BOOK_TRAVEL`, `CONTACT_US`, `DOWNLOAD`, `GET_OFFER`, `GET_QUOTE`, `SUBSCRIBE`, `WATCH_MORE`","platforms":["tiktok","tiktok-ads","ads"],"url":"https://docs.zernio.com/changelog#1777547282111"},{"id":"1777545313493","date":"2026-04-30T10:35:13.493Z","type":"breaking_change","message":"TikTok boost ads now surface clearer requirements and failure modes in `POST /v1/ads/boost`.\n\nFor TikTok boosts, include `targeting.countries` (ISO country codes). TikTok requires locations on the ad group; other platforms can omit it.\n\nThe `422` response can now also indicate the connected TikTok user isn’t authorized as an Identity on the target advertiser (returned with code `ads_connection_required` and an actionable remediation message).\n\nAlso note: `BusinessCenter.advertiserCount` is now nullable (`null` when the BC asset walk is empty/failed, distinct from `0`).","platforms":["tiktok","tiktok-ads","ads"],"url":"https://docs.zernio.com/changelog#1777545313493"},{"id":"1777533395586","date":"2026-04-30T07:16:35.586Z","type":"improvement","message":"Analytics endpoints now support filtering by social account via `accountId`.\n\nThis lets you scope results to a specific connected account (useful when a `profileId` contains multiple accounts).\n\nAdded optional query param `accountId` on:\n• `GET /v1/analytics/best-time`\n• `GET /v1/analytics/content-decay`\n• `GET /v1/analytics/posting-frequency`","platforms":["analytics"],"url":"https://docs.zernio.com/changelog#1777533395586"},{"id":"1777481803389","date":"2026-04-29T16:56:43.389Z","type":"new_feature","message":"New Ads endpoints and expanded TikTok support.\n\nYou can now list TikTok Business Centers.\n\n• List TikTok BCs: `GET /v1/ads/business-centers` with `accountId` → returns `businessCenters[]` (`bcId`, `name`, `advertiserCount`)\n\nTikTok campaign duplication is now supported via `POST /v1/ads/campaigns/{campaignId}/duplicate` by setting `platform` to `tiktok` (Meta still supported).\n\nAd updates now support more platforms/fields:\n• `PUT /v1/ads/{adId}` supports `targeting` + `creative` on `facebook`/`instagram`/`tiktok`. Other platforms return `501` (`unsupported_platform_operation`) when sending `targeting` or `creative`.\n\nAd account listing now supports filtering/limiting:\n• `GET /v1/ads/accounts` adds optional `adAccountId` and `limit`\n\nTikTok boost now supports cross-creator Spark Ads:\n• `POST /v1/ads/boost` adds `sparkAuthCode`\n\nStandalone ad creation “attach” mode now supports TikTok:\n• `POST /v1/ads/create` `adSetId` is now supported for `tiktok` (ad group ID)\n\nNew response fields:\n• `Ad` adds `platformAdAccountName` and `platformCreatedAt`\n• `AdCampaign` and `AdTreeCampaign` add `platformAdAccountName`","platforms":["instagram","tiktok","meta-ads","tiktok-ads","ads"],"url":"https://docs.zernio.com/changelog#1777481803389"},{"id":"1777474669565","date":"2026-04-29T14:57:49.565Z","type":"new_feature","message":"Ads API now supports setting **bid strategy** on campaigns, ad sets, and boosted posts.\n\nYou 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.\n\nSet `bidStrategy` to:\n• `LOWEST_COST_WITHOUT_CAP`\n• `LOWEST_COST_WITH_BID_CAP` (requires `bidAmount`)\n• `COST_CAP` (requires `bidAmount`)\n• `LOWEST_COST_WITH_MIN_ROAS` (requires `roasAverageFloor`)\n\nBoosting posts via `POST /v1/ads/boost` now accepts `bidStrategy`, `bidAmount`, and `roasAverageFloor`. TikTok boosts also add `linkUrl` and `callToAction`.\n\nAd read models now include `bidStrategy` (normalized for Meta + TikTok) and may include `bidAmount` / `roasAverageFloor` on `Ad`, `AdCampaign`, and `AdTree*` responses.\n\nGBP 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.","platforms":["tiktok","google-business","meta-ads","tiktok-ads","ads"],"url":"https://docs.zernio.com/changelog#1777474669565"},{"id":"1777458205683","date":"2026-04-29T10:23:25.683Z","type":"new_feature","message":"GET `/v1/accounts/{accountId}/gmb-location-details` now returns a new **location** summary object in the 200 response.\n\nThis 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.\n\nNew fields under `location`:\n• `name`\n• `placeId`\n• `reviewUrl`\n• `mapsUri`\n• `isVerified`\n\nPopulated when `readMask` includes `metadata` (default). For unverified/new locations, `placeId`/`reviewUrl`/`mapsUri` may be `null`.","platforms":["google-business"],"url":"https://docs.zernio.com/changelog#1777458205683"},{"id":"1777384319037","date":"2026-04-28T13:51:59.037Z","type":"improvement","message":"GET `/v1/ads/accounts` now returns Meta ad account timezone details: `timezoneName` and `timezoneOffsetHoursUtc`.\n\nUse these fields to align daily budget reset timing and Insights day boundaries with the ad account’s local timezone.\n\nNew fields in each item of `accounts[]`:\n• `timezoneName` (IANA timezone, Meta only)\n• `timezoneOffsetHoursUtc` (signed UTC offset in hours, reflects current DST, Meta only)","platforms":["meta-ads","ads","analytics"],"url":"https://docs.zernio.com/changelog#1777384319037"},{"id":"1777367337029","date":"2026-04-28T09:08:57.029Z","type":"improvement","message":"Meta ads now support DSA (EU) advertiser disclosures via two new optional fields on ad creation endpoints.\n\nWhen your targeting intersects EU member states, Meta may require these values (enforced server-side) to comply with DSA Article 26.\n\nSet:\n• `dsaBeneficiary` — legal entity benefiting from the ad\n• `dsaPayor` — legal entity paying for the ad (Meta spelling: `dsa_payor`)\n\nAvailable on:\n• `POST /v1/ads/boost`\n• `POST /v1/ads/create`\n• `POST /v1/ads/ctwa`","platforms":["meta-ads","ads"],"url":"https://docs.zernio.com/changelog#1777367337029"},{"id":"1777317929865","date":"2026-04-27T19:25:29.865Z","type":"improvement","message":"GET `/v1/inbox/conversations/{conversationId}/messages` now supports **cursor-based pagination** and **sort order control**.\n\nYou can page through long conversations using an opaque cursor, and request oldest-first or newest-first results (with platform-specific limitations).\n\nNew query params:\n• `limit` (1–100, default 100)\n• `cursor` (pass prior `pagination.nextCursor`)\n• `sortOrder`: `asc` | `desc` (default `asc`)\n\nNew response fields:\n• `pagination.hasMore`\n• `pagination.nextCursor`\n• `sortOrderApplied`: `asc` | `desc`\n\nAlso: `WebhookPayloadMessage.metadata` adds `referral` (WhatsApp only) for Click-to-WhatsApp ad attribution on the first inbound message after a CTWA ad click (e.g. `ctwa_clid`, `source_url`, `image_url`/`video_url`).","platforms":["whatsapp","ads","inbox","webhooks"],"url":"https://docs.zernio.com/changelog#1777317929865"},{"id":"1777301907329","date":"2026-04-27T14:58:27.329Z","type":"new_feature","message":"POST `/v1/ads/create` now supports Meta-only gender targeting via `gender`.\n\nUse this to restrict the audience by gender when creating Meta (facebook/instagram) ads; non-Meta platforms ignore it.\n\nSet `gender` to: • `all` (default) • `male` • `female`","platforms":["instagram","meta-ads","ads"],"url":"https://docs.zernio.com/changelog#1777301907329"},{"id":"1777284702878","date":"2026-04-27T10:11:42.878Z","type":"new_feature","message":"New endpoints added for Click-to-WhatsApp ads and WhatsApp conversion attribution.\n\nCreate CTWA ads on Meta in one call via `POST /v1/ads/ctwa` (creates campaign → ad set → creative → ad).\nKey fields: `accountId`, `adAccountId`, `name`, `headline`, `body`, `budgetAmount`, `budgetType` (`daily` | `lifetime`), and exactly one of `imageUrl` or `video` (`url`, `thumbnailUrl`). Optional: `objective` (`OUTCOME_ENGAGEMENT` | `OUTCOME_SALES` | `OUTCOME_LEADS`).\n\nSend WhatsApp conversation conversion events to Meta CAPI (business messaging) via `POST /v1/whatsapp/conversions`.\nKey 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.","platforms":["whatsapp","meta-ads","ads","telephony"],"url":"https://docs.zernio.com/changelog#1777284702878"},{"id":"1777282643490","date":"2026-04-27T09:37:23.490Z","type":"improvement","message":"GET `/v1/posts` now supports filtering by social account via the optional query param `accountId`.\n\nUse it to return only posts published via a specific connected account (useful when multiple accounts exist per platform/profile).\n\nNew parameter:\n• `accountId` (string, 24-char hex ObjectId) — filter posts to those published via a specific social account","platforms":["general"],"url":"https://docs.zernio.com/changelog#1777282643490"},{"id":"1777281548230","date":"2026-04-27T09:19:08.230Z","type":"new_feature","message":"Webhooks now support the new event `account.ads.initial_sync_completed`.\n\nThis 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.\n\nSubscribe via `events` on:\n• `POST /v1/webhooks/settings`\n• `PUT /v1/webhooks/settings`\n\nEvent: `account.ads.initial_sync_completed`\nPayload highlights: `account.accountId`, `account.profileId`, `account.platform`, `sync.status` (`success` | `failure`), `sync.totalAds`, `sync.synced`, `sync.failed`, `timestamp`.","platforms":["ads","webhooks"],"url":"https://docs.zernio.com/changelog#1777281548230"},{"id":"1777217755807","date":"2026-04-26T15:35:55.807Z","type":"improvement","message":"Comment automation logs now include separate results for the DM and the optional public comment reply in `GET /v1/comment-automations/{automationId}` and `GET /v1/comment-automations/{automationId}/logs`.\n\nYou can now track whether the DM was sent and whether the public reply was attempted/sent, with separate error messages.\n\nNew log fields:\n• `status` (`sent` | `failed` | `skipped`) — DM outcome\n• `error` — DM error if `status` is `failed`\n• `commentReplyStatus` (`sent` | `failed` | `skipped`) — public reply outcome\n• `commentReplyError` — public-reply error if `commentReplyStatus` is `failed`","platforms":["inbox"],"url":"https://docs.zernio.com/changelog#1777217755807"},{"id":"1777024619867","date":"2026-04-24T09:56:59.867Z","type":"new_feature","message":"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 `/v1/analytics/instagram/account-insights` for consistent client handling.\n\nYouTube channel totals (no per-video looping): `GET /v1/analytics/youtube/channel-insights`\nKey params: `accountId`, `metrics`, `since`, `until`, `metricType` (`total_value`|`time_series`)\nNotes: requires `yt-analytics.readonly` (412 if missing) + Analytics add-on; data is delayed 2–3 days (range clamped).\n\nLinkedIn organization page aggregate analytics: `GET /v1/analytics/linkedin/org-aggregate-analytics`\nKey params: `accountId`, `metrics`, `since`, `until`, `metricType` (`total_value`|`time_series`)\nNotes: requires scopes `r_organization_social` + `r_organization_followers` + `r_organization_admin` (412 if missing) + Analytics add-on; page-view metrics are `total_value` only.\n\nTikTok account-level insights: `GET /v1/analytics/tiktok/account-insights`\nKey params: `accountId`, `metrics`, `since`, `until`, `metricType` (`total_value`|`time_series`)\nNotes: requires `user.info.stats` (412 if missing) + Analytics add-on.\n\nFacebook Page insights (post-Nov-2025 Meta metric names): `GET /v1/analytics/facebook/page-insights`\nKey params: `accountId`, `metrics`, `since`, `until`, `metricType` (`total_value`|`time_series`)\nNotes: deprecated Meta metrics (`page_impressions`, `page_fans`, `page_fan_adds`, `page_fan_removes`) are rejected; use `page_media_view`, `page_follows`, etc. `followers_gained`/`followers_lost` are synthesized.\n\nInstagram follower history (daily follower count time series): `GET /v1/analytics/instagram/follower-history`\nKey params: `accountId`, `metrics` (default `follower_count,followers_gained,followers_lost`), `since`, `until`, `metricType` (`total_value`|`time_series`)\n\nAlso added for LinkedIn personal analytics:\n• `GET /v1/accounts/{accountId}/linkedin-aggregate-analytics` now supports `POST_SAVE` and `POST_SEND` in `metrics`\n• `GET /v1/accounts/{accountId}/linkedin-post-analytics` now returns `saves` and `sends` (personal only; org returns 0)","platforms":["instagram","facebook","tiktok","linkedin","youtube","analytics"],"url":"https://docs.zernio.com/changelog#1777024619867"},{"id":"1776961110922","date":"2026-04-23T16:18:30.925Z","type":"new_feature","message":"LinkedIn post analytics now include `saves` and `sends`.\n\nLinkedIn added POST_SAVE and POST_SEND to `memberCreatorPostAnalytics` in their 202604 API version. Both fields are now returned on:\n• `GET /v1/accounts/{accountId}/linkedin-post-analytics`\n• `GET /v1/accounts/{accountId}/linkedin-aggregate-analytics` (pass `POST_SAVE` and/or `POST_SEND` via the `metrics` query param, or omit `metrics` to get them automatically)\n\nScope: 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 `r_member_postAnalytics` scope already covers it.","platforms":["linkedin","analytics"],"url":"https://docs.zernio.com/changelog#1776961110922"},{"id":"1776848188375","date":"2026-04-22T08:56:28.375Z","type":"breaking_change","message":"`POST /v1/ads/create` now supports 3 mutually-exclusive request shapes (and has updated required fields).\n\nYou can now (Meta-only) create multiple ads in one call via `creatives[]`, or attach a new ad to an existing ad set via `adSetId`.\n\nKey request options:\n• Legacy single-creative: use top-level `goal`, `budgetAmount`, `budgetType`, `headline`, `body`, `imageUrl`, `linkUrl`, `callToAction`\n• Multi-creative (Meta only): set `creatives[]` (min 1). Each item requires `headline`, `body`, `imageUrl`, `linkUrl`, `callToAction`\n• Attach (Meta only): set `adSetId` + a single creative at top-level; `goal`/`budgetAmount`/`budgetType` are inherited\n\n`creatives[]` and `adSetId` are mutually exclusive (400 if both). Non-Meta platforms with `creatives[]`/`adSetId` return 400.\n\nResponse change: 201 may return either `ad` (legacy/attach) or `ads[]` + `platformCampaignId` + `platformAdSetId` (multi-creative).","platforms":["meta-ads","ads"],"url":"https://docs.zernio.com/changelog#1776848188375"},{"id":"1776845972038","date":"2026-04-22T08:19:32.038Z","type":"improvement","message":"Ad metrics now include revenue and ROAS fields for Meta insights.\n\nYou can read monetary action totals via `actionValues` (mirrors `actions`, values in ad-account currency), plus convenience rollups:\n• `purchaseValue` — summed purchase-type action values\n• `roas` — `purchaseValue / spend`\n\nMeta-only; other platforms return `{}` for `actionValues` and `0` for `purchaseValue`/`roas`.","platforms":["meta-ads","ads","analytics"],"url":"https://docs.zernio.com/changelog#1776845972038"},{"id":"1776788749523","date":"2026-04-21T16:25:49.523Z","type":"new_feature","message":"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.\n\nCampaign budget updates (CBO): `PUT /v1/ads/campaigns/{campaignId}`\n• Body: `platform` (`facebook`|`instagram`), `budget.amount`, `budget.type` (`daily`|`lifetime`)\n• If the campaign is ABO, returns `409` (`BUDGET_LEVEL_MISMATCH`) — update the ad set instead\n\nAd set updates (ABO budget and/or status): `PUT /v1/ads/ad-sets/{adSetId}`\n• Body: `platform`, optional `budget`, optional `status` (`active`|`paused`)\n• If parent campaign is CBO and you try to update budget here, returns `409` (`BUDGET_LEVEL_MISMATCH`)\n\nConvenience ad set status toggle: `PUT /v1/ads/ad-sets/{adSetId}/status`\n• Body: `platform`, `status` (`active`|`paused`)\n\nBulk pause/resume campaigns (up to 50): `POST /v1/ads/campaigns/bulk-status`\n• Body: `status` (`active`|`paused`), `campaigns[]` with `platformCampaignId` + `platform`\n• Returns per-campaign results so one failure doesn’t fail the whole batch\n\nDuplicate a campaign (Meta async copy + optional discovery): `POST /v1/ads/campaigns/{campaignId}/duplicate`\n• Key options: `deepCopy` (default true), `statusOption` (`ACTIVE`|`PAUSED`|`INHERITED_FROM_SOURCE`), `syncAfter` (default true)\n\nDelete a campaign (cascades to ad sets/ads): `DELETE /v1/ads/campaigns/{campaignId}`\n• Body: `platform` (`facebook`|`instagram`)\n\nAlso added to campaign responses (`GET /v1/ads/campaigns` and `GET /v1/ads/tree`):\n• `reviewStatus` (`in_review`|`approved`|`rejected`|`with_issues`)\n• `budgetLevel` (`campaign`|`adset`), `campaignBudget`, and `currency` to disambiguate CBO vs ABO budgets\n\nMeta-only for now where noted; other platforms may return `501` for unsupported operations.","platforms":["instagram","meta-ads","ads"],"url":"https://docs.zernio.com/changelog#1776788749523"},{"id":"1776772800000","date":"2026-04-21T12:00:00.000Z","type":"new_feature","message":"**Announcing Zernio Ads.** Run paid social campaigns across 6 platforms directly through the Zernio API.\n\nYou 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.\n\n**Supported platforms:** Meta (Facebook, Instagram), Google Ads, TikTok, LinkedIn, Pinterest, X/Twitter.\n\n**Endpoints:**\n• `POST /v1/ads/boost`: turn an organic post into a paid ad\n• `POST /v1/ads/create`: create standalone ads with custom creative\n• `GET /v1/ads`: list ads with spend, impressions, clicks, CTR, CPC, CPM (up to 90 days)\n• `GET /v1/ads/tree`: nested Campaign > Ad Set > Ad view\n• `GET /v1/ads/campaigns`: aggregated campaign view\n• `GET /v1/ads/audiences`, `POST /v1/ads/audiences`: list and create custom audiences (Meta lookalike, customer list, website pixel)\n• `POST /v1/ads/sync`: pull external ads from platforms into Zernio\n• `POST /v1/ads/conversions`: unified Conversions API relay (Meta, Google Ads)\n• `GET /v1/connect/{platform}/ads`: OAuth connection flow for ad accounts\n\nTargeting supports age (13 to 65), countries, interests, custom audiences, and Meta's Advantage Audience. Goals include `engagement`, `traffic`, `awareness`, `video_views`, `lead_generation`, `conversions`, and `app_promotion` (availability varies by platform).\n\nRequires the **Ads add-on** on your account.","platforms":["instagram","tiktok","x","linkedin","pinterest","meta-ads","google-ads","tiktok-ads","linkedin-ads","pinterest-ads","x-ads","ads","analytics"],"url":"https://docs.zernio.com/changelog#1776772800000"},{"id":"1776758455428","date":"2026-04-21T08:00:55.428Z","type":"improvement","message":"Ad metrics now include conversion reporting and raw action breakdowns via new fields on `AdMetrics`.\n\nThis lets you read conversion counts/cost directly and also extract any Meta action/conversion type from Insights without relying only on derived metrics.\n\nNew fields:\n• `conversions` (integer) — conversion events matching the campaign’s promoted event type (Meta-only for now; others return 0)\n• `costPerConversion` (number) — `spend / conversions` (0 when `conversions` is 0)\n• `actions` (object&lt;string, integer&gt;) — per `action_type` counts (e.g. `link_click`, `offsite_conversion.fb_pixel_purchase`)","platforms":["meta-ads","ads","analytics"],"url":"https://docs.zernio.com/changelog#1776758455428"},{"id":"1776692710701","date":"2026-04-20T13:45:10.701Z","type":"improvement","message":"Meta ads now support **Advantage Audience** controls when creating/boosting ads and when updating Meta targeting.\n\nThis lets you explicitly enable/disable Meta’s targeting automation during ad set creation, and optionally preserve the current setting on updates.\n\nUse:\n• `POST /v1/ads/boost`: `targeting.advantage_audience` = `0` | `1`\n• `PUT /v1/ads/{adId}`: `targeting.advantage_audience` = `0` | `1` (omit to keep existing)\n• `POST /v1/ads/create`: `advantageAudience` = `0` | `1`","platforms":["meta-ads","ads"],"url":"https://docs.zernio.com/changelog#1776692710701"},{"id":"1776672480791","date":"2026-04-20T08:08:00.791Z","type":"improvement","message":"`PUT /v1/inbox/conversations/{conversationId}` can now return `404`.\n\nThis happens when the conversation isn’t found on **WhatsApp** (other platforms upsert instead of returning not found).\n\nHandle the new response: `404 Conversation not found`.","platforms":["whatsapp","inbox"],"url":"https://docs.zernio.com/changelog#1776672480791"},{"id":"1776669529281","date":"2026-04-20T07:18:49.281Z","type":"new_feature","message":"Reddit posting now supports **native video** submissions via new fields in `RedditPlatformData`.\n\nYou 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.\n\nNew fields:\n• `nativeVideo` (boolean, default `true`) - use Reddit native video flow; set `false` to fall back to a legacy link post\n• `videogif` (boolean) - submit as silent `videogif` when `nativeVideo` is active\n• `videoPosterUrl` (uri) - optional thumbnail/poster image for native video posts","platforms":["reddit","publishing"],"url":"https://docs.zernio.com/changelog#1776669529281"},{"id":"1776425022738","date":"2026-04-17T11:23:42.738Z","type":"new_feature","message":"Webhooks now support review events via `events`: `review.new`, `review.updated` (in `POST /v1/webhooks/settings` and `PUT /v1/webhooks/settings`).\n\nSending inbox messages now supports WhatsApp rich interactive messages via `interactive` in `POST /v1/inbox/conversations/{conversationId}/messages`.\n\nSet `interactive.type` to: `list`, `cta_url`, or `flow` (WhatsApp only). When `interactive` is set, it takes priority over `buttons` and `quickReplies`.\n\nInteractive taps/submissions are delivered in the `message.received` webhook under `metadata`: `interactiveType` (`button_reply` | `list_reply` | `nfm_reply`), `interactiveId`, `flowResponseJson`, `flowResponseData`.","platforms":["whatsapp","inbox","webhooks"],"url":"https://docs.zernio.com/changelog#1776425022738"},{"id":"1776339171579","date":"2026-04-16T11:32:51.579Z","type":"new_feature","message":"New Google Business Profile (GBP) capabilities are available: manage Services, update Place Action links, and fetch Reviews across multiple locations in one call.\n\nManage location services:\n• `GET /v1/accounts/{accountId}/gmb-services` (optional `locationId`)\n• `PUT /v1/accounts/{accountId}/gmb-services` replaces the full list via `serviceItems` (structured or free-form, optional `price`)\n\nUpdate a Place Action link:\n• `PATCH /v1/accounts/{accountId}/gmb-place-actions` with `name` (required), and optionally `uri` and/or `placeActionType`\n• `placeActionType`: `APPOINTMENT`, `ONLINE_APPOINTMENT`, `DINING_RESERVATION`, `FOOD_ORDERING`, `FOOD_DELIVERY`, `FOOD_TAKEOUT`, `SHOP_ONLINE`\n\nBatch fetch reviews for multi-location accounts:\n• `POST /v1/accounts/{accountId}/gmb-reviews/batch` with `locationNames`, optional `pageSize` (max `50`) and `pageToken`","platforms":["google-business","inbox"],"url":"https://docs.zernio.com/changelog#1776339171579"},{"id":"1776337007759","date":"2026-04-16T10:56:47.759Z","type":"new_feature","message":"Google Business Platform posts now support **EVENT** and **OFFER** types via `topicType` in `GoogleBusinessPlatformData`.\n\nThis lets you publish event and offer posts (in addition to standard updates) to Google Search/Maps/GBP.\n\nSet `topicType` to: `STANDARD` | `EVENT` | `OFFER` (default: `STANDARD`)\n\nWhen `topicType` is `EVENT`, include `event` (required): `event.title`, `event.schedule.startDate`, `event.schedule.endDate` (optional: `startTime`, `endTime`).\n\nWhen `topicType` is `OFFER`, include `offer` (recommended): `offer.offerType` (`OFFER` | `BUY_ONE_GET_ONE`), `offer.redeemOnlineUrl`, `offer.termsConditions`, `offer.couponCode`.","platforms":["google-business","publishing"],"url":"https://docs.zernio.com/changelog#1776337007759"},{"id":"1776334659436","date":"2026-04-16T10:17:39.436Z","type":"breaking_change","message":"Webhook **comment.received** payloads may now include `null` for Zernio internal post IDs when the post was not published through Zernio.\n\nHandle missing internal IDs by relying on the platform IDs instead.\n\nFields that can now be `null`:\n• `comment.postId`\n• `post.id`\n\nUse `comment.platformPostId` / `post.platformPostId` as the stable identifiers in these cases.","platforms":["webhooks"],"url":"https://docs.zernio.com/changelog#1776334659436"},{"id":"1776330195598","date":"2026-04-16T09:03:15.598Z","type":"new_feature","message":"Discord support has been added: you can now manage Discord account settings, list available guild channels, and post to Discord with Discord-specific options.\n\nManage Discord connection settings:\n• `GET /v1/accounts/{accountId}/discord-settings` (read current channel/guild + webhook identity)\n• `PATCH /v1/accounts/{accountId}/discord-settings` (update `webhookUsername`/`webhookAvatarUrl` and/or switch `channelId`)\n• `GET /v1/accounts/{accountId}/discord-channels` (discover channels: `type` = `0`/`5`/`15`)\n\nPost to Discord via `POST /v1/posts` using `platformSpecificData` = `DiscordPlatformData`:\n• Required: `channelId`\n• Optional: `embeds`, `poll`, `crosspost`, `forumThreadName`, `forumAppliedTags`, `threadFromMessage`, `tts`, `webhookUsername`, `webhookAvatarUrl`\n\nAnalytics responses now include `platformPostId` per platform in `GET /v1/analytics`.\n\nAds goals have been expanded in `POST /v1/ads/boost` and `POST /v1/ads/create`: `goal` now supports `engagement`, `traffic`, `awareness`, `video_views`, `lead_generation`, `conversions`, `app_promotion` (availability varies by platform).","platforms":["discord","ads","publishing","analytics","webhooks"],"url":"https://docs.zernio.com/changelog#1776330195598"},{"id":"1776252594228","date":"2026-04-15T11:29:54.228Z","type":"new_feature","message":"New Ads Conversions API endpoints are available: `POST /v1/ads/conversions` and `GET /v1/accounts/{accountId}/conversion-destinations`.\n\nYou 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.\n\nUse `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).\n\nSend 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).\n\nResponse includes `platform` (`metaads`/`googleads`), `eventsReceived`, `eventsFailed`, `failures[]`, and `traceId`. Requires the Ads add-on.","platforms":["meta-ads","google-ads","ads"],"url":"https://docs.zernio.com/changelog#1776252594228"},{"id":"1776178479584","date":"2026-04-14T14:54:39.584Z","type":"improvement","message":"Meta ads now return additional campaign/ad set details in ad reporting responses.\n\nYou can use these fields to display objective/optimization/bidding context and conversion event setup alongside metrics.\n\nNew fields:\n• On `Ad`: `platformObjective`, `optimizationGoal`, `bidStrategy`, `promotedObject`\n• On `AdTreeCampaign` and `AdCampaign`: `platformObjective`, `optimizationGoal` (can be `string` or `string[]`), `bidStrategy`, `promotedObject`\n• On `AdTreeAdSet`: `optimizationGoal`, `bidStrategy`, `promotedObject`\n\n`promotedObject` may include `custom_event_type`, `pixel_id`, `page_id` (and on `Ad` also `application_id`, `product_set_id`). Fields are `null` when not applicable and are only present for Meta ads.","platforms":["meta-ads","ads","analytics"],"url":"https://docs.zernio.com/changelog#1776178479584"},{"id":"1776167028371","date":"2026-04-14T11:43:48.371Z","type":"new_feature","message":"Discord is now supported as a platform in several endpoints and response schemas.\n\nYou can now use `platform=discord` to connect accounts, validate content, and filter account health.\n\nUpdated enums:\n• `GET /v1/connect/{platform}`: `{platform}` includes `discord`\n• `POST /v1/tools/validate/post`: `platforms[].platform` includes `discord`\n• `GET /v1/accounts/health`: query `platform` includes `discord`\n• Schemas: `SocialAccount.platform`, `PostLog.platform`, `ConnectionLog.platform` include `discord`","platforms":["discord"],"url":"https://docs.zernio.com/changelog#1776167028371"},{"id":"1776094940185","date":"2026-04-13T15:42:20.185Z","type":"improvement","message":"GET `/v1/ads` now supports filtering by platform ad account via `adAccountId`.\n\nUse this to scope results to a specific platform ad account (e.g. Meta `act_123`), consistent with `/v1/ads/campaigns` and `/v1/ads/tree`.\n\nNew query param: `adAccountId`\n\nThe `Ad` schema also adds Meta creative metadata to improve previews for video/moderation states:\n• `creative.videoId` (nullable)\n• `creative.videoUrl` (nullable)\n• `creative.objectType`","platforms":["meta-ads","ads"],"url":"https://docs.zernio.com/changelog#1776094940185"},{"id":"1776074824022","date":"2026-04-13T10:07:04.022Z","type":"new_feature","message":"Webhooks and Inbox messages now support message lifecycle updates: `message.edited`, `message.deleted`, `message.delivered`, `message.read`, `message.failed`.\n\nYou 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`.\n\n`GET /v1/inbox/conversations/{conversationId}/messages` now returns additional fields for message state:\n• Edit tracking: `isEdited`, `editedAt`, `editCount`, `editHistory`\n• Delete tracking: `isDeleted`, `deletedAt`\n• Delivery tracking: `deliveryStatus` (`sent`/`delivered`/`read`/`failed`/`deleted`), `sentAt`, `deliveredAt`, `readAt`, `deliveryError`","platforms":["inbox","webhooks"],"url":"https://docs.zernio.com/changelog#1776074824022"},{"id":"1775988804820","date":"2026-04-12T10:13:24.820Z","type":"breaking_change","message":"`POST /v1/connect/facebook/select-page` now requires `userProfile` in the request body.\n\nIf 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.\n\nRequired fields: `profileId`, `pageId`, `tempToken`, `userProfile`.","platforms":["facebook"],"url":"https://docs.zernio.com/changelog#1775988804820"},{"id":"1775744030434","date":"2026-04-09T14:13:50.434Z","type":"new_feature","message":"Webhooks now support **message sent** events via `message.sent`.\n\nUse this to get notified when a message is sent via the API, including message + conversation context.\n\nSubscribe by adding `message.sent` to `Webhook.events`.\n\nYou can also filter/view deliveries in logs with `GET /v1/webhooks/logs` using `event=message.sent`.\n\nPayload schema: `WebhookPayloadMessageSent` (includes `message`, `conversation`, `account`, `timestamp`).","platforms":["inbox","webhooks"],"url":"https://docs.zernio.com/changelog#1775744030434"},{"id":"1775645769073","date":"2026-04-08T10:56:09.073Z","type":"breaking_change","message":"Thread publishing behavior changed for X (Twitter), Threads, and Bluesky when using `threadItems`.\n\nIf `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]`.\n\nKey fields:\n• `platformSpecificData.twitter.threadItems`\n• `platformSpecificData.threads.threadItems`\n• `platformSpecificData.bluesky.threadItems`","platforms":["threads","x","bluesky","publishing"],"url":"https://docs.zernio.com/changelog#1775645769073"},{"id":"1775424503762","date":"2026-04-05T21:28:23.762Z","type":"improvement","message":"Facebook now supports posting an optional **first comment** for **Reels** as well as feed posts.\n\nUse `platformSpecificData.facebook.firstComment` to add a comment immediately after publishing (not supported for `contentType`=`story`). Skipped when `draft` is `true`.\n\nApplies when `contentType` is `reel` (or omitted for feed posts).","platforms":["publishing"],"url":"https://docs.zernio.com/changelog#1775424503762"},{"id":"1775318630762","date":"2026-04-04T16:03:50.762Z","type":"new_feature","message":"You can now **edit a published X (Twitter) post** via `POST /v1/posts/{postId}/edit`.\n\nThis 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).\n\nRequest body:\n• `platform`: `twitter`\n• `content`: new text\n\nResponse includes:\n• `id` (new tweet ID after edit)\n• `url`\n\nYou can now **start/send X DMs** via `POST /v1/inbox/conversations`.\n\nIf a thread already exists, the message is appended. If the recipient doesn’t accept DMs, you’ll get `422` with `code`=`DM_NOT_ALLOWED` (or bypass the pre-check with `skipDmCheck`).\n\nKey fields:\n• `accountId`\n• `participantId` or `participantUsername`\n• `message` (or `attachment` via `multipart/form-data`)\n• `skipDmCheck`\n\nAlso added:\n• 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)\n• `TwitterPlatformData.longVideo`=`true` to enable long video uploads (requires X Premium; may require allowlisting).","platforms":["x","publishing","inbox"],"url":"https://docs.zernio.com/changelog#1775318630762"},{"id":"1775318216774","date":"2026-04-04T15:56:56.774Z","type":"new_feature","message":"New Google Business Profile analytics endpoints are available.\n\nFetch 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.\n\nKey params:\n• `accountId` (required)\n• `metrics` (comma-separated; defaults to all): `BUSINESS_IMPRESSIONS_DESKTOP_MAPS`, `BUSINESS_IMPRESSIONS_DESKTOP_SEARCH`, `BUSINESS_IMPRESSIONS_MOBILE_MAPS`, `BUSINESS_IMPRESSIONS_MOBILE_SEARCH`, `BUSINESS_CONVERSATIONS`, `BUSINESS_DIRECTION_REQUESTS`, `CALL_CLICKS`, `WEBSITE_CLICKS`, `BUSINESS_BOOKINGS`, `BUSINESS_FOOD_ORDERS`, `BUSINESS_FOOD_MENU_CLICKS`\n• `startDate`, `endDate` (YYYY-MM-DD)\n\nFetch monthly search keywords via `GET /v1/analytics/googlebusiness/search-keywords`. Requires `accountId` and the Analytics add-on; up to 18 months history.\n\nKey params:\n• `accountId` (required)\n• `startMonth`, `endMonth` (YYYY-MM)\n\nNote: keywords below Google’s minimum impression threshold are excluded.","platforms":["google-business","analytics"],"url":"https://docs.zernio.com/changelog#1775318216774"},{"id":"1775250718987","date":"2026-04-03T21:11:58.987Z","type":"new_feature","message":"YouTube analytics now supports **audience demographics** via `GET /v1/analytics/youtube/demographics`.\n\nFetch 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.\n\nKey params:\n• `accountId` (required)\n• `breakdown` = `age`, `gender`, `country` (comma-separated; defaults to all)\n• `startDate`, `endDate`\n\nWhatsApp now supports **Flows** (create/manage/publish and send interactive flow messages).\n\nManage flows:\n• `GET /v1/whatsapp/flows` (`accountId`)\n• `POST /v1/whatsapp/flows` (`accountId`, `name`, `categories`, optional `cloneFlowId`)\n• `GET /v1/whatsapp/flows/{flowId}` (`accountId`, optional `fields`)\n• `PATCH /v1/whatsapp/flows/{flowId}` (`accountId`, `name`/`categories`; DRAFT only)\n• `DELETE /v1/whatsapp/flows/{flowId}` (`accountId`; DRAFT only)\n\nFlow JSON asset:\n• `GET /v1/whatsapp/flows/{flowId}/json` (`accountId`)\n• `PUT /v1/whatsapp/flows/{flowId}/json` (`accountId`, `flow_json`; DRAFT only)\n\nLifecycle:\n• `POST /v1/whatsapp/flows/{flowId}/publish` (`accountId`; irreversible)\n• `POST /v1/whatsapp/flows/{flowId}/deprecate` (`accountId`; irreversible)\n\nSend a flow message:\n• `POST /v1/whatsapp/flows/send` with `accountId`, `to`, `flow_id`, `flow_cta`, `body`\n• Optional `flow_action` = `navigate` | `data_exchange`, `flow_token`, `flow_action_payload`, `draft`","platforms":["youtube","whatsapp","analytics"],"url":"https://docs.zernio.com/changelog#1775250718987"},{"id":"1775025596097","date":"2026-04-01T06:39:56.097Z","type":"improvement","message":"YouTube metadata updates now support additional fields on `POST /v1/posts/{postId}/update-metadata`.\n\nYou can now set COPPA compliance, disclose AI/synthetic media, and add a video to a playlist without re-uploading.\n\nNew optional properties:\n• `madeForKids` (boolean)\n• `containsSyntheticMedia` (boolean)\n• `playlistId` (string)","platforms":["youtube"],"url":"https://docs.zernio.com/changelog#1775025596097"},{"id":"1774985723143","date":"2026-03-31T19:35:23.143Z","type":"new_feature","message":"**YouTube** metadata updates now support setting a custom thumbnail via `POST /v1/posts/{postId}/update-metadata`.\n\nYou can update the thumbnail without re-uploading, including for existing videos not published through Zernio (direct video ID mode).\n\nUse `thumbnailUrl` (public image URL) with `platform` `youtube`. In direct mode also include `videoId` + `accountId`.\n\nKey params: `platform` (`youtube`), `thumbnailUrl`, `videoId`, `accountId`","platforms":["youtube"],"url":"https://docs.zernio.com/changelog#1774985723143"},{"id":"1774889886478","date":"2026-03-30T16:58:06.478Z","type":"new_feature","message":"YouTube posts now support adding a video to a playlist via `playlistId` in `YouTubePlatformData`.\n\nUse the new playlist endpoints to discover playlist IDs and (optionally) store a client-side default for an account.\n\nNew endpoints:\n• `GET /v1/accounts/{accountId}/youtube-playlists` → returns `playlists[]` and `defaultPlaylistId`\n• `PUT /v1/accounts/{accountId}/youtube-playlists` with `defaultPlaylistId` (optional `defaultPlaylistName`)\n\nNew field:\n• `platformSpecificData.youtube.playlistId` (string) — optional playlist to add the video to after upload","platforms":["youtube","publishing"],"url":"https://docs.zernio.com/changelog#1774889886478"},{"id":"1774548026942","date":"2026-03-26T18:00:26.942Z","type":"new_feature","message":"**POST /v1/posts/{postId}/update-metadata** now supports updating YouTube video metadata even if the video was uploaded outside Zernio.\n\nUse one of two modes:\n• Post-based: call with the real `{postId}` and `platform`\n• Direct video ID: set `{postId}` to `_` and include `videoId` + `accountId` + `platform`\n\nKey params:\n• `platform`: `youtube`\n• `videoId` (direct mode)\n• `accountId` (direct mode)\n\nIn direct mode, the 200 response may include `videoId`.","platforms":["youtube"],"url":"https://docs.zernio.com/changelog#1774548026942"},{"id":"1774536584848","date":"2026-03-26T14:49:44.848Z","type":"new_feature","message":"New endpoint: `POST /v1/posts/{postId}/update-metadata` to update metadata on an already-published post without re-uploading media.\n\nCurrently supported for YouTube videos only; the post must be `published` on the target platform and you must provide at least one updatable field.\n\nRequest body: `platform` (`youtube`) plus any of `title`, `description`, `tags`, `categoryId`, `privacyStatus` (`public` | `private` | `unlisted`).","platforms":["youtube"],"url":"https://docs.zernio.com/changelog#1774536584848"},{"id":"1774343552917","date":"2026-03-24T09:12:32.917Z","type":"new_feature","message":"Threads posts now support an explicit **topic tag** via `platformSpecificData.topic_tag`.\n\nUse this to categorize posts for discoverability on Threads; when provided, it overrides auto-extraction from content hashtags.\n\nSet `topic_tag` (string):\n• 1–50 characters\n• cannot contain `.` or `&amp;`","platforms":["threads","publishing"],"url":"https://docs.zernio.com/changelog#1774343552917"},{"id":"1774287922247","date":"2026-03-23T17:45:22.247Z","type":"improvement","message":"Analytics endpoints now support filtering by social account via `accountId`.\n\nThis lets you scope results to a specific connected account (useful when a `profileId` contains multiple accounts).\n\nNew query parameter:\n• `GET /v1/analytics`: `accountId`\n• `GET /v1/analytics/daily-metrics`: `accountId`","platforms":["analytics"],"url":"https://docs.zernio.com/changelog#1774287922247"},{"id":"1774268568612","date":"2026-03-23T12:22:48.612Z","type":"new_feature","message":"You can now send inbox attachments by URL when calling `POST /v1/inbox/conversations/{conversationId}/messages` (JSON body), without using multipart upload.\n\nProvide `attachmentUrl` (publicly accessible) and optionally `attachmentType` (`image`, `video`, `audio`, `file`).\n\nTwitter posts now support polls via `TwitterPlatformData.poll`.\n\nSet `poll.options` (2-4 items, max 25 chars each) and `poll.duration_minutes` (5-10080). Polls are mutually exclusive with media attachments and threads.","platforms":["x","publishing","inbox"],"url":"https://docs.zernio.com/changelog#1774268568612"},{"id":"1774208030372","date":"2026-03-22T19:33:50.372Z","type":"new_feature","message":"Instagram Analytics now supports **account-level insights** and **audience demographics**.\n\nFetch account performance totals or daily reach via `GET /v1/analytics/instagram/account-insights`.\nKey params: `accountId` (required), `metrics`, `since`, `until`, `metricType` (`time_series` | `total_value`), `breakdown` (total_value only). Note: `time_series` only works with `reach`. Max range: `90` days (default last `30`). Data may be delayed up to `48` hours. Requires Analytics add-on.\n\nFetch audience breakdowns via `GET /v1/analytics/instagram/demographics`.\nKey params: `accountId` (required), `metric` (`follower_demographics` | `engaged_audience_demographics`), `breakdown` (age, city, country, gender), `timeframe` (`this_week` | `this_month`). Requires `100+` followers, returns top `45` per dimension, and may be delayed up to `48` hours. Requires Analytics add-on.","platforms":["instagram","analytics"],"url":"https://docs.zernio.com/changelog#1774208030372"},{"id":"1774097797425","date":"2026-03-21T12:56:37.425Z","type":"new_feature","message":"TikTok video posts now support a **custom cover image** via `videoCoverImageUrl` in `TikTokPlatformData`.\n\nProvide a thumbnail image URL (JPG/PNG/WebP, max 20MB). When set, it overrides `videoCoverTimestampMs`.\n\nUse:\n• `videoCoverImageUrl` - custom thumbnail image URL\n• `videoCoverTimestampMs` - frame timestamp (ms), ignored if `videoCoverImageUrl` is provided","platforms":["tiktok","publishing"],"url":"https://docs.zernio.com/changelog#1774097797425"},{"id":"1774097564679","date":"2026-03-21T12:52:44.679Z","type":"new_feature","message":"New endpoint: `GET /v1/accounts/{accountId}/tiktok/creator-info`.\n\nYou 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.\n\nKey params:\n• `accountId` (path)\n• `mediaType` (query, default `video`): `video`, `photo`\n\nAlso added to `TikTokPlatformData` for video posts: `videoCoverImageUrl` (custom thumbnail URL). When set, it overrides `videoCoverTimestampMs`.","platforms":["tiktok"],"url":"https://docs.zernio.com/changelog#1774097564679"},{"id":"1773670004853","date":"2026-03-16T14:06:44.853Z","type":"breaking_change","message":"GET `/v1/analytics` now supports richer analytics sync states and more sorting options.\n\nSingle-post lookups can return `202` when analytics sync is still `pending`, or `424` when analytics are `unavailable` because the post failed on all platforms.\n\nKey changes:\n• Date defaults/limits: `fromDate` defaults to 90 days ago, `toDate` defaults to today, max range `366` days\n• Sorting: `sortBy` now supports `date`, `engagement`, `impressions`, `reach`, `likes`, `comments`, `shares`, `saves`, `clicks`, `views`\n• Single post response adds `latePostId`, `syncStatus` (`synced`/`pending`/`partial`/`unavailable`) and `message`; `status` can be `published`/`failed`/`partial`\n• Per-platform analytics now include `syncStatus` (`synced`/`pending`/`unavailable`), `platformPostUrl`, and `errorMessage`; `analytics` may be `null`\n• List responses add `posts[].latePostId`, `posts[].profileId`, and `overview.dataStaleness`","platforms":["analytics"],"url":"https://docs.zernio.com/changelog#1773670004853"},{"id":"1773408329296","date":"2026-03-13T13:25:29.296Z","type":"new_feature","message":"Late API now supports X/Twitter engagement actions: retweet, bookmark, and follow (plus undo/remove/unfollow).\n\nUse:\n• Retweet: `POST /v1/twitter/retweet` with `accountId`, `tweetId`\n• Undo retweet: `DELETE /v1/twitter/retweet` with `accountId`, `tweetId`\n• Bookmark: `POST /v1/twitter/bookmark` with `accountId`, `tweetId`\n• Remove bookmark: `DELETE /v1/twitter/bookmark` with `accountId`, `tweetId`\n• Follow: `POST /v1/twitter/follow` with `accountId`, `targetUserId` (response may include `pending_follow` for protected accounts)\n• Unfollow: `DELETE /v1/twitter/follow` with `accountId`, `targetUserId`\n\nInbox comment moderation also adds X/Twitter support:\n• `POST /v1/inbox/comments/{postId}/{commentId}/hide`\n• `DELETE /v1/inbox/comments/{postId}/{commentId}/hide`\nFor 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).","platforms":["x","inbox"],"url":"https://docs.zernio.com/changelog#1773408329296"},{"id":"1773392325263","date":"2026-03-13T08:58:45.263Z","type":"improvement","message":"`GET /v1/analytics` now returns richer media metadata for analytics results.\n\nYou can render previews and handle carousels consistently using the new fields on both single-post and list responses.\n\nNew fields:\n• `thumbnailUrl`\n• `mediaType` = `image` | `video` | `gif` | `document` | `carousel` | `text` (single-post also supports `image` | `video` | `carousel` | `text`)\n• `mediaItems` (array of `{ type: image|video, url, thumbnail }`; carousel posts include one entry per slide)","platforms":["analytics"],"url":"https://docs.zernio.com/changelog#1773392325263"},{"id":"1773312316647","date":"2026-03-12T10:45:16.647Z","type":"improvement","message":"The `GET /v1/usage-stats` response now includes `billingAnchorDay`.\n\nUse `billingAnchorDay` (1–31) to determine the day of the month when the billing cycle resets, which can help align usage tracking and reset expectations.","platforms":["analytics"],"url":"https://docs.zernio.com/changelog#1773312316647"},{"id":"1773306620463","date":"2026-03-12T09:10:20.463Z","type":"improvement","message":"GBP endpoints now support overriding the target location via optional `locationId`.\n\nThis 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.\n\nUse `locationId` (query) on:\n• `GET /v1/accounts/{accountId}/gmb-reviews`\n• `GET`/`PUT` `/v1/accounts/{accountId}/gmb-food-menus`\n• `GET`/`PUT` `/v1/accounts/{accountId}/gmb-location-details`\n• `GET`/`POST`/`DELETE` `/v1/accounts/{accountId}/gmb-media`\n• `GET`/`PUT` `/v1/accounts/{accountId}/gmb-attributes`\n• `GET`/`POST`/`DELETE` `/v1/accounts/{accountId}/gmb-place-actions`","platforms":["google-business"],"url":"https://docs.zernio.com/changelog#1773306620463"},{"id":"1773215059022","date":"2026-03-11T07:44:19.022Z","type":"new_feature","message":"Twitter posts now support replying to an existing tweet via `replyToTweetId` in `TwitterPlatformData`.\n\nSet `replyToTweetId` to publish the first tweet (or the root of a thread) as a reply; subsequent thread items chain normally.\n\n`replySettings` also adds `verified` (now: `following`, `mentionedUsers`, `subscribers`, `verified`). Note: `replySettings` cannot be combined with `replyToTweetId`.","platforms":["x","publishing"],"url":"https://docs.zernio.com/changelog#1773215059022"},{"id":"1772960634766","date":"2026-03-08T09:03:54.766Z","type":"new_feature","message":"New endpoint `GET /v1/accounts/{accountId}/linkedin-post-reactions` returns who reacted to a LinkedIn post, including reactor profile details.\n\nCurrently supported only for **LinkedIn organization/company page** accounts (personal profile reaction data is restricted by LinkedIn).\n\nRequired params: `accountId` (path, LinkedIn org account), `urn` (query, LinkedIn post URN)\nOptional: `limit` (1–100, default 25), `cursor`\n\nResponse includes `reactions[]` with `reactionType` (`LIKE`, `PRAISE`, `EMPATHY`, `INTEREST`, `APPRECIATION`, `ENTERTAINMENT`), `reactedAt`, and `from` (e.g., `name`, `headline`, `profileUrl`, `profilePicture`), plus `pagination`.","platforms":["linkedin"],"url":"https://docs.zernio.com/changelog#1772960634766"},{"id":"1772960404118","date":"2026-03-08T09:00:04.118Z","type":"improvement","message":"`GET /v1/connect/{platform}` redirect behavior now includes `accountId` in standard mode.\n\nAfter a successful connect, Zernio’s standard redirect to `redirect_url` now appends `?connected={platform}&profileId=X&accountId=Y&username=Z` (previously no `accountId`).\n\nIn 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`.","platforms":["facebook","linkedin"],"url":"https://docs.zernio.com/changelog#1772960404118"},{"id":"1772813541122","date":"2026-03-06T16:12:21.122Z","type":"new_feature","message":"Posts can now be **recycled** (auto-reposted on a schedule) via `recycling` on `POST /v1/posts` and `PUT /v1/posts/{postId}`.\n\nAfter the original post is published, Zernio will create new scheduled copies at a weekly/monthly interval until an expiration condition is met.\n\nConfigure with `recycling`:\n• `gap` (int)\n• `gapFreq`: `week` | `month`\n• `startDate` (date-time, optional)\n• `expireCount` (int, optional)\n• `expireDate` (date-time, optional)\n• `contentVariations` (string[], optional)\n\nWebhooks: a new event `post.recycled` is available in `POST/PUT /v1/webhooks/settings` and filterable in `GET /v1/webhooks/logs`.\n\nAlso: `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.","platforms":["youtube","publishing","inbox","webhooks"],"url":"https://docs.zernio.com/changelog#1772813541122"},{"id":"1772530384033","date":"2026-03-03T09:33:04.033Z","type":"new_feature","message":"New endpoint `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.\n\nQuery params:\n• `postId` (required) — accepts ExternalPost ID, `platformPostId`, or Zernio Post ID\n• `fromDate` (optional, ISO 8601; default 90 days ago)\n• `toDate` (optional, ISO 8601; default now)\n\nResponse includes `timeline[]` rows with `date`, `platform`, `platformPostId`, and metrics like `impressions`, `reach`, `likes`, `comments`, `shares`, `saves`, `clicks`, `views`.","platforms":["analytics"],"url":"https://docs.zernio.com/changelog#1772530384033"},{"id":"1772187523181","date":"2026-02-27T10:18:43.181Z","type":"new_feature","message":"New validation endpoints are available under `/v1/tools/validate` to preflight-check content before publishing.\n\nValidate weighted character counts per platform with `POST /v1/tools/validate/post-length` (body: `{ text }`) and get per-platform `{ count, limit, valid }`.\n\nDry-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[]`.\n\nValidate 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.\n\nCheck subreddit existence/info with `GET /v1/tools/validate/subreddit` using query `name`.","platforms":["instagram","threads","tiktok","linkedin","youtube","pinterest","reddit","bluesky","telegram","snapchat","google-business"],"url":"https://docs.zernio.com/changelog#1772187523181"},{"id":"1772120176191","date":"2026-02-26T15:36:16.191Z","type":"new_feature","message":"New Analytics endpoints are available for aggregated reporting and scheduling optimization (requires the Analytics add-on).\n\nDaily aggregated metrics: `GET /v1/analytics/daily-metrics`\n• Filters: `platform`, `profileId`, `fromDate`, `toDate`\n• Returns: `dailyData` + `platformBreakdown` with summed metrics (impressions, reach, likes, comments, shares, saves, clicks, views)\n\nBest times to post: `GET /v1/analytics/best-time`\n• Filters: `platform`, `profileId`\n• Returns: `slots` grouped by `day_of_week` (0=Monday..6=Sunday) and `hour` (UTC 0-23) with `avg_engagement`\n\nContent performance decay: `GET /v1/analytics/content-decay`\n• Filters: `platform`, `profileId`\n• Returns: `buckets` with `bucket_label` and `avg_pct_of_final`\n\nPosting frequency vs engagement: `GET /v1/analytics/posting-frequency`\n• Filters: `platform`, `profileId`\n• Returns: `frequency` rows with `posts_per_week`, `avg_engagement_rate`, `weeks_count`","platforms":["analytics"],"url":"https://docs.zernio.com/changelog#1772120176191"},{"id":"1771590919512","date":"2026-02-20T12:35:19.512Z","type":"improvement","message":"LinkedIn document (PDF/carousel) posts now let you control the displayed document title via `platformSpecificData.linkedin.documentTitle`.\n\nLinkedIn requires a title for document posts; if omitted, Zernio will fall back to the media item title, then the filename.\n\nUse:\n• `platformSpecificData.linkedin.documentTitle`\n• `media[].title` (used as the LinkedIn document title if `documentTitle` is not set; otherwise falls back to `filename`)","platforms":["linkedin","publishing"],"url":"https://docs.zernio.com/changelog#1771590919512"},{"id":"1771422791468","date":"2026-02-18T13:53:11.468Z","type":"new_feature","message":"API keys can now be created with restricted access using `scope`, `profileIds`, and `permission` on `POST /v1/api-keys`.\n\nThis lets you issue keys limited to specific profiles and/or make keys read-only for safer analytics/integration use.\n\nNew request fields:\n• `scope`: `full` | `profiles` (default `full`)\n• `profileIds`: required when `scope`=`profiles`\n• `permission`: `read-write` | `read` (default `read-write`)\n\nKey responses now include `scope`, `profileIds` (populated profile objects when scoped), and `permission` (e.g., in `GET /v1/api-keys` and create responses).","platforms":["analytics"],"url":"https://docs.zernio.com/changelog#1771422791468"},{"id":"1771403945055","date":"2026-02-18T08:39:05.055Z","type":"new_feature","message":"Facebook publishing now supports **Reels** via `FacebookPlatformData.contentType`.\n\nSet `contentType` to `story` or `reel` (omit for a feed post). Reels require a single vertical video (9:16, 3–60s).\n\nNew field for reels:\n• `title` — Reel title (only when `contentType=reel`)\n\nNote: `firstComment` applies to feed posts only (not stories or reels).","platforms":["publishing"],"url":"https://docs.zernio.com/changelog#1771403945055"},{"id":"1771265749650","date":"2026-02-16T18:15:49.650Z","type":"new_feature","message":"New endpoint: `POST /v1/posts/{postId}/unpublish`.\n\nYou can now delete a previously published post from a specific social platform while keeping the Zernio post record (platform status becomes `cancelled`).\n\nRequest body: `platform` = `threads` | `facebook` | `twitter` | `linkedin` | `youtube` | `pinterest` | `reddit` | `bluesky` | `googlebusiness` | `telegram`.\n\nNotes: Instagram/TikTok/Snapchat deletion isn’t supported. Telegram deletions may fail for messages older than 48h. YouTube deletion permanently removes the video.","platforms":["instagram","threads","tiktok","linkedin","youtube","pinterest","reddit","bluesky","telegram","snapchat","google-business"],"url":"https://docs.zernio.com/changelog#1771265749650"},{"id":"1771239931734","date":"2026-02-16T11:05:31.734Z","type":"improvement","message":"Instagram photo tagging now supports tagging specific slides in carousel posts via `mediaIndex` in `platformSpecificData.userTags`.\n\nThis lets you place tags on any image within a carousel. Tags without `mediaIndex` still default to the first image (index 0).\n\nUse:\n• `userTags[].mediaIndex` (integer, 0-based)\n\nNotes:\n• Not supported for stories or videos; tags targeting video items (or out-of-range indices) are ignored.","platforms":["instagram"],"url":"https://docs.zernio.com/changelog#1771239931734"},{"id":"1771227921905","date":"2026-02-16T07:45:21.905Z","type":"new_feature","message":"POST `/v1/inbox/comments/{postId}/{commentId}/private-reply` now supports **Facebook** in addition to Instagram.\n\nYou 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).\n\nUse `accountId` (Instagram or Facebook) and `message` in the JSON body. The response `platform` is now `instagram` or `facebook`.","platforms":["instagram","facebook","inbox"],"url":"https://docs.zernio.com/changelog#1771227921905"},{"id":"1771142696777","date":"2026-02-15T08:04:56.777Z","type":"improvement","message":"Google Business Profile posts now support explicitly setting the content language via `languageCode` in `GoogleBusinessPlatformData`.\n\nIf omitted, the language is auto-detected from the post text; set it when auto-detection may be unreliable (short posts, mixed languages, transliteration).\n\nNew field: `languageCode` (BCP 47, e.g. `en`, `de`, `es`, `fr`).","platforms":["google-business"],"url":"https://docs.zernio.com/changelog#1771142696777"},{"id":"1770920131154","date":"2026-02-12T18:15:31.154Z","type":"new_feature","message":"Reddit posting now supports **post flairs** via `flairId` in `platformSpecificData`.\n\nSome subreddits require a flair; you can now fetch available flairs and set the one you want on the post.\n\nNew endpoint: `GET /v1/accounts/{accountId}/reddit-flairs`\nRequired query: `subreddit` (without `r/`)\n\nNew field: `platformSpecificData.reddit.flairId` (string)\nIf omitted, the API will attempt to use the first available flair as a fallback.","platforms":["reddit","publishing"],"url":"https://docs.zernio.com/changelog#1770920131154"},{"id":"1770839219111","date":"2026-02-11T19:46:59.111Z","type":"improvement","message":"Instagram conversations now include optional participant profile context via `instagramProfile`.\n\nThis lets you show follower/verification info (and follower count) alongside DMs, and use it in routing/automation.\n\nAdded to:\n• `GET /v1/inbox/conversations` → `data[].instagramProfile`\n• `GET /v1/inbox/conversations/{conversationId}` → `data.instagramProfile`\n\nFields:\n• `isFollower`, `isFollowing`, `followerCount`, `isVerified`, `fetchedAt`\n\nWebhook update:\n• `WebhookPayloadMessage.message.sender.instagramProfile` (Instagram only) with `isFollower`, `isFollowing`, `followerCount`, `isVerified`.","platforms":["instagram","inbox","webhooks"],"url":"https://docs.zernio.com/changelog#1770839219111"},{"id":"1770816580042","date":"2026-02-11T13:29:40.042Z","type":"new_feature","message":"New **Account Settings** endpoints for managing platform-specific messaging features.\n\n**Facebook Persistent Menu** (`/v1/accounts/{accountId}/messenger-menu`)\nGET/PUT/DELETE to manage the persistent menu shown in Facebook Messenger conversations. Max 3 top-level items, max 5 nested items.\n\n**Instagram Ice Breakers** (`/v1/accounts/{accountId}/instagram-ice-breakers`)\nGET/PUT/DELETE to manage ice breaker prompts shown when users start a new Instagram DM. Max 4 ice breakers, question max 80 chars.\n\n**Telegram Bot Commands** (`/v1/accounts/{accountId}/telegram-commands`)\nGET/PUT/DELETE to manage the bot command menu shown in Telegram chats.","platforms":["instagram","facebook","telegram"],"url":"https://docs.zernio.com/changelog#1770816580042"},{"id":"1770816580041","date":"2026-02-11T13:29:40.041Z","type":"new_feature","message":"**Interactive messaging support** for the Inbox API.\n\n`POST /v1/inbox/conversations/{conversationId}/messages` now supports:\n\n• **Quick replies** (`quickReplies`) - Up to 13 quick reply buttons (Instagram, Facebook, Telegram)\n• **Buttons** (`buttons`) - Up to 3 action buttons: URL, postback, or phone (Instagram, Facebook, Telegram)\n• **Carousels** (`template`) - Generic template with up to 10 elements (Instagram, Facebook)\n• **Telegram keyboards** (`replyMarkup`) - Native inline or reply keyboards\n• **Message tags** (`messageTag`) - Send outside the 24h window: `HUMAN_AGENT` (Instagram), `CONFIRMED_EVENT_UPDATE`, `POST_PURCHASE_UPDATE`, `ACCOUNT_UPDATE`, `HUMAN_AGENT` (Facebook)\n• **Reply to** (`replyTo`) - Reply to a specific message (Telegram)\n\nIncoming messages now include `metadata` with `quickReplyPayload`, `postbackPayload`, `postbackTitle`, and `callbackData` to identify interactive message taps.\n\nAlso added: `PATCH /v1/inbox/conversations/{conversationId}/messages/{messageId}` to edit sent Telegram messages (text and inline keyboard).","platforms":["instagram","facebook","telegram","inbox"],"url":"https://docs.zernio.com/changelog#1770816580041"},{"id":"1770728637816","date":"2026-02-10T13:03:57.816Z","type":"minor","message":"Added 2 new endpoints:\n\n`GET /v1/posts/logs` - Get publishing logs\n`GET /v1/connections/logs` - Get connection logs","platforms":["general"],"url":"https://docs.zernio.com/changelog#1770728637816"},{"id":"1770285643940","date":"2026-02-05T10:00:43.940Z","type":"new_feature","message":"New **Google Business Profile** endpoints are now available for managing your GMB listings programmatically.\n\n**Location Details** (`/v1/accounts/{accountId}/gmb-location-details`)\nGET/PUT to read and update business hours, special hours, description, phone numbers, and website. Use `readMask` and `updateMask` to specify fields.\n\n**Media** (`/v1/accounts/{accountId}/gmb-media`)\nGET/POST/DELETE to manage photos. Upload via public URL with categories:\n`COVER`, `PROFILE`, `LOGO`, `EXTERIOR`, `INTERIOR`, `FOOD_AND_DRINK`, `MENU`, `PRODUCT`, `TEAMS`, `ADDITIONAL`\n\n**Attributes** (`/v1/accounts/{accountId}/gmb-attributes`)\nGET/PUT to manage amenities and services like `has_delivery`, `has_takeout`, `has_outdoor_seating`, `has_wifi`, payment types, etc.\n\n**Place Actions** (`/v1/accounts/{accountId}/gmb-place-actions`)\nGET/POST/DELETE to manage booking and ordering buttons. Types include:\n`APPOINTMENT`, `DINING_RESERVATION`, `FOOD_ORDERING`, `FOOD_DELIVERY`, `FOOD_TAKEOUT`, `SHOP_ONLINE`","platforms":["google-business","publishing"],"url":"https://docs.zernio.com/changelog#1770285643940"},{"id":"1770278662100","date":"2026-02-05T08:04:22.100Z","type":"new_feature","message":"New endpoints for **Google Business Profile food menus**:\n\n`GET /v1/accounts/{accountId}/gmb-food-menus`\nFetch the full menu structure for a connected GBP location.\n\n`PUT /v1/accounts/{accountId}/gmb-food-menus`\nUpdate food menus with sections, items, pricing, dietary info, and allergens.\n\nMenu items support:\n• `price` with currency code\n• `dietaryRestriction` - VEGETARIAN, VEGAN, GLUTEN_FREE, etc.\n• `allergen` - DAIRY, GLUTEN, SHELLFISH, etc.\n• `spiciness`, `servesNumPeople`, `preparationMethods`\n\nOnly available for GBP locations with food menu support (restaurants, cafes, etc.).\n\n**Also:** `PostAnalytics` now includes `saves` for tracking bookmarks on Instagram and Pinterest.","platforms":["instagram","pinterest","google-business","analytics"],"url":"https://docs.zernio.com/changelog#1770278662100"},{"id":"1770198621368","date":"2026-02-04T09:50:21.368Z","type":"new_feature","message":"The Inbox API now supports **Twitter/X** for conversations, comments, and comment interactions.\n\n**Conversations** (`GET /v1/inbox/conversations`):\nFilter by `platform=twitter` to fetch Twitter/X DMs.\n\n**Comments** (`GET /v1/inbox/comments`):\nTwitter/X posts with replies are now included. Also added **Threads** support for comments listing.\n\n**Like/Unlike comments**:\n`POST` and `DELETE /v1/inbox/comments/{postId}/{commentId}/like` now work with Twitter/X replies.","platforms":["threads","x","inbox"],"url":"https://docs.zernio.com/changelog#1770198621368"},{"id":"1770107163013","date":"2026-02-03T08:26:03.013Z","type":"new_feature","message":"The `GET /v1/analytics` endpoint now supports a `source` query parameter to filter posts by origin.\n\nValues:\n• `late` - only posts scheduled/published via Zernio API\n• `external` - only posts synced from the platform (not posted via Late)\n• `all` - all posts (default)\n\nThis makes it easier to separate analytics for posts you scheduled through Zernio from posts that were published directly on the platform.","platforms":["analytics"],"url":"https://docs.zernio.com/changelog#1770107163013"},{"id":"1770056718352","date":"2026-02-02T18:25:18.352Z","type":"new_feature","message":"The `PlatformTarget` schema now includes detailed error information when posts fail to publish.\n\nThree new fields are available:\n\n`errorMessage` - Human-readable explanation of why the publish failed\n\n`errorCategory` - Programmatic category for handling:\n• `auth_expired` - token expired, reconnect needed\n• `user_content` - content doesn't meet platform requirements\n• `user_abuse` - rate limits or spam detection\n• `account_issue` - account configuration problems\n• `platform_rejected` - policy violation\n• `platform_error` - platform-side issues\n• `system_error` - Zernio infrastructure issues\n• `unknown` - unclassified\n\n`errorSource` - Who caused the error:\n• `user` - user action required\n• `platform` - platform-side issue\n• `system` - Zernio system issue\n\nThese fields are populated when a platform's `status` is `failed`. Posts with mixed results now return `status: \"partial\"` at the post level.","platforms":["publishing"],"url":"https://docs.zernio.com/changelog#1770056718352"},{"id":"1770055481719","date":"2026-02-02T18:04:41.719Z","type":"new_feature","message":"YouTube uploads now support **video categories** via the new `categoryId` field in `platformSpecificData`.\n\nYou can now specify which category your video belongs to. Defaults to `22` (People & Blogs) if not set.\n\nCommon category IDs:\n• `1` - Film & Animation\n• `10` - Music\n• `20` - Gaming\n• `24` - Entertainment\n• `27` - Education\n• `28` - Science & Technology","platforms":["youtube","publishing"],"url":"https://docs.zernio.com/changelog#1770055481719"},{"id":"1769777052141","date":"2026-01-30T12:44:12.141Z","type":"new_feature","message":"**Multi-page/multi-location posting** is now supported for Facebook, LinkedIn, and Google Business.\n\nYou 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`:\n\n• Facebook: `pageId` - post to multiple Pages\n• LinkedIn: `organizationUrn` - post to multiple organizations\n• Google Business: `locationId` - post to multiple locations\n\nList available targets via:\n• `GET /v1/accounts/{id}/facebook-page`\n• `GET /v1/accounts/{id}/linkedin-organizations`\n• `GET /v1/accounts/{id}/gmb-locations`\n\nAlso: `content` is now optional when media is attached or all platforms have `customContent` set.","platforms":["facebook","linkedin","google-business","publishing"],"url":"https://docs.zernio.com/changelog#1769777052141"},{"id":"1769602147376","date":"2026-01-28T12:09:07.376Z","type":"new_feature","message":"New endpoint: `POST /v1/inbox/comments/{postId}/{commentId}/private-reply`\n\nSend a private DM to the author of a comment on your Instagram post. Useful for handling customer inquiries or sensitive matters privately.\n\nRequired body:\n• `accountId` - Instagram social account ID\n• `message` - text to send as DM\n\n**Limitations:**\n• Instagram only (not available on other platforms)\n• One private reply per comment\n• Must be sent within 7 days of the comment\n• Only works for comments on posts you own\n• Requires the Inbox addon","platforms":["instagram","inbox"],"url":"https://docs.zernio.com/changelog#1769602147376"},{"id":"1769432998021","date":"2026-01-26T13:09:58.021Z","type":"new_feature","message":"New endpoints for managing **Facebook Pages** and **Google Business Profile locations**.\n\n`GET /v1/accounts/{accountId}/facebook-page`\nList all Facebook pages the connected account can access, see which one is currently selected.\n\n`GET /v1/accounts/{accountId}/gmb-locations`\nList all Google Business Profile locations available to the account.\n\n`PUT /v1/accounts/{accountId}/gmb-locations`\nChange the selected GBP location by passing `selectedLocationId` in the request body.\n\nUseful if you manage multiple pages/locations and need to switch between them programmatically.","platforms":["facebook","google-business"],"url":"https://docs.zernio.com/changelog#1769432998021"},{"id":"1769350724121","date":"2026-01-25T14:18:44.121Z","type":"improvement","message":"**POST /v1/posts** now returns `409 Conflict` when duplicate content is detected.\n\nIf you post identical content to the same account within 24 hours, you'll get:\n<pre>{\n  \"error\": \"This exact content was already posted...\",\n  \"details\": {\n    \"accountId\": \"...\",\n    \"platform\": \"...\",\n    \"existingPostId\": \"...\"\n  }\n}</pre>\n\nAdditionally, `429` responses now include detailed context about which limit was hit:\n• **Velocity limit**: 15 posts/hour per account\n• **Account cooldown**: Escalating delays (10min → 24h) after repeated errors\n• **Daily post limits**: Platform-specific (X: 20, Pinterest: 25, Instagram/Facebook: 100, Threads: 250, others: 50)\n\nNew headers on 429: `Retry-After`, `X-RateLimit-Limit`, `X-RateLimit-Remaining`\n\nThese changes also apply to `POST /v1/posts/bulk-upload` and `POST /v1/posts/{postId}/retry`.","platforms":["instagram","threads","x","pinterest","publishing"],"url":"https://docs.zernio.com/changelog#1769350724121"},{"id":"1769328298290","date":"2026-01-25T08:04:58.290Z","type":"new_feature","message":"YouTube uploads now support `madeForKids` in `platformSpecificData.youtube`.\n\nThis COPPA compliance field declares whether your video is child-directed content. Defaults to `false`.\n\nSet to `true` if your video is made for kids. Note: kid-directed videos have restricted features (no comments, no notifications, limited ad targeting).\n\n**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.","platforms":["youtube","ads","publishing"],"url":"https://docs.zernio.com/changelog#1769328298290"},{"id":"1769193707202","date":"2026-01-23T18:41:47.202Z","type":"improvement","message":"Analytics responses now include `latePostId` field.\n\nWhen 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`.\n\nThe field appears in both single post queries and paginated list responses. It's `null` for posts that weren't scheduled through Zernio.","platforms":["publishing","analytics"],"url":"https://docs.zernio.com/changelog#1769193707202"},{"id":"1769103690658","date":"2026-01-22T17:41:30.658Z","type":"breaking_change","message":"**LinkedIn headless OAuth flow updated**\n\nLinkedIn headless mode now uses a token-based approach to retrieve OAuth data, preventing URI_TOO_LONG errors for users with many organizations.\n\n**What changed:**\nAfter OAuth redirect, you now receive `pendingDataToken` instead of `tempToken`, `userProfile`, and `organizations` in the URL.\n\nCall the new endpoint to fetch the data:\n`GET /v1/connect/pending-data?token=PENDING_DATA_TOKEN`\n\nReturns: `tempToken`, `refreshToken`, `expiresIn`, `userProfile`, `organizations`, `selectionType`\n\n**Important:**\n• One-time use: data is deleted after fetching\n• Data expires after 10 minutes if not fetched\n• No authentication required, just the token\n\nOther platforms (Facebook, Pinterest, Google Business, Snapchat) are unchanged.","platforms":["linkedin","pinterest","snapchat"],"url":"https://docs.zernio.com/changelog#1769103690658"},{"id":"1768985178599","date":"2026-01-21T08:46:18.599Z","type":"new_feature","message":"Instagram Reels now support **thumbnail offset selection** via `thumbOffset` in `platformSpecificData`.\n\nSpecify a millisecond offset from the start of the video to use as the Reel cover image. Defaults to 0 (first frame).\n\n`thumbOffset`: integer, minimum 0 (milliseconds)\n\nIf you provide a custom thumbnail URL via `instagramThumbnail` in `mediaItems`, it takes priority and this offset is ignored.","platforms":["instagram"],"url":"https://docs.zernio.com/changelog#1768985178599"},{"id":"1768735141972","date":"2026-01-18T11:19:01.972Z","type":"new_feature","message":"New webhook event: `account.connected`\n\nYou can now subscribe to notifications when a social account is connected to a profile. The webhook payload includes `accountId`, `profileId`, `platform`, `username`, and `displayName`.\n\nAdd it to your `events` array when creating or updating webhooks.","platforms":["webhooks"],"url":"https://docs.zernio.com/changelog#1768735141972"},{"id":"1768490794315","date":"2026-01-15T15:26:34.315Z","type":"new_feature","message":"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`.","platforms":["youtube","analytics"],"url":"https://docs.zernio.com/changelog#1768490794315"},{"id":"1768474276246","date":"2026-01-15T10:51:16.246Z","type":"improvement","message":"Zernio now automatically compresses media that exceeds platform limits during publishing, simplifying the upload process. \n\nImage compression thresholds are set for platforms like `Twitter/X`: >5 MB, `Instagram`: >8 MB, `Facebook`: >10 MB, and more. \n\nVideo compression thresholds include `Twitter/X`: >512 MB, `Instagram Stories`: >100 MB, `Facebook`: >4 GB, among others.","platforms":["instagram","x","publishing"],"url":"https://docs.zernio.com/changelog#1768474276246"},{"id":"1768378032480","date":"2026-01-14T08:07:12.480Z","type":"breaking_change","message":"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.","platforms":["general"],"url":"https://docs.zernio.com/changelog#1768378032480"},{"id":"1768208688620","date":"2026-01-12T09:04:48.620Z","type":"improvement","message":"YouTube now supports **AI-generated content disclosure** via `containsSyntheticMedia` in `YouTubePlatformData`.\n\nSet `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.","platforms":["youtube"],"url":"https://docs.zernio.com/changelog#1768208688620"},{"id":"1767945131947","date":"2026-01-09T07:52:11.947Z","type":"improvement","message":"Instagram now supports **custom audio names** for Reels via `audioName` in `platformSpecificData`.\n\nYou 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.","platforms":["instagram","publishing"],"url":"https://docs.zernio.com/changelog#1767945131947"},{"id":"1767782636483","date":"2026-01-07T10:43:56.483Z","type":"improvement","message":"**InstagramPlatformData Schema Update**\n\nThe `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. \n\nHere are the details of the new `trialParams` field:\n- `trialParams`: _object_ (optional)\n  - `graduationStrategy`: _string_ (required)\n    - Possible values: `MANUAL`, `SS_PERFORMANCE`\n    - This field determines how a trial reel transitions to a regular reel:\n      - `MANUAL`: Requires manual graduation via the Instagram app.\n      - `SS_PERFORMANCE`: Automatically graduates based on performance metrics with non-followers.\n\nThe rest of the schema remains unchanged, including the parameters `contentType`, `shareToFeed`, `collaborators`, `firstComment`, and `userTags`. \n\nThis 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.","platforms":["instagram","publishing","analytics"],"url":"https://docs.zernio.com/changelog#1767782636483"},{"id":"1767629622312","date":"2026-01-05T16:13:42.312Z","type":"new_feature","message":"**New Endpoints Available:**\n\n1. `GET /v1/logs`\n   - 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. \n   - `status` (string, optional): Filter by log status (options: success, failed, pending, skipped, all).\n   - `platform` (string, optional): Filter by platform (options include tiktok, instagram, facebook, etc.).\n   - `action` (string, optional): Filter by action type (options: publish, retry, media_upload, etc.).\n   - `days` (integer, optional): Number of days to look back (max 7, default 7).\n   - `limit` (integer, optional): Maximum number of logs to return (max 100, default 50).\n   - `skip` (integer, optional): Number of logs to skip for pagination (default 0).\n   - The response includes an array of logs and pagination details.\n\n2. `GET /v1/logs/{logId}`\n   - Use this endpoint to retrieve detailed information about a specific log entry, including full request and response bodies for debugging purposes.\n   - `logId` (string, required): The ID of the log entry you want to retrieve.\n   - The response contains the log entry details, including the full context of the publishing attempt.\n\n3. `GET /v1/posts/{postId}/logs`\n   - This endpoint retrieves all publishing logs for a specific post, showing the complete history of publishing attempts across all platforms.\n   - `postId` (string, required): The ID of the post for which you want to retrieve logs.\n   - `limit` (integer, optional): Maximum number of logs to return (max 100, default 50).\n   - The response includes an array of logs specific to the post and the count of logs returned.","platforms":["instagram","tiktok"],"url":"https://docs.zernio.com/changelog#1767629622312"},{"id":"1767627368944","date":"2026-01-05T15:36:08.944Z","type":"new_feature","message":"**New Endpoints for Pinterest Integration**\n\nWe have added two new endpoints to enhance your integration with Pinterest in headless mode:\n\n1. `GET /v1/connect/pinterest/select-board`\n   - **Summary:** List Pinterest Boards after OAuth (Headless Mode)\n   - **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.\n   - **Parameters:**\n     - `X-Connect-Token` (header, required): Short-lived connect token from the OAuth redirect.\n     - `profileId` (query, required): Your Zernio profile ID.\n     - `tempToken` (query, required): Temporary Pinterest access token from the OAuth callback redirect.\n   - **Responses:**\n     - `200`: Returns a list of Pinterest boards available for connection, including each board's ID, name, description, and privacy setting.\n     - `400`: Missing required parameters.\n     - `401`: Unauthorized access.\n     - `403`: No access to profile.\n     - `500`: Failed to fetch boards.\n\n2. `POST /v1/connect/pinterest/select-board`\n   - **Summary:** Select a Pinterest Board to complete the connection (Headless Mode)\n   - **Description:** Use this endpoint to save the selected board and complete the Pinterest account connection after OAuth.\n   - **Request Body:** Must include the following fields:\n     - `profileId` (string, required): Your Zernio profile ID.\n     - `boardId` (string, required): The Pinterest Board ID selected by the user.\n     - `boardName` (string, optional): The board name for display purposes.\n     - `tempToken` (string, required): Temporary Pinterest access token from OAuth.\n     - `userProfile` (object, optional): User profile data from OAuth redirect.\n     - `refreshToken` (string, optional): Pinterest refresh token if available.\n     - `expiresIn` (integer, optional): Token expiration time in seconds.\n     - `redirect_url` (string, optional): Custom redirect URL after connection completes.\n   - **Responses:**\n     - `200`: Pinterest Board connected successfully, returns a message and account details.\n     - `400`: Missing required fields.\n     - `401`: Unauthorized access.\n     - `403`: No access to profile or profile limit exceeded.\n     - `500`: Failed to save Pinterest connection.","platforms":["pinterest"],"url":"https://docs.zernio.com/changelog#1767627368944"},{"id":"1767604059075","date":"2026-01-05T09:07:39.075Z","type":"improvement","message":"**GET /v1/accounts/{accountId}/linkedin-mentions**\n\nThis 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.\n\n**Parameters:**\n- `accountId` (path, required): The LinkedIn account ID.\n- `url` (query, required): LinkedIn profile URL, company URL, or vanity name. It supports:\n  - Person: `miquelpalet`, `linkedin.com/in/miquelpalet`\n  - Organization: `company/microsoft`, `linkedin.com/company/microsoft`\n- `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.\n\n**Response:**\nOn success (HTTP 200), the response contains:\n- `urn`: The LinkedIn URN (either for a person or organization).\n- `type`: The type of entity, which can be either `person` or `organization`.\n- `displayName`: The display name (provided, from API, or derived from vanity URL).\n- `mentionFormat`: Ready-to-use mention format for post content.\n- `vanityName`: The vanity name/slug (only for organization mentions).\n- `warning`: A warning about clickable mentions (only present for person mentions if `displayName` was not provided).\n\nThis 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.","platforms":["linkedin"],"url":"https://docs.zernio.com/changelog#1767604059075"},{"id":"1767285008197","date":"2026-01-01T16:30:08.197Z","type":"improvement","message":"**GET /v1/profiles**<br>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).<br><br>Response contains:<br>- `profiles`: an array of profile objects, each containing:<br>  - `_id`: string<br>  - `name`: string<br>  - `color`: string<br>  - `isDefault`: boolean<br>  - `isOverLimit`: boolean (only present when `includeOverLimit=true`)<br>  - `createdAt`: string (date-time)<br><br>**GET /v1/accounts**<br>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.<br><br>Response contains:<br>- `accounts`: an array of account objects, each containing:<br>  - `_id`: string<br>  - `platform`: string<br>  - `profileId`: object containing profile details<br>  - `username`: string<br>  - `displayName`: string<br>  - `profileUrl`: string<br>  - `isActive`: boolean<br>- `hasAnalyticsAccess`: boolean (indicates if the user has analytics add-on access)","platforms":["analytics"],"url":"https://docs.zernio.com/changelog#1767285008197"},{"id":"1735577237000","date":"2025-12-30T16:27:17.000Z","type":"improvement","message":"The `account.disconnected` webhook payload now includes additional fields for better context:\n\n- `accountId` - The account's unique identifier (same as used in `/v1/accounts/{accountId}`)\n- `profileId` - The profile's unique identifier this account belongs to\n- `disconnectionType` - Either `intentional` (user disconnected manually) or `unintentional` (token expired or was revoked)\n\nThese fields help you understand why an account was disconnected and quickly look up related data.","platforms":["webhooks"],"url":"https://docs.zernio.com/changelog#1735577237000"}]}