M2M tokens
Machine login for CI and Coderunner. The secret exchanges for a short Control Plane JWT — never a Dataplane key.
M2M tokens (sidebar → Administer) issue a confidential client for workloads and CI. The secret is shown once. Exchange it for a short Control Plane JWT (typ=m2m) and call Vault and Files on Control Plane.
M2M is not an Access subject. v1 is Team Writer on that workspace: full Vault (no collection ACL) and Files. Secrets are off unless you turn on Read secrets for the client (see Read secrets). Do not put the secret in a browser bundle.
Never put DATAPLANE_SERVICE_API_KEY in a Coderunner or federated app. Control Plane uses that key on the server hop only.
Create a client
Open M2M tokens
Administer → M2M tokens. Select the workspace.
New client
Name the client (optional client_id). Tick Can read secrets only if the workload must read Secrets. Copy the secret now — you cannot see it again. Rotate replaces the secret.
Attach or use from CI
Attach the client on a Coderunner, then deploy. Deploy injects GROUNDFLOOR_M2M_CLIENT_ID and GROUNDFLOOR_M2M_CLIENT_SECRET. From a laptop or GitHub Actions, set the same variables and run gf m2m token.
Keep a dedicated client per Coderunner if you need isolated rotate and audit. Use a separate client for GitHub Actions if you need a stable secret that is not tied to one workload.
Call Control Plane
gf m2m token # prints a short JWT; not gf token
curl -sS -H "Authorization: Bearer $(gf m2m token)" \
"$CONTROLPLANE_URL/v1/workspaces/$GROUNDFLOOR_WORKSPACE_ID/vault/collections"Cache the token — do not mint one per request. POST …/m2m/token is rate-limited per client and per IP (default 20/minute each, see M2M API). A workload that calls gf m2m token (or POST …/m2m/token directly) on every incoming request, instead of reusing the JWT for its lifetime, will exhaust that limit under any real traffic and start failing with 429 — this has taken down a workload in practice. Cache the token and its expiry in memory, reuse it until shortly before expires_in (e.g. refresh at 80% of the TTL), and treat a 429/401 from the mint call as a retryable condition, not an unhandled error that crashes the process.
Read secrets
The same token reads Secrets (GET …/secrets/{key}) only if the client has Read secrets on (the Read secrets column in the clients table, or Can read secrets at create; API scope secrets:read). Without it, Secrets calls return 403. With it, the client can list, compare, and reveal every secret in the workspace, across all environments. It can never write, delete, or promote secrets. A change applies to the next token the client mints.
While Coderunner's deploy-time inject_secret_keys is disabled platform-wide (known issue), this is the way to get a secret into a workload: turn on Read secrets for the attached client, mint a token with the client credentials the platform already injects (GROUNDFLOOR_M2M_CLIENT_ID/GROUNDFLOOR_M2M_CLIENT_SECRET), then call GET …/secrets/{key} from the workload's own startup code.
POST /v1/workspaces/{id}/m2m/token with client_id and secret returns the same JWT. gf token is a builder credential from gf login.
New Coderunners cannot call Vault or Files until an M2M client is attached and the workload is redeployed.
Related API
- M2M API —
POST /v1/workspaces/{id}/m2m/token, clients, scopes, rotate, Coderunner attach
Related
- Access — site roles, not M2M
- Coderunner — attach on the workload
- Developers —
gf m2m token - Authentication — optional end-user login