Skip to content

Set the dashboard’s CORS rule in the bucket, once

POST
/v1/projects/{id}/storage/cors
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.

id
required
string

An id from GET /v1/projects

Media type application/json

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
keyId
required

Visible characters, no spaces.

string
>= 8 characters <= 256 characters /^[\x21-\x7e]{8,256}$/
secret
required

Visible characters, no spaces. Never in any response.

string
>= 1 characters <= 1024 characters /^[\x21-\x7e]{1,1024}$/
connection

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
provider
required

aws — Amazon S3; r2 — Cloudflare R2; s3 — any other S3-compatible service (MinIO and the like).

string
Allowed values: aws r2 s3
bucket
required
string
>= 1 characters <= 255 characters
region
string
>= 1 characters <= 64 characters
accountId
string
>= 1 characters <= 64 characters
endpoint
string
>= 1 characters <= 2048 characters
pathStyle
boolean
Example
{
"keyId": "example-key-id-0001",
"secret": "example-not-a-real-secret",
"connection": {
"provider": "aws",
"bucket": "acme-test-results",
"region": "eu-central-1"
}
}

Done (ok: true), or why not (ok: false and a code)

Media type application/json
object
ok
required
boolean
code

Present when ok is false.

string
Allowed values: key_rejected missing_permission unsupported no_bucket unreachable too_many_rules unexpected
Examples

The rule is set

{
"ok": true
}

Invalid body, or the connection in it is not complete — the same rules as PUT; nothing was sent

Media type application/json
object
error
required
string
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

Media type application/json
object
error
required
string
Example
{
"error": "project owner only"
}

No such project among yours, or — no connection in the body — nothing is connected

Media type application/json
object
error
required
string
Examples

No such project among yours

{
"error": "project 'u-shop::default' not found"
}

More than 5 tries in a minute for this project. Retry-After carries the seconds until the window rolls over

Media type application/json
object
error
required
string
Example generated
{
"error": "example"
}
Retry-After
integer

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

Media type application/json
object
error
required
string
Example
{
"error": "storage is not configured on this server — contact your operator"
}