Zernio
Zernio
Overview

Social Posting

XInstagramFacebookLinkedInTikTokYouTubePinterestRedditBlueskyThreadsGoogle Business ProfileSnapchat

Messaging

WhatsAppTelegramDiscordSlack

Telephony

Phone NumbersVoice & CallsSMS

Advertising

Meta AdsGoogle AdsLinkedIn AdsTikTok AdsPinterest AdsX AdsOpenAI Ads

Commerce

Shopify
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
Ignored creative fieldsheadline, imageUrl, callToAction
MediaNone on a create (the ad is a text post); a boost carries the media of the post it promotes
TargetingCountry, postal code, language
AudiencesTailored Audiences (create, member upload, read)
AnalyticsYes
Edits after creationStatus and budget only

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, imageUrl and callToAction have no slot on X and are ignored: 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, languages and zips apply on X. incomeTier is rejected, and cities, regions, metros, customLocations and behaviors are Meta and TikTok only. 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.

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 targeting, and city, region, metro or radius 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.
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 AudiencesAnalyticsWhat you cannot doCommon errorsRelated