Skip to Content
We are live but in Staging 🎉

Bucket CORS

S3-style CORS configuration. See Core Concepts → CORS for the type signature.

RPCHTTP
PutBucketCorsPUT /admin/buckets/:name/cors
GetBucketCorsGET /admin/buckets/:name/cors
DeleteBucketCorsDELETE /admin/buckets/:name/cors

gRPC setup — grpcurl, endpoints, reflection, and field-name casing — is covered once in Conventions → Using gRPC.

These rules bind one door. Per-bucket CORS is evaluated by k3-api’s per_bucket_cors_middleware — i.e. on api.data.dodil.io only. The object.uk-lon-1.dodil.io byte plane carries a single fixed, fleet-wide origin allow-list instead (localhost, dodil.io, dodil.cloud by default) and never reads BucketCorsConfiguration. Setting a rule here will not make a browser on your own origin able to fetch from the object endpoint — point that traffic at the control-plane S3 route. Separately, the S3 ?cors subresource passes straight through to the storage backend and is never read by K3.

PutBucketCors

The HTTP body is the BucketCorsConfiguration value (proto body: "cors_configuration").

Validated before it is stored (services/storage/cors.rs, validate_cors_config):

RuleFailure
cors_rules present, non-emptyINVALID_ARGUMENT — “cors_rules array is required” / “must have at least one rule”
at most 100 rulesINVALID_ARGUMENT — “cors_rules cannot exceed 100 rules”
each rule has a non-empty allowed_originsINVALID_ARGUMENT — “rule N: allowed_origins must have at least one origin”
allowed_methodsGET, HEAD, PUT, POST, DELETEINVALID_ARGUMENT — “rule N: invalid method ’…’”
max_age_seconds ≥ 0INVALID_ARGUMENT — “rule N: max_age_seconds must be non-negative”

(N is the zero-based index of the offending rule.)

A PUT replaces the whole configuration — there is no per-rule patch. Rules are then evaluated in order and the first origin match wins, so put your most specific rule first. An empty allowed_methods matches any method (S3’s permissive default) — be explicit. expose_headers is what the browser is allowed to read back: ETag is not CORS-safelisted, so an upload flow that reads the etag must list it.

Request

curl -sS -X PUT "https://api.data.dodil.io/admin/buckets/kb-prod/cors" \ -H "Authorization: Bearer $DODIL_TOKEN" \ -H "Content-Type: application/json" \ -d '{ "corsRules": [ { "allowedOrigins": ["https://app.example.com"], "allowedMethods": ["GET", "PUT"], "allowedHeaders": ["*"], "exposeHeaders": ["ETag"], "maxAgeSeconds": 3600 } ] }'

Response

A BucketCorsConfiguration — see Core Concepts → CORS.

GetBucketCors

A bucket with no CORS configuration is NOT_FOUND (“no CORS configuration set on this bucket”), not an empty corsRules array. An unknown bucket is NOT_FOUND (“bucket not found”) on all three calls.

Request

curl -sS "https://api.data.dodil.io/admin/buckets/kb-prod/cors" \ -H "Authorization: Bearer $DODIL_TOKEN"

Response

A BucketCorsConfiguration — see Core Concepts → CORS.

DeleteBucketCors

Request

curl -sS -X DELETE "https://api.data.dodil.io/admin/buckets/kb-prod/cors" \ -H "Authorization: Bearer $DODIL_TOKEN"

Response

Empty (DeleteBucketCorsResponse {}).


See also