Skip to main content
A message can carry files as media parts. You can upload a file once and reuse it by its attachment_id, or point Blujay at a public HTTPS URL for a one-off send. Media you receive arrives as media parts too, and you download it through the API.
Media is rejected until the contact has replied to your number. A media part in a first message or in any follow-up before the reply returns 400 PRE_REPLY_CONTENT_BLOCKED, and nothing is sent. See Sending rules.

Choose how to send a file

Each media part sets exactly one of attachment_id or url. A message can have up to 40 media and vcard parts.

Upload a file

1

Declare the file

Tell Blujay the file’s name, type and exact size in bytes:
201 Created
content_type is a lowercase MIME type without parameters, such as image/jpeg. filename is a plain file name with no folder path.upload_url is a signed URL on Blujay’s storage, not on api.blujay.io. The host is returned in the response; allow it in your egress rules rather than hardcoding one.
2

PUT the raw bytes

Send the file’s raw bytes to upload_url with exactly the upload_headers from the response. Don’t use multipart/form-data, and don’t add your API key: the URL already authorizes this one upload.
The URL expires after 15 minutes. If it expires before you upload, declare the file again.
3

Send it

Use the attachment_id in a media part. There’s no separate “complete” call.
The first time you send or read the attachment, Blujay checks the uploaded bytes against what you declared:
  • If the upload hasn’t finished, you get 409 ATTACHMENT_NOT_READY. Retry once the PUT completes.
  • If the bytes don’t match the declared type, you get 422 ATTACHMENT_UPLOAD_INVALID. Declare the file again and upload the correct bytes.
  • If the bytes don’t match the declared size_bytes, the storage service refuses the PUT itself with a 403 and an XML body (the signed URL is bound to the exact size). Nothing is stored and the attachment stays 409 ATTACHMENT_NOT_READY. Declare the file again with the right size and upload again.
After that, reuse the same attachment_id in as many messages as you like. At declaration, a file over 100 MiB returns 413 ATTACHMENT_TOO_LARGE. Executables, scripts, HTML, SVG and XML files return 415 UNSUPPORTED_ATTACHMENT_TYPE. If your workspace has too many uploads waiting for their bytes, you get 429 ATTACHMENT_CAPACITY_EXCEEDED; finish or abandon some uploads and retry.

Accepted file types

Blujay identifies a file by its bytes, not only by content_type, and accepts these: A file whose bytes don’t match the specific type you declared is rejected with 422 ATTACHMENT_UPLOAD_INVALID when the upload is checked. A file Blujay can’t identify is accepted as application/octet-stream; declare it as that. What a service can carry is a separate question; see What each service can carry.

Send from a URL

For a one-off image, video or contact card, put a public HTTPS URL in the part:
202 Accepted
Blujay checks the URL when you send, then downloads the file after it accepts the message. The response replaces the URL with a new attachment_id.
  • The URL must be public HTTPS, up to 2,048 characters. URLs with credentials or that point at private addresses are rejected with 422 ATTACHMENT_FETCH_FAILED before anything is sent.
  • The file can be up to 10 MiB and must be an image, a video or a vCard. Serve a vCard as text/vcard or text/x-vcard, or as a generic binary type with a .vcf file name.
  • Files disguised as another type are rejected.
If the download fails after acceptance, the message fails with a failure_code: ATTACHMENT_FETCH_FAILED, ATTACHMENT_TOO_LARGE or UNSUPPORTED_ATTACHMENT_TYPE. See Track a message.

What each service can carry

The service that carries a message decides which files it can send: If any file in a message can’t go out on the service that carries it, the whole message fails. Blujay never silently drops, converts or shrinks a file. If you might send to SMS recipients, keep files small and stick to images and videos, or check the number first.

Read and download media

Message parts show media with its details once the file is ready:
download_url is a path on the Blujay API. Download the file from https://api.blujay.io plus that path, with your usual Authorization header:
The response body is the file itself, with its Content-Type and a Content-Disposition file name. To get an attachment’s details without downloading it, call GET /v1/attachments/{attachment_id}:

Media in received messages

Media in an inbound message can show up before the file is ready. At first, the part may have only type and attachment_id. The message.received webhook is a snapshot and never changes, and there is no “attachment ready” event. To get the file:
  1. Take attachment_id from the message.received event or the message read.
  2. Call GET /v1/attachments/{attachment_id}. While the file is still arriving, it returns 409 ATTACHMENT_NOT_READY.
  3. Wait a few seconds and call again, with a growing delay. Give up after a few minutes and log the attachment_id.
  4. Once the response has status: ready, read filename, content_type, size_bytes and download_url, then download the file.
Attachment reads count toward the attachment request limit; see Rate limits.
Only send your API key to https://api.blujay.io. Build download links from the download_url path yourself rather than following a full URL from a payload.

Delete an attachment

DELETE /v1/attachments/{attachment_id} stops an uploaded attachment from being sent or downloaded again, effective immediately. Messages that already include it keep the reference in their history. Deleting an attachment you already deleted returns 204 again. An attachment_id that doesn’t exist in your workspace returns 404 NOT_FOUND.

Limits

Attachment endpoints have their own request limit, separate from other reads and writes. See Rate limits. If attachment storage is briefly unavailable, you get 503 ATTACHMENT_STORAGE_UNAVAILABLE; retry shortly.
Common mistakes
  • Sending media before the contact replies returns 400 PRE_REPLY_CONTENT_BLOCKED. Send text first.
  • PUT the raw bytes to upload_url with exactly the upload_headers. A multipart/form-data body or an extra Authorization header makes the upload fail its check.
  • size_bytes must be the exact byte count. A mismatch returns 422 ATTACHMENT_UPLOAD_INVALID.
  • A URL send returns 202 before the download. A bad URL shows up later as a failed message, not as a 4xx.
  • download_url is a path. Prefix it with https://api.blujay.io and send your API key only there.

Limitations

  • No media until the contact replies (400 PRE_REPLY_CONTENT_BLOCKED).
  • Uploads up to 100 MiB; URL sends up to 10 MiB and only images, videos and vCards (413 ATTACHMENT_TOO_LARGE, 415 UNSUPPORTED_ATTACHMENT_TYPE).
  • Executables, scripts, HTML, SVG and XML are never accepted (415 UNSUPPORTED_ATTACHMENT_TYPE).
  • RCS and SMS carry only images, videos and vCards; SMS/MMS up to 5 MiB per message. A message with a file the service can’t carry fails as a whole.
  • Voice memos aren’t supported. Audio goes out as a file.
  • There is no attachment list endpoint and no “attachment ready” event. Poll GET /v1/attachments/{attachment_id}.
  • Deleting an attachment doesn’t remove it from messages that already sent it.

Next steps

Contact cards

Send a saved vCard as a native contact card.

Send messages

Message parts and follow-ups.

Track a message

Statuses and media failure codes.

Receive messages

Handle inbound media as it arrives.