Cookbook: a report after the API worker restarts
Generate a small report in FastAPI, upload the closed file, and return those same bytes from a fresh API process.
A FastAPI worker can build a report for one signed-in user. When that process exits, its disk is gone.
mountable.io is a persistent filesystem for AI agents. This recipe uploads the closed report before it returns an id. A later process reads that same file.
mountable.io maintains the runnable example, separately from FastAPI. The report is synthetic. It holds no customer data. This is not a job queue or a login system.
Prerequisites
Use an existing mountable.io account with a saved payment method, an existing filesystem and an API key that can access it. Storage and file operations follow the usage prices. The example creates no account, filesystem or credits.
You also need Python 3.11 or newer and uv. Set MOUNTABLE_API_KEY in your
private environment or secret manager. Never put a key in a command argument.
git clone https://github.com/writeitai/mountable.git
cd mountable/examples/fastapi-reports
uv sync --locked
export MOUNTABLE_FILESYSTEM_ID="your-filesystem-id"
export REPORT_STORE=mountableThe example uses the Python SDK. It does not mount the filesystem. For one filesystem and a hard monthly spend cap per end user, follow the capped workspace guide. This recipe uses one existing filesystem and a path your backend chooses.
The two fixture tokens are fixture-alice and fixture-bob. They exist
only in this example. Your own API would use its real session. It would
still choose the filesystem and the path, and it would not take either
from the request.
Create a report
Start the API:
uv run uvicorn app:create_default_app --factory --host 127.0.0.1 --port 8000Ask for a report as Alice:
curl -s -H "Authorization: Bearer fixture-alice" \
-X POST http://127.0.0.1:8000/reportsA successful response is 201 and JSON with one field:
{"id":"a1b2c3d4e5f60789"}The id is 16 lowercase hex characters. It is not a filesystem id and it is
not a path. The server writes reports/alice/<id>.txt only after it has
closed the report. The write uses If-None-Match: *, so an existing file
is left as it is.
The file looks like this, with that request's id:
report_id: a1b2c3d4e5f60789
user: alice
kind: synthetic
item,quantity
alpha,1
beta,2If the upload fails, the response is 503 and
{"detail":"upload_failed"}. That JSON has no id. A missing or unknown
token returns 401.
The request body is ignored. Sending a filesystem id or a path does not change where the file goes.
Read it from a fresh process
Stop the server and start it again with the same environment. Then read the report:
curl -s -D - -H "Authorization: Bearer fixture-alice" \
http://127.0.0.1:8000/reports/$IDThe body is the same file. Save it and compare the SHA-256 with the copy you took before the restart:
curl -s -H "Authorization: Bearer fixture-alice" \
http://127.0.0.1:8000/reports/$ID | sha256sumThe two hashes match when every byte matches.
The same id with Bob's token returns 404 and {"detail":"not_found"}.
The server looks only under reports/bob/. Alice's file stays where it is.
One command runs that check for you. It creates a report, restarts the server, compares the SHA-256, checks Bob's refusal, and deletes the file:
uv run python app.py liveIt prints match, the remote path, the SHA-256 and the size. It does not
print the API key.
A 402 with filesystem_spending_cap_reached stops that filesystem's
billable mountable.io work. Other filesystems keep working.
Clean up
uv run python app.py cleanup --user alice --report-id "$ID"This deletes that one file. A file that is already gone is left gone.
Offline checks
From the example directory:
uv run pytest -qThe tests use FastAPI's TestClient and a fake store, and they also start
two local server processes over a temporary directory. They make no paid
cloud calls. They check the report hash, a refusal for the other fixture
user, and that a failed upload returns no id.
Sources: FastAPI testing, FastAPI dependencies, run a FastAPI process, Python SDK.