Storage reference

Config options

OptionDefaultMeaning
basePath/storageWhere the object routes mount
sharePath/sWhere share fetches mount. Kept short — the whole URL gets pasted into a chat window
multipartThresholdBytes100 MiBAbove this, a resumable multipart upload. Cannot exceed the single-PUT ceiling
partSizeBytes64 MiBBytes per part. At most 10,000 parts, so this fixes the largest object you can store
defaultVisibilityprivateFor an upload that does not name one. Private, so a forgotten field can never publish a file
pendingTtlSeconds86400How long an unfinished upload holds its quota reservation
quota—Bytes per owner

Those two size settings multiply into your maximum object size. Raising the part size raises the ceiling and costs a larger retry when one part fails.

Routes

Using Storage has the calls; standing the bucket up is pithy storage.

RoutePurposeVerification
POST /storageStart an upload; returns one PUT URL or the part URLsbearer · session
GET /storageList your files by path prefix, cursor-paginatedbearer · session, owner
POST /storage/:id/completeFinalize, confirming the bytes against the storebearer · session, owner
POST /storage/:id/abortAbandon an upload and drop its partsbearer · session, owner
GET /storage/:id/partsResume: stored parts, plus a fresh URL per missing onebearer · session, owner
POST /storage/:id/copyServer-side copybearer · session, owner
POST /storage/:id/sharesMint a revocable share linkbearer · session, owner
DELETE /storage/shares/:tokenRevoke a sharebearer · session, owner
GET /storage/:id/urlA presigned direct URL, valid five minutesbearer · session, owner or public
GET /storage/:idStream the bytespublic when the object is public, else bearer · session and owner
HEAD /storage/:idMetadata onlybearer · session, owner
PATCH /storage/:idRename, or change visibilitybearer · session, owner
DELETE /storage/:idDelete the file and its rowbearer · session, owner
GET /s/:tokenFetch via a share linkpublic — the token is the credential

Error codes

CodeStatusMeaning
storage/not_found404No such object, or one you may not see
storage/forbidden403A public object you do not own
storage/quota_exceeded413The upload would put the owner over their byte limit
storage/upload_incomplete409Still pending — the upload never finished
storage/multipart_failed500The part plan or the reported part list cannot be completed
storage/share_expired410The share token is past its expiry

A not-found for an object you may not see is deliberate. A 403 there would confirm the object exists.

Response hardening

This is what keeps a user’s file from executing on your origin.

Every object response carries no-sniff and a locked-down content policy.

An active type is served as an attachment whatever was stored — markup, vector images, anything with an XML suffix, script.

Not blocked at upload. Served in a way a browser will not execute.

Bindings

BindingTypeWritten by
DBd1pithy add
STORAGE_BUCKETr2pithy add, with the name provisioning will create
STORAGE_SWEEPworkflow, optionalpithy storage provision

Tables

pithy_storage_objects · pithy_storage_shares

These two carry the only foreign key in the kit — shares reference objects — because that one relationship is internal to the capability and both tables are certain to be in one database.

Secrets

storage-r2-credentials — the account id, the S3 key pair, a scoped token, and the bucket name.

The key pair is supplied, not minted. There is no API for creating one, so you make it by hand and hand it over. Passing one half without the other is refused.

Keys and paths

The store key is server-derived and opaque. A client can never name an object or guess the one beside it.

The logical path is yours — stored, indexed and listable by prefix. Renaming is a metadata change rather than a copy.

The sweep

Daily, reconciling in both directions: rows with no bytes, and bytes with no row.

Peer capabilities

Required: secrets.

Optional: auth — effectively required, since every owner-scoped route denies without it.

What is not here

No transcoding, thumbnails or AI. That is media, which presigns through this package’s object-store seam against its own bucket and its own credential name, and inherits none of these tables or routes.

No public CDN hosting. Objects are private by default and served by your Worker.

No versioning. A write to the same logical path replaces what was there.

ESC