Skip to content
Linq Copy agent prompt

Pre-upload a file

POST/v3/attachments

This endpoint is optional. You can send media by simply providing a URL in your message’s media part — no pre-upload required. Use this endpoint only when you want to upload a file ahead of time for reuse or latency optimization.

Returns a presigned upload URL and a reusable attachment_id you can reference in future messages. Attachments stored on the ephemeral attachments tier (and their URLs) are removed within roughly 24–48 hours of upload, independently of any message retention window. Attachments on the persistent tier are kept until you DELETE them, regardless of message expiry.

Step 1: Request an upload URL

Call POST /v3/attachments with file metadata:

{
  "filename": "photo.jpg",
  "content_type": "image/jpeg",
  "size_bytes": 1024000
}

The response includes an upload_url (valid for 15 minutes) and a reusable attachment_id.

Step 2: Upload the file

Make a PUT request to the upload_url with the raw file bytes as the request body. You must include all headers from required_headers exactly as returned — the presigned URL is signed with these values and S3 will reject the upload if they don’t match.

The request body is the binary file content — not JSON, not multipart form data. The file must equal size_bytes bytes (the value you declared in step 1).

curl -X PUT "<upload_url from step 1>" \
  -H "Content-Type: image/jpeg" \
  -H "Content-Length: 1024000" \
  --data-binary @photo.jpg

Step 3: Send a message with the attachment

Reference the attachment_id in a media part with POST /v3/chats. The ID stays valid for as many messages as you want — unless the attachment is stored on the ephemeral attachments tier, in which case it is removed within roughly 24–48 hours of upload.

{
  "from": "+15559876543",
  "to": ["+15551234567"],
  "message": {
    "parts": [
      { "type": "media", "attachment_id": "<attachment_id from step 1>" }
    ]
  }
}

When to use this instead of a URL in the media part

  • Sending the same file to multiple recipients (avoids re-downloading each time)
  • Large files where you want to separate upload from message send
  • Latency-sensitive sends where the file should already be stored

If you just need to send a file once, skip all of this and pass a url directly in the media part instead.

File Size Limit: 100MB

Unsupported Types: WebP, SVG, FLAC, OGG, and executable files are explicitly rejected.

Body ParametersJSONExpand Collapse
content_type: SupportedContentType

Supported MIME types for file attachments and media URLs.

Images: image/jpeg, image/png, image/gif, image/heic, image/heif, image/tiff, image/bmp, image/svg+xml, image/webp, image/x-icon

Videos: video/mp4, video/quicktime, video/mpeg, video/mpeg2, video/x-msvideo, video/3gpp

Audio: audio/mpeg, audio/x-m4a, audio/x-caf, audio/x-wav, audio/x-aiff, audio/aac, audio/midi, audio/amr

Wallet passes: application/vnd.apple.pkpass

Documents: application/pdf, text/plain, text/markdown, text/vcard, text/rtf, text/csv, text/html, text/calendar, text/xml, application/json, application/msword, application/vnd.openxmlformats-officedocument.wordprocessingml.document, application/vnd.ms-excel, application/vnd.openxmlformats-officedocument.spreadsheetml.sheet, application/vnd.ms-powerpoint, application/vnd.openxmlformats-officedocument.presentationml.presentation, application/x-iwork-pages-sffpages, application/x-iwork-numbers-sffnumbers, application/x-iwork-keynote-sffkey, application/epub+zip, application/zip, application/x-gzip

Transcoded on delivery:

  • audio/x-caf — CAF files are transcoded to audio/mp4 for delivery.

Deprecated (accepted but transcoded):

  • audio/mp3 — Deprecated. Use audio/mpeg instead. Files sent as audio/mp3 will be delivered as audio/mpeg.
  • audio/mp4 — Deprecated. Use audio/x-m4a instead. Files sent as audio/mp4 will be delivered as audio/x-m4a.
  • audio/aiff — Deprecated. Use audio/x-aiff instead. Files sent as audio/aiff will be delivered as audio/x-aiff.
  • image/tiff — Accepted, but TIFF images are transcoded to JPEG for delivery.

Unsupported: FLAC, OGG, and executable files are explicitly rejected.

One of the following:
"image/jpeg"
"image/png"
"image/gif"
"image/heic"
"image/heif"
"image/tiff"
"image/bmp"
"image/svg+xml"
"image/webp"
"image/x-icon"
"video/mp4"
"video/quicktime"
"video/mpeg"
"video/mpeg2"
"video/x-m4v"
"video/x-msvideo"
"video/3gpp"
"audio/mpeg"
"audio/mp3"
"audio/x-m4a"
"audio/mp4"
"audio/x-caf"
"audio/x-wav"
"audio/x-aiff"
"audio/aiff"
"audio/aac"
"audio/midi"
"audio/amr"
"application/pdf"
"application/vnd.apple.pkpass"
"text/plain"
"text/markdown"
"text/vcard"
"text/rtf"
"text/csv"
"text/html"
"text/calendar"
"application/msword"
"application/vnd.openxmlformats-officedocument.wordprocessingml.document"
"application/vnd.ms-excel"
"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"
"application/vnd.ms-powerpoint"
"application/vnd.openxmlformats-officedocument.presentationml.presentation"
"application/x-iwork-pages-sffpages"
"application/x-iwork-numbers-sffnumbers"
"application/x-iwork-keynote-sffkey"
"application/epub+zip"
"text/xml"
"application/json"
"application/zip"
"application/x-gzip"
filename: string

Name of the file to upload

minLength1
maxLength255
size_bytes: number

Size of the file in bytes (max 100MB)

formatint64
minimum1
maximum104857600
ReturnsExpand Collapse
attachment_id: string

Unique identifier for the attachment

formatuuid
download_url: string

Stable CDN URL for the file. Use the attachment_id to reference this file in media parts when sending messages. Files on the ephemeral attachments tier — and this URL — are removed within roughly 24–48 hours of upload, independently of any message retention window.

formaturi
expires_at: string

When the upload URL expires (15 minutes from now)

formatdate-time
http_method: "PUT"

HTTP method to use for upload (always PUT)

required_headers: map[string]

HTTP headers that must be set on the upload request. The presigned URL is signed with these exact values — S3 will reject the upload if they don’t match.

upload_url: string

Presigned URL for uploading the file. PUT the raw binary file content to this URL with the required_headers. Do not JSON-encode or multipart-wrap the body. Expires after 15 minutes. Treat the URL as opaque — the hostname depends on partner configuration and is the same across sandbox and production.

formaturi

Pre-upload a file

curl https://api.linqapp.com/api/partner/v3/attachments \
    -H 'Content-Type: application/json' \
    -H "Authorization: Bearer $LINQ_API_V3_API_KEY" \
    -d '{
          "content_type": "image/jpeg",
          "filename": "photo.jpg",
          "size_bytes": 1024000
        }'
{
  "attachment_id": "550e8400-e29b-41d4-a716-446655440000",
  "upload_url": "https://uploads.linqapp.com/attachments/550e8400?X-Amz-Algorithm=AWS4-HMAC-SHA256&...",
  "download_url": "https://cdn.linqapp.com/uploads/partner-id/550e8400/photo.jpg",
  "http_method": "PUT",
  "expires_at": "2024-01-15T10:45:00Z",
  "required_headers": {
    "Content-Type": "image/jpeg",
    "Content-Length": "1024000"
  }
}
{
  "error": {
    "status": 400,
    "code": 1002,
    "message": "Phone number must be in E.164 format",
    "doc_url": "https://docs.linqapp.com/channel/imessage/error/codes/1xxx/1002/"
  },
  "success": false
}
{
  "error": {
    "status": 401,
    "code": 2004,
    "message": "Unauthorized - missing or invalid authentication token",
    "doc_url": "https://docs.linqapp.com/channel/imessage/error/codes/2xxx/2004/"
  },
  "success": false
}
{
  "error": {
    "status": 500,
    "code": 3006,
    "message": "Internal server error",
    "doc_url": "https://docs.linqapp.com/channel/imessage/error/codes/3xxx/3006/"
  },
  "success": false
}
Returns Examples
{
  "attachment_id": "550e8400-e29b-41d4-a716-446655440000",
  "upload_url": "https://uploads.linqapp.com/attachments/550e8400?X-Amz-Algorithm=AWS4-HMAC-SHA256&...",
  "download_url": "https://cdn.linqapp.com/uploads/partner-id/550e8400/photo.jpg",
  "http_method": "PUT",
  "expires_at": "2024-01-15T10:45:00Z",
  "required_headers": {
    "Content-Type": "image/jpeg",
    "Content-Length": "1024000"
  }
}
{
  "error": {
    "status": 400,
    "code": 1002,
    "message": "Phone number must be in E.164 format",
    "doc_url": "https://docs.linqapp.com/channel/imessage/error/codes/1xxx/1002/"
  },
  "success": false
}
{
  "error": {
    "status": 401,
    "code": 2004,
    "message": "Unauthorized - missing or invalid authentication token",
    "doc_url": "https://docs.linqapp.com/channel/imessage/error/codes/2xxx/2004/"
  },
  "success": false
}
{
  "error": {
    "status": 500,
    "code": 3006,
    "message": "Internal server error",
    "doc_url": "https://docs.linqapp.com/channel/imessage/error/codes/3xxx/3006/"
  },
  "success": false
}