SapixDBSapixDB/Docs
Home
Community · Storage

BlobStore

✓ Shipped

Store and retrieve arbitrary binary files — images, PDFs, archives, model weights — alongside your agent data. Files are content-addressed by their BLAKE3 hash, auto-chunked for large uploads, and linked to the strand via a BLOB_REF nucleotide so they participate in the append-only audit chain.

Enable blob storage

Set the SAPIX_BLOB_DIR environment variable to a directory where SapixDB can write blob chunks. All blob endpoints return 503 Service Unavailable when this variable is unset.

Environment
SAPIX_BLOB_DIR=/var/sapixdb/blobs

Uploading a blob

Send raw bytes to PUT /v1/blobs. The body is the file content — do not wrap it in JSON or multipart. The server sniffs the MIME type from the first 16 bytes (magic bytes), stores the blob content-addressed, writes a BlobMeta sidecar, and appends a BLOB_REF record to the strand.

Upload an image
PUT /v1/blobs
Authorization: Bearer spx_root_YOUR_ROOT_KEY
Content-Type: image/png

<raw PNG bytes>
Upload with a filename hint
PUT /v1/blobs?filename=profile_photo.png
Authorization: Bearer spx_root_YOUR_ROOT_KEY
Content-Type: image/png

<raw PNG bytes>
201 Created
{
  "content_hash":     "3a7bd3f1c2e9a4b5d6e7f8a9b0c1d2e3f4a5b6c7d8e9f0a1b2c3d4e5f6a7b8",
  "size_bytes":       204800,
  "chunked":          false,
  "encrypted":        false,
  "strand_record_id": "018f3c2a-4b1d-7e8f-a3c2-1d4e5f6a7b8c"
}

The content_hash is the stable identifier for this blob. Store it in your agent records to link them to the file. Re-uploading identical bytes is a no-op — the same hash is returned and no new strand record is written.

Downloading a blob

GET /v1/blobs/:hash
GET /v1/blobs/3a7bd3f1c2e9a4b5d6e7f8a9b0c1d2e3f4a5b6c7d8e9f0a1b2c3d4e5f6a7b8
Authorization: Bearer spx_root_YOUR_ROOT_KEY

The response body is the raw file bytes. The Content-Type header is set from the stored BlobMeta sidecar (which contains the MIME type detected at upload time). Use HEAD /v1/blobs/:hash to check existence and retrieve headers without downloading the body.

For range requests, pass a standard Range header:

Partial download
GET /v1/blobs/3a7bd3...
Authorization: Bearer spx_root_YOUR_ROOT_KEY
Range: bytes=0-1048575

Blob metadata

Every uploaded blob gets a BlobMeta sidecar stored in the instance-level metadata store. Retrieve it at any time without downloading the blob body.

GET /v1/blobs/:hash/meta
GET /v1/blobs/3a7bd3f1.../meta
Authorization: Bearer spx_root_YOUR_ROOT_KEY
200 OK
{
  "content_type":      "image/png",
  "original_filename": "profile_photo.png",
  "size_bytes":        204800,
  "uploaded_at_ms":    1756224000000
}
FieldDescription
content_typeMIME type sniffed from magic bytes at upload time.
original_filenameThe ?filename= query param value if provided, otherwise null.
size_bytesTotal blob size in bytes.
uploaded_at_msUnix millisecond timestamp of when the blob was first stored.

MIME type detection

SapixDB detects the MIME type from magic bytes (the first 16 bytes of the body) — not from the Content-Type request header. The header is accepted but ignored for storage purposes. This prevents clients from mislabeling uploads.

Magic bytesDetected MIME type
\xFF\xD8\xFFimage/jpeg
\x89PNG\r\n\x1A\nimage/png
GIF87a or GIF89aimage/gif
RIFF....WEBPimage/webp
%PDFapplication/pdf
PK\x03\x04application/zip
(anything else)application/octet-stream

Upload policy

A global BlobPolicy lets you restrict what can be uploaded. The policy is stored at the instance level — it applies to all blob uploads.

Set policy

PUT /v1/blobs/policy
PUT /v1/blobs/policy
Authorization: Bearer spx_root_YOUR_ROOT_KEY
Content-Type: application/json

{
  "allowed_mime_types": ["image/jpeg", "image/png", "image/webp"],
  "max_size_bytes":     5242880
}

Read current policy

GET /v1/blobs/policy
GET /v1/blobs/policy
Authorization: Bearer spx_root_YOUR_ROOT_KEY
200 OK
{
  "allowed_mime_types": ["image/jpeg", "image/png", "image/webp"],
  "max_size_bytes":     5242880
}
FieldTypeDescription
allowed_mime_typesstring[]MIME types allowed for upload. Empty array = all types allowed (default).
max_size_bytesnumber | nullMaximum upload size in bytes. null = no per-policy cap (1 GiB hard cap still applies).
1 GiB hard cap. The global hard cap is 1 073 741 824 bytes (1 GiB). Setting max_size_bytes to a larger value has no effect — the hard cap still applies.

Deleting a blob

DELETE /v1/blobs/:hash
DELETE /v1/blobs/3a7bd3f1...
Authorization: Bearer spx_root_YOUR_ROOT_KEY

Returns 204 No Content. The blob chunks and the strand BLOB_REF record are removed. The BlobMeta sidecar is also deleted. Deleting a blob that does not exist returns 404.

HTTP API Reference

MethodPathDescription
PUT/v1/blobsUpload a blob (raw body). Returns 201 with content_hash.
GET/v1/blobs/:hashDownload blob bytes. Supports Range header for partial reads.
HEAD/v1/blobs/:hashCheck existence and read headers without downloading.
DELETE/v1/blobs/:hashDelete a blob. Returns 204.
GET/v1/blobs/:hash/metaRead BlobMeta sidecar. Returns 404 if not found.
GET/v1/blobs/policyRead current upload policy.
PUT/v1/blobs/policyReplace upload policy. Returns updated policy.

Upload query params

ParamDescription
filenameOptional original filename stored in BlobMeta.original_filename.
phiIf true, the blob is PHI-encrypted (requires HIPAA add-on). Default false.

Authentication and Scopes

Uploading a blob requires write:blobs. Downloading (or checking existence via HEAD) requires read:blobs. Replacing the upload policy (PUT /v1/blobs/policy) and deleting a blob (DELETE /v1/blobs/:hash) require the root key or a scoped key holding admin:blobs— the upload policy is instance-wide, and the blob store is content-addressed and shared across every tenant, so an unauthorized delete could remove a blob another tenant's record still references.

Downloading a ?phi=true blob additionally requires admin:hipaa (or the root key) — a plain read:blobs key can download an ordinary blob but not one holding PHI. See the HIPAA add-on page.

← Named Procedures→ Security & API Keys→ HIPAA Add-on (PHI blobs)