API reference
Keys
The keys that protect your organization's data, your own keys for encrypting and signing, and your personal keys.
See Keys and secrets. Nothing here returns key material, wrapped or not, or any AWS KMS id: only our own key ids (ck_…) and metadata. keys:read lists and describes every key that protects the organization's data; keys:write rotates, schedules, re-encrypts and manages the organization's own keys; keys:use encrypts, decrypts, signs and verifies with them, and each of those counts towards the plan's monthly key operations. /v1/me/keys shows a person their own keys (Health, their personal Drive and Photos), which no organization sees.
GET /v1/orgs/:orgId/keys
Every key that protects the org's data, grouped by app on the page: Drive, secrets, connectors, integrations and the org's own keys (keys:read). With only keys:use, the org's own keys. Also the plan's limits and this month's operations.
Auth: user access token or platform agent key · Allowed: keys:read; keys:use
| Path parameter | Description |
|---|---|
:orgId | Organization id (org_…). |
Response 200
{
keys: {
uses30d: number
lastUsedAt: null | number
keyId: string
name: string
description: null | string
app: "keys" | "drive" | "health" | "secrets" | "connectors" | "integrations"
appLabel: string
protects: string
scope: {
kind: "org" | "project" | "environment" | "space" | "person"
id: string
}
owner: "org" | "person"
algorithm: "aes-256-gcm" | "ed25519" | "ecdsa-p256" | "s3"
state: "disabled" | "enabled" | "destroyed"
currentVersion: number
managed: boolean
createdAt: number
rotatedAt: null | number
rotateEveryDays: null | number
nextRotationAt: null | number
destroyedAt: null | number
}[]
limits: {
keysMax: number
keyOpsPerMonth: number
secretsMax: number
keyMonthlyPriceCents: number
keyOpsPer10kPriceCents: number
}
operationsThisMonth: number
}Errors
| Status | Message |
|---|---|
403 | Missing scope keys:read |
POST /v1/orgs/:orgId/keys
Makes one of the org's own keys (keys:write).
Auth: user access token or platform agent key · Scope: keys:write
| Path parameter | Description |
|---|---|
:orgId | Organization id (org_…). |
Request body
| Field | Type | Required | Default | Notes |
|---|---|---|---|---|
name | string | Yes | 1–100 characters; trimmed | |
algorithm | "aes-256-gcm" | "ed25519" | "ecdsa-p256" | No | "aes-256-gcm" | |
description | string | No | up to 500 characters | |
scope | object | No | ||
scope.kind | "org" | "project" | "environment" | Yes | ||
scope.id | string | Yes | 1–200 characters | |
rotateEveryDays | integer | No | 1–3650 |
Response 201
{
key: {
keyId: string
name: string
description: null | string
app: "keys" | "drive" | "health" | "secrets" | "connectors" | "integrations"
appLabel: string
protects: string
scope: {
kind: "org" | "project" | "environment" | "space" | "person"
id: string
}
owner: "org" | "person"
algorithm: "aes-256-gcm" | "ed25519" | "ecdsa-p256" | "s3"
state: "disabled" | "enabled" | "destroyed"
currentVersion: number
managed: boolean
createdAt: number
rotatedAt: null | number
rotateEveryDays: null | number
nextRotationAt: null | number
destroyedAt: null | number
}
}Errors
| Status | Message |
|---|---|
400 | An org key's scope is the org. |
GET /v1/orgs/:orgId/keys/:keyId
A key: versions still in use, created, rotated, last used, uses in the last 30 days, recent re-encryption jobs.
Auth: user access token or platform agent key · Allowed: keys:read; keys:use
| Path parameter | Description |
|---|---|
:orgId | Organization id (org_…). |
:keyId | Key id (key_…). |
Response 200
{
key: {
keyId: string
name: string
description: null | string
app: "keys" | "drive" | "health" | "secrets" | "connectors" | "integrations"
appLabel: string
protects: string
scope: {
kind: "org" | "project" | "environment" | "space" | "person"
id: string
}
owner: "org" | "person"
algorithm: "aes-256-gcm" | "ed25519" | "ecdsa-p256" | "s3"
state: "disabled" | "enabled" | "destroyed"
currentVersion: number
managed: boolean
createdAt: number
rotatedAt: null | number
rotateEveryDays: null | number
nextRotationAt: null | number
destroyedAt: null | number
versions: {
version: number
createdAt: number
current: boolean
uses30d: number
adopted: boolean
}[]
uses30d: number
lastUsedAt: null | number
usage: {
day: string
uses: number
}[]
canReencrypt: boolean
jobs: {
error?: string
createdAt: number
kind: "reencrypt"
createdBy: string
keyId: string
total?: number
startedAt?: number
jobId: string
failed: number
app: "keys" | "drive" | "health" | "secrets" | "connectors" | "integrations"
seen: number
moved: number
state: "queued" | "done" | "failed" | "running"
finishedAt?: number
current: number
toVersion: number
}[]
}
}Errors
| Status | Message |
|---|---|
403 | Missing scope keys:read |
404 | Key not found |
GET /v1/orgs/:orgId/keys/:keyId/audit
Who or what used the key, how and when (the org's audit log for this key; keys:read).
Auth: user access token or platform agent key · Scope: keys:read · Allowed: keys:read
| Path parameter | Description |
|---|---|
:orgId | Organization id (org_…). |
:keyId | Key id (key_…). |
| Query parameter | Type | Required | Default | Notes |
|---|---|---|---|---|
cursor | string | No | up to 4,096 characters | |
action | string | No | matches ^[a-z_]{1,32}(\.[a-z_]{1,32})?$ | |
actor | string | No | 1–512 characters | |
limit | integer | No | 50 | 1–100; coerced from a string |
Response 200
{
events: {
eventId: string
orgId: string
action: string
actor: {
type: "user" | "key" | "device" | "system"
id: string
label?: string
}
target: {
type: string
id: string
label?: string
}
metadata?: {
[key: string]: unknown
}
ip?: string
userAgent?: string
createdAt: number
}[]
cursor: null | string
retentionDays?: number
}Errors
| Status | Message |
|---|---|
400 | Invalid cursor |
404 | Key not found |
POST /v1/orgs/:orgId/keys/:keyId/rotate
A new version for new data; older versions keep decrypting. reencrypt: also move existing data to it, in the background.
Auth: user access token or platform agent key · Scope: keys:write · Allowed: keys:read
| Path parameter | Description |
|---|---|
:orgId | Organization id (org_…). |
:keyId | Key id (key_…). |
Request body
| Field | Type | Required | Default | Notes |
|---|---|---|---|---|
reencrypt | boolean | No | false |
Response 200
{
job?: {
error?: string
createdAt: number
kind: "reencrypt"
createdBy: string
keyId: string
total?: number
startedAt?: number
jobId: string
failed: number
app: "keys" | "drive" | "health" | "secrets" | "connectors" | "integrations"
seen: number
moved: number
state: "queued" | "done" | "failed" | "running"
finishedAt?: number
current: number
toVersion: number
}
key: {
keyId: string
name: string
description: null | string
app: "keys" | "drive" | "health" | "secrets" | "connectors" | "integrations"
appLabel: string
protects: string
scope: {
kind: "org" | "project" | "environment" | "space" | "person"
id: string
}
owner: "org" | "person"
algorithm: "aes-256-gcm" | "ed25519" | "ecdsa-p256" | "s3"
state: "disabled" | "enabled" | "destroyed"
currentVersion: number
managed: boolean
createdAt: number
rotatedAt: null | number
rotateEveryDays: null | number
nextRotationAt: null | number
destroyedAt: null | number
}
}Errors
| Status | Message |
|---|---|
404 | Key not found |
POST /v1/orgs/:orgId/keys/:keyId/reencrypt
Moves existing data to the current version, in the background (where the app supports it).
Auth: user access token or platform agent key · Scope: keys:write · Allowed: keys:read
| Path parameter | Description |
|---|---|
:orgId | Organization id (org_…). |
:keyId | Key id (key_…). |
Response 202
{
job: {
error?: string
createdAt: number
kind: "reencrypt"
createdBy: string
keyId: string
total?: number
startedAt?: number
jobId: string
failed: number
app: "keys" | "drive" | "health" | "secrets" | "connectors" | "integrations"
seen: number
moved: number
state: "queued" | "done" | "failed" | "running"
finishedAt?: number
current: number
toVersion: number
}
}Errors
| Status | Message |
|---|---|
404 | Key not found |
GET /v1/orgs/:orgId/keys/:keyId/jobs/:jobId
A re-encryption job's progress: state, seen, moved, current (already on the newest version), failed and total where known. Needs keys:read.
Auth: user access token or platform agent key · Scope: keys:read · Allowed: keys:read
| Path parameter | Description |
|---|---|
:orgId | Organization id (org_…). |
:keyId | Key id (key_…). |
:jobId | Job id: an agent job (job_…), under pipelines a pipeline job (pjb_…), or under crm an import or export (cjb_…); under marketing a list import (mkj_…). |
Response 200
{
job: {
error?: string
createdAt: number
kind: "reencrypt"
createdBy: string
keyId: string
total?: number
startedAt?: number
jobId: string
failed: number
app: "keys" | "drive" | "health" | "secrets" | "connectors" | "integrations"
seen: number
moved: number
state: "queued" | "done" | "failed" | "running"
finishedAt?: number
current: number
toVersion: number
}
}Errors
| Status | Message |
|---|---|
404 | Key not found |
404 | Job not found |
PUT /v1/orgs/:orgId/keys/:keyId/schedule
Rotates every rotateEveryDays days from now; null turns the schedule off.
Auth: user access token or platform agent key · Scope: keys:write · Allowed: keys:read
| Path parameter | Description |
|---|---|
:orgId | Organization id (org_…). |
:keyId | Key id (key_…). |
Request body
| Field | Type | Required | Notes |
|---|---|---|---|
rotateEveryDays | integer | Yes | 1–3650; can be null |
Response 200
{
key: {
keyId: string
name: string
description: null | string
app: "keys" | "drive" | "health" | "secrets" | "connectors" | "integrations"
appLabel: string
protects: string
scope: {
kind: "org" | "project" | "environment" | "space" | "person"
id: string
}
owner: "org" | "person"
algorithm: "aes-256-gcm" | "ed25519" | "ecdsa-p256" | "s3"
state: "disabled" | "enabled" | "destroyed"
currentVersion: number
managed: boolean
createdAt: number
rotatedAt: null | number
rotateEveryDays: null | number
nextRotationAt: null | number
destroyedAt: null | number
}
}Errors
| Status | Message |
|---|---|
404 | Key not found |
POST /v1/orgs/:orgId/keys/:keyId/disable
The org's own keys only: nothing seals or opens with a disabled key until it's enabled again.
Auth: user access token or platform agent key · Scope: keys:write · Allowed: keys:read
| Path parameter | Description |
|---|---|
:orgId | Organization id (org_…). |
:keyId | Key id (key_…). |
Errors
| Status | Message |
|---|---|
400 | Only your organization's own keys can be turned off. |
404 | Key not found |
POST /v1/orgs/:orgId/keys/:keyId/enable
Turns one of the organization's own keys back on after disable. Needs keys:write.
Auth: user access token or platform agent key · Scope: keys:write · Allowed: keys:read
| Path parameter | Description |
|---|---|
:orgId | Organization id (org_…). |
:keyId | Key id (key_…). |
Errors
| Status | Message |
|---|---|
400 | Only your organization's own keys can be turned off. |
404 | Key not found |
DELETE /v1/orgs/:orgId/keys/:keyId
Destroys one of the org's own keys, which must be disabled first: nothing it encrypted can be decrypted again.
Auth: user access token or platform agent key · Scope: keys:write · Allowed: keys:read
| Path parameter | Description |
|---|---|
:orgId | Organization id (org_…). |
:keyId | Key id (key_…). |
Response 204 with no body.
Errors
| Status | Message |
|---|---|
400 | Keys that protect the platform's data are destroyed with that data. |
404 | Key not found |
409 | Disable the key first. |
POST /v1/orgs/:orgId/keys/:keyId/encrypt
Encrypts up to 64 KB with an aes-256-gcm key. context: values the ciphertext is bound to; decrypting needs the same.
Auth: user access token or platform agent key · Scope: keys:use
| Path parameter | Description |
|---|---|
:orgId | Organization id (org_…). |
:keyId | Key id (key_…). |
Request body
| Field | Type | Required | Default | Notes |
|---|---|---|---|---|
plaintext | string | Yes | ||
encoding | "utf8" | "base64" | No | "utf8" | |
context | object | No | keys 1–128 characters; values: string (up to 1,024 characters) |
Response 200
{
ciphertext: string
keyId: string
version: number
}Errors
| Status | Message |
|---|---|
400 | Not base64. |
404 | Key not found |
413 | Up to 64 KB at a time. |
POST /v1/orgs/:orgId/keys/:keyId/decrypt
Decrypts a ciphertext (k1.…) made with this key, with the context it was encrypted with, as text (encoding: "utf8", the default) or base64. Any version of the key opens what it sealed. Counts towards the plan's monthly operations and is an audit event. Needs keys:use.
Auth: user access token or platform agent key · Scope: keys:use
| Path parameter | Description |
|---|---|
:orgId | Organization id (org_…). |
:keyId | Key id (key_…). |
Request body
| Field | Type | Required | Default | Notes |
|---|---|---|---|---|
ciphertext | string | Yes | 1–200,000 characters | |
encoding | "utf8" | "base64" | No | "utf8" | |
context | object | No | keys 1–128 characters; values: string (up to 1,024 characters) |
Response 200
{
plaintext: string
keyId: string
version: number
}Errors
| Status | Message |
|---|---|
404 | Key not found |
POST /v1/orgs/:orgId/keys/:keyId/rewrap
The same ciphertext under the key's current version, without the plaintext leaving the service.
Auth: user access token or platform agent key · Scope: keys:use
| Path parameter | Description |
|---|---|
:orgId | Organization id (org_…). |
:keyId | Key id (key_…). |
Request body
| Field | Type | Required | Notes |
|---|---|---|---|
ciphertext | string | Yes | 1–200,000 characters |
context | object | No | keys 1–128 characters; values: string (up to 1,024 characters) |
Response 200
{
ciphertext: string
keyId: string
version: number
}Errors
| Status | Message |
|---|---|
400 | This ciphertext was made with another key. |
404 | Key not found |
POST /v1/orgs/:orgId/keys/:keyId/sign
Signs up to 64 KB with an ed25519 or ecdsa-p256 key (ECDSA signs the message's SHA-256).
Auth: user access token or platform agent key · Scope: keys:use
| Path parameter | Description |
|---|---|
:orgId | Organization id (org_…). |
:keyId | Key id (key_…). |
Request body
| Field | Type | Required | Default | Notes |
|---|---|---|---|---|
message | string | Yes | ||
encoding | "utf8" | "base64" | No | "utf8" |
Response 200
{
signature: string
keyId: string
version: number
}Errors
| Status | Message |
|---|---|
400 | Not base64. |
404 | Key not found |
413 | Up to 64 KB at a time. |
POST /v1/orgs/:orgId/keys/:keyId/verify
Checks a signature (k1s.…) from sign against message: { valid }. Counts towards the plan's monthly operations. Needs keys:use.
Auth: user access token or platform agent key · Scope: keys:use
| Path parameter | Description |
|---|---|
:orgId | Organization id (org_…). |
:keyId | Key id (key_…). |
Request body
| Field | Type | Required | Default | Notes |
|---|---|---|---|---|
message | string | Yes | ||
signature | string | Yes | 1–2,000 characters | |
encoding | "utf8" | "base64" | No | "utf8" |
Response 200
{
valid: boolean
keyId: string
version: number
}Errors
| Status | Message |
|---|---|
400 | Not base64. |
404 | Key not found |
GET /v1/me/keys
Your personal keys (Health, your own Drive and Photos space) with uses in the last 30 days and when last used. Never key material. Only you see them: people only, not API keys or connected apps.
Auth: user access token or platform agent key
Response 200
{
keys: {
uses30d: number
lastUsedAt: null | number
keyId: string
name: string
description: null | string
app: "keys" | "drive" | "health" | "secrets" | "connectors" | "integrations"
appLabel: string
protects: string
scope: {
kind: "org" | "project" | "environment" | "space" | "person"
id: string
}
owner: "org" | "person"
algorithm: "aes-256-gcm" | "ed25519" | "ecdsa-p256" | "s3"
state: "disabled" | "enabled" | "destroyed"
currentVersion: number
managed: boolean
createdAt: number
rotatedAt: null | number
rotateEveryDays: null | number
nextRotationAt: null | number
destroyedAt: null | number
}[]
}Errors
| Status | Message |
|---|---|
403 | Only you can see your personal keys. |
GET /v1/me/keys/:keyId
One of your personal keys: versions still in use, created, last rotated, last used, uses in the last 30 days.
Auth: user access token or platform agent key
| Path parameter | Description |
|---|---|
:keyId | Key id (key_…). |
Response 200
{
key: {
keyId: string
name: string
description: null | string
app: "keys" | "drive" | "health" | "secrets" | "connectors" | "integrations"
appLabel: string
protects: string
scope: {
kind: "org" | "project" | "environment" | "space" | "person"
id: string
}
owner: "org" | "person"
algorithm: "aes-256-gcm" | "ed25519" | "ecdsa-p256" | "s3"
state: "disabled" | "enabled" | "destroyed"
currentVersion: number
managed: boolean
createdAt: number
rotatedAt: null | number
rotateEveryDays: null | number
nextRotationAt: null | number
destroyedAt: null | number
versions: {
version: number
createdAt: number
current: boolean
uses30d: number
adopted: boolean
}[]
uses30d: number
lastUsedAt: null | number
usage: {
day: string
uses: number
}[]
canReencrypt: boolean
jobs: {
error?: string
createdAt: number
kind: "reencrypt"
createdBy: string
keyId: string
total?: number
startedAt?: number
jobId: string
failed: number
app: "keys" | "drive" | "health" | "secrets" | "connectors" | "integrations"
seen: number
moved: number
state: "queued" | "done" | "failed" | "running"
finishedAt?: number
current: number
toVersion: number
}[]
}
}Errors
| Status | Message |
|---|---|
403 | Only you can see your personal keys. |
404 | Key not found |
GET /v1/me/keys/:keyId/audit
Your key's uses, from your own audit log (nobody else's log has them).
Auth: user access token or platform agent key
| Path parameter | Description |
|---|---|
:keyId | Key id (key_…). |
| Query parameter | Type | Required | Default | Notes |
|---|---|---|---|---|
cursor | string | No | up to 4,096 characters | |
action | string | No | matches ^[a-z_]{1,32}(\.[a-z_]{1,32})?$ | |
actor | string | No | 1–512 characters | |
limit | integer | No | 50 | 1–100; coerced from a string |
Response 200
{
events: {
eventId: string
orgId: string
action: string
actor: {
type: "user" | "key" | "device" | "system"
id: string
label?: string
}
target: {
type: string
id: string
label?: string
}
metadata?: {
[key: string]: unknown
}
ip?: string
userAgent?: string
createdAt: number
}[]
cursor: null | string
retentionDays?: number
}Errors
| Status | Message |
|---|---|
400 | Invalid cursor |
403 | Only you can see your personal keys. |
404 | Key not found |