Skip to content

Check that a connection works

POST
/v1/projects/{id}/storage/test
curl --request POST \
--url https://beta-api.plune.ai/v1/projects/example/storage/test \
--header 'Content-Type: application/json' \
--cookie plune_session=<plune_session> \
--data '{ "provider": "aws", "bucket": "acme-test-results", "region": "eu-central-1", "accessKeyId": "AKIAIOSFODNN7EXAMPLE", "secret": "example-not-a-real-secret" }'

Owner only. Sends the bucket six requests, in this order, and reports each: reach (an unsigned HEAD on the bucket — any HTTP answer counts, a refused one included; a redirect is not followed and fails the step), write (a small text file under <prefix>/.plune-check-…), read (the same file, and its text is what was written), cors (what a browser on the dashboard would be told — a preflight for GET), public (the same file with no signature) and delete. A step that depends on one that failed is skipped; delete runs whenever the write was taken, however the steps between went. A delete that failed — whatever its code — means the probe file may still be in the bucket, and so may one whose write got no answer in time: the data may have arrived all the same. No body checks the SAVED connection, and what it found is kept (verifiedAt, verifyResult of GET); a body with the fields of PUT checks a connection before it is saved and keeps nothing. The Secret may be left out of such a body under the rule of PUT. Every request goes through the one function that refuses private addresses, redirects and anything but https, and takes 5 seconds at most. What the bucket answers is read for one word and its status — never kept, never repeated here. At most 10 checks a minute for a project, counted after the role is checked, so a refusal costs none. Session cookie only.

id
required
string

An id from GET /v1/projects

Media type application/json

A bucket, and the key that opens it. The fields a provider needs differ, and one it has no use for is refused by name: aws takes region; r2 takes accountId (the region is always auto, so a region is refused); s3 takes endpoint and region, and pathStyle for a service that wants <host>/<bucket>/<key>. The bucket, the region, the accountId and the endpoint are judged by the rules for an address before anything is saved or any request is made; a refusal names the field and never what was written in it.

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
prefix

The folder in the bucket the files go under: folder names separated by /, made of letters, digits and . _ -; no empty folder, no . or .. folder, no / at the start or the end.

string
default: plune <= 120 characters /^[A-Za-z0-9._-]+(/[A-Za-z0-9._-]+)*$/
region

Required for aws and s3; refused for r2.

string
>= 1 characters <= 64 characters
accountId

Required for r2; refused for the others.

string
>= 1 characters <= 64 characters
endpoint

Required for s3 — an https address; refused for the others.

string
>= 1 characters <= 2048 characters
pathStyle

s3 only; default false.

boolean
accessKeyId
required

Visible characters, no spaces. Kept as it is, and shown back as its last four characters only.

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

Write-only: stored encrypted, bound to the project, and in no response, no log and no error message. Required for a new connection, and whenever the provider, the accountId, the endpoint or the accessKeyId is not the saved one; when only the bucket, the prefix, the region or pathStyle change, leaving it out keeps the saved one.

string
>= 1 characters <= 1024 characters /^[\x21-\x7e]{1,1024}$/
Example
{
"provider": "aws",
"bucket": "acme-test-results",
"region": "eu-central-1",
"accessKeyId": "AKIAIOSFODNN7EXAMPLE",
"secret": "example-not-a-real-secret"
}

What the check found — ok is true when no step failed; warnings holds bucket_public when the file could be read with no signature (anyone with the link can read the files of this bucket; a warning does not fail the check). A failed step carries one of these codes: unreachable — the address does not answer: not a public https address, a name that does not resolve, a refused connection, a redirect (on AWS a redirect is wrong_region), or no answer in 5 seconds; tls — the server’s certificate cannot be trusted (expired, for another name, self-signed); bad_credentials — the bucket does not know the access key id, or the Secret does not belong to it, or a token has expired; no_bucket — there is no bucket of that name at that address; wrong_region — the bucket is in another region than the one given (AWS answers a request sent to the wrong region with a redirect, which the check does not follow; the first step, reach, names it); write_denied, read_denied, delete_denied — the key may not write, read or delete files in the folder; cors_missing — the bucket tells a browser on the dashboard nothing that lets it read the files (a CORS rule allowing GET from the dashboard is the owner’s to add in the bucket); unexpected — the bucket answered with a status the check did not want and a word it does not know: the step carries that status.

Media type application/json
object
ok
required
boolean
steps
required
Array<object>
object
step
required
string
Allowed values: reach write read cors public delete
result
required
string
Allowed values: ok failed skipped
code

Present exactly when result is failed.

string
Allowed values: unreachable tls bad_credentials no_bucket wrong_region write_denied read_denied delete_denied cors_missing unexpected
status

The HTTP status the bucket answered with, when it answered and the step did not want that. Never its body.

integer
warnings
required
Array<string>
Allowed values: bucket_public
testedAt
required
string format: date-time
Example
{
"ok": true,
"steps": [
{
"step": "reach",
"result": "ok"
},
{
"step": "write",
"result": "ok"
},
{
"step": "read",
"result": "ok"
},
{
"step": "cors",
"result": "ok"
},
{
"step": "public",
"result": "ok"
},
{
"step": "delete",
"result": "ok"
}
],
"warnings": [],
"testedAt": "2026-09-16T09:12:04.151Z"
}

Invalid body, or the connection to check is not complete — the same rules as PUT; nothing was sent to the bucket

Media type application/json
object
error
required
string
Example
{
"error": "validation failed — secret: is required for a new connection, and whenever the provider, the account, the endpoint or the access key id changes"
}

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 body — nothing is connected to check

Media type application/json
object
error
required
string
Examples

No such project among yours

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

More than 10 checks 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; nothing was sent

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