Skip to content
V2 (Legacy) API ReferenceGet started

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 permanent attachment_id you can reference in future messages.

Step 1: Request an upload URL

Call this endpoint with file metadata:

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

The response includes an upload_url (valid for 15 minutes) and a permanent 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. The ID never expires — use it in as many messages as you want.

POST /v3/chats
{
  "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

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"
"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 (for status checks via GET /v3/attachments/{id})

formatuuid
download_url: string

Permanent CDN URL for the file. Does not expire. Use the attachment_id to reference this file in media parts when sending messages.

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.

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"
  },
  "success": false
}
{
  "error": {
    "status": 401,
    "code": 2004,
    "message": "Unauthorized - missing or invalid authentication token"
  },
  "success": false
}
{
  "error": {
    "status": 500,
    "code": 3006,
    "message": "Internal server error"
  },
  "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"
  },
  "success": false
}
{
  "error": {
    "status": 401,
    "code": 2004,
    "message": "Unauthorized - missing or invalid authentication token"
  },
  "success": false
}
{
  "error": {
    "status": 500,
    "code": 3006,
    "message": "Internal server error"
  },
  "success": false
}