# Metadata and uploads

> Inspect host capabilities and document metadata with GET /_/meta, and upload attachments with POST /_/upload.

Source: https://hyperclaylocal.com/docs/metadata-and-uploads/

Documents running in Hyperclay Local discover what the host supports through `GET /_/meta` and upload file attachments through `POST /_/upload`. For how ClayJS uses these endpoints in the browser, see [Host compatibility](https://clayjs.com/docs/compatibility/) and [upload and quickcrop](https://clayjs.com/docs/plugins/upload/).

## `GET /_/meta`

`GET /_/meta` returns the capabilities of the local server and, when a `Document-URL` (or `Page-URL`) header names a document that exists on disk, the metadata and limits for that document:

```http
GET /_/meta
Document-URL: http://localhost:4321/index.html
```

```json
{
  "spec": 1,
  "extensions": ["conditional", "format", "sync", "upload", "live-data", "live-provenance"],
  "document": {
    "etag": "…",
    "writable": true,
    "maxBytes": 20971520,
    "upload": { "allowed": true, "maxBytes": 26214400 }
  }
}
```

- **`extensions`**: Announces supported host capabilities, including `"conditional"` (conditional saves and save receipts), `"format"` (`formathtml="true"` support), `"sync"` (tab live sync), `"upload"` (attachment uploads), `"live-data"`, and `"live-provenance"`.
- **`document`**: Included only when `Document-URL` (or `Page-URL`) names an existing file. If no header is sent or the named document does not exist, the `document` key is omitted.
- **`document.saveId`**: Included when the current bytes on disk were written by a save that carried a `Save-ID` header (see [Saving and stale-write protection](https://hyperclaylocal.com/docs/saving/)).
- **`document.people.me`**: Included when **Use my profile in files** is turned on under **Options** then **Profile…**, returning your Hyperclay account profile (cached for offline use) or your local display name. This field is answered only to requests from the folder's own origin (see [Settings and profile](https://hyperclaylocal.com/docs/settings/)).

## `POST /_/upload`

`POST /_/upload` stores an attachment file in the folder's `uploads/` directory instead of embedding it as a `data:` URL inside the HTML document. Send a `multipart/form-data` request with one part named `file` and a `Document-URL` header:

```bash
curl -X POST http://localhost:4321/_/upload \
  -H 'Document-URL: http://localhost:4321/index.html' \
  -F 'file=@note.txt'
```

```json
{
  "msg": "Uploaded",
  "msgType": "success",
  "uploads": [
    {
      "name": "note-2cf24d.txt",
      "url": "/_/uploads/assets-index/note-2cf24d.txt",
      "bytes": 5
    }
  ]
}
```

### Where uploads are stored and linked

- **Directory layout**: Files are stored in `uploads/assets-<document stem>/` inside the served folder and linked as `/_/uploads/assets-<document stem>/<filename>`, matching the layout used on hyperclay.com so a synced folder stays byte-identical on both sides and moving a document does not strand its files. Uploads into a folder with no document land in that folder's `uploads/assets-<stem>/` directory and keep their `/_/uploads/` link.
- **Content-addressed filenames**: The stored filename is `<stem>-<first 6 hex of the sha256 of the bytes><ext>`. Uploading identical bytes converges on the same file, while different bytes never collide (the hash prefix lengthens automatically if a 6-character prefix would collide). Characters that Windows cannot store (`< > : " | ? *` and control characters) are stripped from upload names so filenames work across macOS, Windows, Linux, and hyperclay.com.
- **URL encoding and serving**: `url` in the JSON response is percent-encoded for every byte outside the unreserved set and can be placed directly into `src` or `href`. The `/_/uploads/` route serves only files inside the real `uploads/` folder (refusing symlinks out of the folder) and sends `X-Content-Type-Options: nosniff` on every response.
- **Zip export**: When you export a document with **Export a File as Zip…**, `/_/uploads/` links are rewritten to relative `uploads/…` paths and packaged alongside the document (see [Export as zip](https://hyperclaylocal.com/docs/export/)).

### Limits, refused file types, and error codes

- **Maximum file size**: `26214400` bytes (`25` MB). A file larger than `25` MB is refused with `413` and `"code":"too-large"`. (When cloud sync is enabled, attachments larger than your hyperclay.com plan's per-file cap are still saved locally and served on `localhost`, but stay on your computer until your plan limit allows them; see [What syncs](https://hyperclaylocal.com/docs/what-syncs/).)
- **Refused extensions**: Web-page and executable script types are refused: `.html`, `.htm`, `.xhtml`, `.htmlclay`, `.js`, `.mjs`, `.cjs`, `.xml`, `.xht`, `.xsl`, and `.xslt`. Uploading any of these returns `415`:
  ```json
  {"msg":"That kind of file cannot be uploaded.","msgType":"error","code":"unsupported-type"}
  ```
  If a web-page file type is placed inside `uploads/` on disk by hand, `/_/uploads/` serves it as a download with `nosniff` so it cannot execute script on the app's origin.
- **SVG files**: `.svg` and `.svgz` files are allowed because `/_/uploads/` and the static file server serve them inert with `Content-Disposition: attachment` and `X-Content-Type-Options: nosniff`.
- **Target document check**: When `Document-URL` names a document path, that document must already exist; otherwise `POST /_/upload` returns `404` with `"code":"not-found"`.
- **Error `code` values**: `bad-request`, `forbidden`, `not-found`, `conflict`, `too-large`, and `unsupported-type`.
