Check that a connection works
const url = 'https://beta-api.plune.ai/v1/projects/example/storage/test';const options = { method: 'POST', headers: { cookie: 'plune_session=<plune_session>', 'Content-Type': 'application/json' }, body: '{"provider":"aws","bucket":"acme-test-results","region":"eu-central-1","accessKeyId":"AKIAIOSFODNN7EXAMPLE","secret":"example-not-a-real-secret"}'};
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/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.
Authorizations
Section titled “Authorizations ”Parameters
Section titled “ Parameters ”Path Parameters
Section titled “Path Parameters ”An id from GET /v1/projects
Request Body
Section titled “Request Body ”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
aws — Amazon S3; r2 — Cloudflare R2; s3 — any other S3-compatible service (MinIO and the like).
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.
Required for aws and s3; refused for r2.
Required for r2; refused for the others.
Required for s3 — an https address; refused for the others.
s3 only; default false.
Visible characters, no spaces. Kept as it is, and shown back as its last four characters only.
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.
Example
{ "provider": "aws", "bucket": "acme-test-results", "region": "eu-central-1", "accessKeyId": "AKIAIOSFODNN7EXAMPLE", "secret": "example-not-a-real-secret"}Responses
Section titled “ Responses ”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.
object
object
Present exactly when result is failed.
The HTTP status the bucket answered with, when it answered and the step did not want that. Never its body.
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
object
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
object
Example
{ "error": "project owner only"}No such project among yours, or — no body — nothing is connected to check
object
Examples
No such project among yours
{ "error": "project 'u-shop::default' not found"}Nothing saved to check
{ "error": "no bucket is connected"}More than 10 checks 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; nothing was sent
object
Example
{ "error": "storage is not configured on this server — contact your operator"}