Create a media upload
const url = 'https://api-v1.tailwind.ai/v1/media/uploads';const options = { method: 'POST', headers: {Authorization: 'Bearer <token>', 'Content-Type': 'application/json'}, body: '{"extension":"jpg"}'};
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/media/uploads \ --header 'Authorization: Bearer <token>' \ --header 'Content-Type: application/json' \ --data '{ "extension": "jpg" }'Get a short-lived, write-only URL for uploading your own file bytes, for media that has no public URL (a Canva export, a photo, a video on disk). Three steps: call this endpoint with the file’s extension; PUT the raw bytes to the returned uploadUrl with a Content-Type header exactly matching the returned contentType; then pass the uploadId to POST /v1/accounts/{accountId}/posts instead of mediaUrl. Bytes go straight to storage and never pass through this API. The uploadId is single-use, scoped to your organization, and expires if it is never attached to a Pin — note that uploadUrl expires much sooner than uploadId does, so upload promptly. Free: staging bytes costs no credits; only scheduling a Pin is billed, and what that costs depends on the organization’s billing system.
Authorizations
Section titled “Authorizations”Request Bodyrequired
Section titled “Request Bodyrequired”object
The file’s extension, with or without a leading dot and in any case (jpg, .PNG, mp4). It determines both the upload’s media type and the Content-Type the signed URL is issued for. Accepted: jpg, jpeg, png, svg, gif, webp, heic, mp4, mov, qt.
Example
jpgResponses
Section titled “Responses”Upload slot created
object
object
object
Opaque id to pass as uploadId on POST /v1/accounts/{accountId}/posts. Single-use.
Write-only URL to PUT the raw file bytes to. Send the bytes as the request body with a Content-Type header exactly matching contentType below — the URL’s signature covers that header, so a mismatch is rejected.
Unix timestamp after which uploadUrl stops accepting bytes. Much sooner than expiresAt — this is the deadline for the PUT itself, not for attaching the Pin.
Unix timestamp after which uploadId no longer resolves and the staged bytes are deleted. This is the deadline for attaching the upload to a Pin.
Media type derived from the extension. Used automatically on create.
The exact Content-Type header value the PUT must send.
object
Unique request ID for debugging
Example
{ "data": { "upload": { "mediaType": "image", "contentType": "image/jpeg" } }}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" }}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" }}