# Delete Forward Attachment

> DELETE /v1/forwards/{id}/attachments/{attachmentId} · Core resources API

Source: https://developer.kustomer.com/api/core-resources/delete-forward-attachment

```http
DELETE https://{orgname}.api.kustomerapp.com/v1/forwards/{id}/attachments/{attachmentId}
Authorization: Bearer <API key>
```

## Authorization

Any API key or signed-in user. No specific role is required; any valid credential works.

Deletes a forward attachment resource.

## About Attachments

An attachment is a file, such as an image, PDF or recording, attached to a message, note, draft, forward or custom object comment. Kustomer stores a record describing the file, and you upload or download the bytes through short-lived signed URLs.

Files show up throughout the agent desktop: a photo a customer sent in chat, a PDF on an outbound email, a screenshot in an internal note. In the API, each file is an attachment record with `name`, `contentType` and `contentLength` (in bytes), plus a link to the object that carries it. The file's bytes never travel through the regular API: you upload them to, and download them from, temporary signed URLs that Kustomer hands you. Attachments on inbound messages are created by Kustomer when the message arrives.

#### Two upload styles

Which flow you use depends on what the file is going on.

| Attaching to | How |
| --- | --- |
| A message or note | Create the record with [`POST /v1/attachments`](https://developer.kustomer.com/api/core-resources/create-attachment), upload the file to `meta.upload.url` as a multipart form with every field from `meta.upload.fields` and the file last, then list the attachment in the message or note's `attachments`. |
| A draft or forward | Create the attachment under the draft or forward. Its `links.related` is a signed URL that you upload the file to with a single `PUT`. |

Upload immediately, because the upload URL expires; a record whose upload never completes stays `uploaded: false`. With `POST /v1/attachments` you can instead register a file you host by passing `externalUrl` (absolute `https://`, no credentials). Kustomer then stores only the link and never fetches or deletes the file. Add `renderMode: "metadata_only"` to keep that URL from ever being returned, so agents see only the file name and size. The [attachments guide](https://developer.kustomer.com/docs/guides/attachments) walks through every step with code.

#### Reading files

The record's `links.related` is a temporary download URL. Download the file promptly and don't store the URL. `GET /v1/attachments/{id}` covers outbound files only, so read files that customers sent through their message, which works for both directions. To find every file in a thread, use [`GET /v1/conversations/{id}/attachments`](https://developer.kustomer.com/api/core-resources/get-conversation-attachments).

Comments are internal notes on custom objects. Their files are set through the [comment's](https://developer.kustomer.com/api/core-resources#comments) `attachments` field, and the comment attachment endpoints read or remove them. A redacted comment attachment returns `410 Gone` when you request it with `redirect=true`. Attachments removed through [message redaction](https://developer.kustomer.com/api/core-resources/redact-a-message) are marked `redacted: true`.

#### Which endpoint to use

| To | Call |
| --- | --- |
| Create a file record for a message or note | [`POST /v1/attachments`](https://developer.kustomer.com/api/core-resources/create-attachment) |
| Read, rename or mark a file redacted | [`GET`](https://developer.kustomer.com/api/core-resources/get-attachments-by-id-outbound-only) or [`PUT /v1/attachments/{id}`](https://developer.kustomer.com/api/core-resources/update-or-upsert-attachment) |
| Download a message's file | [`GET /v1/messages/{message_id}/attachments/{id}`](https://developer.kustomer.com/api/core-resources/get-attachment-by-message-id-and-attachment-id-inbound-or-outbound) |
| Add, list, read or remove a draft's files | [`POST`](https://developer.kustomer.com/api/core-resources/create-draft-attachment) or [`GET /v1/drafts/{id}/attachments`](https://developer.kustomer.com/api/core-resources/get-draft-attachments), [`GET`](https://developer.kustomer.com/api/core-resources/get-draft-attachment) or [`DELETE /v1/drafts/{id}/attachments/{attachmentId}`](https://developer.kustomer.com/api/core-resources/delete-draft-attachment) |
| Add, list, read or remove a forward's files | [`POST`](https://developer.kustomer.com/api/core-resources/create-forward-attachment) or [`GET /v1/forwards/{id}/attachments`](https://developer.kustomer.com/api/core-resources/get-forward-attachments), [`GET`](https://developer.kustomer.com/api/core-resources/get-forward-attachment) or [`DELETE /v1/forwards/{id}/attachments/{attachmentId}`](https://developer.kustomer.com/api/core-resources/delete-forward-attachment) |
| List, read or remove a comment's files | [`GET`](https://developer.kustomer.com/api/core-resources/get-comment-attachments) or [`DELETE /v1/comments/{id}/attachments`](https://developer.kustomer.com/api/core-resources/delete-comment-attachments), [`GET`](https://developer.kustomer.com/api/core-resources/get-comment-attachment) or [`DELETE /v1/comments/{id}/attachments/{attachmentId}`](https://developer.kustomer.com/api/core-resources/delete-comment-attachment) |

Set `contentType` accurately, because the agent desktop uses it to preview images and PDFs, and make `contentLength` match the file you upload. `PUT /v1/attachments/{id}` creates the record if it doesn't exist and needs message update permission. WhatsApp drafts accept only certain media types; see [Create draft for customer](https://developer.kustomer.com/api/core-resources/create-draft-for-customer).

Related: [Upload attachments](https://developer.kustomer.com/docs/guides/attachments), [Messages](https://developer.kustomer.com/api/core-resources#messages), [Drafts](https://developer.kustomer.com/api/core-resources#drafts), [Comments](https://developer.kustomer.com/api/core-resources#comments)

### Recipes

#### 1. Upload a file and attach it to a message

Put a PDF, image or recording on a message you record in Kustomer, such as the invoice your billing system emailed to the customer.

**Step 1.** [`POST /v1/attachments`](https://developer.kustomer.com/api/core-resources/create-attachment.md). Create the attachment record with the file's `name`, `contentType` and `contentLength` in bytes. The response has the attachment `id` in `data`, and an upload form in the top-level `meta.upload`: a `url` and a set of `fields`. Save the whole response, and upload straight away, because the form expires.

```bash title="cURL"
curl --request POST "https://api.kustomerapp.com/v1/attachments" \
  --header "Authorization: Bearer $KUSTOMER_API_KEY" \
  --header "Content-Type: application/json" \
  --data "{\"name\": \"invoice-4417.pdf\", \"contentType\": \"application/pdf\", \"contentLength\": $(wc -c < invoice-4417.pdf)}" \
  > attachment.json
```

**Step 2.** Upload the file as a `multipart/form-data` POST to `meta.upload.url`. Send every key in `meta.upload.fields` as a form field, exactly as returned, and the file last, under the name `file`. Don't send your Kustomer `Authorization` header to this URL. Until the upload succeeds, the record stays `uploaded: false`.

```bash title="cURL"
FORM=()
while IFS= read -r field; do FORM+=(--form "$field"); done \
  < <(jq -r '.meta.upload.fields | to_entries[] | "\(.key)=\(.value)"' attachment.json)
curl --request POST "$(jq -r .meta.upload.url attachment.json)" "${FORM[@]}" \
  --form "file=@invoice-4417.pdf"
```

**Step 3.** [`POST /v1/conversations/{id}/messages`](https://developer.kustomer.com/api/core-resources/create-message-from-conversation.md). Reference the file in the message's `attachments`, as `{ _id, name, contentType, contentLength }` with the same values you registered; `_id` is the record's ID (`jq -r .data.id attachment.json`). Notes take the same shape on [`POST /v1/conversations/{id}/notes`](https://developer.kustomer.com/api/core-resources/create-note-within-conversation).

```bash title="cURL"
curl --request POST "https://api.kustomerapp.com/v1/conversations/$CONVERSATION_ID/messages" \
  --header "Authorization: Bearer $KUSTOMER_API_KEY" \
  --header "Content-Type: application/json" \
  --data "{\"channel\": \"email\", \"app\": \"acme-billing\", \"direction\": \"out\", \"subject\": \"Your invoice\", \"preview\": \"Your invoice for order 4417 is attached.\", \"attachments\": [{\"_id\": \"$ATTACHMENT_ID\", \"name\": \"invoice-4417.pdf\", \"contentType\": \"application/pdf\", \"contentLength\": $(wc -c < invoice-4417.pdf)}]}"
```

```bash title="kusty"
kusty api POST /v1/conversations/$CONVERSATION_ID/messages \
  --field channel=email --field app=acme-billing --field direction=out \
  --field subject="Your invoice" --field preview="Your invoice for order 4417 is attached." \
  --field attachments.0._id=$ATTACHMENT_ID --field attachments.0.name=invoice-4417.pdf \
  --field attachments.0.contentType=application/pdf \
  --field attachments.0.contentLength:=$(wc -c < invoice-4417.pdf)
```

> This records the file on the timeline; it doesn't email it. To send a file to the customer, attach it to a [draft](https://developer.kustomer.com/api/core-resources#drafts) instead. [Upload attachments](https://developer.kustomer.com/docs/guides/attachments) has the same flow in Node.js and Python.

#### 2. Attach a file that stays in your own storage

Show agents a contract or report that must not leave your systems, by registering a link to it instead of uploading the bytes.

**Step 1.** [`POST /v1/attachments`](https://developer.kustomer.com/api/core-resources/create-attachment.md). Pass `externalUrl`, an absolute `https://` URL without credentials in it. There's no upload step: the record is `uploaded: true` straight away, and Kustomer never fetches, copies or deletes the file, so keeping the URL working is up to you. Add `renderMode: "metadata_only"` if agents should only see the file's name and size and the URL should never be returned.

```bash title="cURL"
curl --request POST "https://api.kustomerapp.com/v1/attachments" \
  --header "Authorization: Bearer $KUSTOMER_API_KEY" \
  --header "Content-Type: application/json" \
  --data '{"name": "contract-8812.pdf", "contentType": "application/pdf", "contentLength": 120934, "externalUrl": "https://files.acme.example/contracts/8812.pdf", "renderMode": "metadata_only"}'
```

```bash title="kusty"
kusty api POST /v1/attachments \
  --field name=contract-8812.pdf --field contentType=application/pdf --field contentLength:=120934 \
  --field externalUrl=https://files.acme.example/contracts/8812.pdf --field renderMode=metadata_only \
  | jq -r .data.id
```

**Step 2.** [`POST /v1/conversations/{id}/notes`](https://developer.kustomer.com/api/core-resources/create-note-within-conversation.md). Reference it from a note (or a message) like any other attachment.

```bash title="cURL"
curl --request POST "https://api.kustomerapp.com/v1/conversations/$CONVERSATION_ID/notes" \
  --header "Authorization: Bearer $KUSTOMER_API_KEY" \
  --header "Content-Type: application/json" \
  --data "{\"body\": \"Signed contract for this account, held in our document system.\", \"attachments\": [{\"_id\": \"$ATTACHMENT_ID\", \"name\": \"contract-8812.pdf\", \"contentType\": \"application/pdf\", \"contentLength\": 120934}]}"
```

```bash title="kusty"
kusty api POST /v1/conversations/$CONVERSATION_ID/notes \
  --field body="Signed contract for this account, held in our document system." \
  --field attachments.0._id=$ATTACHMENT_ID --field attachments.0.name=contract-8812.pdf \
  --field attachments.0.contentType=application/pdf --field attachments.0.contentLength:=120934
```

> Signed URLs that expire will break the attachment. Private and loopback hosts are rejected.

#### 3. Send an email with a file attached

Deliver a file to a customer, such as a return label, by attaching it to a draft before Kustomer sends it.

**Step 1.** [`POST /v1/customers/{id}/drafts`](https://developer.kustomer.com/api/core-resources/create-draft-for-customer.md). Create the email draft in the conversation without `sendAt`, so it isn't sent before the file is on it. Keep `data.id`. See [Drafts](https://developer.kustomer.com/api/core-resources#drafts) for choosing the sender.

```bash title="cURL"
curl --request POST "https://api.kustomerapp.com/v1/customers/$CUSTOMER_ID/drafts" \
  --header "Authorization: Bearer $KUSTOMER_API_KEY" \
  --header "Content-Type: application/json" \
  --data "{\"channel\": \"email\", \"app\": \"postmark\", \"conversation\": \"$CONVERSATION_ID\", \"from\": {\"email\": \"$FROM_EMAIL\"}, \"to\": [{\"email\": \"$TO_EMAIL\"}], \"subject\": \"Your return label\", \"body\": \"Your prepaid return label is attached.\"}"
```

```bash title="kusty"
kusty api POST /v1/customers/$CUSTOMER_ID/drafts \
  --field channel=email --field app=postmark --field conversation=$CONVERSATION_ID \
  --field from.email=$FROM_EMAIL --field to.0.email=$TO_EMAIL \
  --field subject="Your return label" --field body="Your prepaid return label is attached." \
  | jq -r .data.id
```

**Step 2.** [`POST /v1/drafts/{id}/attachments`](https://developer.kustomer.com/api/core-resources/create-draft-attachment.md). Create the attachment on the draft. Unlike `POST /v1/attachments`, the response's `links.related` is a signed URL that takes the file in a single `PUT`.

```bash title="cURL"
curl --request POST "https://api.kustomerapp.com/v1/drafts/$DRAFT_ID/attachments" \
  --header "Authorization: Bearer $KUSTOMER_API_KEY" \
  --header "Content-Type: application/json" \
  --data "{\"name\": \"return-label.pdf\", \"contentType\": \"application/pdf\", \"contentLength\": $(wc -c < return-label.pdf)}"
```

```bash title="kusty"
kusty api POST /v1/drafts/$DRAFT_ID/attachments \
  --field name=return-label.pdf --field contentType=application/pdf \
  --field contentLength:=$(wc -c < return-label.pdf) \
  | jq -r .data.links.related
```

**Step 3.** Upload the file to the signed URL right away. It expires quickly and must not get your Kustomer `Authorization` header.

```bash title="cURL"
curl --upload-file return-label.pdf "$UPLOAD_URL"
```

**Step 4.** [`PUT /v1/drafts/{id}`](https://developer.kustomer.com/api/core-resources/update-draft.md). Send the draft with `"sendAt": "now"`. `channel` is required on every draft update.

```bash title="cURL"
curl --request PUT "https://api.kustomerapp.com/v1/drafts/$DRAFT_ID" \
  --header "Authorization: Bearer $KUSTOMER_API_KEY" \
  --header "Content-Type: application/json" \
  --data '{"channel": "email", "sendAt": "now"}'
```

```bash title="kusty"
kusty api PUT /v1/drafts/$DRAFT_ID --field channel=email --field sendAt=now
```

> WhatsApp drafts accept only certain file types; they're listed on [Create draft for customer](https://developer.kustomer.com/api/core-resources/create-draft-for-customer).

#### 4. Download every file in a conversation

Collect the photos, receipts and documents shared in a thread, for example to attach them to a claim in another system.

**Step 1.** [`GET /v1/conversations/{id}/attachments`](https://developer.kustomer.com/api/core-resources/get-conversation-attachments.md). List the conversation's attachments, inbound and outbound. Each has `name`, `contentType` and `contentLength`, `relationships.message` for the message it came with, and `links.related`, a temporary download URL.

```bash title="cURL"
curl "https://api.kustomerapp.com/v1/conversations/$CONVERSATION_ID/attachments?pageSize=100" \
  --header "Authorization: Bearer $KUSTOMER_API_KEY"
```

```bash title="kusty"
kusty api GET /v1/conversations/$CONVERSATION_ID/attachments --all > attachments.jsonl
```

**Step 2.** Download each file promptly, because the URLs expire, and skip any marked `redacted`. Don't store the URLs; list the attachments again, or read one through [its message](https://developer.kustomer.com/api/core-resources/get-attachment-by-message-id-and-attachment-id-inbound-or-outbound), for fresh ones.

```bash title="kusty"
jq -r 'select(.data.attributes.redacted != true) | [.data.attributes.name, .data.links.related] | @tsv' attachments.jsonl \
  | while IFS=$'\t' read -r name url; do curl --silent --output "$name" "$url"; done
```

### Example object

A real record from [`GET /v1/messages/{message_id}/attachments/{id}`](https://developer.kustomer.com/api/core-resources/get-attachment-by-message-id-and-attachment-id-inbound-or-outbound.md):

```json
{
  "id": "5e0e14c103a4e80018b374aa",
  "type": "attachment",
  "attributes": {
    "name": "example.jpg",
    "contentType": "image/jpeg",
    "contentLength": 98153,
    "redacted": false
  },
  "relationships": {
    "org": {
      "links": {
        "self": "/v1/orgs/5d5dae1ccd1e87001a6a7ea4"
      },
      "data": {
        "type": "org",
        "id": "5d5dae1ccd1e87001a6a7ea4"
      }
    },
    "message": {
      "links": {
        "self": "/v1/messages/5e0e14a7af4e8c001a084dba"
      },
      "data": {
        "type": "message",
        "id": "5e0e14a7af4e8c001a084dba"
      }
    }
  },
  "links": {
    "self": "/v1/messages/5e0e14a7af4e8c001a084dba/attachments/5e0e14c103a4e80018b374aa",
    "related": "https://kustomer-prod1-attachments.s3.amazonaws.com/attachments/5d5dae1ccd1e87001a6a7ea4/5e0e14c103a4e80018b374aa-nasdaq.jpg?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Credential=credential-example%2Fus-east-1%2Fs3%2Faws4_request&X-Amz-Date=202…"
  }
}
```

Fields:

- `attributes.name` (string): The file name agents see, including its extension. Rename an outbound file with `PUT /v1/attachments/{id}`.
- `attributes.contentType` (string): The file's MIME type, such as `image/jpeg` or `application/pdf`. The agent desktop uses it to decide whether it can preview the file, so set it accurately.
- `attributes.contentLength` (integer): The file size in bytes. On upload it must match the file you send.
- `attributes.redacted` (boolean): `true` once the file has been removed through [message redaction](https://developer.kustomer.com/api/core-resources/redact-a-message). A redacted file can no longer be downloaded.
- `relationships.message` (object): The message the file came with. Inbound files are created by Kustomer when the message arrives; outbound ones are attached when you create the message.
- `links.self` (string): Where to read this record again. Message files are read through their message, which works for both directions.
- `links.related` (string): A temporary signed URL for the file's bytes. Download it promptly and don't store it; read the attachment again for a fresh one.

### Relationships

- Points to **Messages** via `relationships.message`: The message a file is attached to. A message can carry several attachments, and its files are read through `GET /v1/messages/{message_id}/attachments/{id}`.
- Referenced by **Comments** via `relationships.attachments` (list): Files attached to a comment. Request them with `include=attachments`, or manage them with the comment attachment endpoints.
- Referenced by **Drafts** via `relationships.attachments` (list): Files attached to a draft. Create them with `POST /v1/drafts/{id}/attachments` and upload each file to its signed URL before sending.
- Referenced by **Conversation forwards** via `relationships.attachments` (list): Files added to a forward with `POST /v1/forwards/{id}/attachments`. Each one is uploaded to its signed URL before the forward is sent.

### Permissions

Fewest roles for a read-only integration: `org.permission.attachment.read`. For every endpoint: `org.permission.attachment.read`, `org.permission.attachment.delete`, `org.permission.message.create`, `org.permission.message.update`.

## Path parameters

| Name | Type | Required | Description |
| --- | --- | --- | --- |
| `id` | string | yes | Forward ID. |
| `attachmentId` | string | yes | Attachment ID. |

## Responses

### 204

No Content

### 401

Unauthorized

### 403

Forbidden

### 404

Not Found

## Example request

```bash
curl --request DELETE \
  --url 'https://api.kustomerapp.com/v1/forwards/5a4d1a8a6d92880012ad2923/attachments/5a4d1a8a6d92880012ad2923' \
  --header "Authorization: Bearer $KUSTOMER_API_KEY"
```
