Skip to content
V2 (Legacy) API ReferenceGet started

Pre-upload a file

client.Attachments.New(ctx, body) (*AttachmentNewResponse, error)
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.

ParametersExpand Collapse
body AttachmentNewParams
ContentType param.Field[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.

Filename param.Field[string]

Name of the file to upload

minLength1
maxLength255
SizeBytes param.Field[int64]

Size of the file in bytes (max 100MB)

formatint64
minimum1
maximum104857600
ReturnsExpand Collapse
type AttachmentNewResponse struct{…}
AttachmentID string

Unique identifier for the attachment (for status checks via GET /v3/attachments/{id})

formatuuid
DownloadURL 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
ExpiresAt Time

When the upload URL expires (15 minutes from now)

formatdate-time
HTTPMethod AttachmentNewResponseHTTPMethod

HTTP method to use for upload (always PUT)

RequiredHeaders map[string, 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.

UploadURL 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

package main

import (
  "context"
  "fmt"

  "github.com/linq-team/linq-go"
  "github.com/linq-team/linq-go/option"
)

func main() {
  client := linqgo.NewClient(
    option.WithAPIKey("My API Key"),
  )
  attachment, err := client.Attachments.New(context.TODO(), linqgo.AttachmentNewParams{
    ContentType: linqgo.SupportedContentTypeImageJpeg,
    Filename: "photo.jpg",
    SizeBytes: 1024000,
  })
  if err != nil {
    panic(err.Error())
  }
  fmt.Printf("%+v\n", attachment.AttachmentID)
}
{
  "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
}