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 parameterDescription
:orgIdOrganization 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

StatusMessage
403Missing 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 parameterDescription
:orgIdOrganization id (org_…).

Request body

FieldTypeRequiredDefaultNotes
namestringYes1–100 characters; trimmed
algorithm"aes-256-gcm" | "ed25519" | "ecdsa-p256"No"aes-256-gcm"
descriptionstringNoup to 500 characters
scopeobjectNo
scope.kind"org" | "project" | "environment"Yes
scope.idstringYes1–200 characters
rotateEveryDaysintegerNo1–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

StatusMessage
400An 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 parameterDescription
:orgIdOrganization id (org_…).
:keyIdKey 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

StatusMessage
403Missing scope keys:read
404Key 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 parameterDescription
:orgIdOrganization id (org_…).
:keyIdKey id (key_…).
Query parameterTypeRequiredDefaultNotes
cursorstringNoup to 4,096 characters
actionstringNomatches ^[a-z_]{1,32}(\.[a-z_]{1,32})?$
actorstringNo1–512 characters
limitintegerNo501–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

StatusMessage
400Invalid cursor
404Key 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 parameterDescription
:orgIdOrganization id (org_…).
:keyIdKey id (key_…).

Request body

FieldTypeRequiredDefaultNotes
reencryptbooleanNofalse

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

StatusMessage
404Key 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 parameterDescription
:orgIdOrganization id (org_…).
:keyIdKey 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

StatusMessage
404Key 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 parameterDescription
:orgIdOrganization id (org_…).
:keyIdKey id (key_…).
:jobIdJob 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

StatusMessage
404Key not found
404Job 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 parameterDescription
:orgIdOrganization id (org_…).
:keyIdKey id (key_…).

Request body

FieldTypeRequiredNotes
rotateEveryDaysintegerYes1–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

StatusMessage
404Key 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 parameterDescription
:orgIdOrganization id (org_…).
:keyIdKey id (key_…).

Errors

StatusMessage
400Only your organization's own keys can be turned off.
404Key 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 parameterDescription
:orgIdOrganization id (org_…).
:keyIdKey id (key_…).

Errors

StatusMessage
400Only your organization's own keys can be turned off.
404Key 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 parameterDescription
:orgIdOrganization id (org_…).
:keyIdKey id (key_…).

Response 204 with no body.

Errors

StatusMessage
400Keys that protect the platform's data are destroyed with that data.
404Key not found
409Disable 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 parameterDescription
:orgIdOrganization id (org_…).
:keyIdKey id (key_…).

Request body

FieldTypeRequiredDefaultNotes
plaintextstringYes
encoding"utf8" | "base64"No"utf8"
contextobjectNokeys 1–128 characters; values: string (up to 1,024 characters)

Response 200

{
  ciphertext: string
  keyId: string
  version: number
}

Errors

StatusMessage
400Not base64.
404Key not found
413Up 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 parameterDescription
:orgIdOrganization id (org_…).
:keyIdKey id (key_…).

Request body

FieldTypeRequiredDefaultNotes
ciphertextstringYes1–200,000 characters
encoding"utf8" | "base64"No"utf8"
contextobjectNokeys 1–128 characters; values: string (up to 1,024 characters)

Response 200

{
  plaintext: string
  keyId: string
  version: number
}

Errors

StatusMessage
404Key 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 parameterDescription
:orgIdOrganization id (org_…).
:keyIdKey id (key_…).

Request body

FieldTypeRequiredNotes
ciphertextstringYes1–200,000 characters
contextobjectNokeys 1–128 characters; values: string (up to 1,024 characters)

Response 200

{
  ciphertext: string
  keyId: string
  version: number
}

Errors

StatusMessage
400This ciphertext was made with another key.
404Key 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 parameterDescription
:orgIdOrganization id (org_…).
:keyIdKey id (key_…).

Request body

FieldTypeRequiredDefaultNotes
messagestringYes
encoding"utf8" | "base64"No"utf8"

Response 200

{
  signature: string
  keyId: string
  version: number
}

Errors

StatusMessage
400Not base64.
404Key not found
413Up 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 parameterDescription
:orgIdOrganization id (org_…).
:keyIdKey id (key_…).

Request body

FieldTypeRequiredDefaultNotes
messagestringYes
signaturestringYes1–2,000 characters
encoding"utf8" | "base64"No"utf8"

Response 200

{
  valid: boolean
  keyId: string
  version: number
}

Errors

StatusMessage
400Not base64.
404Key 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

StatusMessage
403Only 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 parameterDescription
:keyIdKey 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

StatusMessage
403Only you can see your personal keys.
404Key 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 parameterDescription
:keyIdKey id (key_…).
Query parameterTypeRequiredDefaultNotes
cursorstringNoup to 4,096 characters
actionstringNomatches ^[a-z_]{1,32}(\.[a-z_]{1,32})?$
actorstringNo1–512 characters
limitintegerNo501–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

StatusMessage
400Invalid cursor
403Only you can see your personal keys.
404Key not found