---
title: Attachments | API Docs
description: Send images, videos, documents, and audio as media parts.
---

Send files (images, videos, documents, audio) with messages by providing a URL in a media part. Pre-uploading via `POST /v3/attachments` is **optional** and only needed for specific optimization scenarios.

### Sending Media via URL (up to 10MB)

Provide a publicly accessible HTTPS URL with a [supported media type](#overview-supported-file-types) in the `url` field of a media part.

```
{
  "parts": [
    { "type": "media", "url": "https://your-cdn.com/images/photo.jpg" }
  ]
}
```

This works with any URL you already host — no pre-upload step required. **Maximum file size: 10MB.**

### Pre-Upload (required for files over 10MB)

Use `POST /v3/attachments` when you want to:

- **Send files larger than 10MB** (up to 100MB) — URL-based downloads are limited to 10MB
- **Send the same file to many recipients** — upload once, reuse the `attachment_id` without re-downloading each time
- **Reduce message send latency** — the file is already stored, so sending is faster

**How it works:**

1. `POST /v3/attachments` with file metadata → returns a presigned `upload_url` (valid for **15 minutes**) and a reusable `attachment_id`
2. PUT the raw file bytes to the `upload_url` with the `required_headers` (no JSON or multipart — just the binary content)
3. Reference the `attachment_id` in your media part when sending messages (stays valid unless deleted — see [Attachment Lifetime](#overview-attachment-lifetime))

**Key difference:** When you provide an external `url`, we download and process the file on every send. When you use a pre-uploaded `attachment_id`, the file is already stored — so repeated sends skip the download step entirely.

### Attachment Lifetime

An `attachment_id` and its CDN URL stay valid until the file is deleted. Three things delete it:

| Trigger                                                  | Applies to                                                                                                                                                                                             |
| -------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `DELETE /v3/attachments/{attachmentId}`                  | Any attachment you own                                                                                                                                                                                 |
| Ephemeral **attachments** tier (24–48h storage backstop) | Attachments on ephemeral-tier partners or phone numbers                                                                                                                                                |
| Ephemeral **messages** tier                              | Does **not** remove attachment bytes on its own: ephemeral-tier objects are removed by the 24–48h storage backstop above, and persistent-tier attachments are kept until you `DELETE` them explicitly. |

Deletion is not reversible, and there is no `attachment.deleted` webhook. On either ephemeral tier, download anything you need to keep when you receive it rather than re-fetching later, and do not assume a pre-uploaded `attachment_id` can be reused indefinitely.

### Domain Allowlisting

Attachment URLs in API responses are served from `cdn.linqapp.com`. This includes:

- `url` fields in media and voice memo message parts
- `download_url` fields in attachment and upload response objects

If your application enforces domain allowlists (e.g., for SSRF protection), add:

```
cdn.linqapp.com
```

### Supported File Types

- **Images:** JPEG, PNG, GIF, HEIC, HEIF, TIFF, BMP
- **Videos:** MP4, MOV, M4V
- **Audio:** M4A, AAC, MP3, WAV, AIFF, CAF, AMR
- **Documents:** PDF, TXT, RTF, CSV, Office formats, ZIP
- **Contact & Calendar:** VCF, ICS

### Audio: Attachment vs Voice Memo

Audio files sent as media parts appear as **downloadable file attachments** in iMessage. To send audio as an **iMessage voice memo bubble** (with native inline playback UI), use the dedicated `POST /v3/chats/{chatId}/voicememo` endpoint instead.

### File Size Limits

- **URL-based (`url` field):** 10MB maximum
- **Pre-upload (`attachment_id`):** 100MB maximum

### Security & Ownership

Every attachment is bound to the partner account that created or received it. The API enforces ownership on every operation that touches an attachment — sending, retrieving, deleting.

**What this means for you:**

- An attachment created under your API key can only be referenced by your API key.
- Submitting another partner’s `attachment_id` returns `404 Not Found`. We do not disclose whether the id exists or belongs to someone else.
- Submitting a CDN URL that resolves to another partner’s attachment is rejected before the send is attempted.
- Ownership enforcement applies uniformly across send, create-chat, voice memo, retrieve, and delete operations.

Every attachment-affecting endpoint requires a valid partner API key. Unauthenticated calls return `401 Unauthorized`.

### Attachment URL Patterns

Attachment URLs in API responses and webhook payloads use one of two layouts, depending on the attachment’s tier:

| Tier                 | URL pattern                                                                            | TTL                                                                                                       |
| -------------------- | -------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------- |
| Persistent (default) | `https://cdn.linqapp.com/attachments/partners/{partner_id}/{attachment_id}/{filename}` | Long-lived — the URL itself does not expire, but see [Attachment Lifetime](#overview-attachment-lifetime) |
| Ephemeral            | Pre-signed URL pointing at the ephemeral prefix on `cdn.linqapp.com`                   | 15 minutes per signed URL — re-fetch via the API for a fresh URL                                          |

Inbound media you receive over webhooks uses the same layout your outbound sends produce, so the URL you store and the URL you build look identical — no special casing in your client.

### Ephemeral Attachments (Privacy Tier)

For regulated or sensitive content, opt in to the **ephemeral attachments** tier by contacting your Linq support contact. You can request it at two scopes:

| Scope                | Effect                                                                                                                     |
| -------------------- | -------------------------------------------------------------------------------------------------------------------------- |
| **Partner-wide**     | Every outbound and inbound attachment on every phone number under your account is routed through the ephemeral tier.       |
| **Per phone number** | Only the specified phone numbers route their attachments through the ephemeral tier. The rest stay on the persistent tier. |

**Behavioral differences vs the persistent default:**

| Aspect                  | Persistent              | Ephemeral                                                                                                                                     |
| ----------------------- | ----------------------- | --------------------------------------------------------------------------------------------------------------------------------------------- |
| Download URL form       | Long-lived CDN URL      | Pre-signed URL with short TTL                                                                                                                 |
| Retention floor         | Until you call `DELETE` | **Hard backstop: 24–48h** — even without an explicit `DELETE`, the platform removes the underlying bytes within roughly 24–48 hours of upload |
| URL re-fetch            | Not required            | Fetch via `GET /v3/attachments/{attachmentId}` for a fresh signed URL after TTL expiry                                                        |
| Cross-partner isolation | Enforced                | Enforced                                                                                                                                      |

**When to choose ephemeral:**

- Your downstream system processes the file immediately on receipt and does not need to re-read it later.
- You have a compliance requirement that the platform must not retain attachments beyond a short window.
- The content is high-sensitivity (PHI, financial documents, identity verification) and you do not want it sitting behind a long-lived URL.

**Important:** ephemeral applies in *both directions* — outbound files you upload **and** inbound media received by the phone numbers in that scope. Download bytes you need to keep promptly, or fetch a fresh signed URL via the API when needed.

### Deleting an Attachment

To permanently remove an attachment you own, use:

```
DELETE /v3/attachments/{attachmentId}
Authorization: Bearer <your_api_key>
```

**What this does:**

1. Verifies the attachment is owned by your account. Returns `404` otherwise.
2. Removes the underlying file from Linq storage.
3. Records an audit entry (timestamp, partner, attachment id).

**Response codes:**

| Status                      | Meaning                                                          |
| --------------------------- | ---------------------------------------------------------------- |
| `204 No Content`            | Deletion succeeded. The attachment is removed from Linq storage. |
| `400 Bad Request`           | `attachmentId` is not a valid UUID.                              |
| `401 Unauthorized`          | Missing or invalid API key.                                      |
| `404 Not Found`             | Attachment does not exist or is not owned by your account.       |
| `500 Internal Server Error` | Transient infrastructure issue — safe to retry.                  |

**Effect on message history:**

- Messages that referenced the deleted attachment remain visible.
- The message part that pointed at the attachment is preserved with no attachment reference.
- Webhook payloads previously delivered to you retain the original URL string, but downloads from that URL return `404` going forward.

Deletion is **irreversible**. Once `204` is returned, the bytes are gone — there is no undelete.

### Inbound Media Flow

When one of your phone numbers receives a message with media (image, video, audio, document), the platform:

1. Stores the file under your partner account.
2. Records metadata linked to the inbound message.
3. Delivers a webhook whose `parts[]` array includes a `media` part with a `url` pointing at `cdn.linqapp.com`.
4. If the receiving phone is opted in to ephemeral, the `url` is a short-TTL signed URL.

You can acknowledge the webhook without fetching the file inline, and lazy-load via `GET /v3/attachments/{attachmentId}` later. For ephemeral attachments, retrieving via the API always returns a freshly-signed URL.

### Data Lifecycle Summary

| Data                                                | Persistent tier                        | Ephemeral tier                                                                                                                                                                                                                                                                                                                                                                                               |
| --------------------------------------------------- | -------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| Attachment bytes                                    | Retained until you `DELETE`            | **Auto-removed within roughly 24–48 hours** of upload, independently of any message window. Also removable via `DELETE`                                                                                                                                                                                                                                                                                      |
| Attachment metadata (id, filename, mime type, size) | Retained until you `DELETE`            | Removed alongside the bytes                                                                                                                                                                                                                                                                                                                                                                                  |
| Message body & parts                                | Retained per message-retention policy  | Retained per message-retention policy — unless the line also has **ephemeral messages** enabled (see the Messages page), in which case the message’s text, formatting, and attachment references are no longer retrievable through the API after that account’s configured retention window (60 minutes – 24 hours, default 24 hours) from creation. Metadata is retained; see the Messages page for details |
| Audit log of deletions                              | Retained per platform retention policy | Retained per platform retention policy                                                                                                                                                                                                                                                                                                                                                                       |

**In transit:** TLS 1.2+ everywhere. **At rest:** AES-256 (server-side encryption).

### Compliance Checklist

If you’re integrating Linq under a security or privacy review, here is the short list:

- Allowlist exactly one outbound domain: `cdn.linqapp.com`.
- Decide whether you need ephemeral attachments (high-sensitivity content) — request enablement through your Linq support contact.
- Implement `DELETE /v3/attachments/{attachmentId}` calls in your deletion workflow.
- Persist any attachments your application needs long-term — Linq is the authoritative source until you delete, but the ephemeral tier auto-purges within roughly 24–48 hours of upload.
- For audit: every deletion is logged on Linq’s side. Surface a confirmation in your application UI based on the `204` response.
- For end-user “right to delete” requests: enumerate attachment ids and `DELETE` each. The platform does not provide a partner-wide wipe endpoint — deletion is per-attachment by design.

## SDK examples

The overview above shows JSON payloads. The same operations from the TypeScript and Python SDKs:

### Send media by URL

- [cURL](#tab-panel-0-0)
- [TypeScript](#tab-panel-0-1)
- [Python](#tab-panel-0-2)
- [Go](#tab-panel-0-3)

Terminal window

```
curl -X POST https://api.linqapp.com/api/partner/v3/chats/{chatId}/messages \
  -H "Authorization: Bearer $LINQ_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
      "message": {
        "parts": [
          {
            "type": "text",
            "value": "Check out this photo!"
          },
          {
            "type": "media",
            "url": "https://example.com/photo.jpg"
          }
        ]
      }
    }'
```

```
await client.chats.messages.send({chatId}, {
  message: {
    parts: [
      {
        type: "text",
        value: "Check out this photo!",
      },
      {
        type: "media",
        url: "https://example.com/photo.jpg",
      },
    ],
  },
});
```

```
client.chats.messages.send(
    {chat_id},
    message={
        "parts": [
            {
                "type": "text",
                "value": "Check out this photo!",
            },
            {
                "type": "media",
                "url": "https://example.com/photo.jpg",
            },
        ],
    },
)
```

```
client.Chats.Messages.Send(context.TODO(), {chatId}, linq.ChatMessageSendParams{
  Message: linq.F(map[string]any{
    Parts: linq.F([]any{
      map[string]any{
        Type: linq.F("text"),
        Value: linq.F("Check out this photo!"),
      },
      map[string]any{
        Type: linq.F("media"),
        Url: linq.F("https://example.com/photo.jpg"),
      },
    }),
  }),
})
```

### Request a pre-signed upload URL

- [cURL](#tab-panel-1-0)
- [TypeScript](#tab-panel-1-1)
- [Python](#tab-panel-1-2)
- [Go](#tab-panel-1-3)

Terminal window

```
curl -X POST https://api.linqapp.com/api/partner/v3/attachments \
  -H "Authorization: Bearer $LINQ_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
      "filename": "photo.jpg",
      "content_type": "image/jpeg",
      "size_bytes": 1024000
    }'
```

```
await client.attachments.create({
  filename: "photo.jpg",
  content_type: "image/jpeg",
  size_bytes: 1024000,
});
```

```
client.attachments.create(
    filename="photo.jpg",
    content_type="image/jpeg",
    size_bytes=1024000,
)
```

```
client.Attachments.Create(context.TODO(), linq.AttachmentNewParams{
  Filename: linq.F("photo.jpg"),
  ContentType: linq.F("image/jpeg"),
  SizeBytes: linq.F(1024000),
})
```

### Send a pre-uploaded attachment

- [cURL](#tab-panel-2-0)
- [TypeScript](#tab-panel-2-1)
- [Python](#tab-panel-2-2)
- [Go](#tab-panel-2-3)

Terminal window

```
curl -X POST https://api.linqapp.com/api/partner/v3/chats/{chatId}/messages \
  -H "Authorization: Bearer $LINQ_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
      "message": {
        "parts": [
          {
            "type": "media",
            "attachment_id": "550e8400-e29b-41d4-a716-446655440000"
          }
        ]
      }
    }'
```

```
await client.chats.messages.send({chatId}, {
  message: {
    parts: [
      {
        type: "media",
        attachment_id: "550e8400-e29b-41d4-a716-446655440000",
      },
    ],
  },
});
```

```
client.chats.messages.send(
    {chat_id},
    message={
        "parts": [
            {
                "type": "media",
                "attachment_id": "550e8400-e29b-41d4-a716-446655440000",
            },
        ],
    },
)
```

```
client.Chats.Messages.Send(context.TODO(), {chatId}, linq.ChatMessageSendParams{
  Message: linq.F(map[string]any{
    Parts: linq.F([]any{
      map[string]any{
        Type: linq.F("media"),
        AttachmentId: linq.F("550e8400-e29b-41d4-a716-446655440000"),
      },
    }),
  }),
})
```

## Related

- [Sending Messages](/channel/imessage/guides/messaging/sending-messages/index.md) — basics of posting a message to a chat
- [Voice Memos](/channel/imessage/guides/messaging/voice-memos/index.md) — audio as an inline iMessage voice bubble
- [API Reference: Attachments](/channel/imessage/api/resources/attachments/index.md)
