Upload attachments

Attach files to messages, notes and drafts using presigned uploads, register files that stay in your own storage, and download attachments.

Attachments are created in two steps: first you create an attachment record that describes the file, then you either upload the bytes with the presigned form Kustomer returns, or point Kustomer at a file you already host. Finally you reference the attachment from a message, note or draft.

Upload URLKustomer APIYouUpload URLKustomer APIYouPOST /v1/attachments { name, contentType, contentLength }1201 { data.id, meta.upload: { url, fields } }2POST multipart/form-data (fields…, file)3204 No Content4POST /v1/conversations/{id}/messages { …, attachments: [{ _id, name, contentType, contentLength }] }5
Diagram source
sequenceDiagram
  autonumber
  participant You
  participant API as Kustomer API
  participant S as Upload URL
  You->>API: POST /v1/attachments { name, contentType, contentLength }
  API-->>You: 201 { data.id, meta.upload: { url, fields } }
  You->>S: POST multipart/form-data (fields…, file)
  S-->>You: 204 No Content
  You->>API: POST /v1/conversations/{id}/messages { …, attachments: [{ _id, name, contentType, contentLength }] }

1. Create the attachment record

post/v1/attachments
cURL
curl -X POST "https://acme.api.kustomerapp.com/v1/attachments" \
  -H "Authorization: Bearer $KUSTOMER_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "name": "invoice-4417.pdf", "contentType": "application/pdf", "contentLength": 48213 }'

name, contentType and contentLength (in bytes) are required. The response includes the attachment id and a temporary upload policy:

201 Created
{
  "data": {
    "id": "5a4d1a8a6d92880012ad2923",
    "type": "attachment",
    "attributes": { "name": "invoice-4417.pdf", "contentType": "application/pdf", "contentLength": 48213, "uploaded": false }
  },
  "meta": {
    "upload": {
      "url": "https://…",
      "fields": {
        "key": "attachments/…/5a4d1a8a6d92880012ad2923-invoice-4417.pdf",
        "Content-Type": "application/pdf",
        "Policy": "…",
        "X-Amz-Signature": "…"
      }
    }
  }
}

2. Upload the file

Send a multipart/form-data POST to meta.upload.url. Include every key in meta.upload.fields as a form field, exactly as returned, and add the file last, under the name file. Don't send your Kustomer Authorization header to this URL.

# Build -F flags from meta.upload.fields, then append the file last
curl -X POST "$UPLOAD_URL" \
  -F "key=$KEY" \
  -F "Content-Type=application/pdf" \
  -F "Policy=$POLICY" \
  -F "X-Amz-Signature=$SIGNATURE" \
  -F "file=@invoice-4417.pdf"

The upload policy expires after a short time, so upload right after creating the record. contentLength must match the file you upload.

3. Attach it

Reference the attachment when you create the message or note. Messages accept attachment objects:

POST /v1/conversations/{id}/messages
{
  "channel": "email",
  "app": "acme-billing",
  "direction": "out",
  "subject": "Your invoice",
  "preview": "Hi Jane, your invoice for order #4417 is attached.",
  "attachments": [
    { "_id": "5a4d1a8a6d92880012ad2923", "name": "invoice-4417.pdf", "contentType": "application/pdf", "contentLength": 48213 }
  ]
}

Notes take the same shape. See Create note within conversation.

Files in your own storage

If files must stay in your storage, register them by URL instead of uploading. Pass externalUrl:

POST /v1/attachments
{
  "name": "contract.pdf",
  "contentType": "application/pdf",
  "contentLength": 120934,
  "externalUrl": "https://files.acme.example/contracts/8812.pdf",
  "renderMode": "default"
}
  • No upload policy is returned, and the attachment is marked uploaded: true straight away.
  • externalUrl must be an absolute https:// URL without credentials in it. Private and loopback hosts are rejected.
  • Kustomer doesn't fetch, scan, copy or delete the file. Keeping the URL working is up to you; a signed URL that expires will break the attachment.
renderModeBehavior
defaultClients get the URL in links.related and can open or download the file.
metadata_onlyThe URL is never returned to clients. Agents and SDKs see a non-interactive placeholder with the file's name and size. Use this for files that must only be opened in your own system.

Draft attachments

Drafts have their own attachment endpoint. The response's links.related is a presigned URL that you upload to with a PUT:

post/v1/drafts/{id}/attachments
Shell
curl --upload-file invoice-4417.pdf "$PRESIGNED_URL"

Downloading attachments

get/v1/messages/{message_id}/attachments/{id}

This works for inbound and outbound message attachments. The response's links.related is a temporary URL for the file content. Download it promptly and don't store the URL.

Limits and tips

  • Create the attachment record and upload in one go. Records whose upload never completes stay uploaded: false.
  • Set contentType accurately: agents' browsers use it to preview images and PDFs.
  • Attachment uploads count toward your rate limit only for the Kustomer API calls, not the upload itself.