Share a public bucket
Goal: give anyone a URL they can hit with curl, aws s3, or a browser to read objects in your bucket — without minting per-request presigned URLs.
Why: presigning is right for one-off short-lived shares, but wrong for documentation portals, dataset distribution, demo content, or anything you want to link to permanently. K3 supports a per-bucket access_mode that lets anonymous reads through while keeping writes locked to the bucket owner.
Anonymous reads use a different door than your own traffic. The S3 wire at
object.uk-lon-1.dodil.iorequires a credential on every request — an unsigned request is refused with401 AccessDeniedbefore any bucket is looked at, and there is no/orgs/route on it at all. Anonymous access is served by the control-plane S3 route onapi.data.dodil.io, the only one that understands the org-scoped/orgs/<org_name>/prefix and evaluates a bucket’s access mode for a caller with no credential. Upload over the signed object endpoint; share the org-scoped control-plane URL.
Anonymous writes are always rejected. Even on a public bucket, uploads and deletes require valid credentials from the bucket owner — an anonymous
PUTorDELETEgets403 AccessDenied.
1. Create the bucket (or flip an existing one)
bucket create takes no --access-mode — a new bucket is always PRIVATE, so flipping it is a second call either way:
dodil data bucket create docs-public -d "Public docs"
dodil data bucket update docs-public --access-mode public
# Or switch an existing bucket
dodil data bucket update kb-prod --access-mode public--access-mode accepts private | public | custom. Access modes (full reference in Core Concepts → Access modes):
| Mode | Anonymous read | Anonymous write |
|---|---|---|
private (default) | ❌ 403 | ❌ 403 |
public | ✅ | ❌ 403 |
custom | governed by Bucket Policy — no policy = denied | governed by Bucket Policy |
2. Upload your content
Use your normal authenticated workflow — aws s3 cp, aws s3 sync, dodil data object create, or the admin RPC. Owner credentials always work:
aws s3 sync ./public-assets s3://docs-public/ \
--endpoint-url https://object.uk-lon-1.dodil.io \
--profile dodil-k3
dodil data object create ./welcome.md -b docs-public -k welcome.md3. Share the URL
Objects in a public bucket are anonymously addressable on the control-plane host, under the org-scoped prefix:
https://api.data.dodil.io/orgs/<org_name>/<bucket>/<key><org_name> is your org slug, not the org UUID — the route resolves (slug, bucket) together, so the same bucket short name in another org never collides.
Drop it into any context that consumes a URL:
<img src="https://api.data.dodil.io/orgs/acme/docs-public/logo.svg" alt="logo" />
<a href="https://api.data.dodil.io/orgs/acme/docs-public/whitepaper.pdf">Whitepaper</a># curl — no flags, no headers
curl -O https://api.data.dodil.io/orgs/acme/docs-public/welcome.md4. Browse with the AWS CLI
For directory listings and recursive downloads, point a standard S3 client at the org-scoped endpoint with --no-sign-request:
aws-cli (one-off)
# List
aws s3 ls \
--endpoint-url https://api.data.dodil.io/orgs/acme \
--no-sign-request \
s3://docs-public/
# Download a single file
aws s3 cp \
--endpoint-url https://api.data.dodil.io/orgs/acme \
--no-sign-request \
s3://docs-public/whitepaper.pdf .
# Mirror the whole bucket
aws s3 cp --recursive \
--endpoint-url https://api.data.dodil.io/orgs/acme \
--no-sign-request \
s3://docs-public/ ./local-mirror/5. Lock it back down
Flipping back to private takes effect immediately — anonymous readers get 403 on their next request:
dodil data bucket update docs-public --access-mode privateFor fine-grained rules (only some prefixes public, only some IPs, etc.) use custom mode with a Bucket Policy instead of public.
Common gotchas
| Symptom | Cause | Fix |
|---|---|---|
401 AccessDenied on every anonymous request | You used object.uk-lon-1.dodil.io. That host demands a credential before it looks at the bucket, and has no /orgs/ route | Use https://api.data.dodil.io/orgs/<org_name>/… |
403 AccessDenied on anonymous GET | Bucket isn’t public (or is custom with no policy) | dodil data bucket update <name> --access-mode public |
404 NoSuchBucket from aws-cli | Wrong <org_name> in the endpoint — the route resolves (slug, bucket) and returns NoSuchBucket when the pair doesn’t match | Check the bucket owner’s org slug |
Anonymous read fails only for keys under objects/, policy/, sources/, rules/, ingest/, search/, metadata/, indexes/, vector-store/ | The control-plane route reserves those first path segments for management endpoints, so they never resolve as object keys | Don’t use those as top-level key prefixes on a public bucket |
Browser blocks fetch() of a public object | Bucket has no CORS configured | Configure CORS — it is evaluated on this same api.data.dodil.io route |
| Need to share without making the whole bucket public | One-off downloads | Use GetObjectUrl for a presigned URL instead |
See also
- S3 Compatibility → Public buckets — SDK setup for anonymous access
- Core Concepts → Access modes — modes + URL shape
- Bucket Policy — fine-grained custom access
- Static site / public asset hosting — production setup with CDN