Secrets API
List, reveal, upsert, and delete workspace-scoped secrets.
Base path: /v1/workspaces/{workspace_id}/secrets
Secrets are encrypted at rest, and kept per environment. Every reveal is logged in the activity log.
Every route except the comparison and promotion routes takes an optional ?environment_id= query parameter to pick the environment. Without it, the workspace-wide secrets are used. Responses include the environment_id they applied to.
Keys must start with a letter or underscore, contain only letters, digits and underscores, and be at most 256 characters. Other names return 422.
See also: Secrets
List secrets
GET /v1/workspaces/{workspace_id}/secretsReturns keys and descriptions — values are redacted. Platform-managed keys are left out of the list.
Permission: read
curl -s -H "Authorization: Bearer $TOKEN" \
"https://platform.groundfloor.cloud/v1/workspaces/$WORKSPACE_ID/secrets" | jq .Response 200
{
"secrets": [
{ "key": "STRIPE_API_KEY", "description": "Payments", "environment_id": null }
],
"environment_id": null
}Reveal secret
GET /v1/workspaces/{workspace_id}/secrets/{key}Returns the plaintext value. Emits audit event secret.revealed.
Permission: write (M2M: secrets:read scope — see M2M callers)
curl -s -H "Authorization: Bearer $TOKEN" \
"https://platform.groundfloor.cloud/v1/workspaces/$WORKSPACE_ID/secrets/STRIPE_API_KEY" | jq .Response 200
{
"key": "STRIPE_API_KEY",
"value": "sk_live_…",
"description": "Payments",
"environment_id": null
}Upsert secret
PUT /v1/workspaces/{workspace_id}/secrets/{key}Create or update a secret. Response returns metadata only — never echoes the value.
Permission: write
curl -s -X PUT -H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{"value":"sk_live_…","description":"Payments"}' \
"https://platform.groundfloor.cloud/v1/workspaces/$WORKSPACE_ID/secrets/STRIPE_API_KEY" | jq .Request body
| Field | Type | Required |
|---|---|---|
value | string | Yes (up to 10,000 characters) |
description | string | No (up to 255 characters) |
Delete secret
DELETE /v1/workspaces/{workspace_id}/secrets/{key}Permission: delete
Returns 204 No Content.
Compare environments
GET /v1/workspaces/{workspace_id}/secrets/compare?source_environment_id=…&target_environment_id=…Shows which keys differ between two environments. Values are never returned. Permission: read.
{
"source_environment_id": "…",
"target_environment_id": "…",
"keys": [
{ "key": "STRIPE_API_KEY", "status": "both_differ" }
]
}status is only_source, only_target, both_same or both_differ. The two environments must differ (otherwise 400) and both must be active (otherwise 409).
Promote secrets
POST /v1/workspaces/{workspace_id}/secrets/promoteCopies secrets from one environment to another.
| Field | Type | Required | Description |
|---|---|---|---|
source_environment_id | string | Yes | Environment to copy from |
target_environment_id | string | Yes | Environment to copy to |
keys | string[] | No | Keys to copy. Omit to copy every non-platform-managed key |
mode | string | No | skip_existing (default) leaves keys that already exist in the target; overwrite replaces them |
dry_run | bool | No | Report what would be copied without writing. Default false |
Returns { source_environment_id, target_environment_id, mode, dry_run, copied: [], skipped: [], failed: [{ key, error }] }. Permission: write. Logged as secret.promoted.
M2M callers
M2M tokens do not get Secrets access from their workspace grant alone:
| Route | M2M with secrets:read | M2M without it |
|---|---|---|
| List, compare, reveal | Allowed | 403 |
| Upsert, delete, promote | 403 | 403 |
Grant the scope on the client (Portal M2M tokens → Read secrets, or scopes on the M2M client API). Human callers are unaffected.
Platform-managed keys
Secrets that Groundfloor manages, such as the data API's key and managed database credentials, can't be changed or deleted through this API.
Errors
| Status | Meaning |
|---|---|
| 403 | Missing permission, platform-managed key mutation, or M2M caller without secrets:read / attempting a write |
| 404 | Secret not found |
| 422 | Key name isn't valid, or the value or description is too long |
| 502 | The secret store is unavailable; retry |