Skip to content

Create a media upload

POST
/v1/media/uploads
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.

Media typeapplication/json
object
extension
required

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.

string
Example
jpg

Upload slot created

Media typeapplication/json
object
data
object
upload
object
uploadId

Opaque id to pass as uploadId on POST /v1/accounts/{accountId}/posts. Single-use.

string
uploadUrl

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.

string format: uri
uploadUrlExpiresAt

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.

integer
expiresAt

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.

integer
mediaType

Media type derived from the extension. Used automatically on create.

string
Allowed values: image video
contentType

The exact Content-Type header value the PUT must send.

string
meta
object
requestId

Unique request ID for debugging

string
Example
{
"data": {
"upload": {
"mediaType": "image",
"contentType": "image/jpeg"
}
}
}

Invalid request

Media typeapplication/json
object
error
object
code

Error code

string
message

Error message

string
reason

Machine-readable denial reason. Present on PAYMENT_REQUIRED errors: insufficient_credits, trial_exhausted, no_access, account_not_connected, legacy_post_limit_exceeded.

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 denial reason. Present on PAYMENT_REQUIRED errors: insufficient_credits, trial_exhausted, no_access, account_not_connected, legacy_post_limit_exceeded.

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"
}
}

Rate limit exceeded

Media typeapplication/json
object
error
object
code

Error code

string
message

Error message

string
reason

Machine-readable denial reason. Present on PAYMENT_REQUIRED errors: insufficient_credits, trial_exhausted, no_access, account_not_connected, legacy_post_limit_exceeded.

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 5000 requests exceeded. Resets at midnight UTC."
},
"meta": {
"requestId": "abc123"
}
}