codai docs
API referenceGateway

Vault

Zero-knowledge credentials vault: encrypted sync, agent broker, approvals and device keys.

codai Vault keeps your credentials encrypted on your own devices: clients seal every item (XChaCha20-Poly1305, keys wrapped with the X-Wing post-quantum hybrid) before upload, so /v1/vault/* stores and syncs ciphertext only. Agents never receive secret values. They see a catalogue of handles ({{vault:<slug>}}), ask for a policy decision (tiers t0-t4 or deny), wait for your approval when the tier requires it, and call brokered HTTPS endpoints whose responses are redacted.

The SDKs expose the whole surface as codai.vault (TypeScript and Python), with device keys under codai.vault.devices.

Device keys and step-up

An approval proves who decided. Besides the v1 signature of your vault key, a decision can carry a v2 proof from a hardware-backed device key (Android Keystore on phone and watch, Windows Hello on desktop) or from a passkey. Device keys are public keys only; the private key never leaves the secure hardware of the device.

Enrolment

  1. POST /v1/vault/devices/challenge returns a single-use nonce (32 random bytes, base64url) valid for 5 minutes.
  2. The device creates its key and both your vault key and the device key sign the enrolment message: codai-vault/device-enroll/v1|<device_id>|<kind>|<spki_sha256>|<nonce>, where spki_sha256 is base64url(SHA-256(SubjectPublicKeyInfo DER)).
  3. POST /v1/vault/devices sends device_id (android-…, wear-… or desktop-…), kind (android_keystore or windows_hello), name, surface (phone, wear or desktop), public_key, nonce, vault_signature, device_signature and, when available, an attestation. The answer is 201 { device }.

Re-enrolling the same device_id replaces its key. GET /v1/vault/devices lists your keys (revoked ones included), PATCH /v1/vault/devices/{id} renames one and DELETE /v1/vault/devices/{id} revokes it at once: its next proof answers 403 device_revoked.

All binary values are base64url without padding. Android signs with ECDSA P-256 SHA-256 (DER); Windows Hello signs with RSASSA-PKCS1-v1_5 SHA-256.

Attestation kinds

attestationMeaning
android-strongboxGoogle key attestation verified, key held in a StrongBox secure element.
android-teeGoogle key attestation verified, key held in the TEE.
tpmA TPM attestation of the Windows Hello key verified against the Microsoft TPM roots.
hello-unattestedA Windows Hello key without a TPM attestation.

Android keys must send attestation: { android_chain: [...] } (leaf first); the attestation challenge is the nonce bytes and the attested key must equal public_key. Windows Hello keys may send attestation: { tpm: { attestation, cert_chain } }; without it the key is hello-unattested, with an invalid one the enrolment fails. Errors: 400 invalid_nonce, possession_failed, attestation_required, attestation_invalid; 403 invalid_signature; 409 device_id_taken; 503 revocation_unavailable (Google's revocation list could not be checked, retry later).

Approval messages

  • v1 (vault key): codai-vault/approve/v1|<approval_id>|<action_hash>|<decision>|<scope>, sent as signature.
  • v2 (device key or passkey): codai-vault/approve/v2|<approval_id>|<action_hash>|<decision>|<scope>|<device_id>, sent as proof: { type: 'device', device_id, signature }.
  • v3 (device key or passkey + number matching): codai-vault/approve/v3|<approval_id>|<action_hash>|<decision>|<scope>|<device_id>|<preview_digest>|<match_number>, sent with match_number next to proof. preview_digest is base64url(SHA-256) of the canonical text of request.preview (codai-vault/preview/v1, then title, agent, mode, host, action, purpose, task as key=value lines, with \, newline and CR escaped), so the signature covers exactly what the approver saw.
  • passkey: the WebAuthn challenge is base64url(SHA-256(the v2 message with device id passkey)), sent as proof: { type: 'passkey', response }.

Send at most one of signature and proof. step_up_acr is still accepted but ignored: a client-claimed step-up proves nothing.

Rules by tier

  • Deny needs no proof (a notification Deny button is enough).
  • T3 approve accepts a valid v1 signature, device proof or passkey proof.
  • T4 approve requires a device or passkey proof; a v1 signature answers 403 step_up_required. Every attestation kind above qualifies today.
  • Wear is T3-only: a watch key cannot be attested as biometric-bound, so a proof from a wear device on a T4 approval answers 403 wear_t3_only.
  • Number matching: every T3/T4 request gets a 2-digit match_number, returned only to the agent (/vault/use, /vault/http, /vault/resolve). The approver sees request.preview and three request.match_choices, never the number. A T4 approve on such a request needs match_number with a v3 proof: none answers 403 match_required; a wrong number denies the request at once and answers 403 match_mismatch. T3 approvals and denies still accept v1/v2.

Other proof errors (403): invalid_signature, unknown_device, device_revoked, passkey_invalid.

POST
/v1/vault/devices/challenge

Authorization

bearerAuth
headerAuthorizationBearer <codai_…>

A codai API key (codai_ prefix). Ephemeral tokens from POST /v1/tokens are accepted only by the media surfaces matching their scope (/v1/realtime, /v1/audio/*, /v1/embeddings).

Response Body

OK.

application/json
  1. response
nonce*string

32 random bytes, base64url. Single use.

expires_at*string

5 minutes after issue.

Formatdate-time
Example Request
curl -X POST 'https://ai.codai.ro/v1/vault/devices/challenge'
Example Response
{  "nonce": "string",  "expires_at": "2019-08-24T14:15:22Z"}
POST
/v1/vault/devices

Authorization

bearerAuth
headerAuthorizationBearer <codai_…>

A codai API key (codai_ prefix). Ephemeral tokens from POST /v1/tokens are accepted only by the media surfaces matching their scope (/v1/realtime, /v1/audio/*, /v1/embeddings).

Request Body

application/json
  1. body
device_id*string
Match^(android|wear|desktop)-[A-Za-z0-9._-]{4,80}$
kind*string
Value in"android_keystore""windows_hello"
name*string
Length1 <= length <= 80
surface*string
Value in"phone""wear""desktop"
public_key*string

SubjectPublicKeyInfo DER of the device key, base64url.

nonce*string

Nonce from POST /v1/vault/devices/challenge.

vault_signature*string

Ed25519 signature of your vault key over codai-vault/device-enroll/v1|<device_id>|<kind>|<spki_sha256>|<nonce>.

device_signature*string

The device key over the same message (ECDSA P-256 DER on Android, RSASSA-PKCS1-v1_5 SHA-256 for Windows Hello), base64url.

attestation?|

Required for android_keystore (certificate chain, leaf first, attestation challenge = nonce bytes). Optional for windows_hello: a TPM attestation gives tpm, none gives hello-unattested.

Response Body

Enrolled.

application/json
  1. response
device*
Example Request
curl -X POST 'https://ai.codai.ro/v1/vault/devices' \  -H 'Content-Type: application/json' \  -d '{  "device_id": "string",  "kind": "android_keystore",  "name": "string",  "surface": "phone",  "public_key": "string",  "nonce": "string",  "vault_signature": "string",  "device_signature": "string"}'
Example Response
{  "device": {    "id": "string",    "kind": "android_keystore",    "name": "string",    "surface": "phone",    "public_key": "string",    "attestation": "android-strongbox",    "attestation_info": {},    "created_at": "2019-08-24T14:15:22Z",    "last_used_at": "2019-08-24T14:15:22Z",    "revoked_at": "2019-08-24T14:15:22Z"  }}
GET
/v1/vault/devices

Authorization

bearerAuth
headerAuthorizationBearer <codai_…>

A codai API key (codai_ prefix). Ephemeral tokens from POST /v1/tokens are accepted only by the media surfaces matching their scope (/v1/realtime, /v1/audio/*, /v1/embeddings).

Response Body

OK.

application/json
  1. response
devices*array<>
Example Request
curl -X GET 'https://ai.codai.ro/v1/vault/devices'
Example Response
{  "devices": [    {      "id": "string",      "kind": "android_keystore",      "name": "string",      "surface": "phone",      "public_key": "string",      "attestation": "android-strongbox",      "attestation_info": {},      "created_at": "2019-08-24T14:15:22Z",      "last_used_at": "2019-08-24T14:15:22Z",      "revoked_at": "2019-08-24T14:15:22Z"    }  ]}
PATCH
/v1/vault/devices/{id}

Authorization

bearerAuth
headerAuthorizationBearer <codai_…>

A codai API key (codai_ prefix). Ephemeral tokens from POST /v1/tokens are accepted only by the media surfaces matching their scope (/v1/realtime, /v1/audio/*, /v1/embeddings).

Path Parameters

id*string

Device id.

Match^(android|wear|desktop)-[A-Za-z0-9._-]{4,80}$

Request Body

application/json
  1. body
name*string
Length1 <= length <= 80

Response Body

OK.

application/json
  1. response
device*
Example Request
curl -X PATCH 'https://ai.codai.ro/v1/vault/devices/string' \  -H 'Content-Type: application/json' \  -d '{  "name": "string"}'
Example Response
{  "device": {    "id": "string",    "kind": "android_keystore",    "name": "string",    "surface": "phone",    "public_key": "string",    "attestation": "android-strongbox",    "attestation_info": {},    "created_at": "2019-08-24T14:15:22Z",    "last_used_at": "2019-08-24T14:15:22Z",    "revoked_at": "2019-08-24T14:15:22Z"  }}
DELETE
/v1/vault/devices/{id}

Authorization

bearerAuth
headerAuthorizationBearer <codai_…>

A codai API key (codai_ prefix). Ephemeral tokens from POST /v1/tokens are accepted only by the media surfaces matching their scope (/v1/realtime, /v1/audio/*, /v1/embeddings).

Path Parameters

id*string

Device id.

Match^(android|wear|desktop)-[A-Za-z0-9._-]{4,80}$

Response Body

Revoked.

application/json
  1. response
ok*boolean
Example Request
curl -X DELETE 'https://ai.codai.ro/v1/vault/devices/string'
Example Response
{  "ok": true}
POST
/v1/vault/approvals/{id}/decide

Authorization

bearerAuth
headerAuthorizationBearer <codai_…>

A codai API key (codai_ prefix). Ephemeral tokens from POST /v1/tokens are accepted only by the media surfaces matching their scope (/v1/realtime, /v1/audio/*, /v1/embeddings).

Path Parameters

id*string

Resource id (uuid).

Formatuuid

Request Body

application/json
  1. body
decision*string
Value in"approve""deny"
scope*string
Value in"once""task""1h""8h"
signature?string

v1 Ed25519 signature of your vault key, base64url.

proof?|

Proven step-up for a decision. device: an enrolled device key signs codai-vault/approve/v2|<approval_id>|<action_hash>|<decision>|<scope>|<device_id>. passkey: a WebAuthn assertion whose challenge is base64url(SHA-256(the same message with device id passkey)). With match_number the message is v3: codai-vault/approve/v3|<approval_id>|<action_hash>|<decision>|<scope>|<device_id>|<preview_digest>|<match_number>, where preview_digest = base64url(SHA-256(the canonical text of request.preview)).

step_up_acr?stringDeprecated

Ignored: a client-claimed step-up proves nothing. Use proof.

via?string
Value in"hub""desktop""phone""wear""extension"
match_number?string

The number the user tapped (one of request.match_choices); requires a device or passkey proof over the v3 message.

Match^[0-9]{2}$

Response Body

OK.

application/json
  1. response
[key: string]?any
Example Request
curl -X POST 'https://ai.codai.ro/v1/vault/approvals/497f6eca-6276-4993-bfeb-53cbbbba6f08/decide' \  -H 'Content-Type: application/json' \  -d '{  "decision": "approve",  "scope": "once"}'
Example Response
{}

On this page