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 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.
Pre-upload a file
import LinqAPIV3 from '@linqapp/sdk';
const client = new LinqAPIV3({
apiKey: process.env['LINQ_API_V3_API_KEY'], // This is the default and can be omitted
});
const attachment = await client.attachments.create({
content_type: 'image/jpeg',
filename: 'photo.jpg',
size_bytes: 1024000,
});
console.log(attachment.attachment_id);{
"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
}