Create Pins in bulk
const url = 'https://api-v1.tailwind.ai/v1/accounts/example/post-batches';const options = { method: 'POST', headers: {Authorization: 'Bearer <token>', 'Content-Type': 'application/json'}, body: '{"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}]}'};
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/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.
Authorizations
Section titled “Authorizations”Parameters
Section titled “Parameters”Path Parameters
Section titled “Path Parameters”Numeric Tailwind account ID returned by GET /v1/accounts
Header Parameters
Section titled “Header Parameters”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.
Request Bodyrequired
Section titled “Request Bodyrequired”object
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.
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
Schedule at this time. Omit both sendAt and useNextOpenSlot for a draft.
Schedule into the account’s next open smart schedule slot. Entries fill slots in request order.
Responses
Section titled “Responses”Batch accepted. Entries refused at submit are counted in summary.rejected and reported by the status poll.
object
object
object
Batch ID. Poll GET /v1/post-batches/{batchId} with it.
Completed straight away when every entry was refused at submit.
object
Entries that will be attempted.
Entries refused at submit; the status poll reports why.
object
Unique request ID for debugging
Example
{ "data": { "batch": { "status": "queued" } }}Invalid request
object
object
Error code
Error message
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.
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 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.
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 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.
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 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.
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 Idempotency-Key was already used for a different batch.
Rate limit exceeded
object
object
Error code
Error message
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.
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 10000 requests for this organization exceeded. Resets at midnight UTC." }, "meta": { "requestId": "abc123" }}Headers
Section titled “Headers”Seconds until the daily limit resets at midnight UTC.
