File storage
A per-user file folder scoped to your app, including presigned uploads for large files.
Each user gets a private folder per app, backed by S3-compatible storage under:
users/{user}/apps/{your app}/...Requires the files.read and files.write scopes.
Upload#
The body is the raw file — not JSON, not multipart:
curl -X PUT https://api.portmodels.com/connect/files/notes/2026-07.md \
-H "Authorization: Bearer pmc_..." \
-H "Content-Type: text/markdown" \
--data-binary @notes.mdDirect uploads through the API are capped at 10MB. Larger files return
413 — use a presigned upload.
Download#
curl https://api.portmodels.com/connect/files/notes/2026-07.md \
-H "Authorization: Bearer pmc_..."List#
curl "https://api.portmodels.com/connect/files?prefix=notes" \
-H "Authorization: Bearer pmc_..."{ "files": [ { "path": "notes/2026-07.md", "size": 4821 } ] }prefix is optional and filters within the user's folder for your app.
Delete#
curl -X DELETE https://api.portmodels.com/connect/files/notes/2026-07.md \
-H "Authorization: Bearer pmc_..."Paths#
Paths are relative to the user's folder for your app. .. traversal is
rejected — you cannot reach another app's folder or another user's data this
way.
Use / to organize:
notes/2026-07.md
exports/report-final.pdf
cache/embeddings.jsonLarge files: presigned URLs#
For files over the 10MB proxy limit, ask for a time-limited URL and upload directly to storage, bypassing the API server:
# Ask for the URL — content_length is required for the quota check
curl "https://api.portmodels.com/connect/files/presign-upload/exports/video.mp4?content_length=52428800" \
-H "Authorization: Bearer pmc_..."Then PUT the bytes to the returned URL. Downloads have the same shortcut:
curl "https://api.portmodels.com/connect/files/presign-download/exports/video.mp4" \
-H "Authorization: Bearer pmc_..."Presigned URLs are time-limited. Generate one when you need it rather than storing it.
Note
Presigned URLs require a real S3 endpoint. A development server running the
in-memory storage backend returns 501 for these routes — use the direct
PUT path in development.
Storage quota#
Private-folder writes are metered against the connected user's storage
subscription. Once a write would take the user past their quota, the request
returns 402 with a storage quota exceeded error. Surface this to the user as
"upgrade or free up space", not as a generic failure.
See App Data Layer plans.
Cross-app folders#
An app can be granted access to another app's folder for the same user through
the concrete scopes files.read:{app_id} and files.write:{app_id}, using:
GET|PUT|DELETE /connect/files/apps/{app_id}/{path}These scopes are only ever grantable between apps owned by the same developer, and the user still has to approve them. Use this when you ship a suite of apps that share output — not as a way around isolation.