Skip to Content
We are live but in Staging 🎉
Data EnginesObjectsRecipesShare a Public Bucket

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.io requires a credential on every request — an unsigned request is refused with 401 AccessDenied before 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 on api.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 PUT or DELETE gets 403 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):

ModeAnonymous readAnonymous write
private (default)403403
public403
customgoverned by Bucket Policyno policy = deniedgoverned 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.md

3. 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.md

4. 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:

# 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 private

For fine-grained rules (only some prefixes public, only some IPs, etc.) use custom mode with a Bucket Policy instead of public.

Common gotchas

SymptomCauseFix
401 AccessDenied on every anonymous requestYou used object.uk-lon-1.dodil.io. That host demands a credential before it looks at the bucket, and has no /orgs/ routeUse https://api.data.dodil.io/orgs/<org_name>/…
403 AccessDenied on anonymous GETBucket isn’t public (or is custom with no policy)dodil data bucket update <name> --access-mode public
404 NoSuchBucket from aws-cliWrong <org_name> in the endpoint — the route resolves (slug, bucket) and returns NoSuchBucket when the pair doesn’t matchCheck 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 keysDon’t use those as top-level key prefixes on a public bucket
Browser blocks fetch() of a public objectBucket has no CORS configuredConfigure CORS — it is evaluated on this same api.data.dodil.io route
Need to share without making the whole bucket publicOne-off downloadsUse GetObjectUrl for a presigned URL instead

See also