Set the dashboard’s CORS rule in the bucket, once
const url = 'https://beta-api.plune.ai/v1/projects/example/storage/cors';const options = { method: 'POST', headers: { cookie: 'plune_session=<plune_session>', 'Content-Type': 'application/json' }, body: '{"keyId":"example-key-id-0001","secret":"example-not-a-real-secret","connection":{"provider":"aws","bucket":"acme-test-results","region":"eu-central-1"}}'};
try { const response = await fetch(url, options); const data = await response.json(); console.log(data);} catch (error) { console.error(error);}curl --request POST \ --url https://beta-api.plune.ai/v1/projects/example/storage/cors \ --header 'Content-Type: application/json' \ --cookie plune_session=<plune_session> \ --data '{ "keyId": "example-key-id-0001", "secret": "example-not-a-real-secret", "connection": { "provider": "aws", "bucket": "acme-test-results", "region": "eu-central-1" } }'Owner only. «Set up CORS for me»: the server makes ONE change to the bucket’s CORS rules with a key typed for this call, and forgets the key. The rule allows exactly the dashboard’s origin (https://app.plune.ai, on the beta https://beta.plune.ai) to GET, HEAD and PUT with the content-type header, under the name plune-dashboard-<host of the origin>-<8 hex of the whole origin>, so the beta’s rule and the production’s in one bucket are two rules; calling again replaces the rule of that origin and no other (and the old fixed name plune-dashboard, when it allows that origin alone). The rules the bucket already has are read first and kept: a bucket whose rules cannot be read is not written to. S3 and the services that copy it: GetBucketCors, then PutBucketCors. Backblaze B2 (an s3.<region>.backblazeb2.com endpoint): b2_authorize_account, b2_list_buckets, b2_update_bucket; the address B2 answers with must belong to backblazeb2.com, or nothing is sent to it. Every request goes through the one function that refuses private addresses, redirects and anything but https. The key is in no response, no log, no audit entry and no row. A refusal of the service is a 200 with ok: false and a code: key_rejected — the service does not know the key; missing_permission — the key may not read or change the bucket’s settings (nothing was changed); unsupported — the service does not take this call, so the rule is the owner’s to set by hand; no_bucket — there is no bucket of that name; unreachable — no answer (not a public https address, a name that does not resolve, a certificate that cannot be trusted, no answer in 5 seconds); too_many_rules — the bucket already has 100 CORS rules besides the dashboard’s own, which is all a bucket takes (nothing was changed); unexpected — an answer the call did not want. At most 5 a minute for a project, counted after the role and the body are checked. Session cookie only.
Authorizations
Section titled “Authorizations ”Parameters
Section titled “ Parameters ”Path Parameters
Section titled “Path Parameters ”An id from GET /v1/projects
Request Body required
Section titled “Request Body required ”A key that may change the bucket’s settings, typed for this one call (Backblaze B2: an application key with listBuckets and writeBuckets; S3 and the services that copy it: s3:GetBucketCORS and s3:PutBucketCORS), and where the bucket is. It is used for this request and forgotten: never saved, never written to a log or the audit feed, never answered. The key Plune keeps for the folder is another one and is not used here.
object
Visible characters, no spaces.
Visible characters, no spaces. Never in any response.
Where the bucket is, when it is not saved yet: the fields of PUT that say which bucket at which service (provider, bucket, and region, accountId, endpoint, pathStyle as that provider takes them). No key and no folder: the keys of the folder have no place in this call and are refused by name. Left out: the saved connection.
object
aws — Amazon S3; r2 — Cloudflare R2; s3 — any other S3-compatible service (MinIO and the like).
Example
{ "keyId": "example-key-id-0001", "secret": "example-not-a-real-secret", "connection": { "provider": "aws", "bucket": "acme-test-results", "region": "eu-central-1" }}Responses
Section titled “ Responses ”Done (ok: true), or why not (ok: false and a code)
object
Present when ok is false.
Examples
The rule is set
{ "ok": true}The key may not change the settings
{ "ok": false, "code": "missing_permission"}Invalid body, or the connection in it is not complete — the same rules as PUT; nothing was sent
object
Example
{ "error": "validation failed — secret: is required"}No / invalid session — a CLI token is refused here
You are in the project, but not as its owner
object
Example
{ "error": "project owner only"}No such project among yours, or — no connection in the body — nothing is connected
object
Examples
No such project among yours
{ "error": "project 'u-shop::default' not found"}Nothing saved
{ "error": "no bucket is connected"}More than 5 tries in a minute for this project. Retry-After carries the seconds until the window rolls over
object
Example generated
{ "error": "example"}Headers
Section titled “Headers ”Seconds until the window rolls over
The server has no key to keep Secrets under, or does not know the dashboard’s address; nothing was sent
object
Example
{ "error": "storage is not configured on this server — contact your operator"}