V-Secrets API
Standard REST over HTTPS. One header for authentication, JSON in and out, nothing to install. Everything the console does is available here.
https://api.vsecrets.dev/api/v1Quickstart
From an empty workspace to reading an encrypted secret. Create the project and runtime key in the console, then everything else is API.
Store a secret
curl -X POST https://api.vsecrets.dev/api/v1/projects/$PROJECT_ID/secrets \
-H "X-API-Key: $VSECRETS_KEY" \
-H "Content-Type: application/json" \
-d '{
"key": "DATABASE_URL",
"value": "postgresql://user:pass@host:5432/db",
"description": "Primary database"
}'Read it back
Reveal returns the decrypted value and writes an audit entry recording which credential asked for it.
curl -X POST \ https://api.vsecrets.dev/api/v1/projects/$PROJECT_ID/secrets/DATABASE_URL/reveal \ -H "X-API-Key: $VSECRETS_KEY"
{
"key": "DATABASE_URL",
"value": "postgresql://user:pass@host:5432/db",
"version": 1,
"updated_at": "2026-08-21T14:22:07Z"
}Authentication
Two credentials, for two different situations.
Runtime keys
For services, CI pipelines and scripts. Send as X-API-Key. Scopeable to a single project, revocable at any time, and never expiring unless you give them a date.
curl https://api.vsecrets.dev/api/v1/projects \ -H "X-API-Key: vsec_live_3loFSNE1..."
Bearer tokens
For user sessions in the console. Short-lived, refreshed automatically. You generally won't use these directly.
curl https://api.vsecrets.dev/api/v1/users/me \ -H "Authorization: Bearer eyJhbGciOiJIUzI1NiIs..."
Scopes
Runtime keys carry explicit permissions. The read/reveal split is the one worth understanding: secrets:read returns names and metadata, while secrets:reveal is what actually decrypts a value.
projects:readList projects and read their metadataprojects:writeCreate and update projectssecrets:readList secret keys — values stay hiddensecrets:revealDecrypt and return secret valuessecrets:writeCreate and update secretssecrets:deletePermanently remove secretsapi_keys:writeIssue and revoke other runtime keysA key with this scope can mint itself broader access. Grant it only to administrative tooling — revoking the original won't contain a leak.Common combinations
Inventoryprojects:read secrets:readDashboards and drift checks — sees what exists, decrypts nothing.Runtime+ secrets:revealWhat a deployed service needs, and nothing more.Deploy+ secrets:writeProvisioning tooling and CI, not a running app.Projects
Projects group secrets and are the unit of cryptographic isolation — each has its own derived encryption key, so compromising one doesn't expose another.
/projects/projects/projects/{id}curl -X POST https://api.vsecrets.dev/api/v1/projects \
-H "X-API-Key: $VSECRETS_KEY" \
-H "Content-Type: application/json" \
-d '{
"name": "checkout-production",
"description": "Payment service credentials",
"environment": "production"
}'Secrets
Values are encrypted with AES-256-GCM before reaching the database. Each ciphertext is bound to its project, key name and version — moving encrypted data between projects makes decryption fail rather than silently succeed.
/projects/{id}/secrets/projects/{id}/secrets/projects/{id}/secrets/{key}/revealVersioning
Updating a secret creates a new version rather than overwriting. Rotations stay reversible, and the audit trail keeps the history intact.
Runtime keys
/api-keys/api-keys/api-keys/{id}/rotate/users/me/api-keys/{id}curl -X POST https://api.vsecrets.dev/api/v1/api-keys \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{
"name": "checkout-service",
"project_id": "b3f2...",
"scopes": ["projects:read", "secrets:read", "secrets:reveal"],
"expires_in_days": 90
}'Rotating a key
Rotation issues a replacement with identical scopes and project binding, and keeps the old key working for a grace period. Your services pick up the new value on their own deploy cycle instead of needing a coordinated cutover.
curl -X POST https://api.vsecrets.dev/api/v1/api-keys/$KEY_ID/rotate \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{"grace_period_hours": 24}'{
"new_key": {
"id": "9f1c...",
"api_key": "vsec_live_3loFSNE1...",
"key_prefix": "vsec_live_3loFSNE1"
},
"old_key_prefix": "vsec_live_yCknU6v3",
"grace_expires_at": "2026-08-22T14:22:07Z",
"message": "Key rotated. The previous key keeps working until..."
}Set grace_period_hours to 0 to revoke the old key immediately. Use that when the key is known to be compromised and you'd rather take the outage than leave it live.
Audit logs
Every read, write and reveal is recorded with the credential that made it, the source address, and the outcome.
/audit-logs?limit=100{
"action": "POST",
"resource_type": "secret",
"request_path": "/projects/b3f2.../secrets/STRIPE_KEY/reveal",
"api_key_id": "9f1c...",
"ip_address": "203.0.113.42",
"status_code": 200,
"created_at": "2026-08-21T14:22:07Z"
}When api_key_id is null, the request came from a user session rather than a runtime key.
Error handling
Errors return a JSON body with a detail field describing what went wrong.
{
"detail": "Secret with key 'DATABASE_URL' already exists"
}400Malformed body, or a duplicate key in the project401Missing, expired or revoked credentialAlso returned when a rotated key is used after its grace period ended.402Plan limit reachedThe request was valid and authorized — the account needs a higher plan.403Valid credential, but scoped elsewhere404Project or secret doesn't exist in this workspaceRate limits
Limits scale with plan. Exceeding them returns 429 — back off and retry.
Free100 requests/minutePro1,000 requests/minuteBusiness2,500 requests/minute