---
title: "composer.attachment@1 · OpenVibe.Services"
canonical: "https://openvibe.services/docs/contracts/composer.attachment"
updated: null
---

# composer.attachment@1 · OpenVibe.Services

HTML: [composer.attachment@1 · OpenVibe.Services](https://openvibe.services/docs/contracts/composer.attachment)

# ComposerAttachment `composer.attachment@1`

Generated at 2026-11-13 06:25:48 UTC from [`openvibe-contracts v0.419.0`](https://github.com/OpenVibers/OpenVibe.Contracts/tree/v0.419.0) and [`openvibe-sdk v0.161.0`](https://github.com/OpenVibers/OpenVibe.SDK/tree/v0.161.0).

- Version: 1.0.0
- Owner: media
- Visibility: public
- Status: active
- Compatibility: backward
- Schema: [`https://openvibe.network/contracts/composer/attachment.v1.json`](/docs/contracts/composer.attachment.json)

composer.attachment@1: one file a person attached to a message in the shared composer. Media stores it and answers this shape from POST /api/v1/attachments (multipart field \`file\`); a client keeps it until the message is sent, then names it in POST /api/v1/attachments/:id/keep. \`kind\` is sniffed from the bytes, never taken from the browser: image, video, audio, file or code. \`thumb\` is a small preview for images and video (absent for the rest), \`w\`/\`h\` the pixel size when the file is one Media can measure. An attachment nobody keeps expires after 24 hours; \`expires\_at\` says when, and a keep extends it to the message's life.

## Fields

| Field | Type | Required | Description | Constraints |
| --- | --- | --- | --- | --- |
| `id` | string | yes | The attachment id (att\_ + ULID): what DELETE /api/v1/attachments/:id and POST /api/v1/attachments/:id/keep name. | - `pattern ^att_[0-9A-HJKMNP-TV-Z]{26}$` |
| `kind` | enum | yes | Decided by Media from the bytes, not the client's claim: image/video/audio when the signature matches, code for recognised source text, otherwise file. Executables are refused at upload. | - `one of "image", "video", "audio", "file", "code"` |
| `name` | string | yes | The file's original name, sanitised for display; never a path. | - `minLength 1`<br>- `maxLength 255` |
| `size` | integer | yes | Bytes stored. | - `minimum 1` |
| `mime` | string | yes | The content type Media serves the bytes as, from the sniffed kind and format. |  |
| `url` | string | yes | Absolute https URL the bytes are served at. It stays valid while the attachment exists; a kept attachment is the message's to serve. | - `format uri` |
| `thumb` | string \| null |  | Absolute URL of a preview image: an image's own small copy or a video's first frame. Null when Media could not make one (or the kind has none). | - `format uri` |
| `w` | integer \| null |  | Pixel width of an image, video or video preview; null when unknown. | - `minimum 1` |
| `h` | integer \| null |  | Pixel height of an image, video or video preview; null when unknown. | - `minimum 1` |
| `expires_at` | string | yes | When an unkept attachment is deleted. The upload answers now + 24 hours; a keep moves it to the kept window. | - `format date-time` |

## Examples

From the contract's own test fixtures: valid ones validate, rejected ones must fail.

 Valid: file

~~~~
{
  "id": "att_01JAB2C3D4E5F6G7H8J9K0MNPS",
  "kind": "file",
  "name": "notes.txt",
  "size": 42,
  "mime": "text/plain",
  "url": "https://openvibe.media/a/01JAB2C3D4E5F6G7H8J9K0MNPS/notes.txt",
  "expires_at": "2026-11-11T12:00:00.000Z"
}
~~~~

Valid: image

~~~~
{
  "id": "att_01JAB2C3D4E5F6G7H8J9K0MNPQ",
  "kind": "image",
  "name": "screenshot.png",
  "size": 184320,
  "mime": "image/png",
  "url": "https://openvibe.media/a/01JAB2C3D4E5F6G7H8J9K0MNPQ/screenshot.png",
  "thumb": "https://openvibe.media/a/01JAB2C3D4E5F6G7H8J9K0MNPQ/thumb.jpg",
  "w": 1280,
  "h": 720,
  "expires_at": "2026-11-11T12:00:00.000Z"
}
~~~~

Valid: video

~~~~
{
  "id": "att_01JAB2C3D4E5F6G7H8J9K0MNPR",
  "kind": "video",
  "name": "clip.mp4",
  "size": 52428800,
  "mime": "video/mp4",
  "url": "https://openvibe.media/a/01JAB2C3D4E5F6G7H8J9K0MNPR/clip.mp4",
  "thumb": "https://openvibe.media/a/01JAB2C3D4E5F6G7H8J9K0MNPR/thumb.jpg",
  "w": 1920,
  "h": 1080,
  "expires_at": "2026-11-11T12:00:00.000Z"
}
~~~~

Rejected: bad-id

~~~~
{
  "id": "att_1",
  "kind": "file",
  "name": "a.txt",
  "size": 1,
  "mime": "text/plain",
  "url": "https://openvibe.media/a/x/a.txt",
  "expires_at": "2026-11-11T12:00:00.000Z"
}
~~~~

Rejected: bad-kind

~~~~
{
  "id": "att_01JAB2C3D4E5F6G7H8J9K0MNPQ",
  "kind": "spreadsheet",
  "name": "book.xlsx",
  "size": 12,
  "mime": "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
  "url": "https://openvibe.media/a/x/book.xlsx",
  "expires_at": "2026-11-11T12:00:00.000Z"
}
~~~~

Rejected: no-url

~~~~
{
  "id": "att_01JAB2C3D4E5F6G7H8J9K0MNPQ",
  "kind": "image",
  "name": "screenshot.png",
  "size": 184320,
  "mime": "image/png",
  "expires_at": "2026-11-11T12:00:00.000Z"
}
~~~~

## Validate

~~~~
const contracts = require('openvibe-contracts');
contracts.validate('composer.attachment@1', value);   // { valid, errors: [{ path, message }] }
~~~~
