# Pinterest API: Posting Reference

Pinterest is supported via the Pinterest Business API. OmniSocials publishes Pins to a specific board, which must be provided on every post.

**Channel ID:** `pinterest`

## Supported content types

| Type | Supported | Notes |
|------|-----------|-------|
| Feed post (Pin) | ✅ | Image or video pin to a specific board |
| Carousel pin | ✅ | 2–5 images in one swipeable pin (see [Carousel pins](#carousel-pins)) |
| Story | - | Not supported |
| Reel | - | Not supported |

## Minimal example

```bash
curl -X POST https://api.omnisocials.com/v1/posts/create \
  -H "Authorization: Bearer $OMNISOCIALS_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "content": { "default": "Spring color palette inspiration" },
    "accounts": ["your-pinterest-account-id"],
    "media_urls": ["https://example.com/palette.jpg"],
    "pinterest": {
      "board_id": "1234567890",
      "title": "Spring palette"
    }
  }'
```

## Platform-specific options

| Field | Type | Description |
|-------|------|-------------|
| `pinterest.board_id` | string | **Required.** The ID of the board to pin to. Get board IDs from `GET /accounts/:id`. Pinterest accounts include a `boards` array with `{ id, name }` entries. |
| `pinterest.title` | string | Pin title. Highly recommended for discoverability. Max 100 characters. |
| `pinterest.link` | string | Destination URL the pin links to when clicked. |
| `pinterest.video_cover` | string | Cover image URL for video pins (JPEG/PNG). If omitted, Pinterest uses a video keyframe at 1 second. |
| `pinterest.alt_text` | string | Accessibility alt text for the pin image (max 500 characters). Improves discoverability and supports screen readers. |
| `pinterest.product_tags` | string[] | Products to tag on the Pin: up to 24 product Pins of the connected account, each as a Pin ID or a Pin link. See [Product tags](#product-tags). |

Posts without `board_id` are rejected at validation.

**Per-media alt fallback.** If `pinterest.alt_text` is not set, a per-media `alt` on the entry itself is used instead, e.g. `"media_urls": [{ "url": "https://example.com/pin.jpg", "alt": "Flat-lay of spring outfit ideas" }]`. Handy when you cross-post the same media (with one shared alt description) to Pinterest alongside Mastodon, Bluesky, X, Instagram, and LinkedIn. `pinterest.alt_text` always wins when both are present.

## Media requirements

| Media | Requirement |
|-------|-------------|
| Image | JPEG, PNG, or WebP (WebP is transcoded to JPEG at post time). Max 20 MB. Minimum 600 × 600. |
| Video | MP4 or MOV, 4 seconds to 15 minutes, max 2 GB |

Pinterest recommends a 2:3 aspect ratio (e.g. 1000 × 1500) for best visibility in the feed. A Pin taller than 2:3 may be cut in the home feed (the Pin page shows it whole); OmniSocials keeps it as it is by default and reports it in the response `warnings` as `image_fit`. Pass `media_fit: { "pinterest": "crop" }` on the post to crop it to 2:3 around the subject instead. See [Image fit per channel](/creating-posts#image-fit-per-channel).

## Video pins

Video pins use a cover image. Pass either:

- An image URL in `pinterest.video_cover`, or
- Omit `video_cover` and Pinterest will use a keyframe at 1 second of the video as the cover (automatic fallback)

## Carousel pins

Attach **2–5 images** to a single Pinterest post and OmniSocials publishes them as one swipeable Pinterest carousel pin (Pinterest's v5 `multiple_image_urls` source type), not separate pins. The pin-level `title`, `description`, `link`, and `alt_text` apply to the whole pin; Pinterest's standard carousel format doesn't expose per-slide metadata.

```bash
curl -X POST https://api.omnisocials.com/v1/posts/create \
  -H "Authorization: Bearer $OMNISOCIALS_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "content": { "pinterest": "Spring lookbook: swipe through the set" },
    "accounts": ["your-pinterest-account-id"],
    "media_urls": {
      "pinterest": [
        "https://example.com/slide-1.jpg",
        "https://example.com/slide-2.jpg",
        "https://example.com/slide-3.jpg",
        "https://example.com/slide-4.jpg"
      ]
    },
    "pinterest": {
      "board_id": "1234567890",
      "title": "Spring lookbook",
      "link": "https://example.com/spring",
      "alt_text": "Four-slide carousel of spring looks"
    }
  }'
```

### Carousel constraints

- **2 to 5 images per pin.** 1 image publishes as a normal image pin; 6+ images return `400 validation_error`.
- **Same aspect ratio across all slides.** Pinterest's carousel endpoints reject mixed-ratio sets. OmniSocials probes slide dimensions when you schedule or publish-now and returns a synchronous `400 validation_error` listing the slides that don't match slide 1; you'll never get a deferred publish failure at cron time. Recommended ratios: **1:1** (square) or **2:3** (vertical).
- **Images only.** Pinterest's `multiple_image_urls` discriminator doesn't accept videos. A post with one video plus images publishes as a single-video pin and the images are dropped.
- **Pinterest Business account in an ads-enabled region.** Personal accounts can't create carousel pins and Pinterest rejects the API call regardless of payload.

Example aspect-ratio error response:

```json
{
  "error": {
    "code": "validation_error",
    "message": "Pinterest carousel pins need every image at the same aspect ratio. Slides 2, 3 don't match slide 1; re-crop or remove them before publishing.",
    "mismatched_slides": [2, 3]
  }
}
```

The `mismatched_slides` array is 1-indexed and lists every slide whose ratio diverges from slide 1's by more than 1%. Re-crop the listed slides to match slide 1 and re-submit.

The same check runs on `POST /posts/create` (with `schedule_at` or `publish_now`), on `POST /posts/{id}/publish`, and on `PATCH /posts/{id}` when the update flips the post to `scheduled` status. **Drafts are exempt**, so you can iterate on media before committing to publish.

## Product tags

Tag products on a Pin so people can shop the items in the image. A product tag is the ID of a **product Pin**: a Pin that Pinterest made from your catalog, or one of your Pins that links to a product page on a website you claimed on Pinterest.

```bash
curl -X POST https://api.omnisocials.com/v1/posts/create \
  -H "Authorization: Bearer $OMNISOCIALS_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "content": { "pinterest": "Weekend outfit. Tap a product to shop it." },
    "accounts": ["your-pinterest-account-id"],
    "media_urls": ["https://example.com/outfit.jpg"],
    "pinterest": {
      "board_id": "1234567890",
      "title": "Weekend outfit",
      "product_tags": ["813744226420795884", "https://www.pinterest.com/pin/813744226420795885/"]
    }
  }'
```

### Find product Pins

`GET /pinterest/products` lists the product Pins of the connected account. Use a returned `pin_id` in `pinterest.product_tags`.

```bash
curl "https://api.omnisocials.com/v1/pinterest/products?source=catalog" \
  -H "Authorization: Bearer $OMNISOCIALS_API_KEY"
```

| Parameter | Description |
|-----------|-------------|
| `source` | `catalog` reads the Pinterest catalog and adds `price`, `currency`, `availability` and `item_id` to each product. `pins` reads the account's own Pins and returns the ones Pinterest marks as product Pins. Default: `catalog` when the connection has catalog access, else `pins`. |
| `product_group_id` | Catalog only. A group `id` from `product_groups` in the response. Default: the group named "All Products". |
| `bookmark` | Cursor from the previous response, to get the next page. |
| `page_size` | Catalog only. 1 to 100, default 25. |

The catalog source needs catalog access on the Pinterest connection. Give it one time in the OmniSocials composer: open the Pinterest options of a post, select **Add products**, then **Connect catalog**. Without it, `source=catalog` answers `error.code = pinterest_catalog_access_required`, and the `pins` source still works.

One `pins` call scans up to 250 Pins. `products` can be empty while `bookmark` is set: call again with the bookmark.

An account with catalog access but without a catalog answers `no_catalog: true` with empty `products` and `product_groups`. Products are added on Pinterest at [pinterest.com/business/catalogs](https://www.pinterest.com/business/catalogs/).

`GET /pinterest/products/validate?id=<Pin ID or link>` checks one Pin before you use it. It returns `{ "valid": true, "pin_id": "...", "title": "..." }` for a product Pin of the account, `{ "valid": false, "reason": "..." }` for a Pin that cannot be tagged, and `{ "valid": false, "unverified": true }` when the check could not run (the publish step is then the final check).

### Product tag rules

- **Max 24 products per Pin.** More, or an entry that is not a Pin ID or Pin link, returns `400 validation_error`. `pin.it` short links are not accepted.
- **Your own products only.** Pinterest accepts a product only when its Pin is public, belongs to the same Pinterest account and links to a website that account claimed. Products of other merchants cannot be tagged through the API.
- **Tags are added right after the Pin is published.** A product that Pinterest refuses never fails the post. The other products are still tagged.
- **Read the outcome on the post.** After publishing, `GET /posts/{id}` returns `pinterest.product_tags_result`:

```json
{
  "requested": 2,
  "tagged": ["813744226420795884"],
  "skipped": [
    { "pin_id": "813744226420795885", "reason": "PIN_NOT_FROM_VERIFIED_DOMAIN" }
  ],
  "error": null
}
```

| Reason | Meaning |
|--------|---------|
| `PIN_MISSING` | The Pin was not found. |
| `PIN_IS_PRIVATE` | The Pin is private. |
| `PRODUCT_METADATA_MISSING` | The Pin is not a product Pin. |
| `PIN_NOT_FROM_VERIFIED_DOMAIN` | The product link is not on a website the account claimed on Pinterest. |
| `PIN_NOT_FROM_SAME_USER_AS_HERO_PIN` | The Pin belongs to another Pinterest account. |

`error` is set when the tag request itself failed (for example Pinterest did not answer); `tagged` is then empty.

- **`PATCH` replaces the `pinterest` object.** Leave `product_tags` out (or send `[]`) to remove the tags from a draft or scheduled post.

## Character limit

Pin descriptions cap at 500 characters.

## Limitations

- Pins must target a specific board. Posting to the user's profile is not supported.
- Rich pins (product, article, recipe) cannot be created through the API. Existing product Pins can be tagged on a Pin (see [Product tags](#product-tags)).
- Collection Pins cannot be created through the API. Pinterest has them in its ads API only.
- Pinterest requires HTTPS URLs for `link`. HTTP links are rejected.
- Carousel pins require a Pinterest Business account in an ads-enabled region (Pinterest restriction, not an OmniSocials one).
