RelayAPI

Pinterest API

Schedule and automate Pinterest pins with RelayAPI — image pins, video pins, board targeting, destination links, and SEO optimization.

Quick Reference

PropertyValue
Platform keypinterest
Auth methodOAuth 2.0
Title limit100 characters
Description limit500 characters
Images per pin1
Videos per pin1
Image formatsJPEG, PNG
Image max size20 MB
Video formatsMP4, MOV
Video max size2 GB
Video duration4 sec - 15 min
Post typesImage Pin, Video Pin
SchedulingYes
AnalyticsLimited (impressions, saves, clicks)

SDK sourceTypeScript · Python · Go · Java · REST API

Before You Start

Pinterest is a search engine, not a social feed. Pins are discovered through search and browse, not by followers. SEO (title, description, board name) matters more than posting time. Pins have a 3-6 month lifespan unlike hours on other platforms. The board_id field is effectively required — always provide it. Media is required for all pins; there are no text-only posts. Only 1 image or 1 video per pin — no carousels or multi-image posts.

Quick Start

Create a pin on Pinterest:

import Relay from '@relayapi/sdk';

const client = new Relay();
const post = await client.posts.create({
  content: 'Modern kitchen renovation ideas for small spaces',
  targets: ['pinterest'],
  media: [
    { url: 'https://cdn.example.com/kitchen.jpg', type: 'image' },
  ],
  scheduled_at: 'now',
  target_options: {
    pinterest: {
      title: 'Modern Kitchen Renovation Ideas',
      board_id: 'YOUR_BOARD_ID',
      link: 'https://myblog.com/kitchen-renovation',
    },
  },
});

console.log(post.id); // post_abc123
from relay import Relay

client = Relay()
post = client.posts.create(
    content='Modern kitchen renovation ideas for small spaces',
    targets=['pinterest'],
    media=[
        {'url': 'https://cdn.example.com/kitchen.jpg', 'type': 'image'},
    ],
    scheduled_at='now',
    target_options={
        'pinterest': {
            'title': 'Modern Kitchen Renovation Ideas',
            'board_id': 'YOUR_BOARD_ID',
            'link': 'https://myblog.com/kitchen-renovation',
        },
    },
)

print(post.id)  # post_abc123
client := relaygo.NewClient()

post, err := client.Posts.New(context.TODO(), relaygo.PostNewParams{
    Content: relaygo.F("Modern kitchen renovation ideas for small spaces"),
    Targets: relaygo.F([]string{"pinterest"}),
    Media: relaygo.F([]relaygo.PostNewParamsMedia{
        {URL: relaygo.F("https://cdn.example.com/kitchen.jpg"), Type: relaygo.F(relaygo.PostNewParamsMediaTypeImage)},
    }),
    ScheduledAt: relaygo.F("now"),
    TargetOptions: relaygo.F(map[string]interface{}{
        "pinterest": map[string]interface{}{
            "title":    "Modern Kitchen Renovation Ideas",
            "board_id": "YOUR_BOARD_ID",
            "link":     "https://myblog.com/kitchen-renovation",
        },
    }),
})

fmt.Println(post.ID) // post_abc123
RelayClient client = RelayOkHttpClient.fromEnv();

PostCreateResponse post = client.posts().create(PostCreateParams.builder()
    .content("Modern kitchen renovation ideas for small spaces")
    .addTarget("pinterest")
    .addMedia(PostCreateParams.Media.builder()
        .url("https://cdn.example.com/kitchen.jpg")
        .type(PostCreateParams.Media.Type.IMAGE)
        .build())
    .scheduledAt("now")
    .targetOptions(PostCreateParams.TargetOptions.builder()
        .putAdditionalProperty("pinterest", JsonValue.from(Map.of(
            "title", "Modern Kitchen Renovation Ideas",
            "board_id", "YOUR_BOARD_ID",
            "link", "https://myblog.com/kitchen-renovation"
        )))
        .build())
    .build());

System.out.println(post.id()); // post_abc123
curl -X POST https://api.relayapi.dev/v1/posts \
  -H "Authorization: Bearer $RELAY_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "content": "Modern kitchen renovation ideas for small spaces",
    "targets": ["pinterest"],
    "media": [
      {"url": "https://cdn.example.com/kitchen.jpg", "type": "image"}
    ],
    "scheduled_at": "now",
    "target_options": {
      "pinterest": {
        "title": "Modern Kitchen Renovation Ideas",
        "board_id": "YOUR_BOARD_ID",
        "link": "https://myblog.com/kitchen-renovation"
      }
    }
  }'

Content Types

Image Pin

Use 2:3 aspect ratio (1000 x 1500 px) for optimal feed display. The link field is critical for driving traffic to your website.

const post = await client.posts.create({
  content: 'Modern kitchen renovation ideas for small spaces',
  targets: ['pinterest'],
  media: [
    { url: 'https://cdn.example.com/kitchen.jpg', type: 'image' },
  ],
  scheduled_at: 'now',
  target_options: {
    pinterest: {
      title: 'Modern Kitchen Renovation Ideas',
      board_id: 'YOUR_BOARD_ID',
      link: 'https://myblog.com/kitchen-renovation',
    },
  },
});
post = client.posts.create(
    content='Modern kitchen renovation ideas for small spaces',
    targets=['pinterest'],
    media=[
        {'url': 'https://cdn.example.com/kitchen.jpg', 'type': 'image'},
    ],
    scheduled_at='now',
    target_options={
        'pinterest': {
            'title': 'Modern Kitchen Renovation Ideas',
            'board_id': 'YOUR_BOARD_ID',
            'link': 'https://myblog.com/kitchen-renovation',
        },
    },
)
post, err := client.Posts.New(context.TODO(), relaygo.PostNewParams{
    Content: relaygo.F("Modern kitchen renovation ideas for small spaces"),
    Targets: relaygo.F([]string{"pinterest"}),
    Media: relaygo.F([]relaygo.PostNewParamsMedia{
        {URL: relaygo.F("https://cdn.example.com/kitchen.jpg"), Type: relaygo.F(relaygo.PostNewParamsMediaTypeImage)},
    }),
    ScheduledAt: relaygo.F("now"),
    TargetOptions: relaygo.F(map[string]interface{}{
        "pinterest": map[string]interface{}{
            "title":    "Modern Kitchen Renovation Ideas",
            "board_id": "YOUR_BOARD_ID",
            "link":     "https://myblog.com/kitchen-renovation",
        },
    }),
})
PostCreateResponse post = client.posts().create(PostCreateParams.builder()
    .content("Modern kitchen renovation ideas for small spaces")
    .addTarget("pinterest")
    .addMedia(PostCreateParams.Media.builder()
        .url("https://cdn.example.com/kitchen.jpg")
        .type(PostCreateParams.Media.Type.IMAGE)
        .build())
    .scheduledAt("now")
    .targetOptions(PostCreateParams.TargetOptions.builder()
        .putAdditionalProperty("pinterest", JsonValue.from(Map.of(
            "title", "Modern Kitchen Renovation Ideas",
            "board_id", "YOUR_BOARD_ID",
            "link", "https://myblog.com/kitchen-renovation"
        )))
        .build())
    .build());
curl -X POST https://api.relayapi.dev/v1/posts \
  -H "Authorization: Bearer $RELAY_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "content": "Modern kitchen renovation ideas for small spaces",
    "targets": ["pinterest"],
    "media": [
      {"url": "https://cdn.example.com/kitchen.jpg", "type": "image"}
    ],
    "scheduled_at": "now",
    "target_options": {
      "pinterest": {
        "title": "Modern Kitchen Renovation Ideas",
        "board_id": "YOUR_BOARD_ID",
        "link": "https://myblog.com/kitchen-renovation"
      }
    }
  }'

Video Pin

Single video per pin. Supports a custom cover image.

const post = await client.posts.create({
  content: 'Quick 5-minute breakfast recipe',
  targets: ['pinterest'],
  media: [
    { url: 'https://cdn.example.com/recipe.mp4', type: 'video' },
  ],
  scheduled_at: 'now',
  target_options: {
    pinterest: {
      title: '5-Minute Breakfast Recipe',
      board_id: 'YOUR_BOARD_ID',
      link: 'https://myrecipes.com/quick-breakfast',
      cover_image_url: 'https://cdn.example.com/recipe-cover.jpg',
    },
  },
});
post = client.posts.create(
    content='Quick 5-minute breakfast recipe',
    targets=['pinterest'],
    media=[
        {'url': 'https://cdn.example.com/recipe.mp4', 'type': 'video'},
    ],
    scheduled_at='now',
    target_options={
        'pinterest': {
            'title': '5-Minute Breakfast Recipe',
            'board_id': 'YOUR_BOARD_ID',
            'link': 'https://myrecipes.com/quick-breakfast',
            'cover_image_url': 'https://cdn.example.com/recipe-cover.jpg',
        },
    },
)
post, err := client.Posts.New(context.TODO(), relaygo.PostNewParams{
    Content: relaygo.F("Quick 5-minute breakfast recipe"),
    Targets: relaygo.F([]string{"pinterest"}),
    Media: relaygo.F([]relaygo.PostNewParamsMedia{
        {URL: relaygo.F("https://cdn.example.com/recipe.mp4"), Type: relaygo.F(relaygo.PostNewParamsMediaTypeVideo)},
    }),
    ScheduledAt: relaygo.F("now"),
    TargetOptions: relaygo.F(map[string]interface{}{
        "pinterest": map[string]interface{}{
            "title":           "5-Minute Breakfast Recipe",
            "board_id":        "YOUR_BOARD_ID",
            "link":            "https://myrecipes.com/quick-breakfast",
            "cover_image_url": "https://cdn.example.com/recipe-cover.jpg",
        },
    }),
})
PostCreateResponse post = client.posts().create(PostCreateParams.builder()
    .content("Quick 5-minute breakfast recipe")
    .addTarget("pinterest")
    .addMedia(PostCreateParams.Media.builder()
        .url("https://cdn.example.com/recipe.mp4")
        .type(PostCreateParams.Media.Type.VIDEO)
        .build())
    .scheduledAt("now")
    .targetOptions(PostCreateParams.TargetOptions.builder()
        .putAdditionalProperty("pinterest", JsonValue.from(Map.of(
            "title", "5-Minute Breakfast Recipe",
            "board_id", "YOUR_BOARD_ID",
            "link", "https://myrecipes.com/quick-breakfast",
            "cover_image_url", "https://cdn.example.com/recipe-cover.jpg"
        )))
        .build())
    .build());
curl -X POST https://api.relayapi.dev/v1/posts \
  -H "Authorization: Bearer $RELAY_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "content": "Quick 5-minute breakfast recipe",
    "targets": ["pinterest"],
    "media": [
      {"url": "https://cdn.example.com/recipe.mp4", "type": "video"}
    ],
    "scheduled_at": "now",
    "target_options": {
      "pinterest": {
        "title": "5-Minute Breakfast Recipe",
        "board_id": "YOUR_BOARD_ID",
        "link": "https://myrecipes.com/quick-breakfast",
        "cover_image_url": "https://cdn.example.com/recipe-cover.jpg"
      }
    }
  }'

Video Pin with Auto-Generated Cover

Instead of providing a cover image, extract a frame from the video at a specific timestamp.

const post = await client.posts.create({
  content: 'DIY home project walkthrough',
  targets: ['pinterest'],
  media: [
    { url: 'https://cdn.example.com/diy.mp4', type: 'video' },
  ],
  scheduled_at: 'now',
  target_options: {
    pinterest: {
      title: 'DIY Bookshelf Build',
      board_id: 'YOUR_BOARD_ID',
      link: 'https://myblog.com/diy-bookshelf',
      cover_image_key_frame_time: 5,
    },
  },
});
post = client.posts.create(
    content='DIY home project walkthrough',
    targets=['pinterest'],
    media=[
        {'url': 'https://cdn.example.com/diy.mp4', 'type': 'video'},
    ],
    scheduled_at='now',
    target_options={
        'pinterest': {
            'title': 'DIY Bookshelf Build',
            'board_id': 'YOUR_BOARD_ID',
            'link': 'https://myblog.com/diy-bookshelf',
            'cover_image_key_frame_time': 5,
        },
    },
)
post, err := client.Posts.New(context.TODO(), relaygo.PostNewParams{
    Content: relaygo.F("DIY home project walkthrough"),
    Targets: relaygo.F([]string{"pinterest"}),
    Media: relaygo.F([]relaygo.PostNewParamsMedia{
        {URL: relaygo.F("https://cdn.example.com/diy.mp4"), Type: relaygo.F(relaygo.PostNewParamsMediaTypeVideo)},
    }),
    ScheduledAt: relaygo.F("now"),
    TargetOptions: relaygo.F(map[string]interface{}{
        "pinterest": map[string]interface{}{
            "title":                      "DIY Bookshelf Build",
            "board_id":                   "YOUR_BOARD_ID",
            "link":                       "https://myblog.com/diy-bookshelf",
            "cover_image_key_frame_time": 5,
        },
    }),
})
PostCreateResponse post = client.posts().create(PostCreateParams.builder()
    .content("DIY home project walkthrough")
    .addTarget("pinterest")
    .addMedia(PostCreateParams.Media.builder()
        .url("https://cdn.example.com/diy.mp4")
        .type(PostCreateParams.Media.Type.VIDEO)
        .build())
    .scheduledAt("now")
    .targetOptions(PostCreateParams.TargetOptions.builder()
        .putAdditionalProperty("pinterest", JsonValue.from(Map.of(
            "title", "DIY Bookshelf Build",
            "board_id", "YOUR_BOARD_ID",
            "link", "https://myblog.com/diy-bookshelf",
            "cover_image_key_frame_time", 5
        )))
        .build())
    .build());
curl -X POST https://api.relayapi.dev/v1/posts \
  -H "Authorization: Bearer $RELAY_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "content": "DIY home project walkthrough",
    "targets": ["pinterest"],
    "media": [
      {"url": "https://cdn.example.com/diy.mp4", "type": "video"}
    ],
    "scheduled_at": "now",
    "target_options": {
      "pinterest": {
        "title": "DIY Bookshelf Build",
        "board_id": "YOUR_BOARD_ID",
        "link": "https://myblog.com/diy-bookshelf",
        "cover_image_key_frame_time": 5
      }
    }
  }'

The link field is critical on Pinterest. It determines where users go when they click the pin. Without it, your pin cannot drive traffic.

Media Requirements

Images

PropertyRequirement
Max per pin1
FormatsJPEG, PNG, WebP, GIF
Max file size32 MB
Recommended1000 x 1500 px (2:3 portrait)
Min dimensions100 x 100 px

Aspect Ratios

TypeRatioNotes
Portrait2:3Optimal for feed display
Square1:1Works well in feed
Long pin1:2.1Maximum vertical ratio

Videos

PropertyRequirement
Max per pin1
FormatsMP4, MOV
Max file size2 GB
Duration4 sec - 15 min
Aspect ratio2:3, 1:1, or 9:16
Recommended1080p, 25+ fps

target_options Fields

All fields go inside target_options.pinterest on your post request.

FieldTypeDescription
contentstringOverride description for Pinterest specifically
mediaobject[]Override media for Pinterest specifically
titlestringPin title (max 100 chars). Falls back to first line of content.
board_idstringTarget board ID. Effectively required for all pins.
linkstringDestination URL when users click the pin. Critical for driving traffic.
cover_image_urlstringCustom cover image URL for video pins
cover_image_key_frame_timenumberAuto-extract a video frame at N seconds for the cover

Common Errors

ErrorCauseFix
Invalid URL or request dataMedia URL is inaccessible or returns non-media contentVerify the URL is a publicly accessible HTTPS link returning actual media bytes.
Unable to reach URLPinterest servers cannot fetch the mediaTest the URL in an incognito browser window. If you see a webpage instead of media, it will not work.
Rate limit reachedToo many API calls in a short windowSpace out pin creation. Avoid bursts of 10+ pins.
Requires boardIdNo board was specifiedAlways provide the board_id field.

Known Quirks

  • Search engine, not social feed — SEO (title, description, board name) matters more than posting time.
  • Pins have a 3-6 month lifespan — unlike hours on Twitter or Instagram, pins continue driving traffic for months.
  • board_id is effectively required — always specify which board to pin to.
  • link field is critical for driving traffic. Without it, users cannot click through to your content.
  • No text-only pins — media is required for every pin.
  • No carousels or multi-image pins — only 1 image or 1 video per pin.
  • Animated GIFs are supported and auto-play in the Pinterest feed.
  • 2:3 aspect ratio (1000 x 1500 px) gets the best feed placement and engagement.

Automations

Pinterest is destination-only in the automations engine — no inbound triggers today.

Send nodes

NodeEndpointRequired fields
pinterest_create_pinPOST https://api.pinterest.com/v5/pinsboard_id, image_url, title, optional description / link

Found something wrong? Help us improve this page.

On this page

Submit an Issue
Requires a GitHub account.View repo