Batteries

Files

Durable storage for what does not belong in Postgres — an upload, a generated PDF, a CSV somebody imported. The bytes never pass through your container: you get an id and a short-lived signed URL, and the client sends them straight to object storage.

Every call below goes to the address in your SAASIE_ANALYTICS_URL environment variable, with Authorization: Bearer <token>. Read both from the environment — they are injected, and hardcoding either is how an app breaks when the platform moves. See getting the credential.

POST /v1/files

Provision a file, and get a link to upload it

Returns an id and a short-lived PUT URL. Send the bytes straight to that URL from wherever they are — they must not pass through your app.

Request body

FieldType
contentType string What you will upload. Sent to the store with the bytes. length 1–200, defaults to application/octet-stream
size* integer How many bytes you are about to upload. Checked against the allowance before the link is signed — send the real length, since what arrives is checked too. 0–9007199254740991
name string A display name, kept as metadata. It is not part of the id and two files may share one. length 1–200

200 — The file, and where to put the bytes

FieldType
file* object —
upload* object —

When it does not work

400Not a usable size or content type
401Missing or invalid service token
403The Saasie is stopped
413Larger than one file may be, or than the allowance left
501This deployment has no files battery
502The object store could not be reached

GET /v1/files

What this Saasie has stored

Available files, newest first. Pass status=pending for provisions that have not been uploaded to yet, or status=rejected for ones that arrived and did not fit.

200 — A page of files

FieldType
files* object[] —
storedBytes* number —
bytesPerSaasie* number —
nextBefore* number | null —

When it does not work

401Missing or invalid service token
403The Saasie is stopped
501This deployment has no files battery

GET /v1/files/{id}

One file's metadata

Also the cheapest way to ask whether an upload landed: a pending file is checked against the store on every read.

200 — The file

FieldType
id* string —
status* string pending is provisioned and not yet uploaded; available is confirmed by the store; rejected arrived and did not fit, and its bytes are gone. pending · available · rejected
size* number | null What actually arrived. Null while pending.
declaredSize* number —
contentType* string —
name* string | null —
createdAt* number —
availableAt* number | null —

When it does not work

400Not a file id
401Missing or invalid service token
403The Saasie is stopped
404No such file
501This deployment has no files battery
502The object store could not be reached

DELETE /v1/files/{id}

Delete a file

The bytes go, and the allowance comes back. Deleting one that is already gone succeeds — a retry after a timeout should not be an error.

When it does not work

400Not a file id
401Missing or invalid service token
403The Saasie is stopped
404No such file
501This deployment has no files battery
502The object store could not be reached

Generated from openapi.json, which the service renders from its own routes. * marks a required field. Every error body is { "error": { "code", "message", "details"? } }.