Skip to content Skip to content

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

GET /api/companies/{companyId}/secret-providers

Returns the providers available for your workspace.

Providers may include cloud-managed storage and external vault integrations. The provider descriptor includes:

  • id
  • label
  • requiresExternalRef

If requiresExternalRef is true, the provider expects an external reference string in addition to the secret value.

Terminal window
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()

Paperclip stores secrets in two layers:

  • company_secrets stores the secret record, metadata, and latest version pointer
  • company_secret_versions stores the versioned material

For each secret, the API exposes metadata such as:

  • id
  • companyId
  • name
  • provider
  • externalRef
  • latestVersion
  • description
  • createdByAgentId
  • createdByUserId
  • createdAt
  • updatedAt

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.


GET /api/companies/{companyId}/secrets

Returns 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.

Terminal window
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()

POST /api/companies/{companyId}/secrets
Content-Type: application/json

Body:

FieldRequiredNotes
nameYesUnique within the company.
valueYesThe plaintext secret value to store.
providerNoDefaults to the workspace default provider.
descriptionNoHuman-readable note for operators.
externalRefNoRequired by some external providers.

The value is stored as a new version immediately:

  • version 1 is created
  • latestVersion is set to 1
  • 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.

Terminal window
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()

PATCH /api/secrets/{secretId}
Content-Type: application/json

Body:

FieldRequiredNotes
nameNoRename the secret. Must still be unique within the company.
descriptionNoUpdate the operator-facing note.
externalRefNoUpdate 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.

Terminal window
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()

POST /api/secrets/{secretId}/rotate
Content-Type: application/json

Body:

FieldRequiredNotes
valueYesThe new plaintext secret value.
externalRefNoIf 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.

Terminal window
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 /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.


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 as latest

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.

Terminal window
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",
}
}
},
},
)

  • The secret name must be unique within the company.
  • Create uses version 1; rotate increments the version counter.
  • update changes metadata only, not the stored value.
  • rotate creates 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.