Configure CORS rules

Let web applications on other origins call a bucket from the browser by adding CORS rules through the REST API, and check whether they're in force.

Add CORS rules to a bucket so that scripts on web pages from other origins can send requests to it. You add and remove rules through the REST API; the console shows a bucket's rules and whether they're in force.

Before you begin

  • You need the Owner or Contributor role, or a custom role with Manage access to Block & Object Storage, on the subscription or on the project.
  • You need an API token with the storage:write scope, in the environment variable PC_TOKEN. See Service accounts and API tokens and Authenticate with API tokens.
  • You need the bucket's ID: on the bucket's Overview tab, copy Bucket ID.
  • You need your project's ID. See Find a project's ID.

How CORS rules apply

The object store keeps one CORS policy per bucket, so the platform merges all of a bucket's rules into that policy:

  • Allowed origins are the origins of all the rules together.
  • Exposed headers, the response headers that browser scripts can read, are the headers of all the rules together.
  • Max age, how long a browser may cache a preflight response, is the longest value of all the rules.

A bucket without CORS rules has no CORS policy of its own.

Rule fields

FieldRules
originsAt least one. Each origin is *, for any origin, or an exact scheme://host with an optional :port, such as https://app.example. Use ASCII only, and write internationalized domain names in punycode. No path, no wildcard in the host, no user name or password, and no spaces. Origins are stored in lowercase, and the object store compares them exactly.
methodsAt least one, such as GET or PUT.
headersThe response headers that browser scripts can read, such as ETag. Each entry is one header name, or *. Can be empty.
maxAgeSeconds0 or more. How long, in seconds, a browser may cache a preflight response.

A bucket can have up to 100 CORS rules. The merged list of origins, and the merged list of exposed headers, must each fit within the object store's limit for one metadata value; a rule that would exceed it is refused, and the error states the limit in bytes.

Add a CORS rule

Send the rule to the bucket's configuration rules:

Add a CORS rule
curl -X POST "https://<api-host>/api/v1/scopes/<project-id>/storage/buckets/<bucket-id>/configuration-rules" \
  -H "Authorization: Bearer $PC_TOKEN" \
  -H "Idempotency-Key: $(uuidgen)" \
  -H "Content-Type: application/json" \
  -d '{"type":"cors","cors":{"origins":["https://app.example"],"methods":["GET","PUT"],"headers":["ETag"],"maxAgeSeconds":3600}}'
  • <api-host>: the host name of the API. See URL structure.
  • <project-id>: the ID of the project that holds the bucket.
  • <bucket-id>: the bucket's ID.

Check the response. A successful request returns 201 Created with the new rule, including its id:

Response
{"id":"<rule-id>","bucketId":"<bucket-id>","type":"cors","cors":{"id":"<rule-id>","origins":["https://app.example"],"methods":["GET","PUT"],"headers":["ETag"],"maxAgeSeconds":3600},"lifecycle":null,"replication":null}

Send an Idempotency-Key header with a new value for each rule, as in the example. If you repeat a request with the same key and body, the API returns the rule that the first request created instead of adding a second one. Without the header, a repeated request adds a second rule. For the request format, see POST/scopes/{scopeId}/storage/buckets/{bucketId}/configuration-rules.

The rule is saved at once and applied to the object store in the background. To see when it's in force, check the bucket's CORS tab.

Check whether the rules are in force

In the console, go to StorageObject Storage, and select the bucket's name.

Select the CORS tab.

The tab lists each rule's Allowed origins, Exposed headers, and Max age. Above the list, a line shows the state of the latest change:

StateWhat it means
In force on the object store.The object store holds the bucket's current rules.
Being applied to the object store…The change is being applied. The platform retries when the object store is briefly unavailable.
Not applied — the object store keeps its previous CORS policy: <reason>The object store refused the change. It keeps its previous CORS policy.

Through the API, GET/scopes/{scopeId}/storage/buckets/{bucketId} returns the rules in corsRules and the state in corsApply.status: pending, applied, or failed. corsApply is null when the bucket's rules have never changed.

Remove a CORS rule

Find the rule's id in corsRules:

Get the bucket
curl "https://<api-host>/api/v1/scopes/<project-id>/storage/buckets/<bucket-id>" \
  -H "Authorization: Bearer $PC_TOKEN"

Delete the rule:

Remove a CORS rule
curl -X DELETE "https://<api-host>/api/v1/scopes/<project-id>/storage/buckets/<bucket-id>/configuration-rules/<rule-id>" \
  -H "Authorization: Bearer $PC_TOKEN"

<rule-id>: the rule's id.

A successful request returns 204 No Content, and the platform applies the remaining rules to the object store. When you remove the last rule, the bucket's CORS policy is removed. See DELETE/scopes/{scopeId}/storage/buckets/{bucketId}/configuration-rules/{configurationRuleId}.

To change a rule, remove it and add a new one. Rules can't be edited.

Result

Browser requests from the allowed origins receive the CORS headers that the bucket's merged policy sets.

Troubleshooting

ResponseCauseResolution
422 with code VALIDATION_FAILED and field cors.originsAn origin isn't * or an exact ASCII scheme://host[:port].Remove the path or the wildcard, and write internationalized names in punycode.
422 with field cors.headersA header entry isn't a single header name or *.Send one header name per entry.
422 with details.reason cors_policy_too_largeThe merged origins or headers would exceed the object store's limit, given in details.limitBytes.Remove origins, headers, or rules first.
409 with code QUOTA_EXCEEDEDThe bucket already has 100 CORS rules.Remove rules, or combine their origins into fewer rules.
409 with code CONFLICTThe bucket is being created or deleted, or it's in an error state.If the bucket is being created, wait until its creation finishes. A bucket that's being deleted or is in an error state can't take new rules.
422 with details.reason lifecycle_not_supported or replication_not_supportedThe rule's type is lifecycle or replication. The platform doesn't apply these rule types.Send CORS rules only.
Not applied — the object store keeps its previous CORS policy: <reason> on the CORS tabThe object store refused the merged policy.Remove the rule you added last. Every change applies the bucket's whole rule set again.

On this page