Save Pins to a board
const url = 'https://api-v1.tailwind.ai/v1/accounts/example/boards/example/pins';const options = { method: 'POST', headers: {Authorization: 'Bearer <token>', 'Content-Type': 'application/json'}, body: '{"pinIds":["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/boards/example/pins \ --header 'Authorization: Bearer <token>' \ --header 'Content-Type: application/json' \ --data '{ "pinIds": [ "example" ] }'Save existing Pins (repins) onto a board. This is the seeding write behind “build me a board about X”: create a board, call get-recommended-pins for it with your keyword, then pass those Pin IDs here.
Billing. Saving onto a board created through Tailwind in the last hour is free for up to 50 Pins in total, across calls. Any other save is billed like scheduling a Pin: one pin_schedule credit per saved Pin on modular billing, or one post from the post allowance on legacy and Shopify billing. A save the plan cannot cover returns 402 with nothing saved.
Three things to plan around:
- Per-Pin failures are counted, not raised. A deleted or unreachable Pin lands in
failedCountand the rest still save. A board ID that does not exist therefore returns200with every Pin failed rather than a404, and the response does not say which Pins failed. - This is not idempotent. Pinterest treats a repeat save as a second Pin, so retrying — or passing the same ID twice in one batch — pins twice. Re-running
get-recommended-pinsis safe: a save stores a copy under a new Pin ID, and that endpoint matches the copy back to the Pin it was saved from, so a second pass returns what is still missing rather than what you just saved. - A large batch can time out. Saves run four at a time against a 10-second-per-Pin Pinterest budget, so a big batch of slow saves can exceed the request budget and return a
504. Saves already in flight are not cancelled, so an unknown number of Pins may still have saved — re-read the board rather than blind-retrying, since a retry can save a Pin twice. Prefer batches in the tens.
Authorizations
Section titled “Authorizations”Parameters
Section titled “Parameters”Path Parameters
Section titled “Path Parameters”Numeric Tailwind account ID returned by GET /v1/accounts
Numeric Pinterest board ID returned by GET /v1/accounts/{accountId}/boards. Tailwind’s own b_-prefixed CSV export format is accepted and normalized; board names and board URLs are rejected with a 400.
Request Bodyrequired
Section titled “Request Bodyrequired”object
Numeric Pinterest Pin IDs to save onto the board, e.g. from get-recommended-pins. Pin URLs and names are rejected with a 400. Duplicates are not removed — a repeated ID saves the Pin twice.
Examplegenerated
{ "pinIds": [ "example" ]}Responses
Section titled “Responses”Save attempted for every Pin. Check both counts — a 200 with savedCount: 0 is a complete failure, not a success.
object
object
Pins saved onto the board
Pins that could not be saved, most often because the Pin was deleted or the board ID does not exist. Which Pins failed is not reported.
object
Unique request ID for debugging
Example
{ "data": { "savedCount": 8, "failedCount": 2 }}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" }}Pinterest refused the operation. Distinct from a 422 reconnect: the account’s authorization is fine. The usual cause is a board the account collaborates on but does not own — those cannot be renamed, deleted or given sections.
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
Examplegenerated
{ "error": { "code": "example", "message": "example", "reason": "example", "resolve_url": "example" }, "meta": { "requestId": "example" }}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 account’s Pinterest connection is no longer usable. Your API key is fine — this is not an authentication failure on Tailwind’s side, which is why it is not a 401. The account holder needs to reconnect Pinterest in Tailwind before this operation can succeed.
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": "UNPROCESSABLE_ENTITY", "message": "Reconnect your Pinterest account to manage Boards.", "reason": "pinterest_reconnect_required" }, "meta": { "requestId": "abc123" }}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.
Pinterest did not respond before the request deadline. The operation may or may not have taken effect — re-read the account’s boards before retrying, since a blind retry after a create that did land produces a duplicate.
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": "GATEWAY_TIMEOUT", "message": "Pinterest did not respond in time, so this may or may not have taken effect. Check the account's boards before trying again rather than repeating the request.", "reason": "pinterest_timeout" }, "meta": { "requestId": "abc123" }}