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
POST /v1/vault/devices/challengereturns a single-usenonce(32 random bytes, base64url) valid for 5 minutes.- 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>, wherespki_sha256is base64url(SHA-256(SubjectPublicKeyInfo DER)). POST /v1/vault/devicessendsdevice_id(android-…,wear-…ordesktop-…),kind(android_keystoreorwindows_hello),name,surface(phone,wearordesktop),public_key,nonce,vault_signature,device_signatureand, when available, anattestation. The answer is201 { 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
attestation | Meaning |
|---|---|
android-strongbox | Google key attestation verified, key held in a StrongBox secure element. |
android-tee | Google key attestation verified, key held in the TEE. |
tpm | A TPM attestation of the Windows Hello key verified against the Microsoft TPM roots. |
hello-unattested | A 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 assignature. - v2 (device key or passkey):
codai-vault/approve/v2|<approval_id>|<action_hash>|<decision>|<scope>|<device_id>, sent asproof: { 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 withmatch_numbernext toproof.preview_digestis base64url(SHA-256) of the canonical text ofrequest.preview(codai-vault/preview/v1, thentitle,agent,mode,host,action,purpose,taskaskey=valuelines, 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 asproof: { 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
weardevice on a T4 approval answers 403wear_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 seesrequest.previewand threerequest.match_choices, never the number. A T4 approve on such a request needsmatch_numberwith a v3 proof: none answers 403match_required; a wrong number denies the request at once and answers 403match_mismatch. T3 approvals and denies still accept v1/v2.
Other proof errors (403): invalid_signature, unknown_device, device_revoked, passkey_invalid.
bearerAuthAuthorizationBearer <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).
OK.
application/json- response
nonce*string32 random bytes, base64url. Single use.
expires_at*string5 minutes after issue.
date-timecurl -X POST 'https://ai.codai.ro/v1/vault/devices/challenge'{ "nonce": "string", "expires_at": "2019-08-24T14:15:22Z"}bearerAuthAuthorizationBearer <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).
application/json- body
device_id*string^(android|wear|desktop)-[A-Za-z0-9._-]{4,80}$kind*string"android_keystore""windows_hello"name*string1 <= length <= 80surface*string"phone""wear""desktop"public_key*stringSubjectPublicKeyInfo DER of the device key, base64url.
nonce*stringNonce from POST /v1/vault/devices/challenge.
vault_signature*stringEd25519 signature of your vault key over codai-vault/device-enroll/v1|<device_id>|<kind>|<spki_sha256>|<nonce>.
device_signature*stringThe 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.
Enrolled.
application/json- response
device*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"}'{ "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" }}bearerAuthAuthorizationBearer <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).
OK.
application/json- response
devices*array<>curl -X GET 'https://ai.codai.ro/v1/vault/devices'{ "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" } ]}bearerAuthAuthorizationBearer <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).
id*stringDevice id.
^(android|wear|desktop)-[A-Za-z0-9._-]{4,80}$application/json- body
name*string1 <= length <= 80OK.
application/json- response
device*curl -X PATCH 'https://ai.codai.ro/v1/vault/devices/string' \ -H 'Content-Type: application/json' \ -d '{ "name": "string"}'{ "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" }}bearerAuthAuthorizationBearer <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).
id*stringDevice id.
^(android|wear|desktop)-[A-Za-z0-9._-]{4,80}$Revoked.
application/json- response
ok*booleancurl -X DELETE 'https://ai.codai.ro/v1/vault/devices/string'{ "ok": true}bearerAuthAuthorizationBearer <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).
id*stringResource id (uuid).
uuidapplication/json- body
decision*string"approve""deny"scope*string"once""task""1h""8h"signature?stringv1 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?stringDeprecatedIgnored: a client-claimed step-up proves nothing. Use proof.
via?string"hub""desktop""phone""wear""extension"match_number?stringThe number the user tapped (one of request.match_choices); requires a device or passkey proof over the v3 message.
^[0-9]{2}$OK.
application/json- response
[key: string]?anycurl -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"}'{}