Skip to content

Latest commit

 

History

History
187 lines (140 loc) · 5.19 KB

API.md

File metadata and controls

187 lines (140 loc) · 5.19 KB

Secrets Manager HTTP API (/v1)

This document is the source of truth for the wire contract between the server and every client (the Rust secrets-client crate, the Go secrets-manager-go module, and any future client). Change this document and the server together; clients track it.

Transport & auth

  • The server binds loopback only and speaks plain HTTP; TLS is terminated by a reverse proxy in front of it. Clients therefore require an https:// server URL. Plain http:// is accepted only for loopback hosts (127.0.0.0/8, ::1, localhost) to support same-host use; any other http:// URL is refused. Base URLs must not contain userinfo, query, or fragment parts.
  • Every endpoint except GET /v1/health requires a bearer token: Authorization: Bearer <token>.
  • A token may be scoped to a single project. A scoped token accessing any other project receives 403. GET /v1/projects returns only the scoped project for such tokens.
  • An unscoped token is an admin token. Token management endpoints require an admin token and return 403 for scoped tokens.
  • Request and response bodies are JSON (Content-Type: application/json). The server caps request bodies at 1 MiB (larger is 413); clients cap response reads at 1 MiB.
  • Tokens can carry an expiry (90-day default at creation). Expired tokens receive 401, exactly like revoked ones.

Validation rules

Field Rule
project name non-empty, ≤ 64 bytes, [A-Za-z0-9_-] only
token name non-empty, ≤ 64 bytes, [A-Za-z0-9_-] only
secret key non-empty, ≤ 128 bytes, [A-Za-z0-9_.-] only

Violations return 400.

Status codes

Code Meaning
200 OK (body follows)
201 Created (project or token)
204 No Content
400 Bad Request (invalid name, malformed/invalid JSON body)
401 Unauthorized (missing/invalid/revoked/expired token)
403 Forbidden (token not scoped to this project)
404 Not Found (unknown project or key; the name itself was valid)
409 Conflict (project already exists)
413 Payload Too Large (request body over 1 MiB)

Name validation runs before existence checks: a syntactically invalid project or key name is always 400, never 404.

No error response ever includes secret material, token values, or the master passphrase.

Endpoints

GET /v1/health — liveness (no auth)

200 OK
{ "status": "ok" }

GET /v1/projects — list projects

Scoped tokens see only their own project.

200 OK
{ "projects": [ { "name": "cdn", "created_at": "2026-07-04T12:00:00Z" } ] }

POST /v1/projects — create a project

// request
{ "name": "cdn" }

201 Created
{ "name": "cdn" }

Returns 400 for an invalid name, 409 if the project already exists. (Provisioning is an admin/API operation; the CLI clients do not expose it.)

GET /v1/projects/{project}/secrets — fetch all secrets

Returns a flat map of decrypted key → value for the project.

200 OK
{ "DATABASE_URL": "postgres://…", "API_KEY": "…" }

404 if the project does not exist.

PUT /v1/projects/{project}/secrets/{key} — set one secret

The value is created or updated (upsert); version is the new monotonic version of that key.

// request
{ "value": "postgres://…" }

200 OK
{ "key": "DATABASE_URL", "version": 2 }

400 for an invalid key, 404 if the project does not exist.

DELETE /v1/projects/{project}/secrets/{key} — delete one secret

204 No Content

404 if the project or key does not exist.

GET /v1/tokens — list tokens

Requires an admin token. The response never includes plaintext tokens or token hashes.

200 OK
{
  "tokens": [
    {
      "name": "ci",
      "scope": "app",
      "created_at": "2026-07-15T00:00:00Z",
      "expires_at": "2026-10-13T00:00:00Z",
      "revoked": false
    }
  ]
}

403 if the caller uses a scoped token.

POST /v1/tokens — issue a token

Requires an admin token. The plaintext token is returned once in the response and is never stored by the server.

// request
{ "name": "windows", "project": "app", "ttl_days": 90 }

201 Created
{
  "name": "windows",
  "scope": "app",
  "expires_at": "2026-10-13T00:00:00Z",
  "token": "shown-once"
}

Omit project for an admin token. Use "no_expiry": true instead of ttl_days for a token without expiry. Returns 400 for invalid names or TTL, and 403 if the caller uses a scoped token.

DELETE /v1/tokens/{name} — revoke tokens

Requires an admin token. Revokes all active tokens with the given name.

200 OK
{ "revoked": 1 }

Returns 400 for an invalid token name and 403 if the caller uses a scoped token.

Notes for client authors

  • Send the token only in the Authorization header — never in the URL, query string, or logs.
  • Treat secret values as sensitive in memory: prefer a wrapping/zeroizable type over plain strings, and never print them (including in Debug/String).
  • Only transport-level failures should trigger any offline-cache fallback; auth/HTTP status failures (401/403/404) must not fall back to cache.