---
title: "Upload an evidence blob (bytes)"
method: POST
path: "/v1/ingest/evidence-blob"
tags: ["Evidence"]
---

# Upload an evidence blob (bytes)

`POST /v1/ingest/evidence-blob`

## Request

- Base URL: `https://brain.inite.ai`
- URL: `https://brain.inite.ai/v1/ingest/evidence-blob`
- Auth: HTTP bearer

Takes BYTES into custody: `multipart/form-data` with the content in the `file` part and the asset metadata as text parts. The server owns identity here — `byteHash` (sha256 of what it received), `byteLength` and `storageRef` are computed, never asserted — so the registered asset is blob-backed with availability derived as `hot`. Media types are an explicit conservative allowlist checked AGAINST the declared modality (application/json, application/pdf, application/vnd.openxmlformats-officedocument.presentationml.presentation, application/vnd.openxmlformats-officedocument.spreadsheetml.sheet, application/vnd.openxmlformats-officedocument.wordprocessingml.document, audio/aac, audio/flac, audio/mp4, audio/mpeg, audio/ogg, audio/wav, image/avif, image/gif, image/jpeg, image/png, image/webp, message/rfc822, text/csv, text/markdown, text/plain, video/mp4, video/quicktime, video/webm); anything else, including SVG, HTML, archives and `application/octet-stream`, is a 400. Transfer is capped at `min(EVIDENCE_MAX_BYTES, 64 MiB)` — an over-cap part is refused mid-upload with 413. Uploaded bytes are EXTERNAL INGEST by definition, so the asset registers with origin `external_ingest` and is SCANNED before it becomes dispatchable: a clean verdict answers 201, a rejection answers 422 with the row tombstoned and the bytes deleted, and a scan hook that cannot decide answers 201 with `quarantineStatus: "scanning"` (still dispatch-denied — the surface never fails open). An optional `packId` starts a FIRE-AND-FORGET processor dispatch that can never fail the upload. Same-user re-upload of identical bytes dedupes; a different principal gets a bare 409. Answers a bare 404 until `EVIDENCE_BLOB_UPLOAD_ENABLED=1` (raised before the body is parsed); answers 503 while `EVIDENCE_SUBSTRATE_ENABLED` or `EVIDENCE_QUARANTINE` is off, or when no storage adapter is configured. Source: src/evidence/evidence-ingest.controller.ts.

Required scope: `brain:write`.

## Response `201`

Stored, registered and scanned asset.

- UploadEvidenceBlobResponse
  - `assetId` string, required
  - `availability` 'hot' | 'cold' | 'external' | 'gone', required
  - `byteHash` string, required
  - `byteLength` integer, required
  - `deduped` boolean, required
  - `dispatched` boolean, required
  - `mediaType` string, required
  - `quarantineStatus` 'clean' | 'scanning', required
  - `storageRef` string, required

## Other responses

- `400`: Validation failed.
- `401`: Missing or unknown bearer key.
- `403`: The key lacks the required scope.
- `404`: No such resource in this tenant.
- `409`: State conflict — e.g. a lost/mismatched claim token, an already claimed work item, or an immutable-version republish.
- `413`: The uploaded part exceeds the effective transfer cap — min(EVIDENCE_MAX_BYTES, the 64 MiB memory-storage ceiling).
- `422`: The uploaded evidence was rejected by the scan hook. The asset row survives as a tombstone (availability `gone`, quarantineStatus `rejected`) and the bytes are deleted; the message is deliberately content-free.
- `503`: The evidence write seam is off (EVIDENCE_SUBSTRATE_ENABLED) — the route exists but every write is refused. Boot-time env-validation warns about this inconsistent flag pair.

---

[API](https://skmtc.dev/inite/apis/inite-brain-platform-api.md) · [All operations](https://skmtc.dev/inite/apis/inite-brain-platform-api/llms.txt) · [OpenAPI document](https://skmtc.dev/inite/apis/inite-brain-platform-api/revisions/c7c3eb5e304b?raw)
