Ads

Pinterest Ads

Run Promoted Pins on a pinterestads account, from a new Pin or one you already published, with targeting, customer lists, the Pinterest tag and conversions.

A Pinterest ad is a Promoted Pin. You can create a fresh Pin and promote it in one request, or put budget behind a Pin that is already live and keep its saves and comments. Both run on a pinterestads connection, and the ad endpoints refer to the network as pinterest.

Connection platform pinterestads
platform on ad endpoints pinterest
Structure Campaign, ad group, Promoted Pin, all created by one call
Goals engagement, traffic, awareness, video_views
Minimum budget 5 in the ad account currency, daily or lifetime
Changes after launch Status and budget
Audiences Customer lists, matched on email
Measurement Pinterest tag and Conversions API

#Connect

Pinterest uses one token for posting and for ads. If the profile already has a Pinterest account connected, the ads connection reuses its token and no consent screen appears.

curl "https://api.pozy.ai/v1/connect/pinterest/ads?profileId=66b2e19d8c3f5a7e9d0b1c2a" \
  -H "Authorization: Bearer $POZY_API_KEY"
Response
{
  "alreadyConnected": true,
  "accountId": "66b2e19d8c3f5a7e9d0b1c2d",
  "platform": "pinterest",
  "username": "@acmehome",
  "displayName": "Acme Home"
}

accountId is the pinterestads connection. Every example below uses it.

  • No Pinterest account in the profile yet. The response carries authUrl and state. Send the person to authUrl; the consent screen asks for ads:read and ads:write along with the posting scopes.
  • Connected before ads existed. A Pinterest account connected without the ads scopes goes through consent once more.
  • Token can no longer reach ad accounts. Calls return 400 asking for a reconnect. Reconnect the Pinterest posting account and the ads connection picks up the new token.

Pinterest does not let Pozy discover ad accounts at connect time, so adAccountId and adAccountIds on the connect call have no effect. List the ad accounts with GET /v1/ads/accounts?accountId=... and pass adAccountId on each request instead.

#Create a Promoted Pin

POST /v1/ads/create creates the Pin, then the campaign, ad group and ad around it. Because the Pin is built from the request, send headline, body, imageUrl and linkUrl together.

curl -X POST https://api.pozy.ai/v1/ads/create \
  -H "Authorization: Bearer $POZY_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: spring-decor-2027-01" \
  -d '{
    "accountId": "66b2e19d8c3f5a7e9d0b1c2d",
    "adAccountId": "549123456789",
    "name": "Spring home decor",
    "goal": "traffic",
    "budgetAmount": 30,
    "budgetType": "daily",
    "headline": "Freshen up your space",
    "body": "New spring pieces, in stock now.",
    "imageUrl": "https://cdn.example.com/spring-decor.jpg",
    "linkUrl": "https://example.com/spring",
    "boardId": "1029384756473829101",
    "countries": ["US", "CA"],
    "ageMin": 25,
    "ageMax": 44
  }'
Response (201)
{
  "ad": {
    "_id": "66f0a1b2c3d4e5f6a7b8c9d0",
    "name": "Spring home decor",
    "platform": "pinterest",
    "status": "pending_review",
    "adType": "standalone",
    "goal": "traffic",
    "budget": { "amount": 30, "type": "daily" },
    "platformCampaignId": "626746010001",
    "platformAdSetId": "2680069880002",
    "platformAdId": "687195120340"
  },
  "message": "Ad created"
}

platformAdSetId is the Pinterest ad group. The ad starts in pending_review and moves to active once Pinterest approves it. Subscribe to ad.review.approved and ad.review.rejected instead of polling.

Pick the board

boardId decides which board the new Pin lands on. Without it, Pinterest Ads puts the Pin on a separate ads board created the first time you launch. Pass a board you own so the Pin sits where your audience expects it.

#Copy and creative

Field Rule
headline Required. Up to 100 characters.
body Up to 500 characters. Send it with the headline.
imageUrl Required. A public URL that returns the image bytes. Pozy fetches it.
linkUrl Required. Where a tap on the Pin goes.
boardId Optional. The board for the new Pin.

To run a video Pin, publish the video to Pinterest first (see Pinterest) and promote it with a boost.

#Budget and schedule

  • budgetAmount is in whole units of the ad account currency, so 30 is $30.00 on a USD account. The minimum is 5.
  • budgetType is daily or lifetime.
  • startDate and endDate set the run window. Without them the Promoted Pin runs until you pause it.
  • Some ad accounts require Campaign Budget Optimization. Those need a lifetime budget with an endDate, and a create without one returns 400 naming endDate.

#Promote a Pin you already published

POST /v1/ads/boost keeps the Pin's existing engagement. Pass the Pozy postId of a post published to Pinterest, or the Pinterest Pin id as platformPostId.

cURL
curl -X POST https://api.pozy.ai/v1/ads/boost \
  -H "Authorization: Bearer $POZY_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: boost-65f1c0a9" \
  -d '{
    "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" }
  }'

The same four goals and the 5 minimum apply. A boost is the route for video Pins. More on boosting across networks is in Boost posts.

#Targeting

Targeting is set on the create and cannot change afterwards.

Field What it targets
countries ISO country codes
regions, metros, zips Sub-country locations. Send regions or zips, never both (400 mutually_exclusive_fields).
ageMin, ageMax, gender Demographics
languages ISO 639-1 codes. A code Pinterest cannot target returns 400.
interests { "id": "..." } values from GET /v1/ads/interests
audienceInclude, audienceExclude Pinterest customer list ids

incomeTier, cities, customLocations and behaviors are not available on Pinterest and return 400. The cross-network field list is in Targeting.

#Customer lists

Create the audience with type: "customer_list", then upload members. Pinterest matches on email only, so a member with only a phone is not matched.

cURL
curl -X POST https://api.pozy.ai/v1/ads/audiences \
  -H "Authorization: Bearer $POZY_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "accountId": "66b2e19d8c3f5a7e9d0b1c2d", "adAccountId": "549123456789", "type": "customer_list", "name": "Repeat buyers" }'

curl -X POST https://api.pozy.ai/v1/ads/audiences/66e5b2c3d4f5a6b7c8d9e0f1/users \
  -H "Authorization: Bearer $POZY_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "users": [{ "email": "jane@example.com" }, { "email": "sam@example.com" }] }'
  • Send plain emails. Pozy hashes each one with SHA-256 before it leaves.
  • One upload takes up to 10,000 users.
  • Pinterest needs members to create a list, so the audience stays pending with no Pinterest id until the first upload. That upload creates it on Pinterest.

#Manage a running campaign

After launch you can change delivery and money, nothing else.

Change Call
Pause or resume the campaign PUT /v1/ads/campaigns/{campaignId}/status with { "status": "paused", "platform": "pinterest" }
Pause or resume an ad group or a Promoted Pin PUT /v1/ads/ad-sets/{adSetId}/status or PUT /v1/ads/{adId}/status
Change the budget PUT /v1/ads/{adId} with budget: { amount, type }. It is written to the Pin's ad group.
Rename the ad group PUT /v1/ads/ad-sets/{adSetId} with name
Stop for good DELETE /v1/ads/{adId}, /v1/ads/ad-sets/{adSetId} or /v1/ads/campaigns/{campaignId}

Each level has its own switch. Resuming a campaign only restarts the ad groups and Pins whose own switch is on. Pinterest does not report a campaign's switch back, so platformCampaignStatus is always null on Pinterest.

Pinterest has no hard delete. Deleting archives the object and marks Pozy's copy cancelled, which keeps its spend in your reports. If you delete a campaign that still has ad groups Pozy does not know about, the call returns 409 invalid_resource_state and nothing changes; delete the ad groups first or sync the ad account and try again.

Sending targeting or creative to PUT /v1/ads/{adId} returns 501 feature_not_available. Create a new ad instead. Campaign duplication is not available on Pinterest either.

#Measure results

GET /v1/ads/{adId}/analytics returns spend, impressions, clicks, CTR, CPC and CPM for a date range, as a summary and a daily series. GET /v1/ads/tree rolls the same numbers up per campaign and ad group.

cURL
curl "https://api.pozy.ai/v1/ads/66f0a1b2c3d4e5f6a7b8c9d0/analytics?fromDate=2027-01-04&toDate=2027-01-11" \
  -H "Authorization: Bearer $POZY_API_KEY"
  • reach reads 0 on Pinterest because it is not synced. Use impressions.
  • On connect, Pozy imports the last 90 days of metrics. Days before that have no data.
  • Demographic breakdowns are not available for Pinterest ads.

Reporting covers the cross-network metrics.

#Pinterest tag and conversions

Pinterest measures site activity with the Pinterest tag, managed through the tracking tags endpoints on the pinterestads connection. A few Pinterest rules to plan around:

  • Create a tag with POST /v1/accounts/{accountId}/tracking-tags. Enhanced match (automaticMatchingFields) can only be set at creation, because Pinterest cannot edit a tag later.
  • Creating a tag is not idempotent and Pinterest has no tag delete. After a timeout, list the tags before you try again.
  • Custom conversion events belong to the ad account: up to 15, named with 1 to 100 letters, digits, _ or -.
  • Install the tag on a connected Shopify, WordPress, Wix or Webflow site with one call. See Pixels.

Server-side events go to POST /v1/ads/conversions with the numeric ad account id as destinationId, because Pinterest attributes to the ad account rather than to a tag. The connected Pinterest user needs a Business Access role on that ad account. Details, hashing and dedupe are in Conversions.

#Errors you will see

Status and code Cause Fix
400 missing_required_field headline, body, imageUrl, linkUrl or goal missing Send all of them. Pinterest builds the Pin from them.
400 invalid_field_value A goal outside the four, a headline over 100 or body over 500 characters Use a supported goal and trim the copy.
400 mutually_exclusive_fields regions and zips together Send one of them.
400 naming endDate The ad account requires Campaign Budget Optimization Send a lifetime budget with endDate.
403 The Pinterest user lacks Business Access on the ad account (tags, conversions) Grant Admin, Analyst, Audience or Campaign access in Pinterest.
403 ads_allowance_exceeded No payment method and the workspace reached its free live-ads allowance Add a payment method.
501 feature_not_available Targeting or creative edits after launch Create a new ad.
502 platform_api_error Pinterest rejected the request Read platformError in the body for Pinterest's own message.

#Endpoints

Endpoint What it does
GET /v1/connect/{platform}/ads Connect ads for a platform
GET /v1/ads/accounts List ad accounts
POST /v1/ads/create Create standalone ad
POST /v1/ads/boost Boost post as ad
GET /v1/ads/tree Get campaign tree
GET /v1/ads/{adId} Get ad details
PUT /v1/ads/{adId} Update ad
PUT /v1/ads/{adId}/status Pause or resume a single ad
PUT /v1/ads/ad-sets/{adSetId} Update an ad set
PUT /v1/ads/ad-sets/{adSetId}/status Pause or resume a single ad set
PUT /v1/ads/campaigns/{campaignId}/status Pause or resume a campaign
DELETE /v1/ads/{adId} Cancel an ad
DELETE /v1/ads/ad-sets/{adSetId} Delete an ad set
DELETE /v1/ads/campaigns/{campaignId} Delete a campaign
GET /v1/ads/{adId}/analytics Get ad analytics
GET /v1/ads/interests Search targeting interests
POST /v1/ads/audiences Create custom audience
POST /v1/ads/audiences/{audienceId}/users Add users to audience
GET /v1/accounts/{accountId}/tracking-tags List tracking tags
POST /v1/accounts/{accountId}/tracking-tags Create a tracking tag
POST /v1/ads/conversions Send conversion events
GET /v1/ads/conversions/quality Get Event Match Quality

#Webhook events

Event When it fires
account.ads.initial_sync_completed Ads initial sync completed.
account.ads.sync_failed Ads sync failed.
account.ads.sync_recovered Ads sync recovered.
ad.status_changed Ad status changed.
ad.review.approved Ad review approved.
ad.review.rejected Ad review rejected.