What the Pinterest API covers
Pinterest’s current API is v5, described in full in an OpenAPI file on GitHub (version 5.28.0 as of this writing). One API covers both organic and paid use:
- Organic content: boards and board sections, Pins (image, video, carousel), user account data and Pin and account analytics.
- Ads: ad accounts, campaigns, audiences and conversion events.
- Catalogs and trends: product catalogs for shopping, and trend data.
This guide is about the organic part: getting access, authenticating and publishing Pins. One boundary from Pinterest’s own reference: pins/create “is intended solely for publishing new content created by the user.” For saving other people’s content to your boards, Pinterest points you to its Save button instead.
Trial vs Standard access
Every app has an access tier, and the tier changes both who sees your Pins and how fast you can call the API:
| Trial access | Standard access | |
|---|---|---|
| How you get it | Default tier once your app is approved | Upgrade request with a video demo of your app |
| Pins and boards you create | Visible only to the creator, as Sandbox entities | Public, like any other Pin |
| Rate limits | Per day, per app: 1,000 requests for all calls | Per minute, per user, per app: 100 requests per second overall |
| Endpoints | Same as Standard | Same as Trial |
| Right for | Building and testing | Anything real users rely on |
Sources: access tiers and rate limits. Read 29 September 2026.
The Trial trap. Your code can be completely correct on Trial access and still look broken: the API returns a Pin ID, and nobody but you can see the Pin. That is by design. To go public, your app must already be approved for Trial access and follow the Developer Guidelines; you then record a video of your app completing an action through the API (Pinterest wants to see the OAuth flow and the live integration) and click Upgrade on the app card in My apps. Pinterest reviews requests “regularly” and emails you the decision. There is no published timeline.
OAuth 2.0: 30-day tokens, 60-day refresh
Send the user to Pinterest’s authorization page with the scopes you need:
https://www.pinterest.com/oauth/
?client_id=YOUR_APP_ID
&redirect_uri=https://yourapp.com/callback
&response_type=code
&scope=boards:read,boards:write,pins:read,pins:write
&state=RANDOM_STRING
Then exchange the code at https://api.pinterest.com/v5/oauth/token. Unlike most APIs, the app credentials go in an HTTP Basic Authorization header, not the body (authentication docs):
# Code -> tokens (HTTP Basic auth with app ID and secret)
curl -X POST https://api.pinterest.com/v5/oauth/token \
-u "YOUR_APP_ID:YOUR_APP_SECRET" \
-d grant_type=authorization_code \
-d code=AUTH_CODE \
-d redirect_uri=https://yourapp.com/callback
# -> access_token (30 days) + refresh_token (60 days, refreshable)
# Apps created before 25 Sep 2025: add -d continuous_refresh=true
# Later: refresh before the access token expires
curl -X POST https://api.pinterest.com/v5/oauth/token \
-u "YOUR_APP_ID:YOUR_APP_SECRET" \
-d grant_type=refresh_token \
-d refresh_token=REFRESH_TOKEN
Access tokens last 30 days (2,592,000 seconds). Refresh tokens are the “continuous” kind: they last 60 days and can be refreshed indefinitely, so a scheduled refresh keeps a connection alive without the user logging in again. Pinterest no longer supports the old 365-day refresh tokens. Apps created on or after 25 September 2025 get continuous tokens automatically; older apps must send continuous_refresh=true in the code exchange.
Pinterest API scopes
| Scope | Allows |
|---|---|
| boards:read | See public boards, including group boards the user joined |
| boards:write | Create, update or delete public boards |
| pins:read | See public Pins |
| pins:write | Create, update or delete public Pins |
| *_secret variants | boards:read_secret, boards:write_secret, pins:read_secret, pins:write_secret for secret boards and Pins |
| user_accounts:read | See the user account and followers |
| ads:*, catalogs:*, billing:*, biz_access:* | Ads, catalogs, billing and business access, read and write |
Source: Pinterest API v5 OpenAPI description, security schemes. Read 29 September 2026.
Creating a Pin requires boards:read, boards:write, pins:read and pins:write.
How to create a Pin with the API
Find the board ID
curl https://api.pinterest.com/v5/boards \
-H "Authorization: Bearer ACCESS_TOKEN"
# -> {"items": [{"id": "987654321", "name": "Weeknight dinners", ...}], "bookmark": ...}Image Pin
curl -X POST https://api.pinterest.com/v5/pins \
-H "Authorization: Bearer ACCESS_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"board_id": "987654321",
"title": "15-minute miso noodles",
"description": "Weeknight dinner with five ingredients.",
"link": "https://example.com/miso-noodles",
"alt_text": "Bowl of noodles in miso broth with scallions",
"media_source": {
"source_type": "image_url",
"url": "https://cdn.example.com/miso-noodles.jpg"
}
}'
The response is the new Pin with its id. Instead of image_url you can send the file itself with source_type: "image_base64", content_type and data. Add board_section_id to drop the Pin into a section.
Carousel Pin
Swap the media source for multiple_image_urls (or multiple_image_base64) with 2 to 5 items. Each slide can carry its own title, description and link:
"media_source": {
"source_type": "multiple_image_urls",
"items": [
{"url": "https://cdn.example.com/step-1.jpg", "title": "Step 1", "link": "https://example.com/r#1"},
{"url": "https://cdn.example.com/step-2.jpg", "title": "Step 2", "link": "https://example.com/r#2"},
{"url": "https://cdn.example.com/step-3.jpg", "title": "Step 3", "link": "https://example.com/r#3"}
]
}Video Pin
Video takes an upload step. You register the upload, post the file to a pre-signed storage URL with the parameters Pinterest gives you, wait for processing, then create the Pin with the media_id (create boards and Pins):
# 1. Register the upload
curl -X POST https://api.pinterest.com/v5/media \
-H "Authorization: Bearer ACCESS_TOKEN" \
-H "Content-Type: application/json" \
-d '{"media_type": "video"}'
# -> {"media_id": "12345", "upload_url": "https://pinterest-media-upload.s3-accelerate.amazonaws.com/",
# "upload_parameters": {"key": "...", "policy": "...", "x-amz-signature": "...", ...}}
# 2. Upload the file with every upload_parameter as a form field
curl -X POST "UPLOAD_URL" \
-F "key=..." -F "policy=..." -F "x-amz-algorithm=..." -F "x-amz-credential=..." \
-F "x-amz-date=..." -F "x-amz-security-token=..." -F "x-amz-signature=..." \
-F "Content-Type=multipart/form-data" \
-F "[email protected]"
# 3. Wait for status "succeeded"
curl https://api.pinterest.com/v5/media/12345 \
-H "Authorization: Bearer ACCESS_TOKEN"
# 4. Create the Pin
# "media_source": {"source_type": "video_id", "media_id": "12345",
# "cover_image_key_frame_time": 3}
The media status moves through registered, processing and succeeded or failed. Only create the Pin after succeeded. For the cover you can pass cover_image_url, a base64 image, or cover_image_key_frame_time in seconds; if that time is past the end of the video, Pinterest uses the last frame.
Test in the Sandbox
Pinterest runs a separate environment at https://api-sandbox.pinterest.com/v5 that does not touch production data. You can generate a 30-day Sandbox token from your app page or get one through OAuth using https://api-sandbox.pinterest.com/v5/oauth/token. Sandbox boards and Pins appear on your own profile only and cannot be moved to production. The main gap: you cannot create video Pins in Sandbox (Sandbox docs).
Pinterest API in Python
You do not need a client library. This script creates an image Pin and a video Pin, including the upload and status loop, with requests:
import time
import requests
API = "https://api.pinterest.com/v5" # Sandbox: https://api-sandbox.pinterest.com/v5
H = {"Authorization": "Bearer ACCESS_TOKEN"}
def create_pin(board_id, media_source, **fields):
r = requests.post(f"{API}/pins", headers=H,
json={"board_id": board_id, "media_source": media_source, **fields})
if r.status_code == 429:
reset = r.headers.get("x-ratelimit-reset")
raise RuntimeError(f"Rate limited, retry in {reset} s")
r.raise_for_status()
return r.json()["id"]
def upload_video(path):
reg = requests.post(f"{API}/media", headers=H, json={"media_type": "video"}).json()
with open(path, "rb") as f:
up = requests.post(reg["upload_url"], data=reg["upload_parameters"],
files={"file": f})
up.raise_for_status()
while True:
status = requests.get(f"{API}/media/{reg['media_id']}", headers=H).json()["status"]
if status == "succeeded":
return reg["media_id"]
if status == "failed":
raise RuntimeError("Pinterest could not process the video")
time.sleep(5)
BOARD = "987654321"
# Image Pin
create_pin(BOARD, {"source_type": "image_url", "url": "https://cdn.example.com/a.jpg"},
title="Before and after", link="https://example.com/renovation")
# Video Pin (not available in Sandbox)
media_id = upload_video("demo.mp4")
create_pin(BOARD, {"source_type": "video_id", "media_id": media_id,
"cover_image_key_frame_time": 2},
title="Kitchen renovation in 30 seconds")For a real integration, add the token refresh job and store the media_id with each queued post, so a restart does not upload the same video twice.
Pinterest API limits
| Limit | Trial access | Standard access |
|---|---|---|
| All requests | 1,000 per day | 100 per second per user per app |
org_write (create Pins, boards) | 300 per day | 100 per minute |
org_read | 1,000 per day | 1,000 per minute |
org_analytics | 1,000 per day | 60 per minute |
Source: Pinterest rate limits. Read 29 September 2026.
| Field or media | Limit |
|---|---|
| Title | 100 characters (the first 40 show in feeds) |
| Description | 800 characters |
| Link | 2,048 characters |
| Alt text | 500 characters |
| Image | PNG or JPEG; up to 20 MB on desktop, 32 MB in the app; 2:3 recommended (1000 × 1500 px) |
| Video | MP4, MOV or M4V; up to 2 GB; 4 seconds to 15 minutes; 1:1, 2:3, 4:5 or 9:16 |
| Carousel | 2 to 5 images, 20 MB each |
Sources: OpenAPI Pin schema and Pinterest product specs. Read 29 September 2026.
Every response carries x-ratelimit-limit, x-ratelimit-remaining and x-ratelimit-reset (seconds until the window resets). Read them instead of guessing, especially on Trial access where 300 writes a day go fast in testing.
Common Pinterest API errors
Errors come back as JSON with a numeric code and a message. What each HTTP status usually means when creating Pins:
| Status | Usual cause | Fix |
|---|---|---|
| 400 | Invalid body: title over 100, description over 800, a carousel with 1 or 6 images, a bad source_type | Validate against the limits above before sending |
| 401 | Access token expired (30 days) or revoked | Refresh with the refresh token; re-run OAuth if that expired too |
| 403 | Token lacks a scope, or the board belongs to someone else | Request boards:write and pins:write; use a board the user owns |
| 404 | Wrong board_id, or a production ID used against Sandbox (or the reverse) | List boards with the same host and token you post with |
| 429 | Daily (Trial) or per-minute (Standard) limit reached | Wait for x-ratelimit-reset; upgrade to Standard |
Media failed | Video outside the specs, or the upload did not include every upload_parameters field | Check format, size and duration; re-register and upload again |
| 200, but invisible | App is on Trial access | Request Standard access |
The shortcut: publish Pins without the Standard access review
If Pinterest is one of several channels, the hard part is not the POST. It is getting Standard access, the token refresh, the S3-style video upload and the status loop. Upload-Post’s Pinterest API runs an approved app, so Pins are public from the first post. You connect the account once and send the file:
# A video Pin to a board section, with cover frame and AI disclosure
curl -X POST https://api.upload-post.com/api/upload \
-H "Authorization: Apikey YOUR_KEY" \
-F "user=my-brand" \
-F "platform[]=pinterest" \
-F "platform[]=tiktok" \
-F "platform[]=instagram" \
-F "[email protected]" \
-F "title=Kitchen renovation in 30 seconds" \
-F "pinterest_board_id=987654321" \
-F "pinterest_board_section_id=555" \
-F "pinterest_link=https://example.com/kitchen" \
-F "pinterest_cover_image_key_frame_time=3" \
-F "pinterest_ai_disclosures=AI_MODIFIED" \
-F "add_to_queue=true"
That queues the video for Pinterest, TikTok and Instagram in the next free slot, with a board section, a destination link, the cover taken from second 3 and an AI disclosure. The board IDs come from GET /api/uploadposts/pinterest/boards, which also lists secret boards and can create boards and sections. Other options in the docs:
- Carousels: send 2 to 5 images to
/api/upload_photoswith per-slidepinterest_carousel_titles[],pinterest_carousel_descriptions[]andpinterest_carousel_links[]. - Pin fields:
pinterest_title,pinterest_description,pinterest_alt_text,pinterest_link. - Covers:
pinterest_cover_image_url, base64 cover data or a key frame time. - Scheduling:
scheduled_datewith atimezone, up to 365 days ahead, oradd_to_queue=true. - Saving Pins: repin an existing Pin to one of your boards with
POST /api/uploadposts/posts/save. - Analytics: Pinterest metrics come back from the same analytics endpoint as the other platforms.
Working from an AI assistant? The Pinterest MCP server gives Claude, ChatGPT and Cursor the same tools, including board lookup.
The Free plan is free forever with no card: 10 uploads a month and 2 profiles, with Pinterest and API access included. Paid plans with unlimited uploads start at $24 a month ($16 billed annually). Compare options in the social media API overview.
Frequently asked questions
Is the Pinterest API free?
Pinterest does not charge for API access. What limits you is the access tier: Trial access is capped at 1,000 requests a day and everything you create is private to you, and Standard access, which lifts both, requires a review with a video demo of your app.
Why can nobody see the Pins I created with the API?
Because your app is on Trial access. Pinterest’s docs say all Pins and boards created with Trial access are only visible to their creator as Sandbox entities. Upgrade to Standard access for Pins that other people can see.
How do I get Standard access to the Pinterest API?
Your app must already be approved for Trial access and follow the Developer Guidelines. Record a video of your app completing an action through the API, showing the OAuth flow and the live Pinterest integration, then choose Upgrade on the app card in My apps. Pinterest emails you when it decides; it does not publish a timeline.
Which OAuth scopes do I need to create a Pin?
The pins/create endpoint lists boards:read, boards:write, pins:read and pins:write. For secret boards and Pins add boards:read_secret, boards:write_secret, pins:read_secret or pins:write_secret. user_accounts:read covers the account profile and follower data.
How long do Pinterest access tokens last?
Access tokens last 30 days (2,592,000 seconds). Continuous refresh tokens last 60 days and can be refreshed indefinitely, so a daily or weekly refresh job keeps a connection alive without sending the user back through OAuth.
Can I create video Pins with the API?
Yes, in three steps: register the upload with POST /v5/media, upload the file to the returned upload_url with the upload_parameters, then create the Pin with media_source.source_type set to video_id and a cover image once the media status is succeeded. Video Pins cannot be created in the Sandbox environment.
How many images can a Pinterest carousel have?
Two to five. The API accepts multiple_image_urls or multiple_image_base64 with 2 to 5 items, each with its own optional title, description and link. Pinterest’s specs allow up to 20 MB per image.
What are the Pinterest API rate limits?
On Trial access, 1,000 requests a day for all calls, with lower category limits such as 300 org_write calls a day. On Standard access, 100 requests per second per user per app, with per-minute category limits such as 100 org_write and 1,000 org_read. Responses include x-ratelimit-limit, x-ratelimit-remaining and x-ratelimit-reset headers.
Can I repin other people’s content with pins/create?
Pinterest says pins/create is intended solely for publishing new content created by the user, and points to its Save button for curated content. Use it for your own images and videos.
Is there an official Pinterest API for Python?
Pinterest publishes an OpenAPI description of API v5 on GitHub, which you can use to generate a client, but the API is plain HTTPS and JSON. The Python example on this page creates image, carousel and video Pins with nothing but requests.