# Create media

> POST /v1/media · Core resources API

Source: https://developer.kustomer.com/api/core-resources/create-media

```http
POST https://{orgname}.api.kustomerapp.com/v1/media
Authorization: Bearer <API key>
```

## Authorization

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

Creates a new media upload document. This includes a temporary upload policy to allow a file upload through a `POST` request.

## Media uploads

Media uploads require a multi-part `POST` request to the URL in the `meta.upload.url` property in the response object:
* The request must include as standard form keys the key-value pairs located in the `meta.upload.fields` property.
* The request must include the file as the last key under the name `file`.

## Blocked extensions

The following file name extensions are blocked and **cannot** be used as attachments:

- .bin
- .bat
- .chm
- .com
- .cpl
- .crt
- .exe
- .hlp
- .hta
- .inf
- .ins
- .isp
- .jse
- .lnk
- .mdb
- .msc
- .msi
- .msp
- .mst
- .pcd
- .pif
- .ps1
- .reg
- .scr
- .sct
- .shs
- .vbe
- .vba
- .vbs
- .wsf
- .wsh
- .wsl

## About Media

Media are files your org stores in Kustomer for content rather than for a conversation, such as images embedded in knowledge base articles. You create a media record, upload the file to the temporary URL it returns, then reference the media ID where the file is used.

Media and [attachments](https://developer.kustomer.com/api/core-resources#attachments) use the same upload flow, but they are kept apart: attachments belong to messages, notes and drafts, while media hold files used in your org's content, such as the knowledge base editor's images. A media ID only works with the media endpoints, and an attachment ID only with the attachment endpoints.

Uploading takes two steps. First, `POST /v1/media` with the file's `name`, `contentType` and `contentLength` (in bytes, up to 10,048,576) creates the record, with `uploaded: false`, and returns a short-lived upload policy in `meta.upload`. Then send a `multipart/form-data` `POST` to `meta.upload.url`, including 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 the upload URL. Upload straight away, because the policy expires.

#### Key fields

| Field | Meaning |
| --- | --- |
| `name` | The file name. Names with executable or script extensions, such as `.exe`, `.bat`, `.msi` or `.ps1`, are rejected. |
| `contentType`, `contentLength` | The MIME type and size in bytes you declared. Upload a file of exactly that size. |
| `uploaded` | Whether the file has been uploaded. |
| `redacted` | Set to `true` to redact the file. A redacted file has no download URL. |

#### Which endpoint to use

| To | Call |
| --- | --- |
| Create a media record and get an upload policy | [`POST /v1/media`](https://developer.kustomer.com/api/core-resources/create-media) |
| Read one or several media records, or download the file | [`GET /v1/media/{id}`](https://developer.kustomer.com/api/core-resources/get-media-by-id) |
| Rename or redact a file | [`PUT /v1/media/{id}`](https://developer.kustomer.com/api/core-resources/update-media-by-id) |

`GET /v1/media/{id}` accepts a comma-separated list of IDs to read several records at once. Add `redirect=true` to get a `302` redirect to the file's URL. Unlike attachment URLs, a media file's URL doesn't expire, so you can use it as an image `src` in your content. A redacted file has no URL, so the request returns the record without redirecting. `PUT` changes only `name` and `redacted`, and returns `404` for an ID that doesn't exist. Pass the `rev` you last read to make sure you aren't overwriting someone else's change.

Related: [Upload attachments](https://developer.kustomer.com/docs/guides/attachments), [Attachments](https://developer.kustomer.com/api/core-resources#attachments), [Knowledge base articles](https://developer.kustomer.com/api/knowledge-base#articles)

### Recipes

#### 1. Add an image to a knowledge base article

Upload a screenshot or diagram from your own tooling and embed it in an article, the way the article editor does.

**Step 1.** [`POST /v1/media`](https://developer.kustomer.com/api/core-resources/create-media.md). Create the media record with the file's `name`, `contentType` and exact size in bytes. The upload policy comes back in `meta.upload`, which kusty doesn't print, so use curl for this step.

```bash title="cURL"
curl --request POST "https://api.kustomerapp.com/v1/media" \
  --header "Authorization: Bearer $KUSTOMER_API_KEY" \
  --header "Content-Type: application/json" \
  --data "{\"name\": \"returns-flow.png\", \"contentType\": \"image/png\", \"contentLength\": $(wc -c < returns-flow.png | tr -d ' ')}" > media.json
MEDIA_ID=$(jq -r .data.id media.json)
```

**Step 2.** Upload the file to `meta.upload.url` as `multipart/form-data`. Send every key in `meta.upload.fields` exactly as returned, then the file last as `file`. Don't send your Kustomer `Authorization` header here, and upload straight away, because the policy expires.

```bash title="cURL"
FORM_ARGS=()
while IFS= read -r field; do FORM_ARGS+=(--form-string "$field"); done \
  < <(jq -r '.meta.upload.fields | to_entries[] | "\(.key)=\(.value)"' media.json)
curl --request POST "$(jq -r .meta.upload.url media.json)" "${FORM_ARGS[@]}" \
  --form "file=@returns-flow.png"
```

**Step 3.** [`GET /v1/media/{id}`](https://developer.kustomer.com/api/core-resources/get-media-by-id.md). Get the file's URL from the `302` redirect that `redirect=true` returns. A media file's URL doesn't expire, so it can go straight into the article.

```bash title="cURL"
IMAGE_URL=$(curl --silent --output /dev/null --write-out '%{redirect_url}' \
  "https://api.kustomerapp.com/v1/media/$MEDIA_ID?redirect=true" \
  --header "Authorization: Bearer $KUSTOMER_API_KEY")
```

**Step 4.** [`PUT /v1/kb/articles/{id}/versions/{lang}/draft`](https://developer.kustomer.com/api/knowledge-base/update-a-draft-version.md). Put the image in the article's draft with the URL as `src` and the media ID as `data-media-id`. Kustomer links the media to the version, and the version's `attachments` relationship lists it. `htmlBody` replaces the draft's body, so send the whole body, not just the image.

```bash title="cURL"
curl --request PUT "https://api.kustomerapp.com/v1/kb/articles/$ARTICLE_ID/versions/en_us/draft" \
  --header "Authorization: Bearer $KUSTOMER_API_KEY" \
  --header "Content-Type: application/json" \
  --data "$(jq -n --arg src "$IMAGE_URL" --arg id "$MEDIA_ID" \
    '{htmlBody: "<p>Start a return from your order page.</p><img src=\"\($src)\" alt=\"The returns page\" data-media-id=\"\($id)\">"}')"
```

```bash title="kusty"
kusty api PUT /v1/kb/articles/$ARTICLE_ID/versions/en_us/draft --fields relationships.attachments \
  --field htmlBody="<p>Start a return from your order page.</p><img src=\"$IMAGE_URL\" alt=\"The returns page\" data-media-id=\"$MEDIA_ID\">"
```

> Names with executable or script extensions, such as `.exe` or `.ps1`, are rejected, and files can be up to 10,048,576 bytes. Publish the draft when it's ready, as in [publishing an article](https://developer.kustomer.com/api/knowledge-base#articles-versioning-and-languages).

#### 2. Take down an image that shouldn't be public

Permanently remove a file, such as a screenshot that shows customer data, once it's out of your articles.

**Step 1.** [`GET /v1/media/{id}`](https://developer.kustomer.com/api/core-resources/get-media-by-id.md). Read the record to check it's the right file by its `name`, `contentType` and `createdAt`.

```bash title="cURL"
curl "https://api.kustomerapp.com/v1/media/$MEDIA_ID" \
  --header "Authorization: Bearer $KUSTOMER_API_KEY"
```

```bash title="kusty"
kusty api GET /v1/media/$MEDIA_ID --fields attributes.name,attributes.contentType,attributes.createdAt
```

**Step 2.** [`PUT /v1/media/{id}`](https://developer.kustomer.com/api/core-resources/update-media-by-id.md). Set `redacted` to `true`. Kustomer deletes the stored file, and the record stays with `redacted: true` and no URL. This can't be undone, so images that still point at the file break.

```bash title="cURL"
curl --request PUT "https://api.kustomerapp.com/v1/media/$MEDIA_ID" \
  --header "Authorization: Bearer $KUSTOMER_API_KEY" \
  --header "Content-Type: application/json" \
  --data '{"redacted": true}'
```

```bash title="kusty"
kusty api PUT /v1/media/$MEDIA_ID --field redacted:=true --fields attributes.redacted
```

> First take the image out of every article that uses it by publishing a new version without it. To only change how the file is listed, send a new `name` instead.

### Relationships

- Referenced by **Knowledge base articles** via `relationships.attachments` (list): The media files embedded in an article version, read-only and built from the `data-media-id` attributes in the version's `htmlBody`. Article versions return them in `relationships.attachments`; the article itself doesn't.

### Permissions

Fewest roles for a read-only integration: `org.permission.kb.read`. For every endpoint: `org.permission.kb.read`, `org.permission.kb.update`.

## Request body

Content type: `application/json` (required)

- `name` (string, required)
- `contentType` (string, required)
- `contentLength` (number, required): (≤ 10048576)

Example:

```json
{
  "name": "example_name",
  "contentType": "application/pdf",
  "contentLength": 0
}
```

## Responses

### 201

Created

- `data` (object, required)
  - `id` (string, required)
  - `type` (string, required)
  - `attributes` (object, required)
    - `name` (string, required)
    - `contentType` (string, required)
    - `contentLength` (integer, required)
    - `redacted` (boolean, required)
    - `uploaded` (boolean, required)
    - `createdAt` (string, required)
    - `updatedAt` (string, required)
    - `context` (string, required)
  - `relationships` (object, required)
    - `org` (object, required)
  - `links` (object, required)
    - `self` (string, required)
- `meta` (object, required)
  - `provider` (string, required)
  - `upload` (object, required)
    - `url` (string, required)
    - `fields` (object, required)

```json
{
  "data": {
    "id": "5da062fb90897d01573e121f",
    "type": "attachment",
    "attributes": {
      "name": "attachment.jpg",
      "contentType": "image/jpeg",
      "contentLength": 512,
      "redacted": false,
      "uploaded": false,
      "createdAt": "2019-10-11T11:09:47.898Z",
      "updatedAt": "2019-10-11T11:09:47.898Z",
      "context": "attachment"
    },
    "relationships": {
      "org": {
        "data": {
          "id": "59c406630f79b70034579acd",
          "type": "org"
        },
        "links": {
          "self": "/v1/orgs/59c406630f79b70034579acd"
        }
      }
    },
    "links": {
      "self": "/v1/attachments/5da062fb90897d01573e121f"
    }
  },
  "meta": {
    "provider": "s3",
    "upload": {
      "url": "https://s3.amazonaws.com/kustomer-staging-user-content",
      "fields": {
        "key": "attachments/59c406630f79b70034579acd/5da062fb90897d01573e121f-attachment.jpg",
        "acl": "private",
        "Content-Type": "image/jpeg",
        "X-Amz-Meta-Attachment-Id": "5da062fb90897d01573e121f",
        "bucket": "aws-bucket",
        "X-Amz-Algorithm": "aws-algorithm",
        "X-Amz-Credential": "aws-temp-key",
        "X-Amz-Date": "20180103T180146Z",
        "Policy": "aws-temp-policy-jwt",
        "X-Amz-Signature": "aws-temp-signature-value"
      }
    }
  }
}
```

### 400

Bad Request

### 401

Unauthorized

### 403

Forbidden

## Example request

```bash
curl --request POST \
  --url 'https://api.kustomerapp.com/v1/media' \
  --header "Authorization: Bearer $KUSTOMER_API_KEY" \
  --header 'Content-Type: application/json' \
  --data '{
  "name": "example_name",
  "contentType": "application/pdf",
  "contentLength": 0
}'
```
