Overview
Files you attach in Allternit are stored in the private R2 bucketallternit-user-files at u/<userId>/<fileId>/<name>. The cloud API never streams file bytes: it checks your plan, hands you a presigned URL, and verifies the result. All routes need a Clerk session and only ever touch your own files.
Plan limits
Deleting a file frees its space immediately.
Upload
{ fileId, key, putUrl, expiresInSeconds }. PUT the bytes to putUrl within 15 minutes. The URL is signed for exactly bytes, so a different size is rejected by R2. Then confirm:
201). A file that never completes leaves no record and uses no quota.
Download and delete
Permanent links
The presigned URLs above expire, so a link stored in an old message would stop loading.GET /api/v1/files/{fileId} therefore also returns linkUrl, a permanent capability link:
token is base64url(HMAC-SHA256(secret, fileId)). Opening the link answers 302 to a fresh 10-minute presigned GET (Cache-Control: private, max-age=300), so the link itself never expires. Anyone holding the link can read that one file, so treat it like the file. Deleting the file kills its link: the same URL then answers 404, as does a wrong token. When the server has no ALLTERNIT_FILE_LINK_SECRET, linkUrl is null and /raw answers 503 file-links-unavailable; clients keep using the presigned url. ALLTERNIT_PUBLIC_API_URL overrides the base (default https://api.allternit.com).
Runtime uploads
A runtime (a bot’s own files) uploads with its device credential instead of a user session, charged to the device owner’s plan and caps:Authorization: Bearer allternit_runtime_…; a user token is refused (401). The errors are the same as for /files/uploads. linkUrl is the permanent link, or null when the server has no ALLTERNIT_FILE_LINK_SECRET. allternit-api wraps the three steps as cloud_files::upload_file / upload_bytes. MMS pictures received on a number are copied into the same storage; see Phone numbers and SMS (runtime).
Channel attachments
Files a bot sends on a channel, and files people send it on Telegram or Slack, are kept in the owner’s files under the same plan limits. The runtime stores them with the runtime upload above; a Slack file from the shared Allternit app is downloaded and stored by the cloud (POST /api/v1/channels/slack/file, runtime credential), because the install’s token never leaves the cloud. Each thread message then carries files: [{ name, mime, size, url }], where url is the permanent link, so the app shows a download chip that keeps working in old messages. A file that couldn’t be kept (no cloud pairing, over the plan cap, the platform refused the download) carries name and a short error instead of url, and still shows by name.
Errors
Errors are{ "code", "message" }.
Custom voice recordings
The consent clip for a custom voice is stored in the same bucket atvoices/<userId>/<voiceId>.wav, outside your file quota. Revoking the voice deletes the object. The voice service still reads it through the same cloud API route.