Home Pricing Docs Blog Changelog About Log in Go to your tickets
Browse docs

Uploading files

Overview

Attachments and inline images are uploaded in two steps. First you upload the file on its own, which streams it to storage and returns a short lived token. Then you reference that token when you create a ticket or post a message. This keeps the message endpoints as plain JSON while letting you send large files without encoding them into the request body.

The same token mechanism powers both regular attachments and inline images.

Step 1: Upload the file

Send the file as multipart/form-data to the upload endpoint.

POST /api/attachments

Request Body

Field Type Required Description
file file Yes The file to upload, up to 25 MB

Example Request

curl -X POST https://there-there.app/api/attachments \
  -H "Authorization: Bearer YOUR_API_TOKEN" \
  -H "Accept: application/json" \
  -F "file=@screenshot.png"

Example Response (201 Created)

{
    "data": {
        "token": "upl_screenshot_8f3c1a...",
        "filename": "screenshot.png",
        "mime_type": "image/png",
        "size": 84213,
        "expires_at": "2026-06-24T14:30:00+00:00"
    }
}

The token is single use. It includes a slug of the file name (for example upl_screenshot_...) to make it easier to recognise while debugging, but you should treat the whole value as opaque. The expires_at field tells you when the upload will be deleted if you do not attach it to a message (see Lifetime and cleanup).

Step 2: Reference the token

Every message endpoint accepts an attachment_ids array of upload tokens. List each token you want to attach to the message you are creating.

Endpoint Where to put attachment_ids
POST /api/tickets inside the message object
POST /api/tickets/{ticket}/reply top level
POST /api/tickets/{ticket}/forward top level
POST /api/tickets/{ticket}/note top level

A message can reference up to 10 tokens.

Example: reply with an attachment

curl -X POST https://there-there.app/api/tickets/01HX9F3K2M.../reply \
  -H "Authorization: Bearer YOUR_API_TOKEN" \
  -H "Accept: application/json" \
  -H "Content-Type: application/json" \
  -d '{
    "body": "<p>Here is the file you asked for.</p>",
    "attachment_ids": ["upl_screenshot_8f3c1a..."]
  }'

Example: create a ticket with an attachment

curl -X POST https://there-there.app/api/tickets \
  -H "Authorization: Bearer YOUR_API_TOKEN" \
  -H "Accept: application/json" \
  -H "Content-Type: application/json" \
  -d '{
    "channel": "01HX...",
    "subject": "Imported ticket",
    "from_contact": { "email": "john@customer.com" },
    "message": {
      "body": "<p>Original message.</p>",
      "attachment_ids": ["upl_screenshot_8f3c1a..."]
    }
  }'

Inline images

To place an uploaded image inside the message body (rather than as a separate attachment), embed it with a cid: reference to its token, then also list the token in attachment_ids.

<p>See the highlighted area:</p>
<p><img src="cid:upl_screenshot_8f3c1a..."></p>

When the message is created, There There replaces the cid: reference with the image and renders it inline, both in the conversation and in any outbound email. A token whose cid: reference appears in the body becomes an inline image. A token that is only listed in attachment_ids becomes a regular attachment.

curl -X POST https://there-there.app/api/tickets/01HX9F3K2M.../reply \
  -H "Authorization: Bearer YOUR_API_TOKEN" \
  -H "Accept: application/json" \
  -H "Content-Type: application/json" \
  -d '{
    "body": "<p>See below:</p><p><img src=\"cid:upl_screenshot_8f3c1a...\"></p>",
    "attachment_ids": ["upl_screenshot_8f3c1a..."]
  }'

Lifetime and cleanup

An upload is parked on its own until you attach it to a message.

  • A token is single use. Once a message references it, the file moves onto that message and the token can no longer be used.
  • An upload that is never attached is deleted roughly 30 minutes after it was created. The expires_at field in the upload response is the exact deadline.
  • A token can only be used by the same API token that created it. A token from a different API token, or from a different workspace, is rejected.

These rules keep parked uploads from accumulating, so the upload endpoint cannot be used as general purpose storage.

Limits

Limit Value
Maximum file size 25 MB
Attachments per message 10
Unattached upload lifetime 30 minutes

Executable file types and other disallowed content types are rejected at upload time. Read only API tokens cannot upload.

Errors

Status Meaning
422 The file is too large, has a disallowed type, or a referenced token is unknown, already used, expired, or owned by another token
429 Too many uploads in a short period, or too much is currently parked for the workspace