Secrets
Encrypted, write-only project credentials — the API keys your providers and tools authenticate with.
Overview
A secret is a named, encrypted value scoped to one project.
You write the value once; nothing ever reads it back out through the API. What
consumes it is another resource referencing it by id: an
AI provider names a secret_id to authenticate with
(Create a provider
does exactly that), and a
tool can interpolate one into an outbound request header with a
{{secret:…}} token, resolved at call time.
This module is a verbatim mirror of the runtime: every field, method, status code and error shape is the runtime's own, re-rooted under the project in the path.
See the OpenAPI spec for the full endpoint and schema reference, or browse it rendered under API Reference → Secrets.
Data Model
Secret
| Field | Type | Description |
|---|---|---|
id | string | Public secret ID. |
project_id | string | The owning project. |
name | string | Human-readable name. |
has_value | boolean | Whether a value is stored. The value itself is never returned. |
created_at | string (date-time) | |
updated_at | string (date-time) |
Key Concepts
Write-only by construction
POST /v1/projects/{project_id}/secrets
takes name and value; the response — and every later read — carries
has_value instead of the value, as
Create a provider
shows. There is no endpoint that returns it, so a
leaked read credential cannot exfiltrate the key it was used to store. To
rotate, send a new value to
PATCH /v1/projects/{project_id}/secrets/{secret_id};
the resources referencing the secret keep working, because they reference the
id and not the value.
Deleting a referenced secret
DELETE /v1/projects/{project_id}/secrets/{secret_id}
refuses a secret an AI provider still points at, answering
409 SECRET_HAS_DEPENDENTS with the number of dependents in meta. Repoint
those providers first (secret_id on
PATCH /v1/projects/{project_id}/ai-providers/{ai_provider_id}),
then delete the secret.
force=true deletes it anyway — and destroys every AI provider referencing
it rather than leaving them unauthenticated. That is a delete of records you
did not name, so reach for it only when those providers are going away with the
credential.
Examples
Store a provider API key
- CLI
- SDK
- curl
naturali create-secret \
--project-id proj_V1StGXR8Z5jdHi6B \
--name openai-production \
--value "$OPENAI_API_KEY"
const { data: secret } = await naturali.secrets.createSecret({
path: { project_id: 'proj_V1StGXR8Z5jdHi6B' },
body: { name: 'openai-production', value: process.env.OPENAI_API_KEY! },
});
curl -X POST https://api.naturali.ai/v1/projects/proj_V1StGXR8Z5jdHi6B/secrets \
-H "Authorization: Bearer $NATURALI_TOKEN" \
-H "Content-Type: application/json" \
-d "{ \"name\": \"openai-production\", \"value\": \"$OPENAI_API_KEY\" }"
Rotate it
- CLI
- SDK
- curl
naturali update-secret \
--project-id proj_V1StGXR8Z5jdHi6B \
--secret-id sec_V1StGXR8Z5jdHi6B \
--value "$OPENAI_API_KEY_NEW"
await naturali.secrets.updateSecret({
path: {
project_id: 'proj_V1StGXR8Z5jdHi6B',
secret_id: 'sec_V1StGXR8Z5jdHi6B',
},
body: { value: process.env.OPENAI_API_KEY_NEW! },
});
curl -X PATCH https://api.naturali.ai/v1/projects/proj_V1StGXR8Z5jdHi6B/secrets/sec_V1StGXR8Z5jdHi6B \
-H "Authorization: Bearer $NATURALI_TOKEN" \
-H "Content-Type: application/json" \
-d "{ \"value\": \"$OPENAI_API_KEY_NEW\" }"
List them
- CLI
- SDK
- curl
naturali list-secrets --project-id proj_V1StGXR8Z5jdHi6B
const { data: secrets } = await naturali.secrets.listSecrets({
path: { project_id: 'proj_V1StGXR8Z5jdHi6B' },
});
curl https://api.naturali.ai/v1/projects/proj_V1StGXR8Z5jdHi6B/secrets \
-H "Authorization: Bearer $NATURALI_TOKEN"