Zernio
Zernio
Overview

Social Posting

XInstagramFacebookLinkedInTikTokYouTubePinterestRedditBlueskyThreadsGoogle Business ProfileSnapchat

Messaging

WhatsAppRCSiMessageTelegramDiscordSlack

Telephony

Phone NumbersVoice & CallsSMS

Advertising

Meta AdsGoogle AdsLinkedIn AdsTikTok AdsPinterest AdsX AdsOpenAI AdsReddit Ads

Blogs & Commerce

ShopifyWordPress
Dashboard
llms.txtOpenAPI
OverviewPlatformsAPI ReferenceResources

X Ads

Create campaigns, promote posts you already published and upload Tailored Audiences on an xads account, with OAuth 1.0a signing handled server-side.


Create a campaign with POST /v1/ads/create, or promote a post you already published with POST /v1/ads/boost, on an xads account for X (platform value twitter). Zernio signs every X Ads call with OAuth 1.0a on its own servers, so your integration sends $ZERNIO_API_KEY the same way it does for every other platform.

Quick reference

PropertyValue
HierarchyCampaign > Line Item > Promoted Tweet, created in one call
Goals (goal)engagement, traffic, awareness, video_views, app_promotion
Ad textbody, 280 characters including the shortened link
Rejected creative fieldsheadline, description, imageUrl, video, callToAction (return 400)
MediaNone on a create (the ad is a text post); a boost carries the media of the post it promotes
TargetingCountry, region, metro, city, postal code, interests, custom audiences, age (rounded out to X's fixed ranges), gender, language
AudiencesTailored Audiences (create, member upload, read)
AnalyticsYes
Edits after creationStatus and budget only
X PixelCreate, read, first-party cookie setting, conversion events, stats and Shopify / WordPress install (X Pixel)

Before you start

X Ads is a separate OAuth from X posting, so the profile needs both: a connected X posting account to author the ads, and the xads account created from it. There is no ads-only mode. You also need the X Ads account id, a base36 string such as 18ce54d4x5t, from List ad accounts.

accountId takes either shape. Pass the X posting account id or the xads account id and Zernio resolves the sibling internally. When the resolved profile has no X Ads connection, the call returns 422 with code ads_connection_required.

Connect

Call GET /v1/connect/twitter/ads with profileId and the accountId of the X posting account the ads are authored by (Connect ads). accountId is required here: X ads carry a real author, so there is no ads-only path.

curl "https://zernio.com/api/v1/connect/twitter/ads?profileId=66a1f0c2a4b9d3e8f1a2b3c4&accountId=66b2e19d8c3f5a7e9d0b1c2d" \
  -H "Authorization: Bearer $ZERNIO_API_KEY"

Response (200):

{
  "authUrl": "https://api.x.com/oauth/authorize?oauth_token=...",
  "state": "..."
}

Send the user to authUrl, as in the connecting accounts guide. X Ads keeps its own token, so this round trip always happens the first time; afterwards the same call returns alreadyConnected: true with the xads accountId. On failure the browser lands on your redirect_url with platform=xads and an error slug such as x_ads_denied, x_ads_auth_failed or x_ads_token_failed. Pass adAccountId on the connect call to scope ad sync to one X Ads account, or adAccountIds (repeated or comma-separated) for several. Sending neither leaves whatever scope is already stored; sending one removes ads already synced from the accounts it drops.

Create a campaign

Call POST /v1/ads/create. On X, body carries the ad text, at most 280 characters, and X counts a linkUrl as about 24 of them because it shortens the link. headline, description, imageUrl, video and callToAction have no slot on X and return 400: the ad X publishes is a text post built from body and linkUrl. To run an X ad with an image or a video, promote a post that already carries it, within X's media limits.

import Zernio from '@zernio/node';

const zernio = new Zernio();

const { data: created } = await zernio.adcampaigns.createStandaloneAd({
  body: {
    accountId: '66b2e19d8c3f5a7e9d0b1c2d',
    adAccountId: '18ce54d4x5t',
    name: 'Q2 product awareness',
    goal: 'awareness',
    budgetAmount: 75,
    budgetType: 'daily',
    body: 'Ship faster with platform engineering done right.',
    linkUrl: 'https://example.com/platform',
    countries: ['US']
  }
});

console.log(created.ad.platformAdSetId);

Response (201):

{
  "ad": {
    "_id": "66f0a1b2c3d4e5f6a7b8c9d0",
    "name": "Q2 product awareness",
    "platform": "twitter",
    "status": "pending_review",
    "adType": "standalone",
    "goal": "awareness",
    "budget": { "amount": 75, "type": "daily" },
    "platformAdId": "5gzhb",
    "platformCampaignId": "f4x8j",
    "platformAdSetId": "9r2mv"
  },
  "message": "Ad created"
}

budgetAmount is in whole currency units of the ad account, so 75 is $75.00 on a USD account. platformAdSetId is the X line item. Every sample below reuses the zernio and client constructors from this one.

Targeting fields

countries, regions, cities, metros, zips and interests apply on X. Take the geo keys and interest ids from GET /v1/ads/targeting/search with dimension=geo or dimension=interest on the X connection: they are X's own targeting values, so a key from another platform does not work. incomeTier, customLocations and behaviors are rejected (X removed behavior targeting from its Ads API). audienceInclude and audienceExclude take the X custom audience id of a Tailored Audience.

Promote an existing post

POST /v1/ads/boost promotes a post that is already published, keeping its engagement.

const { data: boosted } = await zernio.adcampaigns.boostPost({
  body: {
    postId: '65f1c0a9e2b5af0012ab34cd',
    accountId: '66b2e19d8c3f5a7e9d0b1c2d',
    adAccountId: '18ce54d4x5t',
    name: 'Boost launch post',
    goal: 'engagement',
    budget: { amount: 50, type: 'daily' },
    schedule: { startDate: '2027-01-04', endDate: '2027-01-11' }
  }
});

console.log(boosted.ad._id);

Response (201):

{
  "ad": {
    "_id": "66f0a1b2c3d4e5f6a7b8c9d1",
    "name": "Boost launch post",
    "platform": "twitter",
    "status": "pending_review",
    "adType": "boost",
    "goal": "engagement",
    "budget": { "amount": 50, "type": "daily" }
  },
  "message": "Ad created"
}

Boosts are not idempotent. Send an Idempotency-Key header to make a retry replay the original 201 instead of creating a second ad (idempotency).

Tailored Audiences

Create the audience with POST /v1/ads/audiences (type: "customer_list"), then upload members with POST /v1/ads/audiences/{audienceId}/users. X matches on email, ignores phone, and takes at most 10,000 users per request; Zernio hashes every value with SHA-256 before it leaves.

const { data: audience } = await zernio.adaudiences.createAdAudience({
  body: {
    accountId: '66b2e19d8c3f5a7e9d0b1c2d',
    adAccountId: '18ce54d4x5t',
    type: 'customer_list',
    name: 'Trial signups'
  }
});

const { data: upload } = await zernio.adaudiences.addUsersToAdAudience({
  path: { audienceId: audience.audience.id },
  body: { users: [{ email: 'jane@example.com' }, { email: 'sam@example.com' }] }
});

console.log(upload.numReceived);

Response (201):

{
  "audience": { "id": "66e5b2c3d4f5a6b7c8d9e0f1", "name": "Trial signups", "type": "customer_list", "platform": "twitter", "status": "pending" },
  "message": "Audience created"
}

Response (200):

{ "message": "Users added", "numReceived": 2, "numInvalid": 0 }

An audience must match at least 100 recently active users before X allows targeting against it, so its size reads as 0 until then.

X Pixel

Create and read an ad account's X Pixel, manage its conversion events through the platform-neutral tracking tags API on your xads account, check whether each event is tracking, and install the pixel on a connected Shopify store or WordPress site.

The X Pixel is the ad account's universal website tag, and X allows one per ad account, so:

FieldValue
tagIdThe X ad account id (for example 18ce54d4x5t)
siteTagIdThe pixel id (the universal website tag's id), what twq('config') loads
eventsThe account's conversion events (web event tags; the ones X auto-creates with the pixel are left out), each with type (PURCHASE, ADD_TO_CART, ...), status, clickWindowDays, viewWindowDays and siteEventId = tw-<pixel>-<event>, the id twq('event') and the Conversions API send
const { data } = await zernio.trackingtags.getTrackingTag({
  path: { accountId: '66b2e19d8c3f5a7e9d0b1c2d', tagId: '18ce54d4x5t' }
});

Response (200):

{
  "platform": "xads",
  "tag": {
    "id": "18ce54d4x5t",
    "siteTagId": "o8z1q",
    "name": "X Pixel o8z1q",
    "platform": "xads",
    "kind": "pixel",
    "status": "active",
    "code": "<!-- X conversion tracking base code -->\n<script>...twq('config','o8z1q');</script>",
    "lastFiredTime": 1804150800,
    "installed": true,
    "ownerAdAccountId": "18ce54d4x5t",
    "events": [
      { "id": "14cgjq", "siteEventId": "tw-o8z1q-rg1cq", "name": "Purchase", "type": "PURCHASE", "status": "TRACKING", "siteEvent": "purchase", "clickWindowDays": 30, "viewWindowDays": 1 }
    ]
  }
}

status is active when any of the account's web event tags is TRACKING, and lastFiredTime is the most recent last_tracked_at across them. A per-tag call on an ad account without a pixel answers 400: create one first.

OperationBehavior
ListOne pixel per ad account on the connection; adAccountId scopes it to one.
Stats (GET .../stats)One row per web event tag with eventId, name, type, autoCreated, status and lastTrackedTime. X exposes no per-pixel or per-event counts, only campaign WEB_CONVERSION metrics.
Create (POST .../tracking-tags)Creates the ad account's pixel (adAccountId = the X ad account id) with first-party cookies on. name is not stored, since X pixels have no name. An account that already has a pixel answers 409 without writing: X allows one and cannot delete it.
Update (PATCH .../tracking-tags/{tagId})firstPartyCookieStatus only, first_party_cookie_enabled or first_party_cookie_disabled (the pixel's allow_1p_cookie).
Conversion events (.../events)List, create, update and delete. Fields: name, type (X's event type, such as PURCHASE, SIGN_UP, CUSTOM) or siteEvent, clickWindowDays (1, 7, 14, 30, 60 or 90) and viewWindowDays (0, 1, 7, 14, 30, 60 or 90, no longer than the click window). A create defaults to 30 and 1 days with retargeting on, as Events Manager does, and is not idempotent. Delete is permanent. The events X auto-created with the pixel cannot be changed or deleted (400).
Share501: an X Pixel belongs to exactly one ad account, and X has no share endpoint.
Diagnostics, users and partners501.
DeleteNot offered: DELETE on a website tag answers 405 at X.
InstallShopify and WordPress. WordPress gets X's uwt.js base code, whose twq('config') is the page view. Shopify sends the page view and each event that has a matching conversion event (the most recently tracked one of a type wins) to analytics.twitter.com/1/i/adsct in uwt.js's own request shape (reverse-engineered: X documents only the legacy image pixel), with conversion_id = the event id so X deduplicates against the Conversions API, and the twclid click id kept first-party.

Analytics

Call GET /v1/ads/{adId}/analytics with the ad's _id for spend, impressions, clicks, CTR, CPC and CPM over a date range (Get ad analytics).

curl "https://zernio.com/api/v1/ads/66f0a1b2c3d4e5f6a7b8c9d0/analytics?fromDate=2027-01-04&toDate=2027-01-11" \
  -H "Authorization: Bearer $ZERNIO_API_KEY"

Response (200), trimmed:

{
  "ad": { "id": "66f0a1b2c3d4e5f6a7b8c9d0", "name": "Q2 product awareness", "platform": "twitter", "status": "active", "currency": "USD" },
  "analytics": {
    "summary": { "spend": 421.7, "impressions": 203800, "clicks": 3140, "ctr": 1.54, "cpc": 0.13, "cpm": 2.07, "engagement": 5820 },
    "daily": [ { "date": "2027-01-04", "spend": 59.8, "impressions": 29100, "clicks": 448 } ]
  }
}

X reports reach as 0, because Zernio does not sync it, and the demographic breakdowns parameter is Meta and TikTok only. GET /v1/ads/tree rolls the same metrics up per campaign and line item (Get campaign tree).

What you cannot do

X Ads through Zernio does not support:

  • Targeting or creative edits after creation. PUT /v1/ads/{adId} takes status and budget; targeting or creative returns 501 with code unsupported_platform_operation.
  • Campaign duplication. POST /v1/ads/campaigns/{campaignId}/duplicate returns 501 on X.
  • An ads-only connection. Every X ad is authored by a connected posting account.
  • Keyword targeting and follower look-alike targeting.
  • Uploading creative with an ad. A create publishes text; media comes from the post a boost promotes.
  • Income tier, radius and behavior targeting.

Common errors

ErrorCauseFix
422 with code ads_connection_requiredThe profile has no xads accountRun the connect flow with the X posting account id.
400 with code missing_required_fieldbody or linkUrl is absent on a createSend the ad text and its destination.
400 with code invalid_field_valueA goal outside the 5 X values, or ad text over 280 charactersUse a supported goal and trim the text, counting about 24 characters for the link.
403 with code ads_allowance_exceededThe team has no payment method on file and has reached 500 live adsAdd a card to resume creating ads.
502 with code platform_api_errorX rejected the requestRead platformError for X's own payload.

A call against a profile with no X Ads connection returns:

{
  "error": "X Ads is not connected for this profile",
  "type": "invalid_request_error",
  "code": "ads_connection_required",
  "param": "accountId"
}

Error handling covers the envelope and the stable codes.

Related

  • X: the posting account that authors the ads.
  • Connecting accounts: the OAuth flow behind authUrl.
  • Create standalone ad and Boost post: every field of both requests.
  • Create ad audience and Add users to ad audience.
  • Get ad analytics: metrics, date ranges and rollups.
  • Pixels and tracking tags: the pixel API across platforms.
Was this page helpful?

Pinterest Ads

Create Promoted Pin campaigns, promote Pins you already published, upload customer list audiences and read ad metrics on a pinterestads account.

OpenAI Ads

Run ChatGPT ads on an openaiads account, from connecting an API key to creating chat card campaigns, pixels and server-side conversions.

On this page

Quick referenceBefore you startConnectCreate a campaignTargeting fieldsPromote an existing postTailored AudiencesX PixelAnalyticsWhat you cannot doCommon errorsRelated