Skip to content

Create post

POST
/v1/accounts/{accountId}/posts
curl --request POST \
--url https://api-v1.tailwind.ai/v1/accounts/example/posts \
--header 'Authorization: Bearer <token>' \
--header 'Content-Type: application/json' \
--data '{ "mediaUrl": "https://example.com", "uploadId": "example", "mediaType": "image", "carouselItems": [ { "mediaUrl": "https://example.com", "uploadId": "example" } ], "title": "example", "description": "example", "url": "https://example.com", "boardId": "example", "boardSectionId": "example", "intervalBoards": [ { "boardId": "example", "boardSectionId": "example" } ], "intervalType": "daily", "intervalDays": 1, "intervalMode": "slots", "altText": "example", "sendAt": "2026-04-15T12:00:00Z", "useNextOpenSlot": false, "isSimplifiedPin": true, "productTagPinIds": [ "example" ] }'

Create a new post. Pass useNextOpenSlot or sendAt to schedule it; with neither, it is saved as a draft. useNextOpenSlot places the Pin in the account’s next open smart schedule slot, honoring the account’s Pin Spacing rules for the Pin’s destination url — the same calculation the Pin Scheduler runs, and the reason it is preferable to computing a time from GET /v1/accounts/{accountId}/timeslots yourself. Scheduling either way goes through the same pin_schedule charge, and what it costs depends on the organization’s billing system: modular-billing accounts with Publishing access, whether subscribed or granted, spend one credit per Pin, except on winback plans, which schedule without charge; modular accounts without Publishing draw on a limited free-trial allowance; and legacy and Shopify accounts spend no credits at all — their plan’s post limit for the billing period governs instead. A 422 means the account’s schedule has no open slot; a 503 means resolving one timed out. Neither writes anything, and neither falls back to an invented time.

accountId
required
string

Numeric Tailwind account ID returned by GET /v1/accounts

Media typeapplication/json
One of:

Provide exactly one media source: mediaUrl (a publicly accessible URL) or uploadId (your own file bytes, uploaded first via POST /v1/media/uploads).

object
mediaUrl
required

URL of the media file to pin. Can be any publicly accessible URL — external media is automatically uploaded and processed. Mutually exclusive with uploadId.

string format: uri
uploadId

An upload from POST /v1/media/uploads whose bytes you have already PUT to its uploadUrl. Single-use: it can be attached to exactly one Pin, and belongs to the organization that created it. Mutually exclusive with mediaUrl. When provided, mediaType is taken from the upload itself and any mediaType in this request is ignored.

string
mediaType

Type of media. Set to ‘video’ for video files (.mp4, .mov, .qt). Defaults to ‘image’ if omitted. Ignored when uploadId is provided — the upload’s own extension determines its type.

string
default: image
Allowed values: image video
carouselItems

Turns this into a carousel Pin: further images in order after the cover, which stays in mediaUrl/uploadId. One to four entries, so a carousel holds the two to five images Pinterest allows. Images only — video is refused, as are product tags, and every image must share the same width-to-height ratio. Each entry takes a mediaUrl or an uploadId, and the two forms can be mixed across entries.

Array<object>
>= 1 items <= 4 items
One of:
object
mediaUrl
required

URL of this image. Mutually exclusive with uploadId on the same entry.

string format: uri
uploadId

An upload from POST /v1/media/uploads for this image. Mutually exclusive with mediaUrl on the same entry, and no uploadId may appear twice in one request.

string
title

Pin title. Required when scheduling.

string
<= 100 characters
description

Pin description. Required when scheduling.

string
<= 800 characters
url

Destination URL when pin is clicked. Required when scheduling — it is also what Pin Spacing rules are keyed on, so useNextOpenSlot needs it.

string format: uri
boardId

Target board ID. Must be the numeric Pinterest board ID from GET /v1/accounts/{accountId}/boards — not a board name or URL. A leading “b_” (Tailwind CSV export format) is accepted and stripped. Required when scheduling, unless intervalBoards is provided instead. Mutually exclusive with intervalBoards.

string
boardSectionId

Section of boardId to publish into. Requires boardId, and is rejected with a 400 if it is not a section of that board. Omit to publish to the board root. Use GET /v1/accounts/{accountId}/boards/{boardId}/sections to look up section IDs. Not for use with intervalBoards — put each Board’s section on its own intervalBoards entry instead.

string
intervalBoards

Publish one Pin across several Boards, spaced by intervalType/intervalDays/intervalMode, in the order given — the first entry is the Board the Pin itself lands on. Mutually exclusive with boardId. Requires sendAt or useNextOpenSlot (an interval spaces Boards relative to the Pin’s send time, so it cannot be saved as a draft), and intervalType. No Board may repeat.

Array<object>
>= 2 items <= 20 items
object
boardId
required

Target board ID

string
boardSectionId

Section of this Board to publish into. Omit to publish to the board root.

string
intervalType

Spacing preset between each Board in intervalBoards. Required when intervalBoards is provided; only valid with intervalBoards.

string
Allowed values: daily 2_days 3_days 4_days 5_days 6_days weekly monthly
intervalDays

Overrides intervalType’s day count with an exact number of days (e.g. intervalType “weekly” + intervalDays 14 for a real two-week gap, since the preset alone rounds to the nearest one). Only valid with intervalBoards.

integer
>= 1 <= 365
intervalMode

How each Board’s send time is placed: “slots” (default) snaps to the account’s smart-schedule slots, “exact” places it exactly intervalType/intervalDays after the previous Board regardless of slots. Only valid with intervalBoards.

string
default: slots
Allowed values: slots exact
altText

Alt text for accessibility

string
<= 500 characters
sendAt

Explicit publish time, in the future and within 10 years. Cannot be combined with useNextOpenSlot; omit both to save a draft. Requires boardId or intervalBoards, title, description, and url.

string format: date-time
useNextOpenSlot

Schedule into the account’s next open smart schedule slot instead of naming a time, honoring the account’s Pin Spacing rules for this Pin’s url. Cannot be combined with sendAt; omit both to save a draft. Requires boardId or intervalBoards, title, description, and url.

boolean
isSimplifiedPin

Whether to create a simplified pin. Defaults to true. Set to false for a standard pin.

boolean
default: true
productTagPinIds

Pinterest product-catalog pin IDs to tag on the pin (business accounts only; max 24). Providing any forces a simplified pin, since Pinterest rejects tags on standard pins.

Array<string>
<= 24 items

Post created

Media typeapplication/json
object
data
object
post
object
id

Post ID

string
status

Post status

string
Allowed values: draft queued sent uploading
mediaUrl

URL of the media file

string
mediaType

Type of media

string
Allowed values: image video
carouselItems

Further images of a carousel Pin, in order after the cover in mediaUrl. Absent on a single-image Pin. Per-slide title, description and link are reported when the Pin carries them, but cannot be set through this API yet.

Array<object>
object
url

URL of this image

string
title

Per-slide title, when the Pin has one

string
description

Per-slide description, when the Pin has one

string
link

Per-slide destination, when the Pin has one

string
title

Pin title

string
nullable
description

Pin description

string
nullable
url

Destination URL

string
nullable
boardId

Target board ID

string
nullable
boardSectionId

Section of boardId the Pin publishes into. Null when it publishes to the board root.

string
nullable
altText

Alt text for accessibility

string
nullable
sendAt

Scheduled send time (Unix timestamp)

integer
nullable
sentAt

Actual send time (Unix timestamp)

integer
nullable
createdAt

Creation time (Unix timestamp)

integer
pinId

Pinterest pin ID (only present after publishing)

string
nullable
isSimplifiedPin

Whether this is a simplified pin (as opposed to a standard pin)

boolean
productTagPinIds

Pinterest product-catalog pin IDs tagged on the pin (business accounts only).

Array<string>
generationId

ID of the AI generation that produced this post, when it was generated from a URL (matches the id returned by POST /v1/accounts/{accountId}/generations). Absent for posts created any other way.

string
nullable
intervalPosts

Every Pin the intervalBoards fan-out created, in Board order and including post itself. Present only when intervalBoards produced more than one Pin.

Array<object>
object
id

Post ID

string
status

Post status

string
Allowed values: draft queued sent uploading
mediaUrl

URL of the media file

string
mediaType

Type of media

string
Allowed values: image video
carouselItems

Further images of a carousel Pin, in order after the cover in mediaUrl. Absent on a single-image Pin. Per-slide title, description and link are reported when the Pin carries them, but cannot be set through this API yet.

Array<object>
object
url

URL of this image

string
title

Per-slide title, when the Pin has one

string
description

Per-slide description, when the Pin has one

string
link

Per-slide destination, when the Pin has one

string
title

Pin title

string
nullable
description

Pin description

string
nullable
url

Destination URL

string
nullable
boardId

Target board ID

string
nullable
boardSectionId

Section of boardId the Pin publishes into. Null when it publishes to the board root.

string
nullable
altText

Alt text for accessibility

string
nullable
sendAt

Scheduled send time (Unix timestamp)

integer
nullable
sentAt

Actual send time (Unix timestamp)

integer
nullable
createdAt

Creation time (Unix timestamp)

integer
pinId

Pinterest pin ID (only present after publishing)

string
nullable
isSimplifiedPin

Whether this is a simplified pin (as opposed to a standard pin)

boolean
productTagPinIds

Pinterest product-catalog pin IDs tagged on the pin (business accounts only).

Array<string>
generationId

ID of the AI generation that produced this post, when it was generated from a URL (matches the id returned by POST /v1/accounts/{accountId}/generations). Absent for posts created any other way.

string
nullable
meta
object
requestId

Unique request ID for debugging

string
Example
{
"data": {
"post": {
"status": "draft",
"mediaType": "image"
},
"intervalPosts": [
{
"status": "draft",
"mediaType": "image"
}
]
}
}

Invalid request

Media typeapplication/json
object
error
object
code

Error code

string
message

Error message

string
reason

Machine-readable denial reason. Present on PAYMENT_REQUIRED errors: insufficient_credits, trial_exhausted, no_access, account_not_connected, legacy_post_limit_exceeded.

string
resolve_url

Present on PAYMENT_REQUIRED errors: where the denial can be resolved. Reason-specific, because buying credits does not clear a trial, access or connection denial. Requests sent with X-Request-Source: mcp receive an informational plans page instead, so agent surfaces never present a purchase link.

string
meta
object
requestId

Unique request ID for debugging

string
Example
{
"error": {
"code": "BAD_REQUEST",
"message": "Invalid request body"
},
"meta": {
"requestId": "abc123"
}
}

Authentication required or invalid bearer credentials

Media typeapplication/json
object
error
object
code

Error code

string
message

Error message

string
reason

Machine-readable denial reason. Present on PAYMENT_REQUIRED errors: insufficient_credits, trial_exhausted, no_access, account_not_connected, legacy_post_limit_exceeded.

string
resolve_url

Present on PAYMENT_REQUIRED errors: where the denial can be resolved. Reason-specific, because buying credits does not clear a trial, access or connection denial. Requests sent with X-Request-Source: mcp receive an informational plans page instead, so agent surfaces never present a purchase link.

string
meta
object
requestId

Unique request ID for debugging

string
Example
{
"error": {
"code": "UNAUTHORIZED",
"message": "Invalid API key"
},
"meta": {
"requestId": "abc123"
}
}

Billing denied — scheduling requires an available credit or trial allowance on the account, or would exceed the plan’s post limit for the billing period

Media typeapplication/json
object
error
object
code

Error code

string
message

Error message

string
reason

Machine-readable denial reason. Present on PAYMENT_REQUIRED errors: insufficient_credits, trial_exhausted, no_access, account_not_connected, legacy_post_limit_exceeded.

string
resolve_url

Present on PAYMENT_REQUIRED errors: where the denial can be resolved. Reason-specific, because buying credits does not clear a trial, access or connection denial. Requests sent with X-Request-Source: mcp receive an informational plans page instead, so agent surfaces never present a purchase link.

string
meta
object
requestId

Unique request ID for debugging

string
Example
{
"error": {
"code": "PAYMENT_REQUIRED",
"message": "Not enough credits to schedule this post.",
"reason": "insufficient_credits"
},
"meta": {
"requestId": "abc123"
}
}

Resource not found

Media typeapplication/json
object
error
object
code

Error code

string
message

Error message

string
reason

Machine-readable denial reason. Present on PAYMENT_REQUIRED errors: insufficient_credits, trial_exhausted, no_access, account_not_connected, legacy_post_limit_exceeded.

string
resolve_url

Present on PAYMENT_REQUIRED errors: where the denial can be resolved. Reason-specific, because buying credits does not clear a trial, access or connection denial. Requests sent with X-Request-Source: mcp receive an informational plans page instead, so agent surfaces never present a purchase link.

string
meta
object
requestId

Unique request ID for debugging

string
Example
{
"error": {
"code": "NOT_FOUND",
"message": "Account not found"
},
"meta": {
"requestId": "abc123"
}
}

The uploadId has already been attached to a Pin. An upload is single-use — request a new one with POST /v1/media/uploads.

Media typeapplication/json
object
error
object
code

Error code

string
message

Error message

string
reason

Machine-readable denial reason. Present on PAYMENT_REQUIRED errors: insufficient_credits, trial_exhausted, no_access, account_not_connected, legacy_post_limit_exceeded.

string
resolve_url

Present on PAYMENT_REQUIRED errors: where the denial can be resolved. Reason-specific, because buying credits does not clear a trial, access or connection denial. Requests sent with X-Request-Source: mcp receive an informational plans page instead, so agent surfaces never present a purchase link.

string
meta
object
requestId

Unique request ID for debugging

string
Example
{
"error": {
"code": "CONFLICT",
"message": "This upload has already been used. An uploadId can only be attached to one Pin — request a new one with POST /v1/media/uploads."
},
"meta": {
"requestId": "abc123"
}
}

The uploadId expired before it was attached. Staged media is kept for a limited window — request a new upload and attach it sooner.

Media typeapplication/json
object
error
object
code

Error code

string
message

Error message

string
reason

Machine-readable denial reason. Present on PAYMENT_REQUIRED errors: insufficient_credits, trial_exhausted, no_access, account_not_connected, legacy_post_limit_exceeded.

string
resolve_url

Present on PAYMENT_REQUIRED errors: where the denial can be resolved. Reason-specific, because buying credits does not clear a trial, access or connection denial. Requests sent with X-Request-Source: mcp receive an informational plans page instead, so agent surfaces never present a purchase link.

string
meta
object
requestId

Unique request ID for debugging

string
Example
{
"error": {
"code": "GONE",
"message": "This upload expired before it was used. Staged media is kept for a limited window — request a new one with POST /v1/media/uploads and attach it sooner."
},
"meta": {
"requestId": "abc123"
}
}

Media could not be processed, or (useNextOpenSlot only) the account’s smart schedule has no open slot to publish into.

Media typeapplication/json
object
error
object
code

Error code

string
message

Error message

string
reason

Machine-readable denial reason. Present on PAYMENT_REQUIRED errors: insufficient_credits, trial_exhausted, no_access, account_not_connected, legacy_post_limit_exceeded.

string
resolve_url

Present on PAYMENT_REQUIRED errors: where the denial can be resolved. Reason-specific, because buying credits does not clear a trial, access or connection denial. Requests sent with X-Request-Source: mcp receive an informational plans page instead, so agent surfaces never present a purchase link.

string
meta
object
requestId

Unique request ID for debugging

string
Example
{
"error": {
"code": "UNPROCESSABLE_ENTITY",
"message": "Failed to process media URL. Ensure the URL is publicly accessible and points to a valid media file."
},
"meta": {
"requestId": "abc123"
}
}

Rate limit exceeded

Media typeapplication/json
object
error
object
code

Error code

string
message

Error message

string
reason

Machine-readable denial reason. Present on PAYMENT_REQUIRED errors: insufficient_credits, trial_exhausted, no_access, account_not_connected, legacy_post_limit_exceeded.

string
resolve_url

Present on PAYMENT_REQUIRED errors: where the denial can be resolved. Reason-specific, because buying credits does not clear a trial, access or connection denial. Requests sent with X-Request-Source: mcp receive an informational plans page instead, so agent surfaces never present a purchase link.

string
meta
object
requestId

Unique request ID for debugging

string
Example
{
"error": {
"code": "RATE_LIMIT_EXCEEDED",
"message": "Daily rate limit of 5000 requests exceeded. Resets at midnight UTC."
},
"meta": {
"requestId": "abc123"
}
}

UseNextOpenSlot only: timed out resolving the next open slot. Nothing was scheduled; retry, or pass an explicit sendAt instead.

Media typeapplication/json
object
error
object
code

Error code

string
message

Error message

string
reason

Machine-readable denial reason. Present on PAYMENT_REQUIRED errors: insufficient_credits, trial_exhausted, no_access, account_not_connected, legacy_post_limit_exceeded.

string
resolve_url

Present on PAYMENT_REQUIRED errors: where the denial can be resolved. Reason-specific, because buying credits does not clear a trial, access or connection denial. Requests sent with X-Request-Source: mcp receive an informational plans page instead, so agent surfaces never present a purchase link.

string
meta
object
requestId

Unique request ID for debugging

string
Examplegenerated
{
"error": {
"code": "example",
"message": "example",
"reason": "example",
"resolve_url": "example"
},
"meta": {
"requestId": "example"
}
}