Create post
const url = 'https://api-v1.tailwind.ai/v1/accounts/example/posts';const options = { method: 'POST', headers: {Authorization: 'Bearer <token>', 'Content-Type': 'application/json'}, body: '{"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"]}'};
try { const response = await fetch(url, options); const data = await response.json(); console.log(data);} catch (error) { console.error(error);}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.
Authorizations
Section titled “Authorizations”Parameters
Section titled “Parameters”Path Parameters
Section titled “Path Parameters”Numeric Tailwind account ID returned by GET /v1/accounts
Request Bodyrequired
Section titled “Request Bodyrequired”Provide exactly one media source: mediaUrl (a publicly accessible URL) or uploadId (your own file bytes, uploaded first via POST /v1/media/uploads).
object
URL of the media file to pin. Can be any publicly accessible URL — external media is automatically uploaded and processed. Mutually exclusive with 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.
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.
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.
object
URL of this image. Mutually exclusive with uploadId on the same entry.
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.
object
URL of this image. Mutually exclusive with uploadId on the same entry.
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.
Pin title. Required when scheduling.
Pin description. Required when scheduling.
Destination URL when pin is clicked. Required when scheduling — it is also what Pin Spacing rules are keyed on, so useNextOpenSlot needs it.
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.
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.
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.
object
Target board ID
Section of this Board to publish into. Omit to publish to the board root.
Spacing preset between each Board in intervalBoards. Required when intervalBoards is provided; only valid with intervalBoards.
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.
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.
Alt text for accessibility
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.
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.
Whether to create a simplified pin. Defaults to true. Set to false for a standard pin.
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.
Provide exactly one media source: mediaUrl (a publicly accessible URL) or uploadId (your own file bytes, uploaded first via POST /v1/media/uploads).
object
URL of the media file to pin. Can be any publicly accessible URL — external media is automatically uploaded and processed. Mutually exclusive with 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.
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.
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.
object
URL of this image. Mutually exclusive with uploadId on the same entry.
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.
object
URL of this image. Mutually exclusive with uploadId on the same entry.
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.
Pin title. Required when scheduling.
Pin description. Required when scheduling.
Destination URL when pin is clicked. Required when scheduling — it is also what Pin Spacing rules are keyed on, so useNextOpenSlot needs it.
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.
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.
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.
object
Target board ID
Section of this Board to publish into. Omit to publish to the board root.
Spacing preset between each Board in intervalBoards. Required when intervalBoards is provided; only valid with intervalBoards.
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.
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.
Alt text for accessibility
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.
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.
Whether to create a simplified pin. Defaults to true. Set to false for a standard pin.
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.
Responses
Section titled “Responses”Post created
object
object
object
Post ID
Post status
URL of the media file
Type of media
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.
object
URL of this image
Per-slide title, when the Pin has one
Per-slide description, when the Pin has one
Per-slide destination, when the Pin has one
Pin title
Pin description
Destination URL
Target board ID
Section of boardId the Pin publishes into. Null when it publishes to the board root.
Alt text for accessibility
Scheduled send time (Unix timestamp)
Actual send time (Unix timestamp)
Creation time (Unix timestamp)
Pinterest pin ID (only present after publishing)
Whether this is a simplified pin (as opposed to a standard pin)
Pinterest product-catalog pin IDs tagged on the pin (business accounts only).
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.
Every Pin the intervalBoards fan-out created, in Board order and including post itself. Present only when intervalBoards produced more than one Pin.
object
Post ID
Post status
URL of the media file
Type of media
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.
object
URL of this image
Per-slide title, when the Pin has one
Per-slide description, when the Pin has one
Per-slide destination, when the Pin has one
Pin title
Pin description
Destination URL
Target board ID
Section of boardId the Pin publishes into. Null when it publishes to the board root.
Alt text for accessibility
Scheduled send time (Unix timestamp)
Actual send time (Unix timestamp)
Creation time (Unix timestamp)
Pinterest pin ID (only present after publishing)
Whether this is a simplified pin (as opposed to a standard pin)
Pinterest product-catalog pin IDs tagged on the pin (business accounts only).
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.
object
Unique request ID for debugging
Example
{ "data": { "post": { "status": "draft", "mediaType": "image" }, "intervalPosts": [ { "status": "draft", "mediaType": "image" } ] }}Invalid request
object
object
Error code
Error message
Machine-readable denial reason. Present on PAYMENT_REQUIRED errors: insufficient_credits, trial_exhausted, no_access, account_not_connected, legacy_post_limit_exceeded.
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.
object
Unique request ID for debugging
Example
{ "error": { "code": "BAD_REQUEST", "message": "Invalid request body" }, "meta": { "requestId": "abc123" }}Authentication required or invalid bearer credentials
object
object
Error code
Error message
Machine-readable denial reason. Present on PAYMENT_REQUIRED errors: insufficient_credits, trial_exhausted, no_access, account_not_connected, legacy_post_limit_exceeded.
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.
object
Unique request ID for debugging
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
object
object
Error code
Error message
Machine-readable denial reason. Present on PAYMENT_REQUIRED errors: insufficient_credits, trial_exhausted, no_access, account_not_connected, legacy_post_limit_exceeded.
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.
object
Unique request ID for debugging
Example
{ "error": { "code": "PAYMENT_REQUIRED", "message": "Not enough credits to schedule this post.", "reason": "insufficient_credits" }, "meta": { "requestId": "abc123" }}Resource not found
object
object
Error code
Error message
Machine-readable denial reason. Present on PAYMENT_REQUIRED errors: insufficient_credits, trial_exhausted, no_access, account_not_connected, legacy_post_limit_exceeded.
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.
object
Unique request ID for debugging
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.
object
object
Error code
Error message
Machine-readable denial reason. Present on PAYMENT_REQUIRED errors: insufficient_credits, trial_exhausted, no_access, account_not_connected, legacy_post_limit_exceeded.
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.
object
Unique request ID for debugging
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.
object
object
Error code
Error message
Machine-readable denial reason. Present on PAYMENT_REQUIRED errors: insufficient_credits, trial_exhausted, no_access, account_not_connected, legacy_post_limit_exceeded.
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.
object
Unique request ID for debugging
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.
object
object
Error code
Error message
Machine-readable denial reason. Present on PAYMENT_REQUIRED errors: insufficient_credits, trial_exhausted, no_access, account_not_connected, legacy_post_limit_exceeded.
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.
object
Unique request ID for debugging
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
object
object
Error code
Error message
Machine-readable denial reason. Present on PAYMENT_REQUIRED errors: insufficient_credits, trial_exhausted, no_access, account_not_connected, legacy_post_limit_exceeded.
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.
object
Unique request ID for debugging
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.
object
object
Error code
Error message
Machine-readable denial reason. Present on PAYMENT_REQUIRED errors: insufficient_credits, trial_exhausted, no_access, account_not_connected, legacy_post_limit_exceeded.
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.
object
Unique request ID for debugging
Examplegenerated
{ "error": { "code": "example", "message": "example", "reason": "example", "resolve_url": "example" }, "meta": { "requestId": "example" }}