# Pinterest Ads

- Tool: Bubble
- Difficulty: Intermediate
- Time required: 2–3 hours
- Last updated: July 2026

## TL;DR

Connect Bubble to Pinterest Marketing API v5 using OAuth 2.0 and the API Connector's Private header to keep your access token server-side. Pull campaign analytics into Repeating Groups, watch for the micro-dollar conversion trap, and build a Backend Workflow to refresh the 24-hour access token so your dashboard keeps running overnight.

## Build a Pinterest Ads Analytics Dashboard in Bubble

Pinterest Ads is a powerful channel for brands targeting visual, discovery-intent shoppers. Connecting Bubble to Pinterest Marketing API v5 lets you build ROAS dashboards, campaign monitors, and ad performance reports entirely in Bubble — without logging into Pinterest Ads Manager for every check. The integration path is REST-based: create a Pinterest Developer App, go through the OAuth 2.0 Authorization Code flow to get an access token, and configure the API Connector with a Private Bearer header. From there, you can pull ad account lists, campaign data, and analytics by date range. One critical gotcha stands apart from other ad platform integrations: Pinterest returns conversion values in micro-dollars, so a conversion value of $50.00 comes back as 50000000. Every Bubble expression that displays money from Pinterest must divide by 1,000,000. The second gotcha is the 24-hour access token lifespan — a Pinterest-specific constraint that requires a token refresh workflow to prevent your dashboard from breaking every night.

## Before you start

- A Pinterest business account (required to access Pinterest Ads and the Developer Portal)
- A Bubble app on any plan (Free plan works for initial setup, but a paid plan is required for the token refresh Backend Workflow)
- A Pinterest Developer App created at app.pinterest.com/business/apps with the Marketing API enabled
- Pinterest Developer App review approval — Pinterest manually reviews Marketing API access, which can take several days; development-mode testing is possible while you wait
- An OAuth 2.0 access token and refresh token obtained via the Authorization Code flow

## Step-by-step guide

### 1. Create Your Pinterest Developer App and Request Marketing API Access

Go to app.pinterest.com/business/apps and click Create app. Give it a name like 'Bubble ROAS Dashboard', set the redirect URI to a URL you control (this will receive the authorization code during OAuth — you can use a temporary URL like https://yourbubblebapp.bubbleapps.io/pinterest-callback for now), and enable the Marketing API scope. After saving, Pinterest will show a Development mode notice — your app only returns sandboxed test data until Pinterest approves your Marketing API request. Submit the app for review from the same portal. You'll receive an email when approved, typically within 2–5 business days. Note your App ID and App Secret — you'll need both for the OAuth exchange. Important: do not wait for full approval before building. Development mode lets you test all API calls against real-looking data so you can complete the Bubble integration while approval is in progress.

**Expected result:** You have a Pinterest Developer App with an App ID and App Secret, Marketing API enabled, and a redirect URI configured. The app shows 'In Development' status while awaiting review.

### 2. Complete the OAuth 2.0 Flow and Obtain Your Access Token

Pinterest uses the Authorization Code flow. To get your first access token, construct the authorization URL manually: https://www.pinterest.com/oauth/?client_id=YOUR_APP_ID&redirect_uri=YOUR_REDIRECT_URI&response_type=code&scope=ads:read,pins:read&state=random_string. Open this URL in your browser while logged into your Pinterest business account, click Authorize, and Pinterest will redirect you to your redirect URI with a code= parameter in the URL. Copy that code value. Then use a REST client (like Insomnia, Postman, or even curl if you're comfortable) to exchange it: send a POST request to https://api.pinterest.com/v5/oauth/token with the body parameters grant_type=authorization_code, code=YOUR_CODE, redirect_uri=YOUR_REDIRECT_URI, and an Authorization header of Basic base64(YOUR_APP_ID:YOUR_APP_SECRET). The response includes an access_token (valid 24 hours), a refresh_token (valid 365 days), and a refresh_token_expires_in field. Copy both tokens and store them securely — you'll put the access_token into Bubble's API Connector header now, and the refresh_token into a Bubble Data Type for automated renewal later.

```
// POST to https://api.pinterest.com/v5/oauth/token
// Headers:
// Authorization: Basic base64(APP_ID:APP_SECRET)
// Content-Type: application/x-www-form-urlencoded

// Body (form-encoded):
grant_type=authorization_code
&code=THE_CODE_FROM_REDIRECT
&redirect_uri=https://yourapp.bubbleapps.io/pinterest-callback

// Response:
{
  "access_token": "pina_ABC123...",
  "token_type": "bearer",
  "expires_in": 86400,
  "refresh_token": "pinr_XYZ789...",
  "refresh_token_expires_in": 31536000,
  "scope": "ads:read pins:read"
}
```

**Expected result:** You have a valid access_token starting with 'pina_' and a refresh_token starting with 'pinr_'. Both tokens are stored safely — the access token ready to paste into Bubble's API Connector, the refresh token saved in a Bubble Data Type.

### 3. Configure the API Connector with Your Private Pinterest Bearer Token

In your Bubble editor, open the Plugins tab in the left sidebar and click Add plugins. Search for API Connector — the official plugin by Bubble — and install it. Click API Connector in your installed plugins list, then click Add another API. Name the API group Pinterest Ads. In the Authentication section, choose None (we'll handle auth manually via a header). Under Shared headers for this API, click Add a shared header. Set the key to Authorization, set the value to Bearer YOUR_ACCESS_TOKEN (replacing YOUR_ACCESS_TOKEN with the pina_ token from Step 2), and check the Private checkbox to the right of the value field. This is the most important security step: the Private checkbox tells Bubble's server to keep this header value out of any client-side requests — your access token stays on Bubble's servers and never reaches the browser. Add a second shared header: key Content-Type, value application/json (this one does not need to be Private). Set the API group's base URL to https://api.pinterest.com/v5. Click Expand to add your first API call.

```
// API Connector group: Pinterest Ads
// Base URL: https://api.pinterest.com/v5

// Shared headers:
{
  "Authorization": "Bearer pina_YOUR_ACCESS_TOKEN",  // <-- mark Private
  "Content-Type": "application/json"
}
```

**Expected result:** The API Connector group 'Pinterest Ads' is configured with the base URL set to https://api.pinterest.com/v5 and two shared headers — Authorization (Private, containing your Bearer token) and Content-Type. The plugin is installed and visible in your Bubble editor.

### 4. Add and Initialize the Ad Accounts Call

Within the Pinterest Ads API Connector group, click Add a call. Name this call Get Ad Accounts. Set the method to GET and the endpoint to /ad_accounts. Leave query parameters empty for now. Click Initialize call — Bubble will make a live request to Pinterest and needs a successful response to detect the field schema. For this to work, your access token must be valid and your Pinterest Developer App must have at least one ad account accessible. If you see an error at this stage, check that the Authorization header is set correctly (the word Bearer followed by a space and your token) and that the token has not expired. When the initialize succeeds, Bubble automatically detects the response fields — you will see items listed as a list of Pinterest Ad Accounts, each with fields like id, name, owner, country, and currency. Set Use as to Data so Bubble treats the response as data you can bind to UI elements. Note the returned id field — this is your ad_account_id, which you must include as a path segment in all subsequent campaign and analytics calls. Store this ID in your App Config Data Type or note it for hardcoding in Step 5.

```
// GET /ad_accounts
// Full URL: https://api.pinterest.com/v5/ad_accounts
// Headers: inherited from group (Authorization Private, Content-Type)
// No additional parameters

// Expected response shape:
{
  "items": [
    {
      "id": "123456789",
      "name": "My Brand Ads",
      "owner": { "username": "mybrand" },
      "country": "US",
      "currency": "USD"
    }
  ],
  "bookmark": null
}
```

**Expected result:** Bubble shows the response fields detected from the /ad_accounts call. You can see 'items' as a list with sub-fields including 'id' and 'name'. The call is set to Use as Data and is ready to bind to a Repeating Group.

### 5. Add Campaign Analytics Call with Date Range Parameters

Add a second call in the Pinterest Ads group. Name it Get Campaign Analytics. Set the method to GET and the endpoint to /ad_accounts/AD_ACCOUNT_ID/campaigns/analytics — replacing AD_ACCOUNT_ID with your actual numeric account ID (or use a dynamic path parameter by creating a call-level URL parameter named ad_account_id and inserting it as /ad_accounts/<ad_account_id>/campaigns/analytics). Add the following URL parameters: start_date (value: 2024-01-01 for initialization), end_date (value: 2024-01-31), granularity (value: DAY), columns (value: SPEND_IN_DOLLAR,TOTAL_CLICK_THROUGH,IMPRESSION_1,TOTAL_CONVERSION_VALUE_IN_MICRO_DOLLAR). Click Initialize call — Bubble needs a real response to detect the analytics schema. Once the initialize succeeds, add the campaign_ids URL parameter as optional if you want to filter by specific campaigns. Now, in your Bubble UI, add two Date Picker elements labeled 'From' and 'To'. In the workflow for your 'Run Analytics' button, use a Make API call action pointing to this Pinterest call and bind start_date to the From date picker's value formatted as YYYY-MM-DD (use the :formatted as YYYY-MM-DD operator in the dynamic expression), and end_date to the To picker similarly. Bind the results to a Repeating Group. CRITICAL: When displaying TOTAL_CONVERSION_VALUE_IN_MICRO_DOLLAR, always divide by 1,000,000 in your Bubble expression — for example, Current cell's value :divided by 1000000. Skipping this step will display values that are one million times too large.

```
// GET /ad_accounts/{ad_account_id}/campaigns/analytics
// Full URL example:
// https://api.pinterest.com/v5/ad_accounts/123456789/campaigns/analytics

// URL Parameters:
{
  "start_date": "2024-01-01",         // bind to Bubble date picker :formatted as YYYY-MM-DD
  "end_date": "2024-01-31",           // bind to Bubble date picker :formatted as YYYY-MM-DD
  "granularity": "DAY",
  "columns": "SPEND_IN_DOLLAR,TOTAL_CLICK_THROUGH,IMPRESSION_1,TOTAL_CONVERSION_VALUE_IN_MICRO_DOLLAR"
}

// Sample response item:
{
  "campaign_id": "987654321",
  "date": "2024-01-15",
  "SPEND_IN_DOLLAR": 45.50,
  "TOTAL_CLICK_THROUGH": 312,
  "IMPRESSION_1": 8420,
  "TOTAL_CONVERSION_VALUE_IN_MICRO_DOLLAR": 142500000
  // NOTE: 142500000 / 1000000 = $142.50 actual conversion value
}
```

**Expected result:** The campaign analytics call is configured with date range parameters and the correct columns. Bubble's Initialize shows the response schema with spend, click, impression, and conversion fields. Your Repeating Group displays formatted campaign metrics with correct dollar values.

### 6. Build a Token Refresh Backend Workflow (Paid Plan Required)

Pinterest access tokens expire in 24 hours. Without a refresh mechanism, your Bubble dashboard will return 401 errors every morning. Here is how to build automated token refresh on a paid Bubble plan. First, make sure your Pinterest refresh_token is stored in a Bubble Data Type (e.g., 'App Config', a single record, with fields pinterest_access_token TEXT and pinterest_refresh_token TEXT). In your API Connector, add a new call to the Pinterest Ads group named Refresh Token. Set it to POST with endpoint /v5/oauth/token. Override the Authorization header at the call level to be Basic base64(APP_ID:APP_SECRET) (the token refresh endpoint uses Basic auth with your App credentials, not the Bearer token). Set the Content-Type header to application/x-www-form-urlencoded. Add the body parameters grant_type=refresh_token and refresh_token=YOUR_STORED_REFRESH_TOKEN as dynamic parameters. Now go to Settings → API in your Bubble editor and enable 'This app exposes a Workflow API'. Go to Backend Workflows and create a new API workflow named 'Refresh Pinterest Token'. Add the action Make API Call → Refresh Token (passing the stored refresh_token from your App Config record). Add a second action: Make changes to Thing → update your App Config's pinterest_access_token to the access_token from the API response. Finally, set up a recurring event (available on paid plans under Backend Workflows → Schedule recurring event) to run this workflow every 20 hours — before the 24-hour expiry. Update your API Connector Authorization header to read its value dynamically from the App Config Data Type's pinterest_access_token field. If you are using RapidDev to build this integration, the team can set up the full OAuth refresh loop as part of a scoping session — visit rapidevelopers.com/contact.

```
// POST to https://api.pinterest.com/v5/oauth/token
// (Add as a separate call in API Connector with call-level header override)

// Headers (call-level, override group headers):
// Authorization: Basic base64(YOUR_APP_ID:YOUR_APP_SECRET)
// Content-Type: application/x-www-form-urlencoded

// Body (form-encoded, use dynamic parameters in Bubble):
grant_type=refresh_token
&refresh_token=pinr_YOUR_STORED_REFRESH_TOKEN

// Response:
{
  "access_token": "pina_NEW_TOKEN_HERE",
  "token_type": "bearer",
  "expires_in": 86400,
  "refresh_token": "pinr_SAME_OR_NEW_REFRESH_TOKEN"
}
```

**Expected result:** A recurring Backend Workflow runs every 20 hours, calls Pinterest's token refresh endpoint, and updates the stored access token in your App Config Data Type. The API Connector reads the updated token dynamically, and your Pinterest Ads dashboard remains authenticated around the clock.

## Best practices

- Always mark the Authorization header Private in the API Connector group. Never put your Bearer token in a Text element, a URL parameter visible in the browser, or a client-side workflow — the Private flag is what keeps it server-side.
- Divide all TOTAL_CONVERSION_VALUE_IN_MICRO_DOLLAR values by 1,000,000 before displaying. Add a helper note in your Bubble app's internal description fields as a reminder for future editors.
- Store both the access_token and refresh_token in a Bubble Data Type (not in the API Connector value directly) so your Backend Workflow can update the token dynamically without requiring a manual API Connector edit each time.
- Build the token refresh Backend Workflow before launching your dashboard to any real users. A 24-hour token lifespan without refresh automation means the app breaks every morning.
- Use Pinterest's Development mode to build and test the full integration before the Marketing API review completes. Development mode returns sandboxed data that mirrors production response shapes, so your Bubble bindings and Repeating Group configurations will work correctly when you switch to live.
- Set up Data tab → Privacy rules for any Bubble Data Type that caches Pinterest analytics results. Ad spend and conversion data should only be visible to admin users or the account owner, not to all app users.
- Prefer the webhook alternative for high-frequency updates. Pinterest Ads analytics are inherently batched (not real-time), so daily polling via a scheduled Backend Workflow is more WU-efficient than triggering analytics calls on every page load.

## Use cases

### Campaign ROAS Dashboard

Pull spend, clicks, impressions, and conversion values for all active Pinterest campaigns into a Bubble dashboard. Display computed ROAS (conversion value divided by spend) alongside CTR and CPC, updated daily, so founders can monitor performance without opening Pinterest Ads Manager.

Prompt example:

```
Build a Pinterest Ads ROAS dashboard in Bubble that shows spend, clicks, conversions, and ROAS for each campaign, with a date range picker and auto-refresh every morning.
```

### Multi-Channel Ad Reporting Tool

Combine Pinterest Ads analytics with data from Google Ads and Facebook Ads in a single Bubble reporting app. Display all channels side-by-side so marketing teams can compare platform performance and reallocate budget without switching between multiple ad platforms.

Prompt example:

```
Create a Bubble page that shows campaign metrics from Pinterest Ads, Google Ads, and Facebook Ads in a unified table sorted by ROAS, with a channel filter dropdown.
```

### Pinterest Ad Account Manager for Agencies

Build an internal Bubble tool where agency team members can view all client Pinterest Ads accounts from a single interface, see campaign status, and pull date-range analytics reports without giving every employee access to the main Pinterest Ads Manager.

Prompt example:

```
Build a Bubble admin panel that lists all Pinterest ad accounts, lets the user select one, then shows campaigns with status, spend, and conversion totals for a chosen date range.
```

## Troubleshooting

### Initialize call returns 'There was an issue setting up your call' with no other detail

Cause: The Authorization header value is malformed — either missing the word 'Bearer', missing the space between 'Bearer' and the token, or the token itself has expired (Pinterest access tokens last only 24 hours).

Solution: Double-check the header value is exactly: Bearer pina_yourtoken (capital B, one space, token starting with pina_). If the token is older than 24 hours, complete the OAuth flow again to get a new access token. Also confirm that the Private checkbox is checked — without it, Bubble may behave unexpectedly on the initialize call.

### API returns 401 Unauthorized after working correctly the previous day

Cause: The Pinterest access token has expired. Access tokens are valid for only 24 hours — this is a Pinterest platform constraint that applies to all developers.

Solution: If you have a refresh_token stored, use Pinterest's token refresh endpoint (POST /v5/oauth/token with grant_type=refresh_token) to get a new access token. If you do not yet have the Backend Workflow from Step 6 set up, refresh manually now and paste the new token into the API Connector Authorization header. Build the recurring Backend Workflow on a paid Bubble plan to prevent this from happening again.

### Conversion values look unrealistically large — for example, $50 showing as $50,000,000

Cause: Pinterest's analytics API returns TOTAL_CONVERSION_VALUE_IN_MICRO_DOLLAR in micro-dollars (1/1,000,000th of a dollar). Displaying the raw value without dividing gives figures 1,000,000 times too large.

Solution: In every Bubble dynamic expression that displays this field, add :divided by 1000000 after the field reference. For example: Current cell's TOTAL_CONVERSION_VALUE_IN_MICRO_DOLLAR :divided by 1000000 :formatted as currency.

### The Initialize call succeeds but the analytics call returns an empty items array

Cause: The date range parameters may be in the wrong format, or the ad account has no campaign data in the specified date range. Pinterest expects dates as YYYY-MM-DD strings.

Solution: Confirm the start_date and end_date parameters are formatted as YYYY-MM-DD (for example, 2024-01-15, not January 15 2024 or 01/15/2024). In your Bubble workflow, use the date picker's value :formatted as YYYY-MM-DD operator. Also verify the date range falls within a period when your ad account had active campaigns.

## Frequently asked questions

### Do I need a paid Pinterest plan to use the Marketing API?

Pinterest Marketing API access is free after your Developer App is reviewed and approved. You do need a verified Pinterest business account and an active ad account. The costs you incur are your actual ad spend on Pinterest — the API itself has no additional fee.

### Why does my access token stop working overnight?

Pinterest access tokens expire after 24 hours — this is a platform-level constraint that all Pinterest developers must work around. The solution is to store your refresh_token (valid for 365 days) in Bubble's database and build a Backend Workflow that calls Pinterest's token refresh endpoint every 20 hours. This requires a paid Bubble plan for the scheduling capability.

### Can I use the Pinterest API in Bubble's Free plan?

You can set up the API Connector, create calls, and build the UI on Bubble's Free plan. However, the token refresh Backend Workflow (which requires scheduling recurring events) needs a paid Bubble plan. On the Free plan, you must manually refresh your access token every 24 hours — practical for testing but not for a production dashboard.

### What does 'Marketing API access requires app review' mean in practice?

Pinterest manually reviews Developer Apps requesting Marketing API access to ensure they meet their usage policies. During this review period (typically 2–5 business days), your app runs in Development mode, which returns sandboxed test data that mirrors real API responses. You can build and test the full Bubble integration during this period — when approval comes through, you flip to production data with no code changes.

### Why is my ad analytics call returning empty results even though I have active campaigns?

The most common causes are: (1) date format is wrong — Pinterest requires YYYY-MM-DD, not MM/DD/YYYY or other formats; (2) the date range precedes when your campaigns started; or (3) your Developer App is in Development mode returning only sandboxed data. Check the columns parameter is correct and run the Initialize call to inspect the raw response from Pinterest.

---

Source: https://www.rapidevelopers.com/bubble-integrations/pinterest-ads
© RapidDev — https://www.rapidevelopers.com/bubble-integrations/pinterest-ads
