Skip to content

Create Pins in bulk

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

Create up to 175 Pins in one request, each entry its own post with its own image, title, description, link, Board and schedule. Each entry takes the same fields as POST /v1/accounts/{accountId}/posts, except intervalBoards and carouselItems, which a batch does not accept; create those Pins one at a time. Asynchronous: the response returns a batch id to poll with GET /v1/post-batches/{batchId}, which reports every entry as created, failed or pending, in request order. Entries run one after another, so a full batch can take a few minutes. A draft entry (no sendAt and no useNextOpenSlot) may leave out mediaUrl and uploadId if it has a title, description or url: it is saved with Tailwind’s placeholder image, cannot be scheduled until a real image replaces it with Change media in Tailwind’s Pin Scheduler, and the API cannot replace it. Billing matches single creates: drafts are free and each scheduled entry is charged when it is created. The request is refused with a 402, and nothing is created, when the account cannot cover every scheduled entry; credits that run out part way through fail only the entries after that point. Not idempotent unless you send an Idempotency-Key header: without one, resending a batch creates its Pins again, so after a failure resend only the failed entries. With one, repeating the same request returns the original batch. Results are kept for 7 days.

accountId
required
string

Numeric Tailwind account ID returned by GET /v1/accounts

Idempotency-Key
string
<= 255 characters

Up to 255 characters. Repeating a request with the same key and body returns the original batch instead of creating its Pins again; the same key with a different body is a 409.

Media typeapplication/json
object
posts
required

One entry per Pin, each with the fields of a single create. intervalBoards and carouselItems are refused per entry. A draft entry may omit mediaUrl and uploadId if it has a title, description or url.

Array<object>
>= 1 items <= 175 items

One Pin in a batch. Each field means what it does on CreatePostRequest. Provide at most one of mediaUrl and uploadId; both may be left out only for a draft with a title, description or url, which gets the placeholder image.

object
mediaUrl
string format: uri
uploadId
string
mediaType
string
default: image
Allowed values: image video
title
string
<= 100 characters
description
string
<= 800 characters
url
string format: uri
boardId
string
boardSectionId
string
altText
string
<= 500 characters
isSimplifiedPin
boolean
default: true
productTagPinIds
Array<string>
<= 24 items
sendAt

Schedule at this time. Omit both sendAt and useNextOpenSlot for a draft.

string format: date-time
useNextOpenSlot

Schedule into the account’s next open smart schedule slot. Entries fill slots in request order.

boolean

Batch accepted. Entries refused at submit are counted in summary.rejected and reported by the status poll.

Media typeapplication/json
object
data
object
batch
object
id

Batch ID. Poll GET /v1/post-batches/{batchId} with it.

string
accountId
string
status

Completed straight away when every entry was refused at submit.

string
Allowed values: queued processing completed
createdAt
string format: date-time
summary
object
total
integer
accepted

Entries that will be attempted.

integer
rejected

Entries refused at submit; the status poll reports why.

integer
meta
object
requestId

Unique request ID for debugging

string
Example
{
"data": {
"batch": {
"status": "queued"
}
}
}

Invalid request

Media typeapplication/json
object
error
object
code

Error code

string
message

Error message

string
reason

Machine-readable reason. On PAYMENT_REQUIRED errors it is the denial reason: insufficient_credits, trial_exhausted, no_access, account_not_connected, legacy_post_limit_exceeded. Other errors may carry an operation-specific reason, such as pinterest_reconnect_required, pinterest_rate_limited, pinterest_timeout, pinterest_request_failed or list_boards_upstream_failed; each operation’s documented error responses show the values it returns. Branch on reason rather than parsing message.

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 reason. On PAYMENT_REQUIRED errors it is the denial reason: insufficient_credits, trial_exhausted, no_access, account_not_connected, legacy_post_limit_exceeded. Other errors may carry an operation-specific reason, such as pinterest_reconnect_required, pinterest_rate_limited, pinterest_timeout, pinterest_request_failed or list_boards_upstream_failed; each operation’s documented error responses show the values it returns. Branch on reason rather than parsing message.

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 reason. On PAYMENT_REQUIRED errors it is the denial reason: insufficient_credits, trial_exhausted, no_access, account_not_connected, legacy_post_limit_exceeded. Other errors may carry an operation-specific reason, such as pinterest_reconnect_required, pinterest_rate_limited, pinterest_timeout, pinterest_request_failed or list_boards_upstream_failed; each operation’s documented error responses show the values it returns. Branch on reason rather than parsing message.

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 reason. On PAYMENT_REQUIRED errors it is the denial reason: insufficient_credits, trial_exhausted, no_access, account_not_connected, legacy_post_limit_exceeded. Other errors may carry an operation-specific reason, such as pinterest_reconnect_required, pinterest_rate_limited, pinterest_timeout, pinterest_request_failed or list_boards_upstream_failed; each operation’s documented error responses show the values it returns. Branch on reason rather than parsing message.

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 Idempotency-Key was already used for a different batch.

Rate limit exceeded

Media typeapplication/json
object
error
object
code

Error code

string
message

Error message

string
reason

Machine-readable reason. On PAYMENT_REQUIRED errors it is the denial reason: insufficient_credits, trial_exhausted, no_access, account_not_connected, legacy_post_limit_exceeded. Other errors may carry an operation-specific reason, such as pinterest_reconnect_required, pinterest_rate_limited, pinterest_timeout, pinterest_request_failed or list_boards_upstream_failed; each operation’s documented error responses show the values it returns. Branch on reason rather than parsing message.

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 10000 requests for this organization exceeded. Resets at midnight UTC."
},
"meta": {
"requestId": "abc123"
}
}
Retry-After
integer

Seconds until the daily limit resets at midnight UTC.