# Media

> The Media operations of the Kabu API: images, audio, video and files, uploaded once and referenced by id.

Images, audio, video and files, uploaded once and referenced by id.

Generated from the contract, version `2.0.0-draft.2` ([OpenAPI](/openapi.yaml)). Base URL `https://usekabu.com/api`. Errors are problem details, each [code](/docs/errors) with its own page.

## `POST /v2/media` · Upload a file up to 25 MiB

Returns status ready: the bytes are verified, hashed and pinned before the response, so the id can be attached to an ask immediately. An org owner may call it with a user token for `purpose=brand_logo` or `purpose=avatar` only (an image); any other purpose with a user token is 403.

- **Auth:** an API key, or a signed-in human's token (the Kabu apps).
- **Scopes:** `media:write`.
- **Signed-in humans:** `owner`.

| Parameter | In | Type | Required | Description |
|---|---|---|---|---|
| `Idempotency-Key` | header | string, at most 255 characters | no | Retry-safe key. For messages, sessions and responses it binds to the created object for its life; elsewhere for 24 h. |
| `Kabu-Org` | header | string | no | Select a live membership (default personal org); non-member is 404. Human tokens are live-only. Caller user id is distinct from billing owner. |

**Request body**: `multipart/form-data`: [MediaUpload](#schema-MediaUpload).

| Status | Means | Body |
|---|---|---|
| `201` | OK | [Media](/docs/api/messages#schema-Media) |
| `4XX` | Error | [Problem](/docs/api/discovery#schema-Problem) |
| `5XX` | Error | [Problem](/docs/api/discovery#schema-Problem) |

```sh
curl -X POST "https://usekabu.com/api/v2/media" \
  -H "Authorization: Bearer $KABU_API_KEY" \
  -H "Idempotency-Key: $(uuidgen)" \
  -F "file=@./report.pdf"
```

## `POST /v2/media/uploads` · Presigned (resumable) upload up to 2 GiB

PUT the bytes to upload_url. A durable finaliser (with periodic reconciliation for lost storage events) verifies size, sha256 and content type, pins an immutable object version and only then marks the media ready; the upload URL cannot overwrite ready content. Replaying with the same Idempotency-Key while uploading returns a fresh upload_url; after 24 h unfinished uploads expire (410 upload_expired) and their reservation is released.

- **Auth:** an API key.
- **Scopes:** `media:write`.

| Parameter | In | Type | Required | Description |
|---|---|---|---|---|
| `Idempotency-Key` | header | string, at most 255 characters | no | Retry-safe key. For messages, sessions and responses it binds to the created object for its life; elsewhere for 24 h. |

**Request body**: [UploadCreate](#schema-UploadCreate).

| Status | Means | Body |
|---|---|---|
| `201` | OK | [Upload](#schema-Upload) |
| `4XX` | Error | [Problem](/docs/api/discovery#schema-Problem) |
| `5XX` | Error | [Problem](/docs/api/discovery#schema-Problem) |

```sh
curl -X POST "https://usekabu.com/api/v2/media/uploads" \
  -H "Authorization: Bearer $KABU_API_KEY" \
  -H "Idempotency-Key: $(uuidgen)" \
  -H "Content-Type: application/json" \
  -d '{"filename":"…","content_type":"…","size":1,"sha256":"…"}'
```

## `GET /v2/media/{media_id}` · Get media with a fresh signed URL; with wait, hold until ready or failed

- **Auth:** an API key, or a signed-in human's token (the Kabu apps).
- **Scopes:** `messages:read`.
- **Signed-in humans:** `audience`.

| Parameter | In | Type | Required | Description |
|---|---|---|---|---|
| `media_id` | path | string | yes | A `med_…` id. |
| `wait` | query | integer, from 0 to 50 | no | Hold up to N seconds for the state to settle. 0 or absent returns at once. The kabu CLI and the MCP tools wait 25. |
| `Kabu-Org` | header | string | no | Select a live membership (default personal org); non-member is 404. Human tokens are live-only. Caller user id is distinct from billing owner. |

| Status | Means | Body |
|---|---|---|
| `200` | OK | [Media](/docs/api/messages#schema-Media) |
| `4XX` | Error | [Problem](/docs/api/discovery#schema-Problem) |
| `5XX` | Error | [Problem](/docs/api/discovery#schema-Problem) |

```sh
curl "https://usekabu.com/api/v2/media/$MEDIA_ID" \
  -H "Authorization: Bearer $KABU_API_KEY"
```

## `DELETE /v2/media/{media_id}` · Erase the bytes

- **Auth:** an API key.
- **Scopes:** `media:write`.

| Parameter | In | Type | Required | Description |
|---|---|---|---|---|
| `media_id` | path | string | yes | A `med_…` id. |

| Status | Means | Body |
|---|---|---|
| `204` | Done |  |
| `4XX` | Error | [Problem](/docs/api/discovery#schema-Problem) |
| `5XX` | Error | [Problem](/docs/api/discovery#schema-Problem) |

```sh
curl -X DELETE "https://usekabu.com/api/v2/media/$MEDIA_ID" \
  -H "Authorization: Bearer $KABU_API_KEY"
```

## Schemas

### MediaUpload

| Property | Type | Required | Description |
|---|---|---|---|
| `file` | string (binary) | yes | The file part, with a filename and content type. |
| `purpose` | string, one of `"attachment"`, `"avatar"`, `"brand_logo"` | no | `brand_logo` and `avatar` accept an image only and are the two purposes an org owner may upload with a user token (an avatar is the photo of a project or agent); every other purpose needs an API key. |

Unknown properties are refused.

### UploadCreate

| Property | Type | Required | Description |
|---|---|---|---|
| `filename` | string, at most 255 characters | yes |  |
| `content_type` | string | yes |  |
| `size` | integer, from 1 to 2147483648 | yes |  |
| `sha256` | string, matching `^[a-f0-9]{64}$` | yes |  |

Unknown properties are refused.

### Upload

| Property | Type | Required | Description |
|---|---|---|---|
| `object` | `"upload"` | yes |  |
| `media` | [Media](/docs/api/messages#schema-Media) | yes |  |
| `upload_url` | string (uri) | yes |  |
| `headers` | object | yes |  |
| `resumable` | boolean | yes | True only when upload_url is Kabu's own endpoint implementing IETF Resumable Uploads; a presigned object-storage PUT is false. |
| `expires_at` | [Timestamp](/docs/api/messages#schema-Timestamp) | yes |  |
