Skip to content

Authentication

Two credentials, deliberately separate: a session for a person at a browser, a bearer token for a machine. The session mints tokens; the token cannot mint anything.

POST /v1/auth/request { "email": "[email protected]" } → 202
GET /v1/auth/verify?token=… → 200 + Set-Cookie

The link is single-use and lives 15 minutes.

Rate limits. Five requests per 15 minutes per address, twenty per 15 minutes per IP. Both answer 429 with Retry-After and the same body — which limit you hit is not part of the answer.

Set-Cookie: plune_session=…; Max-Age=604800; Path=/; HttpOnly; Secure; SameSite=Lax

Seven days, HttpOnly (script cannot read it), Secure, and SameSite=Lax — which is what stops a cross-site POST from carrying it. The value is signed; a cookie with the wrong signature is a 401, not an unknown user.

POST /v1/tokens → 201 { "token": "plune_…" } ← the only time the value exists
GET /v1/tokens → the list, metadata only
DELETE /v1/tokens/{id} → 204

Only a hash of the token is stored, so the list can never show you the value again and a database leak yields nothing usable. Send it as Authorization: Bearer plune_….

Ninety days. Long enough to survive the thing a token exists for — a pipeline configured once and left alone — and short enough to be distinguishable from forever. The expiry is shown twice: beside the value when you create it, and in the list afterwards. The second one matters more, since “why did my CI stop?” arrives months after anyone thought about the token.

Ten live tokens per account. Expired ones do not occupy a slot. The list says “N of 10”, and past the ceiling the Generate button is disabled with the way out named — revoke one; the API answers 409 with the same explanation. Minting has a three-second cooldown: a second request inside it answers 429 with Retry-After: 3, and the dashboard keeps the button down for those three seconds so you do not see that answer by accident.