# Upload File

- **Method:** `POST`
- **Path:** `/api/v1/files`
- **Operation ID:** `upload_file`
- **Tags:** files

One file, multipart, into this tenant's own prefix of the evidence store.

The answer is a `saram-file://<tenant_id>/<file_id>` ref, which is the only form a run spec may name a
file by - in a run started by any key or member of the same tenant, and by nobody else's. What is
refused, and why each: more bytes than the per-file limit, 25 MiB unless the deployment sets another
(413, and the body is cut off at the cap rather than read and then measured); a name that is not one path
segment, a media type the deployment does not accept, or magic bytes that disagree with the declared type
(422 each); and a caller that may not own files (403). Accepted by default: PNG, JPEG, WebP, GIF, PDF,
CSV, plain text, JSON, and Word and Excel documents (`.docx`, `.xlsx`); not HTML, SVG, scripts,
archives or executables.

## Effective servers

- `https://api.saram.sh`

  Saram

## Authentication

- **HTTPBearer**: HTTP bearer

  a Saram API key (`sk_saram_<env>_...`) or a person's AuthKit access token, as `Authorization: Bearer <credential>`. Both are told apart by shape and produce the same principal.

## Request body

**Required:** `true`

**Content type:** `multipart/form-data`

- **`file` (required)**: `string`, format: `binary`

  The bytes, with the part's Content-Type declaring the media type. One file per request.
- **`name`**: `string`

  What to store the file as; overrides the part's filename. One path segment: A-Z, a-z, 0-9, space, '.', '\_' or '-', no leading dot.

**Example:**

```json
{
  "file": "@filename",
  "name": ""
}
```

## Responses

### 201 Successful Response

**Content type:** `application/json`

schema: `FileRecord`, no additional properties

One file this tenant uploaded, as `POST /api/v1/files` and the two read routes answer it.

Metadata only, and deliberately: the API never serves an uploaded file's bytes back. Nothing needs them -
the dashboard shows names - and `GET /api/v1/blobs` stays evidence-only, which is what keeps a `files/`
key unreachable there. The tenant is not a field of its own: it is the ref's first segment, and it is
always the tenant of whoever asked.

- **`expires_at` (required)**: `string`

  When the file is deleted: its upload time plus how long this deployment keeps uploads. A run naming a file
  past this is refused at `POST /runs`.
- **`media_type` (required)**: `string`
- **`name` (required)**: `string`
- **`ref` (required)**: `string`

  `saram-file://<tenant_id>/<file_id>`: what a run spec's `files[].ref` names, and the only handle a caller
  ever gets on the object. A file uploaded before files were a tenant's keeps the caller name it was uploaded
  under in that segment.
- **`sha256` (required)**: `string`
- **`size_bytes` (required)**: `integer`
- **`uploaded_at` (required)**: `string`

**Example:**

```json
{
  "ref": "",
  "name": "",
  "size_bytes": 1,
  "media_type": "",
  "sha256": "",
  "uploaded_at": "",
  "expires_at": ""
}
```

### 403 this caller may not own files, or the API key lacks the files:write scope

### 413 larger than the most one file may be on this deployment; the detail names the limit

### 422 bad name, a media type off the allowlist, or bytes that disagree with it

## Schemas

- `FileRecord` — shown above.
