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

Pinterest Ads

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


Create a Promoted Pin campaign with POST /v1/ads/create, or promote a Pin you already published with POST /v1/ads/boost, on a pinterestads account. The ads account reuses the token of the Pinterest posting account in the same profile, so there is no second OAuth flow and no Pinterest developer application to file.

Quick reference

PropertyValue
HierarchyCampaign > Ad Group > Promoted Pin, created in one call
Goals (goal)engagement, traffic, awareness, video_views
HeadlineRequired, 100 characters
BodyRequired, 500 characters
CreativeimageUrl and linkUrl, both required
Minimum budget$5 (budgetAmount, or budget.amount on a boost)
BoardboardId, optional
TargetingCountry, region, metro, postal code, age, gender, language, interests
AudiencesCustomer list
AnalyticsYes
Edits after creationStatus and budget only
Pinterest tagCreate (with enhanced match), read, custom events, stats and Shopify / WordPress install (Pinterest tag)
Conversions APIYes, with Event Quality Score (Conversions API)
Catalog and shopping adsRoadmap

Before you start

You need a connected Pinterest account in the profile (Pinterest) and the Pinterest ad account id, from List ad accounts. Ads run on 2 scopes (scopes):

ScopeWhat it enables
ads:readRead ad accounts and reporting
ads:writeCreate and manage campaigns, ad groups and Promoted Pins

New Pinterest connections request both as part of the standard flow. An account connected before Pinterest Ads existed re-consents to these 2 scopes.

Connect

Call GET /v1/connect/pinterest/ads with profileId (Connect ads). Pinterest shares one token between posting and ads, so when the profile already holds an active Pinterest account the pinterestads account inherits that token and no OAuth happens.

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

Response (200):

{
  "alreadyConnected": true,
  "accountId": "66b2e19d8c3f5a7e9d0b1c2d",
  "platform": "pinterest",
  "username": "@acmehome",
  "displayName": "Acme Home"
}

Without an active Pinterest account the response carries authUrl and state instead; send the user to authUrl, as in the connecting accounts guide. A Pinterest account whose stored token cannot reach ad accounts fails with 400 RECONNECT_REQUIRED, which reconnecting the posting account clears. The accountId in the response is the pinterestads account every sample below uses.

Pinterest has no ads discovery, so the adAccountId and adAccountIds scoping parameters are accepted and ignored on this call. Pass the ad account per request instead.

Create a Promoted Pin

Pinterest creates the Pin and promotes it in the same call, so headline, body, imageUrl and linkUrl are all required. boardId chooses the board; without it the Pin lands on a Zernio Ads board that Zernio creates once. startDate and endDate set the run window at the top level of the body, where the boost below takes a schedule object instead; omit them and the Promoted Pin runs until you pause it. An ad account that requires Campaign Budget Optimization needs endDate with a lifetime budget and returns a 400 naming endDate without one.

import Zernio from '@zernio/node';

const zernio = new Zernio();

const { data: created } = await zernio.adcampaigns.createStandaloneAd({
  body: {
    accountId: '66b2e19d8c3f5a7e9d0b1c2d',
    adAccountId: '549123456789',
    name: 'Spring home decor',
    goal: 'traffic',
    budgetAmount: 30,
    budgetType: 'daily',
    headline: 'Freshen up your space',
    body: 'Spring drops, limited time.',
    imageUrl: 'https://cdn.example.com/spring-decor.jpg',
    linkUrl: 'https://example.com/spring-decor',
    countries: ['US', 'CA'],
    ageMin: 25,
    ageMax: 44
  }
});

console.log(created.ad.platformAdSetId);

Response (201):

{
  "ad": {
    "_id": "66f0a1b2c3d4e5f6a7b8c9d0",
    "name": "Spring home decor",
    "platform": "pinterest",
    "status": "pending_review",
    "adType": "standalone",
    "goal": "traffic",
    "budget": { "amount": 30, "type": "daily" },
    "platformAdId": "687195120340",
    "platformCampaignId": "626746010001",
    "platformAdSetId": "2680069880002"
  },
  "message": "Ad created"
}

budgetAmount is in whole currency units of the ad account, so 30 is $30.00 on a USD account. Every sample below reuses the zernio and client constructors from this one.

Targeting fields

countries, regions, metros, zips, ageMin, ageMax, gender, languages (ISO 639-1; a code Pinterest cannot target returns 400) and interests (from GET /v1/ads/interests) apply on Pinterest, and audienceInclude and audienceExclude take the Pinterest customer list id of an audience. Pinterest's geo spec takes region codes or postal codes but never both, so sending regions and zips together returns a 400 with code mutually_exclusive_fields. incomeTier is rejected on Pinterest, and cities, customLocations and behaviors are Meta and TikTok only.

Promote an existing Pin

POST /v1/ads/boost promotes a Pin that is already published, keeping its engagement. Pass the Zernio postId (or the Pinterest platformPostId), the account, the ad account, a goal and a budget.

const { data: boosted } = await zernio.adcampaigns.boostPost({
  body: {
    postId: '65f1c0a9e2b5af0012ab34cd',
    accountId: '66b2e19d8c3f5a7e9d0b1c2d',
    adAccountId: '549123456789',
    name: 'Boost decor Pin',
    goal: 'traffic',
    budget: { amount: 30, type: 'daily' },
    schedule: { startDate: '2027-01-04T09:00:00Z', endDate: '2027-02-04T23:59:00Z' }
  }
});

console.log(boosted.ad._id);

Response (201):

{
  "ad": {
    "_id": "66f0a1b2c3d4e5f6a7b8c9d1",
    "name": "Boost decor Pin",
    "platform": "pinterest",
    "status": "pending_review",
    "adType": "boost",
    "goal": "traffic",
    "budget": { "amount": 30, "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).

Customer list audiences

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

Pinterest needs the member file at creation time, so the audience is provisioned lazily: the create records it with status pending and no platform id, and the first upload creates it on Pinterest.

const { data: audience } = await zernio.adaudiences.createAdAudience({
  body: {
    accountId: '66b2e19d8c3f5a7e9d0b1c2d',
    adAccountId: '549123456789',
    type: 'customer_list',
    name: 'Repeat buyers'
  }
});

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": "Repeat buyers", "type": "customer_list", "platform": "pinterest", "status": "pending" },
  "message": "Audience created"
}

Response (200):

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

Media requirements

TypeFormatsMax sizeNotes
Standard PinJPEG, PNG20 MB1000 x 1500 px (2:3) recommended
Video PinMP4, MOV, M4V2 GB4 seconds to 15 minutes, 9:16 or 1:1

Creatives are fetched from the URL you pass, which must be public and return the media bytes (media uploads).

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). Pinterest reports saves and closeups alongside those standard metrics.

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": "Spring home decor", "platform": "pinterest", "status": "active", "currency": "USD" },
  "analytics": {
    "summary": { "spend": 186.5, "impressions": 74300, "clicks": 1240, "ctr": 1.67, "cpc": 0.15, "cpm": 2.51 },
    "daily": [ { "date": "2027-01-04", "spend": 27.9, "impressions": 10600, "clicks": 181 } ]
  }
}

Pinterest 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 ad group (Get campaign tree).

Pinterest tag

Create and read Pinterest tags and manage their custom events through the platform-neutral tracking tags API on your pinterestads account, check when a tag last fired and its Event Quality Score, and install it on a connected Shopify store or WordPress site.

FieldValue
tagIdThe numeric Pinterest tag id. It is also what the on-site code loads, so siteTagId equals id.
adAccountIdThe numeric ad account id. A tag belongs to one ad account; without adAccountId, per-tag calls find the owner across every ad account the connection can read.

kind is tag. GET .../tracking-tags lists the tags of every readable ad account (one account refusing access does not hide the others), and GET .../tracking-tags/{tagId} adds the tag's code (Pinterest's base code), lastFiredTime and installed.

const { data } = await zernio.trackingtags.createTrackingTag({
  path: { accountId: '66b2e19d8c3f5a7e9d0b1c2d' },
  body: { adAccountId: '549755885175', name: 'Storefront tag' }
});

Response (201):

{
  "platform": "pinterestads",
  "tag": {
    "id": "2612345678901",
    "siteTagId": "2612345678901",
    "name": "Storefront tag",
    "platform": "pinterestads",
    "kind": "tag",
    "status": "active",
    "lastFiredTime": null,
    "installed": false,
    "ownerAdAccountId": "549755885175",
    "code": "<script type=\"text/javascript\">...pintrk('load', '2612345678901');...</script>"
  }
}

The create takes a name and, optionally, automaticMatchingFields to switch on automatic enhanced match for those fields (em, ph, fn, ln, ct, st, zp, country, external_id, ...), which is the only time Zernio can set it: Pinterest cannot edit a tag afterwards. The create is not idempotent: a retry creates a second tag, and Pinterest has no delete, so never retry it blindly.

OperationBehavior
Stats (GET .../stats)No time range (startTime/endTime answer 400). Rows: the tag (lastFiredTime, status, enhancedMatchStatus), the events Pinterest has seen on it (page_visit and ocpm_eligible sources, with the matching siteEvent), and the ad account's Event Quality Score for the last 1 and 14 days. An account Pinterest cannot score yet reports why in its row.
Update501: API v5 only creates, lists and reads conversion tags (PATCH and PUT answer 405); rename a tag or change enhanced match in Pinterest Ads Manager.
Share501: a tag belongs to one ad account; use Pinterest Business Access.
Conversion events (.../events)Pinterest's advertiser defined events: a custom event name the site sends, mapped to a standard type. List, create (name plus type or siteEvent), update (only the type: the name is the event's id, so it cannot be renamed) and delete (Pinterest stops tracking it, state: "disabled"). They belong to the ad account, so every tag of that account lists the same ones. At most 15 per ad account, names of 1 to 100 letters, digits, _ or -, case-insensitive. type is one of Pinterest's optimizable types (CHECKOUT, ADD_TO_CART, LEAD, SIGNUP, PAGE_VISIT, ...).
Diagnostics, users and partners501.
DeleteNot offered: DELETE on a conversion tag answers 405 at Pinterest.
InstallShopify and WordPress. WordPress gets Pinterest's base code with pintrk('page') and a pagevisit event (official fallback: Pinterest for WooCommerce, WooCommerce stores only). Shopify sends every storefront event to ct.pinterest.com/v3/, the endpoint Pinterest's noscript tag and core.js use, with core.js's parameter names, the epik click id kept in _epik and event_id set for dedup with the Conversions API.

A 403 means the connected Pinterest user lacks a Business Access role on the ad account (Admin, Analyst, Audience or Campaign).

Conversions API

Send server-side events to Pinterest with POST /v1/ads/conversions on your pinterestads account (Pinterest's POST /v5/ad_accounts/{id}/events). They deduplicate against the Pinterest tag by eventId, the same id the Shopify install sends.

FieldPinterest
destinationIdThe numeric ad account id: Pinterest attributes to the ad account, not to a tag. GET /v1/accounts/{accountId}/conversion-destinations lists them.
eventNameStandard names map to Pinterest's: Purchase = checkout, AddToCart, InitiateCheckout, AddPaymentInfo, Lead, CompleteRegistration = signup, Subscribe, ViewContent, Search, PageView = page_visit. Any other string is sent as is, so it matches a custom event of that name.
userNeeds email, or ipAddress plus userAgent. Identifiers are hashed before they leave Zernio.
user.clickIds.epikThe epik click id (the _epik cookie), sent as click_id.
actionSourceweb stays web; offline, crm, phone_call, system_generated and business_messaging become offline. App events need platformData.action_source app_android or app_ios.
testCodeAny value sends the batch with test=true: Pinterest validates it without recording it.
consent.adUserData: "DENIED"Sets opt_out on every event.
const { data } = await zernio.conversions.sendConversions({
  body: {
    accountId: '66b2e19d8c3f5a7e9d0b1c2d',
    destinationId: '549755885175',
    events: [{
      eventName: 'Purchase',
      eventTime: 1804150800,
      eventId: 'order_1042',
      value: 59.9,
      currency: 'USD',
      user: { email: 'ana@example.com', clickIds: { epik: 'dj0yJnU9...' } }
    }]
  }
});

Response (200):

{
  "platform": "pinterestads",
  "eventsReceived": 1,
  "eventsFailed": 0,
  "failures": []
}

Pinterest answers per event, so one bad event does not fail the batch: an event without an identity is listed in failures with code INVALID_EVENT before sending, and one Pinterest rejects with PINTEREST_EVENT_FAILED. The connected Pinterest user needs a Business Access role on the ad account (403 otherwise).

GET /v1/ads/conversions/quality?accountId=...&destinationId=<ad account id> (reference) returns Pinterest's Event Quality Score for Conversions API web events over the last 14 days: one row per event name, each identifier's coverage in matchKeys, and eventCoveragePercentage, the share of events whose event_id overlaps with the tag. Pinterest has no composite score, so compositeScore is absent.

What you cannot do

Pinterest 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 Pinterest.
  • Ad-account scoping on the connect call, because Pinterest has no ads discovery.
  • Income tier, city and radius targeting. Regions, metros and postal codes are supported.
  • Catalog or shopping ads.

Common errors

ErrorCauseFix
400 with code missing_required_fieldOne of headline, body, imageUrl or linkUrl is absentSend all 4; Pinterest builds the Pin from them.
400 with code invalid_field_valueA goal outside the 4 Pinterest values, or a headline over 100 or a body over 500 charactersUse a supported goal and trim the copy.
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.
501 with code unsupported_platform_operationtargeting or creative sent to PUT /v1/ads/{adId}Change status or budget, and recreate the ad for anything else.
502 with code platform_api_errorPinterest rejected the requestRead platformError for Pinterest's own payload.

A rejected create returns the flat envelope:

{
  "error": "headline is required",
  "type": "invalid_request_error",
  "code": "missing_required_field",
  "param": "headline"
}

Error handling covers the envelope and the stable codes.

Related

  • Pinterest: the posting account this ads account inherits its token from.
  • 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 tag API across platforms.
Was this page helpful?

TikTok Ads

Run Spark Ads, standalone video campaigns and website conversion campaigns on a tiktokads account, with Custom Audiences and Insights.

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.

On this page

Quick referenceBefore you startConnectCreate a Promoted PinTargeting fieldsPromote an existing PinCustomer list audiencesMedia requirementsAnalyticsPinterest tagConversions APIWhat you cannot doCommon errorsRelated