Secrets
Secrets are how Paperclip keeps sensitive values out of agent configs while still letting agents use them at runtime. The API is board-only and company-scoped.
Use this API when you need to:
- list the secrets stored for a company
- create a new secret value
- rotate a secret without changing how agents reference it
- update secret metadata like the display name or description
- remove a secret entirely
- inspect which providers are available in your workspace
Secret Providers
Section titled “Secret Providers”GET /api/companies/{companyId}/secret-providersReturns the providers available for your workspace.
Providers may include cloud-managed storage and external vault integrations. The provider descriptor includes:
idlabelrequiresExternalRef
If requiresExternalRef is true, the provider expects an external reference string in addition to the secret value.
curl "https://api.paperclip.inc/api/companies/company-1/secret-providers" \ -H "Authorization: Bearer <board-token>"const res = await fetch("https://api.paperclip.inc/api/companies/company-1/secret-providers", { headers: { Authorization: `Bearer ${boardToken}`, },});
const providers = await res.json();import requests
response = requests.get( "https://api.paperclip.inc/api/companies/company-1/secret-providers", headers={ "Authorization": f"Bearer {board_token}", },)
providers = response.json()What Is Stored
Section titled “What Is Stored”Paperclip stores secrets in two layers:
company_secretsstores the secret record, metadata, and latest version pointercompany_secret_versionsstores the versioned material
For each secret, the API exposes metadata such as:
idcompanyIdnameproviderexternalReflatestVersiondescriptioncreatedByAgentIdcreatedByUserIdcreatedAtupdatedAt
What you do not get back is the plaintext secret value itself.
Secret values are encrypted at rest using the configured cloud provider. The version rows also keep a SHA-256 hash of the original value.
List Secrets
Section titled “List Secrets”GET /api/companies/{companyId}/secretsReturns the company secrets, newest first by creation time.
Use this when you want to see:
- which secrets exist
- which provider each secret uses
- what version is currently the latest
- whether a secret has a description or external reference
The secret values themselves are never returned.
curl "https://api.paperclip.inc/api/companies/company-1/secrets" \ -H "Authorization: Bearer <board-token>"const res = await fetch("https://api.paperclip.inc/api/companies/company-1/secrets", { headers: { Authorization: `Bearer ${boardToken}`, },});
const secrets = await res.json();import requests
response = requests.get( "https://api.paperclip.inc/api/companies/company-1/secrets", headers={ "Authorization": f"Bearer {board_token}", },)
secrets = response.json()Create Secret
Section titled “Create Secret”POST /api/companies/{companyId}/secretsContent-Type: application/jsonBody:
| Field | Required | Notes |
|---|---|---|
name | Yes | Unique within the company. |
value | Yes | The plaintext secret value to store. |
provider | No | Defaults to the workspace default provider. |
description | No | Human-readable note for operators. |
externalRef | No | Required by some external providers. |
The value is stored as a new version immediately:
- version
1is created latestVersionis set to1- the API returns the secret metadata, not the plaintext
If another secret already exists with the same name in the same company, the API returns a conflict.
curl -X POST "https://api.paperclip.inc/api/companies/company-1/secrets" \ -H "Authorization: Bearer <board-token>" \ -H "Content-Type: application/json" \ -d '{ "name": "anthropic-api-key", "value": "sk-ant-...", "description": "Primary Claude key for worker agents" }'const res = await fetch("https://api.paperclip.inc/api/companies/company-1/secrets", { method: "POST", headers: { Authorization: `Bearer ${boardToken}`, "Content-Type": "application/json", }, body: JSON.stringify({ name: "anthropic-api-key", value: "sk-ant-...", description: "Primary Claude key for worker agents", }),});
const created = await res.json();import requests
response = requests.post( "https://api.paperclip.inc/api/companies/company-1/secrets", headers={ "Authorization": f"Bearer {board_token}", "Content-Type": "application/json", }, json={ "name": "anthropic-api-key", "value": "sk-ant-...", "description": "Primary Claude key for worker agents", },)
created = response.json()Update Secret
Section titled “Update Secret”PATCH /api/secrets/{secretId}Content-Type: application/jsonBody:
| Field | Required | Notes |
|---|---|---|
name | No | Rename the secret. Must still be unique within the company. |
description | No | Update the operator-facing note. |
externalRef | No | Update the provider reference without creating a new secret version. |
This endpoint does not change the secret value.
Use it when you want to tidy up metadata or point an external-backed secret at a new provider reference without changing how the secret is versioned in Paperclip.
curl -X PATCH "https://api.paperclip.inc/api/secrets/secret-uuid" \ -H "Authorization: Bearer <board-token>" \ -H "Content-Type: application/json" \ -d '{ "name": "anthropic-api-key-prod", "description": "Production Claude key" }'const res = await fetch("https://api.paperclip.inc/api/secrets/secret-uuid", { method: "PATCH", headers: { Authorization: `Bearer ${boardToken}`, "Content-Type": "application/json", }, body: JSON.stringify({ name: "anthropic-api-key-prod", description: "Production Claude key", }),});
const updated = await res.json();import requests
response = requests.patch( "https://api.paperclip.inc/api/secrets/secret-uuid", headers={ "Authorization": f"Bearer {board_token}", "Content-Type": "application/json", }, json={ "name": "anthropic-api-key-prod", "description": "Production Claude key", },)
updated = response.json()Rotate Secret
Section titled “Rotate Secret”POST /api/secrets/{secretId}/rotateContent-Type: application/jsonBody:
| Field | Required | Notes |
|---|---|---|
value | Yes | The new plaintext secret value. |
externalRef | No | If omitted, Paperclip keeps the existing external reference for the secret. |
Rotation creates a new immutable version and advances latestVersion.
Important behavior:
- the secret ID stays the same
- existing references using
version: "latest"automatically pick up the new value - references pinned to a numeric version keep using that version
- the old versions remain in storage as version history
This is the endpoint to use when the credential changes but the secret identity stays the same.
curl -X POST "https://api.paperclip.inc/api/secrets/secret-uuid/rotate" \ -H "Authorization: Bearer <board-token>" \ -H "Content-Type: application/json" \ -d '{ "value": "sk-ant-new-value..." }'const res = await fetch("https://api.paperclip.inc/api/secrets/secret-uuid/rotate", { method: "POST", headers: { Authorization: `Bearer ${boardToken}`, "Content-Type": "application/json", }, body: JSON.stringify({ value: "sk-ant-new-value...", }),});
const rotated = await res.json();import requests
response = requests.post( "https://api.paperclip.inc/api/secrets/secret-uuid/rotate", headers={ "Authorization": f"Bearer {board_token}", "Content-Type": "application/json", }, json={ "value": "sk-ant-new-value...", },)
rotated = response.json()Delete Secret
Section titled “Delete Secret”DELETE /api/secrets/{secretId}Deletes the secret and its version history.
This is a hard delete at the API layer:
- the secret row is removed
- the version rows cascade away with it
- future runtime resolution will fail for any configs still pointing at that secret
Delete only when you are sure nothing should resolve that secret anymore.
Using Secrets In Agent Config
Section titled “Using Secrets In Agent Config”Agent adapter configs can reference secrets in env instead of storing inline plaintext.
The supported binding format is:
{ "env": { "ANTHROPIC_API_KEY": { "type": "secret_ref", "secretId": "secret-uuid", "version": "latest" } }}You can also pin to a numeric version:
{ "env": { "ANTHROPIC_API_KEY": { "type": "secret_ref", "secretId": "secret-uuid", "version": 2 } }}What happens at runtime:
- Paperclip validates that the secret belongs to the same company
- it resolves the requested version
- it decrypts or fetches the underlying value through the provider
- it injects the plaintext into the agent runtime environment
Versioning rules:
version: "latest"tracks future rotations automatically- a numeric version stays pinned to that exact historical value
- if you omit
version, Paperclip treats it aslatest
The secret reference form is the preferred pattern for anything sensitive.
Tip: Use
version: "latest"for credentials you expect to rotate. Use a pinned numeric version only when you need the agent to keep using a known historical value.
curl -X POST "https://api.paperclip.inc/api/companies/company-1/agents" \ -H "Authorization: Bearer <board-token>" \ -H "Content-Type: application/json" \ -d '{ "name": "Worker", "role": "engineer", "adapterType": "http", "adapterConfig": { "env": { "ANTHROPIC_API_KEY": { "type": "secret_ref", "secretId": "secret-uuid", "version": "latest" } } } }'await fetch("https://api.paperclip.inc/api/companies/company-1/agents", { method: "POST", headers: { Authorization: `Bearer ${boardToken}`, "Content-Type": "application/json", }, body: JSON.stringify({ name: "Worker", role: "engineer", adapterType: "http", adapterConfig: { env: { ANTHROPIC_API_KEY: { type: "secret_ref", secretId: "secret-uuid", version: "latest", }, }, }, }),});import requests
requests.post( "https://api.paperclip.inc/api/companies/company-1/agents", headers={ "Authorization": f"Bearer {board_token}", "Content-Type": "application/json", }, json={ "name": "Worker", "role": "engineer", "adapterType": "http", "adapterConfig": { "env": { "ANTHROPIC_API_KEY": { "type": "secret_ref", "secretId": "secret-uuid", "version": "latest", } } }, },)Practical Notes
Section titled “Practical Notes”- The secret name must be unique within the company.
- Create uses version
1; rotate increments the version counter. updatechanges metadata only, not the stored value.rotatecreates a new stored value and updates the latest pointer.- The available secret providers are returned by
GET /api/companies/{companyId}/secret-providers. - The API is board-only and company-scoped throughout.