Documentation Index
Fetch the complete documentation index at: https://docs.emergence.ai/llms.txt
Use this file to discover all available pages before exploring further.
Use Shared Storage
The platform exposes object storage throughobstore, a thin abstraction that lets the same code run against AWS S3, GCS, Azure Blob, and local MinIO. Solutions use it for artifacts, intermediate data, files served to the UI, and anything else that doesn’t belong in the relational database.
For the platform-side abstraction model, see Platform Overview. For data-source connections (databases + storage credentials registered by customers), see Platform › Data Connections.
Why obstore
Three things you get for free:- One code path, all clouds. No
if AWS: ... elif GCP: ...in your service. - Credential rotation handled by the platform. Connections fetch fresh credentials per request via the secrets pipeline.
- Per-tenant path isolation enforced by convention. Every operation is scoped by
org_id+project_id(passed viaX-Project-ID).
obs.put / obs.get with a path scoped by org_id/project_id/solution; the obstore client picks the actual backend from env config; the same code runs against any of S3/GCS/Azure/MinIO.
Path conventions
Stick to this prefix layout for everything you write:Provision a connection
Customers register a Data Connection (types3, gcs, or minio) via Assets. Your solution looks it up by name when it needs to read or write.
- Customer-supplied (typical)
- Helm values (when you own the bucket)
The customer registers the connection in the UI or via the Assets API, then grants your solution permission. Your code:
Code patterns
Theobstore Python package gives you a single API across providers. Install with uv add obstore.
Initialize a client from env
packages/api/src/api/storage.py
Put / get / list / delete
Stream a large object (avoid loading into memory)
bytes().
Per-tenant isolation
X-Project-ID is mandatory on every storage operation. Build the prefix from request context:
Local development with MinIO
The Local Development page shows the docker-compose snippet. Pre-create the bucket once:OBSTORE_BACKEND=s3, OBSTORE_ENDPOINT=http://localhost:9000, OBSTORE_PATH_STYLE=true. The same code that talks to AWS S3 in prod talks to MinIO in dev.
Common errors
SignatureDoesNotMatch (MinIO)
SignatureDoesNotMatch (MinIO)
Set
OBSTORE_PATH_STYLE=true. MinIO doesn’t support virtual-hosted-style addressing without DNS configuration.403 AccessDenied (S3 / GCS)
403 AccessDenied (S3 / GCS)
Check that the bucket policy / Workload Identity SA grants the required action. For GCS Workload Identity, verify the K8s ServiceAccount is annotated with
iam.gke.io/gcp-service-account=<gsa> and the GSA has roles/storage.objectAdmin on the bucket.Region not configured (S3)
Region not configured (S3)
AWS S3 SDKs require a region; set
OBSTORE_REGION even if you’re targeting a non-AWS endpoint via OBSTORE_ENDPOINT.Cross-tenant read succeeded — bug!
Cross-tenant read succeeded — bug!
Your
storage_prefix helper isn’t being used everywhere, OR your route accepts a user-controlled key without prefixing. Audit by grepping for obs.get_async\|obs.put_async\|obs.delete_async in your service and confirming every call site composes the path through storage_prefix(...).Verification
Next steps
Data connections
How customers register storage and DB connections.
Multi-tenancy
Org/project isolation — what enforces it where.
Manage secrets
Storage credentials follow the same secret-injection pattern.
Troubleshooting
More storage error scenarios.

