# How to create a Pin with the Pinterest API

To post a Pin, send `POST https://api.pinterest.com/v5/pins` with a `board_id` and a `media_source` (an image URL, base64
image data, 2 to 5 images, or an uploaded video), plus an optional `title` (100 characters), `description` (800), `link`
(2,048) and `alt_text` (500). Authenticate with an OAuth user access token that has the `boards:read`, `boards:write`,
`pins:read` and `pins:write` scopes. A new app gets **Trial access**: 300 write requests a day for the whole app, Pin
creates included, and every Pin it creates is visible only to the person who made it. Public Pins need **Standard
access**, which Pinterest grants after reviewing a video of your app.

Checked on November 3, 2026 against Pinterest's developer docs: [Create Pin](https://developers.pinterest.com/docs/api/v5/pins-create),
[creating boards and Pins](https://developers.pinterest.com/docs/api-features/creating-boards-and-pins/),
[authentication and scopes](https://developers.pinterest.com/docs/getting-started/set-up-authentication-and-authorization/),
[access tiers](https://developers.pinterest.com/docs/key-concepts/access-tiers/),
[rate limits](https://developers.pinterest.com/docs/reference/rate-limits/) and [Sandbox](https://developers.pinterest.com/docs/developer-tools/sandbox/),
Pinterest's [OpenAPI description](https://github.com/pinterest/api-description) (v5.28.0) and its
[Pin specs](https://help.pinterest.com/en/article/review-pin-specs).

## What you need first

- **A Pinterest business account** to own the app.
- **An app with Trial access.** In [My apps](https://developers.pinterest.com/apps/), accept the developer terms, choose
  **Connect app**, fill in the form and submit it. Pinterest reviews each request and emails you the answer. Once
  approved, My apps shows the app ID and app secret.
- **A redirect URI** added on the app's **Configure** tab. It has to match the one you send exactly.
- **An access token for the account that posts.** To try the API on your own account, Pinterest lets a Trial app generate
  a test token without OAuth; it lasts 24 hours. For anyone else, use the OAuth flow below.

### Get a token with OAuth

Send the person to Pinterest's consent page:

```text
https://www.pinterest.com/oauth/?client_id=YOUR_APP_ID&redirect_uri=https%3A%2F%2Fyour.app%2Fcallback&response_type=code&scope=boards:read,boards:write,pins:read,pins:write&state=RANDOM_STATE
```

Pinterest sends them back to your redirect URI with a `code` and your `state`, which you check. Exchange the code for a
token, signing in with your app ID and secret as HTTP Basic credentials:

```bash
curl -X POST https://api.pinterest.com/v5/oauth/token \
  -u "$PINTEREST_APP_ID:$PINTEREST_APP_SECRET" \
  --data-urlencode grant_type=authorization_code \
  --data-urlencode code="$CODE" \
  --data-urlencode redirect_uri=https://your.app/callback
```

The answer holds an `access_token` (it starts with `pina` and lasts 30 days, `expires_in` 2,592,000) and a
`refresh_token` (it starts with `pinr`). Refresh before the 30 days are up with `grant_type=refresh_token`. The refresh
token lasts 60 days and renews each time you use it. A token can stop working early, for example when the person changes
their password, and Pinterest doesn't always tell you; a `401` means start the flow again.

## Post it

Every Pin goes on a board, and the API never picks one for you. List the account's boards with `GET /v5/boards` and use
the board's `id`.

### curl

```bash
# 1. List the boards. Each item has an id and a name.
curl "https://api.pinterest.com/v5/boards?page_size=250" \
  -H "Authorization: Bearer $PINTEREST_TOKEN"

# 2. Create the Pin on one of them.
curl -X POST https://api.pinterest.com/v5/pins \
  -H "Authorization: Bearer $PINTEREST_TOKEN" \
  -H "Content-Type: application/json" \
  -d "{\"board_id\": \"$BOARD_ID\",
       \"title\": \"Weeknight tomato soup\",
       \"description\": \"Ready in 25 minutes, with pantry staples.\",
       \"link\": \"https://example.com/recipes/tomato-soup\",
       \"alt_text\": \"A bowl of tomato soup with basil on a wooden table\",
       \"media_source\": {\"source_type\": \"image_url\", \"url\": \"https://example.com/images/tomato-soup.jpg\"}}"
```

Pinterest answers `201 Created` with the new Pin. Its `id` is the Pin's ID:

```json
{
  "id": "654321654321654321",
  "board_id": "123456123456123456",
  "title": "Weeknight tomato soup",
  "link": "https://example.com/recipes/tomato-soup",
  "created_at": "2026-11-03T15:04:05",
  "media": { "media_type": "image" }
}
```

Pinterest downloads the image from `url`, so it has to be reachable from the internet. To send the file itself, use
`{"source_type": "image_base64", "content_type": "image/jpeg", "data": "<base64>"}`; `content_type` is `image/jpeg` or
`image/png`.

### Python

Pinterest's own Python SDK covers ads and campaigns. For Pins, `requests` is all you need:

```python
import os

import requests

API = "https://api.pinterest.com/v5"
headers = {"Authorization": f"Bearer {os.environ['PINTEREST_TOKEN']}"}

# Find the board by name. Boards come in pages; follow the bookmark to the end.
boards, params = [], {"page_size": 250}
while True:
    page = requests.get(f"{API}/boards", headers=headers, params=params, timeout=30)
    page.raise_for_status()
    page = page.json()
    boards += page["items"]
    if not page.get("bookmark"):
        break
    params["bookmark"] = page["bookmark"]
board = next(b for b in boards if b["name"] == "Recipes")

resp = requests.post(
    f"{API}/pins",
    headers=headers,
    json={
        "board_id": board["id"],
        "title": "Weeknight tomato soup",
        "description": "Ready in 25 minutes, with pantry staples.",
        "link": "https://example.com/recipes/tomato-soup",
        "alt_text": "A bowl of tomato soup with basil on a wooden table",
        "media_source": {"source_type": "image_url", "url": "https://example.com/images/tomato-soup.jpg"},
    },
    timeout=30,
)
resp.raise_for_status()
print(resp.json()["id"])
```

### Node.js (18 or later)

```js
const API = 'https://api.pinterest.com/v5';
const auth = { Authorization: `Bearer ${process.env.PINTEREST_TOKEN}` };

async function pinterest(path, init = {}) {
  const r = await fetch(`${API}${path}`, { ...init, headers: { ...auth, ...init.headers } });
  if (!r.ok) throw new Error(`Pinterest answered ${r.status}: ${await r.text()}`);
  return r.json();
}

// Find the board by name, following the bookmark through every page.
const boards = [];
let bookmark = null;
do {
  const page = await pinterest(`/boards?page_size=250${bookmark ? `&bookmark=${encodeURIComponent(bookmark)}` : ''}`);
  boards.push(...page.items);
  bookmark = page.bookmark;
} while (bookmark);
const board = boards.find((b) => b.name === 'Recipes');

const pin = await pinterest('/pins', {
  method: 'POST',
  headers: { 'Content-Type': 'application/json' },
  body: JSON.stringify({
    board_id: board.id,
    title: 'Weeknight tomato soup',
    description: 'Ready in 25 minutes, with pantry staples.',
    link: 'https://example.com/recipes/tomato-soup',
    alt_text: 'A bowl of tomato soup with basil on a wooden table',
    media_source: { source_type: 'image_url', url: 'https://example.com/images/tomato-soup.jpg' },
  }),
});
console.log(pin.id);
```

To put the Pin in a board section, add `board_section_id`. To post 2 to 5 images as one Pin, use
`{"source_type": "multiple_image_urls", "items": [{"url": "…"}, {"url": "…"}]}`, or `multiple_image_base64` with
`content_type` and `data` for each item.

## Trial access: your Pins are visible only to you

This is the surprise most first Pins hit. The call succeeds, you get a Pin ID, and nobody else can see the Pin. Pinterest's
access tiers page says all Pins and boards created with Trial access are visible only to their creator. You see them
on your own profile; your followers don't.

To make Pins public, upgrade the app to Standard access: choose **Upgrade** on the app's card in My apps, confirm your
use case and privacy policy, and upload a video of your app sending a user through the OAuth flow and doing something
with the API. Pinterest asks for the video even if you are the app's only user, and accepts a screen recording of calls in
a terminal or Postman. Standard also raises the rate limits.

Two more things stop first Pins:

- **Business accounts.** If your app was registered on or after September 14, 2026, Pinterest may answer `403` with
  `PINNER_DATA_ACCESS_DENIED` when you read a board or Pin of a personal account. Connect business accounts.
- **Sandbox can't test video.** Pinterest's Sandbox (`https://api-sandbox.pinterest.com/v5`, with a Sandbox token from My
  apps) takes the same calls without touching production, but can't create video Pins. Don't create a Sandbox Pin on a
  group board: the board's owner may get a notification.

## Video Pins: register, upload, wait, create

A video takes four calls. Register the upload with `POST /v5/media`, upload the file to the `upload_url` Pinterest gives
you, wait until `GET /v5/media/{media_id}` says `succeeded`, then create the Pin with `source_type` `video_id` and a cover:

```python
import os
import time

import requests

API = "https://api.pinterest.com/v5"
headers = {"Authorization": f"Bearer {os.environ['PINTEREST_TOKEN']}"}

# 1. Register the upload.
reg = requests.post(f"{API}/media", headers=headers, json={"media_type": "video"}, timeout=30)
reg.raise_for_status()
reg = reg.json()

# 2. Upload the file with every upload parameter. No Pinterest token here; a success is 204.
with open("soup.mp4", "rb") as f:
    up = requests.post(reg["upload_url"], data=reg["upload_parameters"], files={"file": f}, timeout=600)
up.raise_for_status()

# 3. Wait for Pinterest to process it.
while True:
    status = requests.get(f"{API}/media/{reg['media_id']}", headers=headers, timeout=30)
    status.raise_for_status()
    status = status.json()["status"]
    if status == "succeeded":
        break
    if status == "failed":
        raise RuntimeError("Pinterest couldn't process the video")
    time.sleep(5)

# 4. Create the Pin.
resp = requests.post(
    f"{API}/pins",
    headers=headers,
    json={
        "board_id": os.environ["BOARD_ID"],
        "title": "Weeknight tomato soup",
        "media_source": {
            "source_type": "video_id",
            "media_id": reg["media_id"],
            "cover_image_url": "https://example.com/images/tomato-soup-cover.jpg",
        },
    },
    timeout=30,
)
resp.raise_for_status()
print(resp.json()["id"])
```

Pinterest's guide says a valid cover image URL avoids a `400`. Instead of a URL, you can send `cover_image_data` with
`cover_image_content_type`, or pick a frame with `cover_image_key_frame_time` in whole seconds; past the video's end,
Pinterest uses the last frame. A new video Pin can show `creative_type` `REGULAR` for a few seconds before it turns
`VIDEO`.

## Limits

| | Limit |
|---|---|
| Title | 100 characters |
| Description | 800 characters |
| Alt text | 500 characters |
| Link | 2,048 characters |
| Images in one Pin | 2 to 5, with `multiple_image_urls` or `multiple_image_base64` |
| Image files | BMP, JPEG, PNG, TIFF or WebP, up to 20 MB on the web. Base64 uploads take JPEG or PNG |
| Video files | MP4, MOV or M4V, H.264 or H.265, 4 seconds to 5 minutes |
| All requests, Trial | 1,000 a day |
| All requests, Standard | 100 a second, per user, per app |
| Creating Pins and media uploads (`org_write`), Trial | 300 a day, per app |
| Creating Pins and media uploads (`org_write`), Standard | 100 a minute, per user, per app |
| Reading boards, Pins and media (`org_read`), Trial | 1,000 a day, per app |
| Reading boards, Pins and media (`org_read`), Standard | 1,000 a minute, per user, per app |
| Access token | 30 days. The refresh token lasts 60 days and renews when used |

Past a limit, Pinterest answers `429`. The `x-ratelimit-limit`, `x-ratelimit-remaining` and `x-ratelimit-reset` headers
show where you are. Pinterest says its rate limits can change without notice; to ask for more, open an API Technical
Support request in its Help Center.

## Do it yourself, or let an agent draft it through Flaghand

Call the API yourself for a script that pins to your own business account, or for an app you'll take through Standard
review. It's a few calls, and nothing waits for a person.

If an AI agent writes the Pins, a token with `pins:write` lets it post anything, to any board, at once. Flaghand is a
publishing desk for that case. You connect a Pinterest business account with Pinterest's own sign-in. Your agent connects
to Flaghand's MCP server (`https://app.flaghand.com/mcp`) or its API, lists your boards live and drafts an image Pin, a 2-to-5-image
Pin or a video Pin with a cover, with its title, description, link, alt text, board and section. Flaghand never picks a
board for it. Before you approve, Flaghand checks the board still exists and measures the Pin against these limits:
100-character title, 800-character description, 500 characters of alt text, a 2,048-character link, JPEG, PNG or WebP
images, and MP4, M4V or MOV video of 4 seconds to 5 minutes, read from the file itself. You approve the exact Pin with
your passkey; Flaghand posts it at its time and reads it back to check its title, description, link, board, section, alt
text and media type. No token Flaghand issues can approve, schedule or post. Flaghand's Pinterest access is in Trial today,
so Pinterest shows the Pins it posts only to you for now, and the approval sheet says so. Board lists are read live and
never kept. Sign-up takes no card ([current terms](/pricing)).
[Get started with Flaghand](/schedule).

More on keeping an agent's posts in your hands: [let an AI agent post safely](/docs/guides/let-an-ai-agent-post-safely)
and [a social media approval workflow](/docs/guides/social-media-approval-workflow).

## Questions

**How do I get Pinterest API access?** Sign in to a Pinterest business account, accept the developer terms in My apps,
choose Connect app and submit the form. Pinterest reviews the request and emails you. Approval gives Trial access; public
Pins and higher limits need Standard access, which takes a video of your app's OAuth flow.

**Why can't anyone else see the Pin I created with the API?** Your app has Trial access. Pinterest makes every Pin and
board a Trial app creates visible only to its creator. Upgrade the app to Standard access to post public Pins.

**Which scopes does creating a Pin need?** Pinterest's docs list `boards:read`, `boards:write`, `pins:read` and
`pins:write` for Create Pin. Listing boards needs `boards:read`. Secret boards and Pins need `boards:read_secret`,
`boards:write_secret`, `pins:read_secret` or `pins:write_secret`.

**How many Pins can I create through the Pinterest API?** With Trial access, up to 300 a day for the whole app: Pin creates
share that limit with every other write, inside 1,000 requests a day overall. With Standard access, 100 a minute for each user of your app.

**Why does the Pinterest API answer PINNER_DATA_ACCESS_DENIED?** Apps registered on or after September 14, 2026 may get
this `403` when they read a board or Pin of a personal account. Connect a business account instead.

**Can an AI agent post to Pinterest for me?** With your token it can post anything, immediately. Through Flaghand it
drafts and asks for approval, and only your passkey approval of the exact Pin makes it go out.

## Sources

Checked November 3, 2026:

- Pinterest: [Create Pin](https://developers.pinterest.com/docs/api/v5/pins-create),
  [List boards](https://developers.pinterest.com/docs/api/v5/boards-list) and
  [Register media upload](https://developers.pinterest.com/docs/api/v5/media-create)
- Pinterest: [Create boards and Pins](https://developers.pinterest.com/docs/api-features/creating-boards-and-pins/)
- Pinterest: [Set up authentication and authorization](https://developers.pinterest.com/docs/getting-started/set-up-authentication-and-authorization/)
- Pinterest: [Connect app](https://developers.pinterest.com/docs/getting-started/connect-app/) and
  [access tiers](https://developers.pinterest.com/docs/key-concepts/access-tiers/)
- Pinterest: [Rate limits](https://developers.pinterest.com/docs/reference/rate-limits/)
- Pinterest: [Sandbox](https://developers.pinterest.com/docs/developer-tools/sandbox/)
- Pinterest: [OpenAPI description, v5.28.0](https://github.com/pinterest/api-description)
- Pinterest Help Center: [Review Pin specs](https://help.pinterest.com/en/article/review-pin-specs)

[All guides](/docs/guides.md)
