Pre-upload a file
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 ParametersJSON
Returns
Unique identifier for the attachment (for status checks via GET /v3/attachments/{id})
Permanent CDN URL for the file. Does not expire. Use the attachment_id
to reference this file in media parts when sending messages.
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
}