Secrets
Encrypted storage for sensitive values such as API keys and credentials.
Overview
Secrets belong to a project. Values are encrypted at rest with AES-256-GCM, never returned, and can only be replaced. Every response carries has_value.
Secrets supply credentials to AI Providers at inference time: Connect Third-Party LLMs - Step 3 (Store provider credentials as secrets) and Step 4 (Create provider records).
See the Permissions Reference for the IAM action strings for this module.
Related Tutorials
- Connect Third-Party LLMs - Step 3 (Store provider credentials as secrets)
- Connect Third-Party LLMs - Step 4 (Create provider records)
- Call AWS and GCP APIs from an Agent - Step 2 (Store the AWS credentials as secrets)
Data Model
| Field | Type | Description |
|---|---|---|
id | string | Public identifier (e.g. sec_…) |
project_id | string | ID of the owning project |
name | string | Human-readable label |
has_value | boolean | true when an encrypted value is stored |
created_at | string | ISO 8601 creation timestamp |
updated_at | string | ISO 8601 last-updated timestamp |
Key Concepts
Secret References ({{secret:...}})
A string field supporting secret references may embed:
{{secret:sec_01HXYZ...}}
The token, not the value, is stored and echoed by GET/LIST; it is resolved at the point of use (e.g. before an outbound HTTP request). The token names the secret by id; any other form, such as the secret's name, is 400 INVALID_TEMPLATE_TOKEN at create/update. The secret must belong to the same project as the resource, else 400 SECRET_NOT_FOUND at create/update.
Supported fields:
| Resource | Field | Resolved when |
|---|---|---|
Tool (http) | execute.url, execute.headers values | The tool is called |
Tool (mcp) | mcp.url, mcp.headers values | The MCP server is contacted (tool listing and calls) |
| Trigger | tool_context values | The trigger fires |
{
"execute": {
"url": "https://api.example.com/convert",
"headers": { "Authorization": "Bearer {{secret:sec_01HXYZ}}" }
}
}
Updating the secret's value rotates the credential for every referencing tool on its next call.
For referencing a secret created in the same Formation template, see Sub Expressions.
Deletion
Deleting a secret still referenced by AI providers returns 409 Conflict; ?force=true cascade-deletes the dependents.
Configuration
| Environment Variable | Required | Description |
|---|---|---|
SECRETS_ENCRYPTION_KEY | Yes | 64-character hex string (32 bytes). Used for AES-256-GCM encryption of all stored secret values. |
Generate a key with:
openssl rand -hex 32
Examples
Create a secret
- CLI
- SDK
- curl
soat create-secret --project-id proj_ABC --name "OpenAI Key" --value "sk-abc123..."
import { SoatClient } from '@soat/sdk';
const soat = new SoatClient({ baseUrl: 'https://api.example.com', token: 'sk_...' });
const { data, error } = await soat.secrets.createSecret({
body: { project_id: 'proj_ABC', name: 'OpenAI Key', value: 'sk-abc123...' },
});
if (error) throw new Error(JSON.stringify(error));
curl -X POST https://api.example.com/api/v1/secrets \
-H "Authorization: Bearer <token>" \
-H "Content-Type: application/json" \
-d '{"project_id": "proj_ABC", "name": "OpenAI Key", "value": "sk-abc123..."}'
Update secret value
- CLI
- SDK
- curl
soat update-secret --secret-id sec_01 --value sk-abc123...
const { data, error } = await soat.secrets.updateSecret({
path: { secret_id: 'sec_01' },
body: { value: 'sk-abc123...' },
});
if (error) throw new Error(JSON.stringify(error));
curl -X PATCH https://api.example.com/api/v1/secrets/sec_01 \
-H "Authorization: Bearer <token>" \
-H "Content-Type: application/json" \
-d '{"value": "sk-abc123..."}'