# Zernio API Documentation > Social media management API for scheduling, publishing, and analytics ## Documentation Pages - [Billing](https://docs.zernio.com/billing.mdx) - When your card is charged, and how daily proration and graduated tiers compute your invoice - [Changelog](https://docs.zernio.com/changelog.mdx) - Stay up to date with the latest API changes and improvements - [Glossary](https://docs.zernio.com/glossary.mdx) - Every Zernio term defined in two sentences - teams vs profiles vs accounts, accountId vs profileId, external posts, headless mode, and more. - [Quickstart](https://docs.zernio.com/.mdx) - Get started with the Zernio API - authenticate, connect accounts, and schedule your first post in minutes. - [Refer & earn](https://docs.zernio.com/refer-and-earn.mdx) - Earn 20% recurring commission for 12 months when you refer new customers to Zernio. - [Bluesky](https://docs.zernio.com/platforms/bluesky.mdx) - Schedule and automate Bluesky posts with Zernio API - Text posts, images, videos, threads, and App Password authentication - [Discord](https://docs.zernio.com/platforms/discord.mdx) - Send messages, DMs, embeds, polls, forum posts, threads, schedule events, and manage roles in Discord servers with Zernio API - No bot hosting, no gateway, no intents approval - [Facebook](https://docs.zernio.com/platforms/facebook.mdx) - Schedule and automate Facebook Page posts with Zernio API - Feed posts, Stories, multi-image, multi-link carousels, GIFs, and first comments - [Overview](https://docs.zernio.com/platforms.mdx) - Complete guide to all social media platforms supported by Zernio API - [Instagram](https://docs.zernio.com/platforms/instagram.mdx) - Schedule and automate Instagram posts with Zernio API - Feed, Stories, Reels, Carousels, collaborators, and user tags - [LinkedIn](https://docs.zernio.com/platforms/linkedin.mdx) - Schedule and automate LinkedIn posts with Zernio API - Personal profiles, company pages, images, videos, documents, and multi-organization posting - [OpenAI Ads](https://docs.zernio.com/platforms/openai-ads.mdx) - Run and measure ChatGPT ads via Zernio API - campaigns, reporting, pixels and server-side conversions - [Pinterest Ads](https://docs.zernio.com/platforms/pinterest-ads.mdx) - Create Promoted Pin campaigns via Zernio API - No Pinterest developer approval required - [Pinterest](https://docs.zernio.com/platforms/pinterest.mdx) - Schedule and automate Pinterest Pins with Zernio API - Image pins, video pins, boards, destination links, and cover images - [Reddit](https://docs.zernio.com/platforms/reddit.mdx) - Schedule and automate Reddit posts with Zernio API - Text posts, link posts, image posts, subreddit targeting, flair selection, and gallery posts - [Slack](https://docs.zernio.com/platforms/slack.mdx) - Schedule and publish messages to Slack channels with Zernio API - Threads, file uploads, per-message bot identity, and a realtime inbox your agent can reply from - [Snapchat](https://docs.zernio.com/platforms/snapchat.mdx) - Schedule and automate Snapchat posts with Zernio API - Stories, Saved Stories, Spotlight content, and Public Profile management - [Telegram](https://docs.zernio.com/platforms/telegram.mdx) - Schedule and automate Telegram channel and group posts with Zernio API - Text, images, videos, media albums, silent messages, and bot management - [Threads](https://docs.zernio.com/platforms/threads.mdx) - Schedule and automate Threads posts with Zernio API - Text, images, videos, carousels, and thread sequences - [TikTok Ads](https://docs.zernio.com/platforms/tiktok-ads.mdx) - Create campaigns and Spark Ads via Zernio API - No TikTok Business Center developer onboarding required - [TikTok](https://docs.zernio.com/platforms/tiktok.mdx) - Schedule and automate TikTok posts with Zernio API - Videos, photo carousels, privacy settings, and AI disclosure - [X Ads](https://docs.zernio.com/platforms/x-ads.mdx) - Create campaigns and promote tweets via Zernio API - OAuth 1.0a and approval handled for you - [YouTube](https://docs.zernio.com/platforms/youtube.mdx) - Schedule and automate YouTube video uploads with Zernio API - Videos, Shorts, thumbnails, visibility, and COPPA settings - [Open Source](https://docs.zernio.com/resources/open-source.mdx) - Open-source projects and OpenAPI specifications built with and for the Zernio API - [Create group](https://docs.zernio.com/account-groups/create-account-group.mdx) - Creates a new account group with a name and a list of social account IDs. Accounts can belong to different profiles; the caller must have access to every account's profile. Group names must be unique per user. - [Delete group](https://docs.zernio.com/account-groups/delete-account-group.mdx) - Permanently deletes an account group. The accounts themselves are not affected. - [List groups](https://docs.zernio.com/account-groups/list-account-groups.mdx) - Returns all account groups visible to the authenticated user. Groups can contain accounts from multiple profiles. For API keys scoped to specific profiles, only groups whose accounts all live in allowed profiles are returned. - [Update group](https://docs.zernio.com/account-groups/update-account-group.mdx) - Updates the name or account list of an existing group. You can rename the group, change its accounts, or both. - [Delete IG ice breakers](https://docs.zernio.com/account-settings/delete-instagram-ice-breakers.mdx) - Removes the ice breaker questions from an Instagram account's Messenger experience. - [Delete FB persistent menu](https://docs.zernio.com/account-settings/delete-messenger-menu.mdx) - Removes the persistent menu from Facebook Messenger conversations for this account. - [Delete TG bot commands](https://docs.zernio.com/account-settings/delete-telegram-commands.mdx) - Clears all bot commands configured for a Telegram bot account. - [Get IG ice breakers](https://docs.zernio.com/account-settings/get-instagram-ice-breakers.mdx) - Get the ice breaker configuration for an Instagram account. - [Get FB persistent menu](https://docs.zernio.com/account-settings/get-messenger-menu.mdx) - Get the persistent menu configuration for a Facebook Messenger account. - [Get TG bot commands](https://docs.zernio.com/account-settings/get-telegram-commands.mdx) - Get the bot commands configuration for a Telegram account. - [Set IG ice breakers](https://docs.zernio.com/account-settings/set-instagram-ice-breakers.mdx) - Set ice breakers for an Instagram account. Max 4 ice breakers, question max 80 chars. - [Set FB persistent menu](https://docs.zernio.com/account-settings/set-messenger-menu.mdx) - Set the persistent menu for a Facebook Messenger account. Max 3 top-level items, max 5 nested items. - [Set TG bot commands](https://docs.zernio.com/account-settings/set-telegram-commands.mdx) - Set bot commands for a Telegram account. - [Disconnect account](https://docs.zernio.com/accounts/delete-account.mdx) - Disconnects and removes a connected social account. - [Check account health](https://docs.zernio.com/accounts/get-account-health.mdx) - Returns detailed health info for a specific account including token status, permissions, and recommendations. - [Check accounts health](https://docs.zernio.com/accounts/get-all-accounts-health.mdx) - Returns health status of all connected accounts including token validity, permissions, and issues needing attention. - [Get follower stats](https://docs.zernio.com/accounts/get-follower-stats.mdx) - Returns follower count history and growth metrics for connected social accounts. Requires analytics add-on subscription. Follower counts are refreshed once per day. - [Check whether an Instagram user follows the account](https://docs.zernio.com/accounts/get-instagram-follow-status.mdx) - Resolves the follow relationship between an Instagram user and the connected account, plus their public profile counters. `userId` is the Instagram-scoped id (IGSID) Meta gives you on a webhook: `sender.id` on `message.received`, `comment.author.id` on `comment.received`. **Meta only answers for people who have MESSAGED the account.** Commenting grants no consent, so a commenter who has never DMed you is unresolvable - that is a platform rule, not a limitation of this endpoint. When it cannot be resolved the response is still `200` with `isFollower: null` and an `unavailableReason`, because "unknown" is a normal state to branch on: * `consent_required` - the user has never messaged this account. * `dm_access_disabled` - the account owner turned off Instagram Direct API access. * `not_messageable` - the id is not a messaging-scoped id. * `error` - a transient Graph API failure. To gate a comment automation on this, use the automation's `audience` rules instead of calling this per comment - they run the same lookup only on comments that actually match a keyword, and can ask the commenter to confirm with one tap. Answers are cached briefly per (account, user). Pass `refresh=true` right after asking someone to follow, so a follow from a moment ago is visible. - [Get Slack account settings](https://docs.zernio.com/accounts/get-slack-settings.mdx) - Returns the connected Slack channel details and the default message identity (name and avatar shown as the author on every post, with Slack's APP badge). The identity applies to messages only; the app's own Slack profile is global and cannot be changed per workspace. - [Get TikTok creator info](https://docs.zernio.com/accounts/get-tiktok-creator-info.mdx) - Returns TikTok creator details, available privacy levels, posting limits, and commercial content options for a specific TikTok account. Only works with TikTok accounts. - [List accounts](https://docs.zernio.com/accounts/list-accounts.mdx) - Returns connected social accounts. Only includes accounts within the plan limit by default. Follower data requires analytics add-on. Supports optional server-side pagination via page/limit params. When omitted, returns all accounts (backward-compatible). page and limit must be supplied together; out-of-range page/limit values are rejected with 400 rather than silently clamped. - [Move account to another profile](https://docs.zernio.com/accounts/move-account-to-profile.mdx) - Moves a connected social account to a different profile owned by the same user. The target profile must belong to the same user as the account. For API keys restricted to specific profiles, BOTH the source account's current profile AND the target profile must be in the key's allowed set. Calls with a target profile outside the key's scope return 403. - [Update account](https://docs.zernio.com/accounts/update-account.mdx) - Updates a connected social account's display name or username override. For X/Twitter accounts on usage-based billing, also accepts an `xCapabilities` object to toggle background API operations that incur X API pass-through costs. Both fields are opt-in (default `false`) — when off, no analytics syncs or DM polling are performed for that account, and no API call is metered for those operations. Publishing and deleting posts are always available regardless of these toggles. Setting `xCapabilities` on a non-X account returns 400. - [Update Slack account settings](https://docs.zernio.com/accounts/update-slack-settings.mdx) - Set or clear the default message identity for this channel. Empty string clears a field; per-post platformSpecificData.username/iconUrl still override these defaults. - [Create or reuse a custom conversion](https://docs.zernio.com/ad-accounts/create-custom-conversion.mdx) - Provision the Meta custom conversion an ads flow optimises toward, and hand back the `customConversionId` for `promotedObject.customConversionId` on POST /v1/ads/create. Removes the manual "create it in Ads Manager first" step. **Reuse is ours, not Meta's.** Meta's create is not idempotent, so a retried request would otherwise mint a duplicate carrying none of the original's optimisation history. A non-archived conversion with the same `name` on the same `pixelId` is returned instead of created, with `reused: true` and a 200 rather than a 201. `rule` is forwarded verbatim in Meta's own grammar (e.g. `{"url": {"i_contains": "thank-you"}}`); Meta validates it and rejects a malformed one with "A conversion rule is required at creation time". - [Schedule a budget increase](https://docs.zernio.com/ad-accounts/create-high-demand-period.mdx) - Pre-schedule a temporary budget increase (Black Friday, a launch, a sale) instead of editing the budget by hand on the day. Same target rule as the GET: exactly one of `campaignId` / `adSetId`. Two Meta constraints worth knowing before you call it. `timeStart` / `timeEnd` must fall on a 15-minute boundary, and a campaign cannot mix `ABSOLUTE` and `MULTIPLIER` across its schedules — the second type is rejected with "Can't mix your budget scaling selection". Window rules (must sit inside the campaign's run dates, minimum lead time, no overlap) are Meta's and its message is forwarded verbatim. - [Create a value rule set](https://docs.zernio.com/ad-accounts/create-value-rule-set.mdx) - Creates a value rule set on the ad account (Meta's `POST /act_X/value_rule_set`). Attach the returned id to an ad set with `valueRuleSetId` on `POST /v1/ads/create` or `PUT /v1/ads/ad-sets/{adSetId}`. **Rule order is semantic**: rules are evaluated in array order and only the first matching rule adjusts the bid for an overlapping audience. `adjustValue` is an unsigned magnitude in percent; the direction lives in `adjustSign`. `INCREASE` accepts 1-1000, `DECREASE` accepts 1-90. There is no signed field and 0 is out of range. `criteriaValueTypes` is positionally paired with `criteriaValues` (same length, same order). Every type is the literal `"NONE"` except on `LOCATION`, which uses `LOCATION_COUNTRY` / `LOCATION_REGION` / `LOCATION_CITY` / `LOCATION_COMSCORE_MARKET` and may mix them within one criterion. Location values are Targeting-Search keys: a two-letter country code for `LOCATION_COUNTRY`, a numeric key for the rest. `LOCATION_DMA` was replaced by `LOCATION_COMSCORE_MARKET` on 2026-06-22 and rules using DMAs are no longer active, so this API rejects it. `AUDIENCE_LABEL` values (e.g. `HIGH_VALUE`) are applied to a Custom Audience in Ads Manager. There is no API to provision them, so label strings are passed through unvalidated and a typo produces a rule that never fires. Ads Manager turns a rule set read-only (this API stays editable) when a rule uses more than 2 criteria, a custom age range, or the placements `FB_MARKETPLACE`, `FB_SEARCH`, `FB_VIDEO` or `IG_EXPLORE`. Limits: 6 rule sets per ad account, 10 rules per set, 4 criteria per rule. The per-account cap is enforced by Meta, not here. - [Delete a value rule set](https://docs.zernio.com/ad-accounts/delete-value-rule-set.mdx) - Deletes the rule set (Meta's `POST /{value-rule-set-id}/delete_rule_set`, a custom action edge rather than an HTTP DELETE on its side). Ad sets pointing at it are not modified here; detach them first with `valueRulesApplied: false` on `PUT /v1/ads/ad-sets/{adSetId}`. - [Ad account finances](https://docs.zernio.com/ad-accounts/get-ad-account-finance.mdx) - Finances of one Meta ad account: prepaid `balance`, lifetime `amountSpent`, account `spendCap` (null = no cap) and the `fundingSource`. Money values are converted from Meta's minor units to whole units of `currency`. - [List comments on an ad](https://docs.zernio.com/ad-accounts/get-ad-comments.mdx) - Returns comments on an ad's underlying creative post. Useful for moderating or analyzing engagement on dark posts (ad creatives that never went live organically), which the regular GET /v1/inbox/comments/{postId} endpoint cannot serve because dark posts are not in Zernio's post database. An ad that runs on both Facebook feed and Instagram feed has two separate underlying posts with separate comment threads (the creative's effective_object_story_id and effective_instagram_media_id). Use the `placement` query param to pick one; with no param the Instagram side is returned when it exists, otherwise Facebook. The identifiers are read from the ad record (persisted during sync) with a Marketing-API fallback for ads that predate the field. For Instagram-placed comments, the Instagram account that runs the ad must be connected to Zernio — those comments are read through that account's token. If no connected Instagram account on the profile can read the ad's media, the call returns ads_connection_required (the Facebook side, if any, is still readable via ?placement=facebook). Meta-only. Other ad platforms (TikTok, LinkedIn, Pinterest, Google, X) do not expose a public per-ad comments API and return feature_not_available. Requires the Ads add-on. Response shape matches GET /v1/inbox/comments/{postId}. The `{adId}` path segment accepts any identifier dialect Zernio indexes for the ad: Zernio internal `_id` (24-char hex), Meta's numeric `platformAdId` (the value shipped in `comment.received` webhooks as `comment.ad.id`), or the creative's `effective_object_story_id` / `effective_instagram_media_id`. Caller doesn't need a translation step. - [Ad account change / audit log](https://docs.zernio.com/ad-accounts/get-ads-activity-log.mdx) - Account-level audit log from Meta's `/act_X/activities`: who changed what and when (creates, edits, status flips, budget changes...) with Meta's translated event names and the structured before/after in `extra_data`. Rows are returned verbatim. Meta has no server-side per-object filter on this edge, so `objectId` filters the returned page client-side (combine with paging to walk history for one campaign/ad set/ad). - [Get ad account DSA defaults](https://docs.zernio.com/ad-accounts/get-dsa-defaults.mdx) - Returns the default DSA beneficiary and payor currently set on a Meta ad account, whether they were set via `PATCH /v1/ads/accounts` or in Meta Ads Manager. Fields are omitted when no default is configured. Meta accounts only. - [List DSA beneficiary/payor suggestions](https://docs.zernio.com/ad-accounts/get-dsa-recommendations.mdx) - Returns Meta's suggested beneficiary/payor names for an ad account, derived by Meta from the account's recent activity. Useful for prefilling `dsaBeneficiary`/`dsaPayor` inputs, or the defaults sent to `PATCH /v1/ads/accounts`, in your own UI. Meta returns a single flat list. Entries are not labeled as beneficiary or payor, and since these are legal disclosures Zernio never applies them automatically: let your user pick the right entity. The list may be empty for accounts with little activity. Meta accounts only. - [Read a value rule set](https://docs.zernio.com/ad-accounts/get-value-rule-set.mdx) - Reads one value rule set including every nested rule id and criterion id. This is step one of any edit: `PUT` is a full replace, so you need the ids before you can keep the objects you are not changing. Meta's own read returns `GENDER` values lowercase (`"male"`) while writes require `"MALE"`. Values are passed through untouched, so never case-compare a stored rule against a fetched one. - [List ad accounts](https://docs.zernio.com/ad-accounts/list-ad-accounts.mdx) - Returns the platform ad accounts available for the given social account (e.g. Meta ad accounts, TikTok advertiser IDs, Google Ads customer IDs). For TikTok agencies: enumerates every advertiser under every Business Center the token can read (paginated server-side), then chunks the lookup against TikTok's `/advertiser/info/` endpoint (which has a per-call cap of ≤100 IDs). Solo advertisers without a BC fall back to the OAuth-time `advertiser_ids` list. Cached for 1h on the SocialAccount; lazy-refreshed on first call after expiry. For Google Ads: responds `429` when Google's API quota is temporarily exhausted (instead of an empty list). Retry after a delay. - [Ad labels](https://docs.zernio.com/ad-accounts/list-ad-labels.mdx) - Lists the ad account's organizational labels (Meta's `/act_X/adlabels`), rows returned verbatim (id, name, created/updated time). - [A/B tests and lift studies](https://docs.zernio.com/ad-accounts/list-ad-studies.mdx) - Lists the ad account's A/B tests and lift studies (Meta's `/act_X/ad_studies`), rows returned verbatim. The default projection covers id, name, type, timing and cells with split percentages; `fields` is a raw-passthrough override. - [List TikTok Business Centers](https://docs.zernio.com/ad-accounts/list-ads-business-centers.mdx) - Returns the TikTok Business Centers (BCs) the connected `tiktokads` account can read. Each BC reports its advertiser count so callers can build agency-style pickers without re-walking `/v1/ads/accounts` per BC. TikTok-only. Solo advertisers (non-agency tokens) return an empty array. - [List custom conversions](https://docs.zernio.com/ad-accounts/list-custom-conversions.mdx) - The ad account's Meta custom conversions, including archived ones (`isArchived`). - [High demand periods / budget schedules](https://docs.zernio.com/ad-accounts/list-high-demand-periods.mdx) - Scheduled budget increases (Meta's budget-scheduling API). The Graph edge lives on the campaign and ad-set nodes only, so exactly one of `campaignId` / `adSetId` (platform ids) is required. Rows returned verbatim (budget_value, budget_value_type, time window, recurrence). - [Businesses list](https://docs.zernio.com/ad-accounts/list-meta-businesses.mdx) - Business Manager portfolios the connected Meta user belongs to (Meta's `/me/businesses`), rows returned verbatim (id, name, verification_status, created_time). Token-scoped, so no `adAccountId` is needed. For TikTok Business Centers use `GET /v1/ads/business-centers`. - [List value rule sets](https://docs.zernio.com/ad-accounts/list-value-rule-sets.mdx) - Lists the ad account's value rule sets (Meta's `/act_X/value_rule_set`). A value rule set adjusts the auction bid up or down for audience segments you value differently; attach one to an ad set with `valueRuleSetId` on `POST /v1/ads/create` or `PUT /v1/ads/ad-sets/{adSetId}`. Rows are returned in the same camelCase shape the `PUT` body takes, ids included, so a set round-trips 1:1: **the update is a full replace, not a patch**, so you GET, mutate and send the whole thing back. Limits: 6 rule sets per ad account, 10 rules per set, 4 criteria per rule. **Rule order is semantic.** Rules are evaluated in array order and only the FIRST matching rule adjusts the bid for an overlapping audience. The order you send is the order that is stored and returned. Eligibility: value rule sets apply only to ad sets on the `LOWEST_COST_WITHOUT_CAP` (auto-bid) or `COST_CAP` bid strategies. Meta rejects the rest server-side. - [Update ad account settings](https://docs.zernio.com/ad-accounts/update-ad-account.mdx) - Sets the default DSA beneficiary and payor on a Meta ad account (EU DSA, Article 26). Set them once and every EU-targeted call to `/v1/ads/create`, `/v1/ads/boost` and `/v1/ads/ctwa` on that ad account can omit `dsaBeneficiary`/`dsaPayor`: Meta applies the defaults automatically. The values are written to the ad account on Meta, the same setting Ads Manager edits. Nothing is stored in Zernio, and defaults already set in Ads Manager work identically. Zernio never guesses these values for you. Beneficiary and payor are legal disclosures shown to EU users, so you must provide the entity names explicitly. Use `GET /v1/ads/dsa-recommendations` to offer suggestions in your UI. If `defaultDsaPayor` is omitted, the beneficiary is also set as the payor, which covers the common case where the same entity benefits from and pays for the ads. Read the current values back with `GET /v1/ads/dsa-defaults`. Currently supported for Meta accounts only; other platforms return 400. - [Replace a value rule set](https://docs.zernio.com/ad-accounts/update-value-rule-set.mdx) - **THIS IS A FULL REPLACE, NOT A PATCH.** Meta's update is declarative: the body you send becomes the rule set. - `GET /v1/ads/value-rule-sets/{valueRuleSetId}` FIRST. - Keep a rule or criterion by echoing its `id`. - Create one by including the object WITHOUT an `id`. - Delete one by OMITTING it from the array. There is no warning and no undo. `name` and `rules` are both required for exactly this reason: a partial body would silently destroy every rule left out. **Rule order is semantic**: the array order you send is the evaluation order, and only the first matching rule adjusts the bid for an overlapping audience. Existing rule sets created elsewhere may contain `LOCATION_DMA` criteria. Those went inert on 2026-06-22 and are rejected here; migrate them to `LOCATION_COMSCORE_MARKET`. - [Add users to audience](https://docs.zernio.com/ad-audiences/add-users-to-ad-audience.mdx) - Upload user data to a customer_list audience. Data is SHA256-hashed server-side before sending to the platform. Email is used on every platform; phone is used on Meta only (other platforms ignore it). On TikTok and Pinterest, the first upload also provisions the audience (deferred create). LinkedIn uploads are full-replace. Max 10,000 users per request. - [Create custom audience](https://docs.zernio.com/ad-audiences/create-ad-audience.mdx) - Create a custom audience. `customer_list` is supported on Meta, Google, X, LinkedIn, TikTok, and Pinterest; `website` and `lookalike` are Meta-only. `saved_targeting` stores a reusable TargetingSpec (no member upload, no adAccountId) that you reference later via `savedTargetingId` on `POST /v1/ads/create`. Upload-backed audiences are created empty, add members via `POST /v1/ads/audiences/{audienceId}/users`. On TikTok and Pinterest the audience is provisioned lazily on the first member upload (until then its status is `pending`). Create is not idempotent, never auto-retry. - [Delete custom audience](https://docs.zernio.com/ad-audiences/delete-ad-audience.mdx) - Deletes the audience from both the platform and the local database. `saved_targeting` audiences exist only on Zernio, so only the local record is removed. - [Get audience details](https://docs.zernio.com/ad-audiences/get-ad-audience.mdx) - Returns the local audience record and fresh data from Meta (if available). - [List custom audiences](https://docs.zernio.com/ad-audiences/list-ad-audiences.mdx) - Returns custom audiences for the given ad account. Supports Meta, Google, TikTok, Pinterest, LinkedIn, and X (Twitter). - [Update an audience](https://docs.zernio.com/ad-audiences/update-ad-audience.mdx) - Update an audience. `saved_targeting` audiences accept `name`, `description`, and `spec` (full replacement, no merge, Zernio-only, no platform call). Platform audiences (uploaded/website/lookalike) accept `name` and `description` only, updated on the platform first and then mirrored locally; their rules are immutable, so `spec` returns 400 for them. Platform audience updates are Meta-only for now (other platforms return 501). Ads already created from a saved_targeting audience are unaffected, they snapshot the targeting at creation. - [Boost post as ad](https://docs.zernio.com/ad-campaigns/boost-post.mdx) - Creates a paid ad from an existing published post, keeping the post's engagement. By default it provisions the whole hierarchy (campaign, ad set, ad). **Attach shape (Meta).** Send `adSetId` to put the ad under an EXISTING ad set instead, so that ad set keeps its learning phase. It then owns `budget`, `schedule` and `targeting`, and sending any of those alongside `adSetId` is a 400 rather than a silent drop. `budget` is required only without `adSetId`. `instagramAccountId`, `destinationType` and `adSetId` are Meta-only and return 400 on other platforms. **Retries.** Boosts are NOT idempotent and can take minutes when Meta requires re-hosting an Instagram video, so do not retry on client timeout. Send an Idempotency-Key header to make retries safe: same key and body replays the original 201, and distinct keys always create distinct ads. Without the header, an identical request is treated as a retry: while one is in flight it returns 409, and within 10 minutes of a completed boost it returns the already-created ad instead of creating another. To intentionally duplicate an ad, send distinct Idempotency-Keys (or vary the body, e.g. the name). - [Pause or resume many campaigns](https://docs.zernio.com/ad-campaigns/bulk-update-ad-campaign-status.mdx) - Process up to 50 campaigns in one call. Each campaign is updated concurrently and the response contains a per-campaign result so a single bad row does not fail the whole batch. - [Create a standalone campaign](https://docs.zernio.com/ad-campaigns/create-ad-campaign.mdx) - Creates a campaign WITHOUT its first ad set / ad (the ODAX shell only). Ad sets join it later via `existingCampaignId` on the create endpoints. A budget here is campaign-level (CBO) by definition; omit it for ABO (each ad set carries its own budget). Created `PAUSED` unless `status: ACTIVE`. The campaign materializes in `/v1/ads/tree` via the next sync discovery pass. **Idempotency:** send an `Idempotency-Key` header to make retries safe. - [Create standalone ad](https://docs.zernio.com/ad-campaigns/create-standalone-ad.mdx) - Creates a paid ad with custom creative across Meta, Google Ads, Pinterest, TikTok, X/Twitter, LinkedIn, and OpenAI Ads (ChatGPT Ads). Supports three mutually-exclusive request shapes selected by the body, a legacy single-creative shape (all platforms, default), a Meta-only multi-creative shape via the creatives array (one ad set with N ads sharing budget and targeting), and an attach shape via adSetId that adds one new ad to an existing ad set, inheriting its budget, targeting, and schedule (Meta, TikTok, and LinkedIn; on LinkedIn adSetId is the existing Campaign id, and the budget, schedule, targeting and bidding fields must be omitted). Per-platform required fields, budget minimums, and video-ad rules are documented on each property below. LinkedIn creates a Single Image or Single Video Ad backed by a Direct Sponsored Content "dark post" authored by a Company Page (see `organizationId`); supported goals are engagement, traffic, awareness, and video_views (video ads use the `video` field; video_views requires a video), and traffic ads require `linkUrl`. **Idempotency:** this endpoint is not idempotent at the platform level (a blind retry creates a second campaign/ad set/ad). Send an `Idempotency-Key` header to make retries safe: the first request with a given key creates the ad and we store the response; a retry with the same key replays that exact response (with `Idempotent-Replayed: true`) instead of creating duplicates. Reusing a key with a different body returns 422; a key whose first request is still in flight returns 409 (retry after a short backoff). Keys are scoped to your credential and expire after 24h. - [Delete a campaign](https://docs.zernio.com/ad-campaigns/delete-ad-campaign.mdx) - Deletes the whole campaign on the platform, cascading to its ad sets and ads. Locally, all Ad documents for this campaign are marked `status: cancelled`. **Empty campaigns.** A campaign with zero ads has no local Ad documents to resolve, so it is invisible to `/v1/ads/tree` and this endpoint would 404. That state is produced by the two-step create flow (campaign, then ads via `existingCampaignId`) whenever Meta rejects the ad step. To delete such a shell, send `accountId` in the body: we skip the local lookup entirely and forward the delete to Meta. `accountId` is ignored when the campaign does have ads. - [Cancel an ad](https://docs.zernio.com/ad-campaigns/delete-ad.mdx) - Cancels the ad on the platform and marks it as cancelled in the database. The ad is preserved for history. OpenAI Ads has no delete API; the ad is archived instead (a terminal state, the closest equivalent). - [Duplicate a campaign](https://docs.zernio.com/ad-campaigns/duplicate-ad-campaign.mdx) - Duplicates a campaign, including its ad sets, ads, creatives, and targeting by default (`deepCopy: true`). The copy is created paused so callers can review before launching. Per-platform implementation: - **Meta** uses the native `POST /{campaign-id}/copies` endpoint. - **TikTok** has no native copy primitive; Zernio walks the source graph (`/v2/campaign/get/`, `/v2/adgroup/get/`, `/v2/ad/get/`) and recreates each entity via the corresponding `/create/` endpoints, carrying over budget / targeting / bid_type / bid_price / deep_bid_type / creative fields. Spark Ad linkage (`tiktok_item_id`) is preserved. - **LinkedIn** has no native copy primitive; Zernio walks the source CampaignGroup → Campaigns → Creatives and recreates each entity, carrying over `type` / `costType` / `unitCost` / `optimizationTargetType` / `creativeSelection` / `objectiveType` / `format` / `dailyBudget` / `totalBudget` / `targetingCriteria` / `runSchedule` and every Creative's `content` object verbatim. `statusOption: INHERITED_FROM_SOURCE` is evaluated **per entity**: any Group / Campaign / Creative whose source is `ACTIVE` gets its clone activated too. Duplicating an ACTIVE campaign with `INHERITED_FROM_SOURCE` starts a second front of spend the moment the clone activates — the safe default is `PAUSED`. The new hierarchy is asynchronous to materialize in our DB — we trigger sync discovery automatically. Set `syncAfter: false` to skip and poll `/v1/ads/tree` on your own cadence. Other platforms return 501 Not Implemented. - [Duplicate an ad set](https://docs.zernio.com/ad-campaigns/duplicate-ad-set.mdx) - Duplicates an ad set, including its ads and creatives by default (`deepCopy: true`), via Meta's native `POST /{adset-id}/copies`. The copy is created paused so callers can review before launching. `campaignId` retargets the copy into another campaign; omitted = the source's own campaign. The new hierarchy materializes asynchronously — sync discovery is triggered automatically (`syncAfter: false` to skip). - [Duplicate an ad](https://docs.zernio.com/ad-campaigns/duplicate-ad.mdx) - Duplicates a single ad via Meta's native `POST /{ad-id}/copies`. The copy is created paused. `adSetId` retargets the copy into another ad set; omitted = the source's own ad set. Accepts the Zernio ad id or the platform ad id. Sync discovery is triggered automatically (`syncAfter: false` to skip). - [Live ad-set details incl. learning phase](https://docs.zernio.com/ad-campaigns/get-ad-set-details.mdx) - Reads the ad set live from Meta, returned verbatim. The default projection includes `learning_stage_info` (learning-phase status: LEARNING / SUCCESS / FAIL / WAIVING — Meta omits its `status` key on paused ad sets), delivery settings, budgets, schedule and targeting. `fields` is a raw-passthrough override; unknown fields return Meta's 400 verbatim. - [Get campaign tree](https://docs.zernio.com/ad-campaigns/get-ad-tree.mdx) - Returns a nested Campaign > Ad Set > Ad hierarchy with rolled-up metrics at each level. Uses a two-stage aggregation: ads are grouped into ad sets, then ad sets into campaigns. Metrics are computed over an optional date range, then rolled up from ad level to ad set and campaign levels. Pagination is at the campaign level. Ads without a campaign or ad set ID are grouped into synthetic "Ungrouped" buckets. If no date range is provided, defaults to the last 90 days. Date range is capped at 730 days max. Pass `timeIncrement=1` to also get a daily breakdown: each node gains a `daily[]` array of per-day metrics (same fields as the aggregated `metrics`) in the same call. Use `dailyLevel` (`campaign` default, or `adset` / `ad`) to choose which levels carry the series. This replaces calling the tree once per day for per-campaign daily trends. **Deleted objects stay in the tree.** Deleting an ad or a campaign is a soft delete: the Ad documents move to `status: cancelled` and are kept indefinitely, so their historical spend still counts toward the metrics of any date range they fall in. There is no pruning job and no retention window. Filter on `status` if your view should hide them, but do that after reading the totals, not before. - [Get ad details](https://docs.zernio.com/ad-campaigns/get-ad.mdx) - Returns an ad with its creative, targeting, status, and performance metrics. The `{adId}` path segment accepts any identifier dialect Zernio indexes for the ad: - the Zernio internal `_id` (24-char hex) - Meta's numeric `platformAdId` (the value shipped in `comment.received` webhooks as `comment.ad.id`) - the creative's `effective_object_story_id` (`{pageId}_{postId}` shape, Facebook side) - the creative's `effective_instagram_media_id` (Instagram side) Any of the four resolve to the same ad. Caller doesn't need a translation step. - [Get daily account metrics](https://docs.zernio.com/ad-campaigns/get-ads-timeline.mdx) - Returns daily aggregate metrics across all ads in a SocialAccount as a single time series — one row per calendar day in the requested range. Use this for dashboards that draw a daily-spend or daily-conversions chart, instead of calling `/v1/ads/tree` once per day. `accountId` is required. The lookup is sibling-expanded so passing the `metaads` ID also includes ads under the linked `facebook` / `instagram` posting account (and vice-versa) — same convention as `/v1/ads/tree` and `/v1/ads`. Date range defaults to the last 90 days. Capped at 730 days. Ranges older than the ingested history return a `202` immediately with the covered part and `backfillPending: true` while the rest is backfilled in the background; repeat the request shortly until it returns 200 with full data. - [List campaigns](https://docs.zernio.com/ad-campaigns/list-ad-campaigns.mdx) - Returns campaigns as virtual aggregations over ad documents grouped by platform campaign ID. Metrics (spend, impressions, clicks, etc.) are summed across all ads in each campaign. Campaign status is derived from child ad statuses (active > pending_review > paused > error > completed > cancelled > rejected). - [List Search keywords](https://docs.zernio.com/ad-campaigns/list-ad-keywords.mdx) - Returns the Google Search keyword criteria (positive and negative) synced from connected Google Ads accounts, one row per ad-group keyword. Refreshed about once a week per Google Ads customer (the keyword sweep rides the ads discovery pass on a slower slot, to stay inside Google's shared daily API quota), so keywords added on Google can take several days to appear. A customer synced for the first time is populated on the next discovery pass rather than waiting for its weekly slot, and connecting an account or triggering a manual sync refreshes it immediately. Campaign-level negative keywords are not included; only ad-group-level criteria are. - [List ads](https://docs.zernio.com/ad-campaigns/list-ads.mdx) - Returns a paginated list of ads with metrics computed over an optional date range. Use source=all to include externally-synced ads from platform ad managers. If no date range is provided, defaults to the last 90 days. Date range is capped at 730 days max. To find the Zernio ad behind a comment you see in Meta Business Manager, filter by platformAdId (the Meta ad ID), effectiveObjectStoryId (Facebook), or effectiveInstagramMediaId (Instagram) — those are the post/media the ad's engagement lives on, and are also returned on each ad's `creative` object. Then call GET /v1/ads/{adId}/comments with the returned ad id. - [Pause or resume a campaign](https://docs.zernio.com/ad-campaigns/update-ad-campaign-status.mdx) - Updates the status of all ads in a campaign. Makes one platform API call (not per-ad) since status cascades through the campaign hierarchy. Ads in terminal statuses (rejected, completed, cancelled) are automatically skipped. - [Update a campaign](https://docs.zernio.com/ad-campaigns/update-ad-campaign.mdx) - Campaign-level edits. Send at least one of `budget`, `bidStrategy`, `name` or `platformSpecificData`. An unsupported field is always an error, never a silent drop. | Body field | Meta | Google | Others | |---|---|---|---| | `bidStrategy` | Yes | Yes | 501 | | `bidAmount`, `roasAverageFloor` | 400 — ad-set level | Yes | 400 | | `budget` (CBO; ABO returns 409) | Yes | 501 | 501 | | `name` | Yes | 501 | 501 | | `platformSpecificData.spendCap` | Yes | 400 | 400 | | `accountId` (empty campaigns) | Yes | - | - | Google maps the shared enum onto its own strategies: `LOWEST_COST_WITHOUT_CAP` to Maximize Clicks, `LOWEST_COST_WITH_BID_CAP` to Maximize Clicks with a max CPC (`bidAmount`), `COST_CAP` to Target CPA (`bidAmount`), `LOWEST_COST_WITH_MIN_ROAS` to Target ROAS (`roasAverageFloor`). A campaign on a PORTFOLIO bidding strategy is rejected: detach it in Google Ads first, since it is shared across campaigns. `accountId` forwards the update straight to Meta for a campaign with zero ads, which would otherwise 404; the response then carries `updated: 0`. - [Pause or resume a single ad set](https://docs.zernio.com/ad-campaigns/update-ad-set-status.mdx) - Ad-set-scoped pause/resume (doesn't touch sibling ad sets). Thin wrapper over PUT /v1/ads/ad-sets/{adSetId} for callers that only want the status toggle and prefer a symmetric URL to /v1/ads/campaigns/{campaignId}/status. - [Update an ad set](https://docs.zernio.com/ad-campaigns/update-ad-set.mdx) - Ad-set-level writes. Use this for ABO budget updates, ad-set-scoped pause/resume, bid-strategy edits, Meta value-rule-set attach/detach, and Meta-only post-launch delivery settings via `platformSpecificData`. At least one updatable field is required. Value rule sets (Meta only, see `/v1/ads/value-rule-sets`): - ATTACH or REPLACE: send `valueRuleSetId`. Attachment is driven by the id's presence, so `valueRulesApplied: true` is optional. Sending a different id replaces the previous association; there is no separate replace call. - DETACH: send `valueRulesApplied: false` and OMIT `valueRuleSetId`. - Sending `valueRulesApplied: false` TOGETHER with `valueRuleSetId` returns 400 `mutually_exclusive_fields`. This is deliberate: Meta attaches the rule set whenever `value_rule_set_id` is present, even with `value_rules_applied` false, so echoing stored state while asking to detach would silently keep the bid adjustments live. - Eligibility: only ad sets on `LOWEST_COST_WITHOUT_CAP` or `COST_CAP`. Meta rejects the rest server-side. - Read back with `GET /v1/ads/ad-sets/{adSetId}?fields=value_rule_set_id`. Meta does not document `value_rules_applied` as a readable ad-set field, so the boolean cannot be read back. Bid strategy compatibility (per Meta's spec): - `LOWEST_COST_WITHOUT_CAP`: no `bidAmount`, no `roasAverageFloor`. - `LOWEST_COST_WITH_BID_CAP` / `COST_CAP`: `bidAmount` REQUIRED (whole currency units). - `LOWEST_COST_WITH_MIN_ROAS`: `roasAverageFloor` REQUIRED (decimal multiplier, e.g. 2.0 = 2.0x ROAS). - Meta only: send `bidAmount` WITHOUT `bidStrategy` to change the cap amount on an ad set under a COST_CAP / LOWEST_COST_WITH_BID_CAP parent campaign, leaving the strategy itself (inherited from the campaign) untouched. `roasAverageFloor` without `bidStrategy` is rejected (it has no meaning outside LOWEST_COST_WITH_MIN_ROAS). Delivery settings are validated by Meta against the campaign objective; incompatible combinations (e.g. a billingEvent the optimization goal doesn't allow) surface as 400s from Meta. When updating `budget` on an ABO campaign: if the parent campaign is CBO, the response is 409 with code BUDGET_LEVEL_MISMATCH — route to PUT /v1/ads/campaigns/{campaignId} instead. - [Pause or resume a single ad](https://docs.zernio.com/ad-campaigns/update-ad-status.mdx) - Ad-scoped pause/resume — touches ONLY this ad, never its parent ad set or campaign (so sibling ads keep running). Thin wrapper over the `status` field of PUT /v1/ads/{adId}, for callers that want a URL symmetric to /v1/ads/campaigns/{campaignId}/status and /v1/ads/ad-sets/{adSetId}/status. `{adId}` accepts the same identifier dialects as GET/PUT /v1/ads/{adId} (Zernio hex `_id`, Meta numeric `platformAdId`, or the creative's effective story/media IDs). `platform` is inferred from the ad, so it's not required in the body. Ads in terminal statuses (rejected, completed, cancelled) and no-op flips (already in the target state) are skipped. - [Update ad](https://docs.zernio.com/ad-campaigns/update-ad.mdx) - Patch one or more fields on an ad. Status, budget, targeting, and creative changes are propagated to the platform. Per-platform support: - **Meta** (Facebook + Instagram): all fields supported. - **TikTok**: status, budget, targeting (via `/v2/adgroup/update/`), and creative (via `/v2/ad/update/` patch-style — `headline` is ignored, `body` becomes `ad_text`). - **Google**: status, budget, and KEYWORD edits via `targeting.keywords` / `targeting.negativeKeywords` — each list you send becomes the FULL new set of its kind on the ad group (criteria not in the list are removed); a kind left out is untouched. Any other `targeting` field returns 400: Google cannot mutate broad targeting post-create without recreating the campaign. `creative` returns 501. - **Pinterest / X / LinkedIn / OpenAI Ads**: status + budget only. Sending `targeting` or `creative` returns 501 with code `unsupported_platform_operation`. OpenAI Ads budget is lifetime-only (see `budget.type` below). - [Create a standalone creative](https://docs.zernio.com/ad-creatives/create-ad-creative.mdx) - Creates a creative in the library WITHOUT an ad, reusable on the create endpoints via `existingCreativeId`. Provide exactly one of `imageUrl` (uploaded server-side), `imageHash` (from POST /v1/ads/images or the library list), or `carouselCards` (2-10 hand-built cards). The Page (and linked Instagram account, when present) is resolved from `accountId` as the story actor. - [Delete a creative](https://docs.zernio.com/ad-creatives/delete-ad-creative.mdx) - Deletes a creative from the library. Meta only allows deleting creatives not referenced by any ad — otherwise its 400 surfaces verbatim. - [Render pre-create ad previews](https://docs.zernio.com/ad-creatives/generate-ad-previews.mdx) - Renders how a creative would look per placement BEFORE any ad exists, via Meta's `/generatepreviews`. Provide exactly one creative source: `existingCreativeId` or `creativeSpec`. Each preview is an HTML `