Skip to main content
Before you start. Pinterest gates pin creation on production (api.pinterest.com) until your app passes Standard Access review — Trial Access apps must point at api-sandbox.pinterest.com. letmepost reads PINTEREST_API_BASE from env, so during the trial window you set it to the sandbox host and clear it back to default the moment your app goes Standard.

Quick reference

Limit / capabilityValue
Description (text)mapped to Pinterest description; no documented grapheme cap (Pinterest enforces a soft 800-char display limit)
Titleoptional, free-form
Media items per pin1 (image or video; no carousel in v1)
Image formatsjpeg, png, webp
Image size20,000,000 bytes (20 MB)
Video formatsmp4, mov, m4v
Video size2,000,000,000 bytes (~2 GB)
Boardrequired (per-post pinterest.boardId or account default)
Destination URLoptional click-through; defaults to the image URL
Cover image URLrequired for video pins (Pinterest mandates a still-frame URL)
Post types supportedimage pin, video pin
SchedulingYes (via scheduledAt)
Reply / first commentNot supported in v1
Inbox / DMNot supported in v1
AnalyticsNot supported in v1

Connect an account

POST /v1/accounts/connect/pinterest. OAuth 2.0. After the callback the provider calls GET /v5/user_account to pin platformAccountId to the real Pinterest user id.

Scopes

boards:read         — read the caller's boards
boards:write        — create boards from the dashboard
pins:read           — required alongside pins:write (some endpoints echo the pin back)
pins:write          — create pins
user_accounts:read  — required to mint the platform_account_id at connect
Extended: pins:read_secret (off by default) for callers that publish to secret boards.

Token lifecycle

30-day access tokens, refreshable. The provider refreshes ~24 h before expiry; subscribe to token.expiring to know when a re-auth window is coming.

Post types

Image pin

image-pin.json
{
  "targets": [
    {
      "accountId": "...",
      "options": {
        "platform": "pinterest",
        "boardId": "<board id>",
        "destinationUrl": "https://example.com/blog-post",
        "title": "Pin title"
      }
    }
  ],
  "text": "Pin description",
  "media": [{ "kind": "image", "mediaId": "med_…" }]
}
The options extension is optional. If you set a default board on the account, options.boardId can be omitted; if you don’t supply destinationUrl we fall back to the image’s letmepost URL.

Video pin

coverImageUrl is required — Pinterest mandates a still-frame URL for the pre-play poster. Preflight rejects video pins without it (pinterest.video.cover_required) so you don’t lose the upload to a vague upstream 400.
video-pin.json
{
  "targets": [
    {
      "accountId": "...",
      "options": {
        "platform": "pinterest",
        "boardId": "<board id>",
        "destinationUrl": "https://example.com/walkthrough",
        "coverImageUrl": "https://cdn.example.com/cover.jpg"
      }
    }
  ],
  "text": "Walkthrough",
  "media": [{ "kind": "video", "mediaId": "med_…" }]
}
Behind the scenes the publisher runs four steps for video:
  1. POST /v5/media — register an upload slot. Failures: pinterest.media.register_failed.
  2. Multipart POST to the presigned S3 endpoint. Failures: pinterest.video.upload_failed.
  3. Poll GET /v5/media/{id} every 2 s up to a 5-minute deadline. Failures: pinterest.video.transcode_failed, pinterest.video.transcode_timeout.
  4. POST /v5/pins with media_source: { source_type: "video_id", media_id, cover_image_url }.

Wisdom (platform-specific things that bite)

  • Video pins need a cover image URL. Pinterest will 400 createPin with a vague message if it’s missing. Preflight catches the absence before any upload starts.
  • Destination URL and image URL must be publicly reachable. Both go through a HEAD probe at preflight (pinterest.destination_url.reachable, pinterest.image_url.reachable). Signed S3 URLs that expire mid-flight will fail — use stable public URLs.
  • One media item per pin. Carousel pins (multi-image) are not in v1 — preflight rejects with pinterest.media.single_only.
  • Board id is required. Either pass pinterest.boardId per-post, or set a default board on the connected account; missing both fails preflight with pinterest.board.required.
  • Trial vs Standard access. Pinterest’s Trial Access apps must point at the sandbox host (api-sandbox.pinterest.com); production calls return 403 until your app clears review.
  • Image content-type is checked from the URL response headers, not from the file extension — a .jpg URL serving image/webp will trip pinterest.image.mime_allowed.
  • The publisher uses GET for the reachability probe (not HEAD) because several Pinterest-hosted CDNs reject HEAD or skip content-length on it.

Common errors

Error ruleWhat it meansHow to fix
pinterest.board.requiredNo boardId per-post and no account defaultPass pinterest.boardId or set a default board on the account.
pinterest.media.requiredNo media on the pinAttach media: [{ kind: "image" / "video", … }].
pinterest.media.single_onlyMore than one media itemSend a single media item; carousel pins aren’t in v1.
pinterest.video.cover_requiredVideo pin without coverImageUrlPass pinterest.coverImageUrl pointing at a public still frame.
pinterest.image.mime_allowedImage URL served an unsupported mimeServe jpeg/png/webp from the URL.
pinterest.image.size_maxImage > 20 MBCompress under 20,000,000 bytes.
pinterest.image_url.reachablePinterest couldn’t fetch the image URLVerify it’s publicly reachable and returns 2xx.
pinterest.cover_image_url.reachableCover image URL unreachableVerify the cover URL is publicly reachable + serves an image mime.
pinterest.video.transcode_failedPinterest’s transcoder rejected the videoRe-encode the source (H.264 + AAC in an MP4 container is the safe path).
pinterest.video.transcode_timeoutTranscode poll exceeded 5 minutesRetry; large videos occasionally exceed the SLA.

What you can’t do (yet)

  • Carousel pins (multi-image).
  • Idea pins.
  • Tagging products on a pin.
  • Reading pin engagement (saves, impressions).
  • Comments on pins.
  • Posting to secret boards without the extended pins:read_secret scope.

API reference