Kodwen docs
API referenceGateway

Vault

Credentials vault, zero-knowledge by default: encrypted sync, agent broker, approvals and device keys.

Kodwen 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 by default /v1/vault/* stores and syncs ciphertext that Kodwen cannot read. The vault is zero-knowledge by default, with one exception that is off by default and set per item: the owner of a vault can mark an item usable in the cloud (PUT /v1/vault/items/{id}/cloud). The client then sends the decrypted item, and the gateway keeps a second copy sealed with a Kodwen KMS key, which it can open to serve agent requests (/v1/vault/http, /v1/vault/resolve, /v1/vault/derive) while your devices are offline. Only the owner of the vault can switch it on or off; switching it off, changing the item or deleting it removes that copy. 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.

Items: sync, writes and roles

Reading and syncing items

GET /v1/vault/items answers { items, server_time, has_more }. Each item is the stored row: id, vault_id, revision, wrapped_key, ciphertext, kind, slug, agent_meta, honeytoken, cloud_usable, deleted_at, last_used_at, created_at, updated_at.

  • Full list (no since): live items only, most recently changed first, at most 5000. has_more is true when more than 5000 live items exist.
  • Changes (?since=<ISO 8601 date-time>): every item changed after since, deleted items included (deleted_at is set: remove the item locally), oldest first, at most 1000 per call.
  • Deleted items: an item deleted more than 30 days ago keeps only a marker row and its wrapped_key and ciphertext are empty strings, so a client must never decrypt a row that has deleted_at.
  • has_more: true: call again at once with since = the server_time you received, which is then the updated_at of the last item returned. Rely on has_more, not on the number of items: a page is never cut inside one millisecond, so it can hold fewer than 1000 items, and it holds more than 1000 only when over 1000 items changed in the same millisecond.
  • has_more: false: you are up to date. server_time is the database clock, read before the items, minus 5 seconds; store it as the since of your next sync. It can be earlier than the since you sent. An item changed in those last seconds can arrive again: replace your copy of that id.
  • A since that cannot be read as a date-time, an empty value included, answers 400 invalid_since. Times are compared by the millisecond.

Writing items

PUT /v1/vault/items/{id} { vault_id, revision, wrapped_key, ciphertext, kind, slug?, agent_meta?, honeytoken? } answers 201 { id, revision } for a new item and 200 { id, revision } for an update. The body has no id: the path names the item, and a key the route does not know answers 400.

  • A new item sends revision: 1; an update sends the stored revision + 1. Anything else answers 409 { error: { code: 'revision_conflict' }, revision } with the stored revision: reload the item and retry.
  • 409 item_deleted: the item was deleted on another device and stays deleted. Remove it locally; to keep its content, create a new item with a new id.
  • The kind and the vault of an item are fixed when it is created; a change answers 400.
  • Agent exposure (slug and agent_meta, always sent together) and honeytoken are decisions of the owner or a manager of the vault. An editor who creates an item with them, or changes them, gets 403. Every update sends the stored slug, agent_meta and honeytoken back unchanged; leaving them out counts as a change.
  • A slug already used in the same vault answers 400. A content change removes the cloud copy of the item.

DELETE /v1/vault/items/{id} keeps the row as a deleted item for sync, revokes every grant on it and removes its cloud copy. Deleting an item that is already deleted answers the same 200 { deleted: true } and changes nothing.

Recording a local use

POST /v1/vault/items/{id}/used { via } records that a passkey or login was used on a device (via: windows_plugin, android_provider, ios_provider, desktop or phone) and answers { id, last_used_at }. Only the time is stored, never the contents. The route keeps one stamp per item per 60 seconds: a repeat inside that window answers 200 with the stored last_used_at, writes nothing and records no item.used event. It never answers 429. An unknown, deleted or inaccessible item answers 404.

What each role may do on items

Actionviewereditormanagerowner
Read and sync items (GET /v1/vault/items)yesyesyesyes
Record a local use (POST /v1/vault/items/{id}/used)yesyesyesyes
Create, change and delete items; set limits and the rotation schedulenoyesyesyes
Set or change agent exposure (slug, agent_meta) and honeytokennonoyesyes
Switch cloud use on or off (PUT /v1/vault/items/{id}/cloud); set or remove a derivernononoyes

A caller who is not a member of the vault gets 404, the same answer as for a vault that does not exist.

429 answers

Every 429 has the body { error: { message, type: 'rate_limit_error', code } } and a Retry-After header in seconds.

codeRoutesWhenRetry-After
vault_rate_limitedPOST /v1/vault/use, /v1/vault/http, /v1/vault/resolve, /v1/vault/derivea per-handle limit of the item is reached60 for the per-minute limit, 3600 for the daily limits
rate_limit_exceededGET /v1/vault/hibp/range/{prefix}more than 300 calls per user in one hour600
rate_limit_exceededPOST /v1/vault/add-requests20 add requests are already pending60

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-…, desktop-…, ios-… or cli-…), kind (android_keystore, windows_hello, ios_secure_enclave or cli_ed25519), name, surface (phone, wear, desktop or cli), 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; cli_ed25519 (the software key codaid vault enroll creates) signs with Ed25519 (raw 64-byte signature, public_key = Ed25519 SubjectPublicKeyInfo DER).

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.
ios-unattestedAn iPhone Secure Enclave P-256 key (ios_secure_enclave, surface phone, Face ID per signature). No attestation yet, so it approves T3 requests only; a T4 approve answers 403 step_up_required.
softwareA cli_ed25519 key kept in a file on the machine. It can only prove /v1/vault/resolve calls; it can never approve or unlock (403 device_not_allowed).

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. iPhone keys (ios_secure_enclave) send no attestation at all (sending one fails with attestation_invalid); the signature is ECDSA P-256 SHA-256 in DER, like Android. 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.

Resolve proof (local brokers)

POST /v1/vault/resolve returns values to a local broker (codaid, desktop) that injects them into a child process and redacts its output. The broker must prove it holds an enrolled device key; the x-codai-agent header alone is not trusted.

  1. POST /v1/vault/resolve/challenge returns a single-use challenge valid for 120 s.
  2. The broker signs codai-vault/resolve/v1|<challenge>|<device_id>|<digest> with its device key, where digest = base64url(SHA-256(the handles joined with \n, then \n\n, then action)), handles exactly as sent and in the same order.
  3. The resolve body carries proof: { device_id, challenge, signature }.

Errors: 400 proof_required (no proof), 400 invalid_challenge (unknown, used or expired), 403 invalid_device_proof (key not yours, revoked or bad signature), 403 broker_only (not a Kodwen broker). The audit event use.resolved records the device_id.

Anomaly guards

Every agent use is also checked against your anomaly guards, read from GET /v1/vault/account (guards) and changed with PATCH /v1/vault/settings { default_tier?, guards? } (send at least one; guards is partial and merged over the current values, the answer is the effective { default_tier, guards }).

GuardDefaultFires whenReason
new_hosttrueno allowed use or grant for this item and host in the last 30 daysanomaly:new_host
burst_max / burst_window_s5 / 60at least burst_max agent requests for the item within the window (burst_max: 0 = off)anomaly:burst
hour_start / hour_end / tz7 / 23 / Europe/Bucharestthe use falls outside [hour_start, hour_end) in tz (both hours null = off)anomaly:hour

An anomaly forces the decision to t4: the user approves with a device key or passkey (number matching included), and standing grants that were not approved at t4 are ignored for that use. A deny stays deny.

Panic lock and unlock

POST /v1/vault/lock (body optional, { via? }) is the panic button: it revokes every live grant, denies every pending approval and locks the vault, answering { locked_at, grants_revoked, approvals_denied }. Device keys stay enrolled so you can unlock. The call is idempotent. While locked, GET /v1/vault/account shows locked_at and lock_reason (panic or honeytoken) and every agent use is denied with reason vault_locked (/v1/vault/http and /v1/vault/resolve answer 403 vault_denied).

Unlocking needs a hardware-backed proof over a single-use challenge:

  1. POST /v1/vault/unlock/challenge returns { challenge, expires_at, message_template } (32 random bytes, base64url, valid 5 minutes).
  2. Build the message codai-vault/unlock/v1|<challenge>|<device_id|passkey>:
    • device key (phone, desktop): the key signs the UTF-8 message with your device id, sent as proof: { type: 'device', device_id, signature }. Watch keys are refused (wear_t3_only).
    • passkey (hub): the WebAuthn challenge is base64url(SHA-256(the message with passkey in place of the device id)), sent as proof: { type: 'passkey', response }.
  3. POST /v1/vault/unlock { challenge, proof, via? } answers { unlocked: true }. Errors (403): challenge_invalid, unknown_device, device_revoked, wear_t3_only, invalid_signature, passkey_invalid.

The audit log records vault.locked, vault.unlocked, vault.unlock_failed and use.locked.

Honeytokens

An item marked honeytoken is a decoy no legitimate agent should touch. A use of it is denied and raises an alarm, and it now also locks the vault (lock_reason: 'honeytoken') and sends a data push to every active device: { kind: 'vault.alarm', reason: 'honeytoken', locked: '1', title?, agent?, host? } (a panic lock pushes the same alarm with reason: 'panic'). Unlock as above once you know what happened.

Per-handle limits

Cap how often agents may use one item. GET /v1/vault/limits lists { limits: [{ item_id, per_minute, per_day, units_per_day }] }; PUT /v1/vault/items/{id}/limits { per_minute, per_day, units_per_day } sets them (all three keys required, null removes one cap, all null removes the limits; editor or above on the item's vault).

Only allowed uses count. An http use costs 2 units, every other mode 1. Over a cap, /v1/vault/use, /v1/vault/http and /v1/vault/resolve answer 429 { error: { code: 'vault_rate_limited', type: 'rate_limit_error', message } } with Retry-After (60 s for the per-minute cap, 3600 s for the daily caps), and the audit log records use.limited.

Grants end with their task

A grant created for a task (scope task, or any grant carrying the task you sent on /v1/vault/use) is revoked when the task ends: on POST /v1/tasks/{id}/confirm, on DELETE /v1/sessions/{id}, and by a sweeper every 5 minutes for closed tasks. Agents and brokers can end them explicitly with POST /v1/vault/grants/revoke-task { task }, which answers { revoked: n }. GET /v1/vault/grants is unchanged; clients group its rows by task to show live leases.

Add requests

When an agent needs a credential the vault does not have, it must never ask you to paste it into the chat. It creates an add request instead, and you type the value on a Kodwen page where it is encrypted in your browser (or in the desktop app) before it is saved.

  • POST /v1/vault/add-requests { kind, title, slug?, hosts?, env_var?, fields?, purpose, task? } answers 201 { id, url, status, expires_at }. url is https://hub.codai.ro/vault/add?req=<token>; the token appears only in this response. fields are field names to ask for (at most 10), never values. A request lives 10 minutes; at most 20 can be pending per user.
  • GET /v1/vault/add-requests/{id} answers { id, status, slug, handle, item_id, expires_at }, where status is pending, completed, cancelled or expired and handle is {{vault:<slug>}} once completed.
  • DELETE /v1/vault/add-requests/{id} cancels it.
  • POST /v1/vault/add-requests/lookup { token } returns the request to the signed-in user it belongs to (desktop and phone deep links); anyone else gets 404.
  • POST /v1/vault/add-requests/{id}/complete { item_id } marks it completed; the item must be in one of your vaults. Repeating the call with the same item is a no-op.

The desktop app opens codai://vault/add?req=<token> when it is installed; the phone opens the hub page in a Custom Tab. The audit log records add.requested, add.completed and add.cancelled.

From a terminal, codaid vault add --title <t> [--kind api_key] [--slug s] [--host h]… [--env-var NAME] [--field f]… [--purpose p] [--json] creates the same request and prints the link to open.

From MCP: URL elicitation

The vault MCP toolset (gateway and codaid vault mcp) has two tools for this: vault_add_secret (same inputs as the REST call) and vault_add_status { id }.

  • initialize answers with an Mcp-Session-Id header that records whether your client declared capabilities.elicitation.url. The header is signed and stateless (v1.<payload>.<HMAC-SHA256>, valid 30 days); send it back on later calls.
  • With URL elicitation declared, tools/call vault_add_secret answers the JSON-RPC error -32042 with data.elicitations = [{ mode: 'url', elicitationId, url, message }], as defined by the MCP specification (2025-11-25). The client shows the link; after you add the secret, the agent calls vault_add_status (or calls vault_add_secret again with the same slug or title within 10 minutes, which reuses the request) and receives the handle.
  • Fallback: without the capability (or without a session id), the tool returns a normal result with the link and the instruction "open it, then call vault_add_status".

Security

  • The URL carries only a single-use 32-byte token; the server stores its SHA-256, never the token itself.
  • The link is not pre-authenticated: the hub page requires sign-in and shows the request only to the user it was created for. Anyone else sees "not found".
  • The page never receives anything secret from the server. It seals the item in the browser with your vault keys and saves it through the normal item path; the agent only ever sees the handle.

Emergency access

Name another Kodwen user who can read chosen vaults if you cannot, after a waiting period you control.

StatusMeaningNext
invitedInvite sent by email (valid 14 days).Contact accepts in their hub.
acceptedContact accepted; no keys shared yet.Your client seals the vault keys to the contact.
sealedVault keys sealed to the contact's X-Wing key.Contact may request access.
requestedContact asked; release_at = now + delay_hours.You deny or approve, or the delay passes.
grantedContact can read the chosen vaults.You revoke.
revokedEnded by either side; sealed keys deleted.—
  • GET /v1/vault/emergency answers { outgoing, incoming }.
  • POST /v1/vault/emergency { email, delay_hours, vault_ids? } always answers 202 { id }, with the same shape whether or not the address has a Kodwen account, so it cannot be used to discover accounts. delay_hours is 24, 48 or 72; vault_ids defaults to your personal vault. At most 5 contacts.
  • POST /v1/vault/emergency/accept { token } (contact): needs a vault account (404 vault_not_setup) and cannot be your own invite.
  • PUT /v1/vault/emergency/{id}/seal { grantee_fingerprint, sealed_keys } (you): sealed_keys must cover exactly vault_ids. If the contact's key fingerprint is not the one you verified, the answer is 409 fingerprint_changed. Compare the fingerprint shown on both devices before sealing.
  • PATCH /v1/vault/emergency/{id} { delay_hours?, vault_ids? } (you): changing the vaults drops the sealed keys and returns the contact to accepted.
  • DELETE /v1/vault/emergency/{id}: you revoke or the contact declines.
  • POST /v1/vault/emergency/{id}/request { reason? } (contact, status sealed), then POST /v1/vault/emergency/{id}/deny or /approve (you, status requested). A sweeper grants access once release_at has passed.
  • GET /v1/vault/emergency/{id}/vault-data (contact, status granted) returns the ciphertext of the chosen vaults and the sealed vault keys; the contact decrypts locally. Access is read-only and never extends to agents; every read is logged as emergency.accessed in your audit log.

Every transition notifies the other side: FCM data push { kind: 'vault.emergency', contact_id, event, other_name, release_at } on phone, Web Push in the hub and an email. The audit log records emergency.invited|accepted|sealed|requested|denied|approved|granted|revoked|accessed.

Rotation and breach watch

Breach check (HIBP)

GET /v1/vault/hibp/range/{prefix} proxies the Pwned Passwords range API with k-anonymity: the client hashes each password with SHA-1 locally, sends only the first 5 hex characters and matches the suffix in the answer itself. The gateway never sees a password or a full hash. Responses use Add-Padding: true so their size does not reveal the prefix, are cached 24 hours and are limited to 300 calls per user per hour (429 above that, 503 hibp_unavailable when the upstream fails). The client then reports POST /v1/vault/rotation/breach-report { results: [{ item_id, count }] } (at most 500); a newly breached item sends a vault.breach push.

Rotation queue

  • GET /v1/vault/rotation lists { items: [{ item_id, slug, title, kind, interval_days, rotated_at, due_at, overdue, breached, breach_count, breach_checked_at, snoozed_until, provider, create_url }] }, breached first, then overdue, then soonest due.
  • PUT /v1/vault/items/{id}/rotation { interval_days?, provider?, snoozed_until? } sets the schedule (1–730 days) and the provider; POST /v1/vault/items/{id}/rotated records a rotation and clears the breach flag.
  • Defaults: 90 days for api_key, oauth_token and env items; logins are watched for breaches only; items with a deriver are skipped (their credentials are short-lived).
  • A sweeper every 6 hours sends one vault.rotation push per overdue item per cycle (not while snoozed).
  • Keys that cannot be rotated through an API stay static, and the list links to the page where you create a new one:
providercreate_url
stripe (restricted keys)https://dashboard.stripe.com/apikeys
github_pat (fine-grained PAT)https://github.com/settings/personal-access-tokens/new
openaihttps://platform.openai.com/api-keys
google_api_keyhttps://console.cloud.google.com/apis/credentials

JIT credentials

A deriver turns a root credential into a short-lived, scoped one each time it is used, so the long-lived secret never reaches the command.

  • GET /v1/vault/derivers, PUT /v1/vault/items/{id}/deriver { provider, config, max_ttl_s } (300–43200 s), DELETE /v1/vault/items/{id}/deriver. config is non-secret.
providerconfigRoot item fieldsDerived fields → env
aws_stsrole_arn, region (default us-east-1), session_policy? (≤ 2048 chars), external_id?, duration_s 900–43200access_key_id, secret_access_keyaccess_key_id, secret_access_key, session_token → AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY, AWS_SESSION_TOKEN
gcp_samode key | impersonate, target_service_account?, scopes (default cloud-platform), lifetime_s 300–3600json (service-account key)token → CLOUDSDK_AUTH_ACCESS_TOKEN, GOOGLE_OAUTH_ACCESS_TOKEN
cloudflareaccount_id?, policies, ttl_s 300–86400token (allowed to create tokens)token → CLOUDFLARE_API_TOKEN
github_appapp_id, installation_id, repositories?, permissions?private_key (PEM)token → GH_TOKEN, GITHUB_TOKEN

{{vault:slug}} on a derived item is the first derived field; {{vault:slug.<field>}} selects one.

POST /v1/vault/derive is for Kodwen brokers only (codaid, desktop): body { handle, action, purpose?, provenance?, task?, ttl_s?, proof }, with the same device proof as /v1/vault/resolve over handles = [handle]. The proof is checked before any policy call; without it the answer is 400 proof_required, a non-broker caller gets 403 broker_only. After the usual policy (env mode, approvals included) the item must be cloud-usable (409 not_cloud_usable) and have a deriver (409 no_deriver). The answer is { provider, values, env, expires_at } with Cache-Control: no-store; an upstream failure answers 502 derive_failed with the provider message scrubbed of anything token-shaped. The audit log records derive.issued { provider, expires_at, device_id }, never the value.

Items that are not cloud-usable derive in the local broker instead (codaid vault run, desktop), with the same providers; the broker injects the env names into the child and redacts every derived value from its output.

/v1/vault/http on an item with a gcp_sa, cloudflare or github_app deriver injects the derived bearer token instead of the root secret. The token is cached in gateway memory until 60 seconds before it expires.

Agent autofill

The desktop agent browser can sign in to sites without the model seeing the password. In browser_type, write {{vault:slug.field}} (username, password, totp or any field). The desktop app then:

  1. reads the page origin over CDP and checks the item's bound hosts (agent_meta.hosts); an unbound host fails with vault_host_mismatch;
  2. asks for a policy decision with mode autofill and host = the page host (approval on your device when the tier requires it);
  3. decrypts locally, checks that the page is still on the same origin, and injects the value; the tool answers { ok, filled: true } without the value.

Snapshots never show the value of a password input, and browser_eval / click output is redacted against every value filled in that browser session ([vault:<slug>]).

The browser extension also accepts agent_fill requests from the desktop app over native messaging and fills only when the active tab is on exactly the requested origin.

Browser extension and Windows passkeys

Kodwen Vault for Chrome and Edge (Manifest V3, distributed unlisted on the Chrome Web Store and hidden on Edge Add-ons):

  • Works on its own: the vault crypto is bundled as WASM (no remote code). Unlock with your password and Secret Key; the unlocked session lives only in the service worker's memory and locks after 10 minutes idle by default.
  • Fills only on the origin bound to the login: the service worker checks the tab origin, and the content script checks location.origin again before writing a field.
  • Optional native messaging to the desktop app (host ro.codai.vault): unlock with Windows Hello on desktop, fill from the desktop vault (candidates, fill) and agent fills. The host manifest's allowed_origins lists only the store extension ids, and the desktop app checks the extension id and the page origin on every request again.
  • Permissions: storage, scripting, activeTab, contextMenus, alarms, nativeMessaging, idle; access to all sites is optional and asked at runtime for inline fill. Gateway calls use via: 'extension'.

Windows 11 passkey provider: the Microsoft Store (MSIX) build of the desktop app registers as a passkey provider, so Windows can create and use passkeys stored as passkey items in your vault, after Windows Hello. Every request from Windows is checked against the operation signing key Windows registered for the plugin. NSIS installs do not register it and show "available in the Microsoft Store version".

Android: passwords and passkeys for other apps

The Kodwen Android app downloaded from GitHub (not the Play build) is a Credential Manager provider on Android 14+ and an Autofill service on Android 11–13. Enable it in Settings › Passwords & accounts (or Autofill service).

  • Fill: matching logins (androidapp://<package> or the site's domain and subdomains) are offered while the vault is unlocked; each fill asks for a biometric pass.
  • Save: when you sign up or change a password in another app or a browser, the phone offers to save it to your vault: the first personal vault by default, or any vault where you are owner, manager or editor. If a login with the same username and address exists, it offers to update it. The item is sealed on the phone and uploaded as ciphertext (PUT /v1/vault/items/:id); it is never agent-visible (no handle).
  • Passkeys: sites and apps can create and use passkeys (ES256) stored as passkey items in your vault, the same format the desktop Windows provider uses, so a passkey created on the phone also works on desktop. The private key stays inside the vault item; signing happens in the vault core on the device after a biometric pass, and the signature counter is saved back to the item. Browser requests are accepted only from browsers on Google's privileged browser list and only when the relying party id matches the page origin.
  • The CTAP / WebAuthn core (authenticator data, COSE key, none attestation, assertion) is shared by the desktop provider and the phone (crates/codai-vault, feature passkey).

Org vaults

An organisation owner or admin creates a shared vault with POST /v1/vault/vaults { id, encrypted_meta, sealed_key, org_id } (kind: shared). GET /v1/vault/vaults then shows, per vault, org_id, your role (owner, manager, editor, viewer), quorum, rotation_needed_at and key_epoch. The server never sees a vault key: every member gets their own copy sealed to their X-Wing public key by an owner or manager's client.

  • GET /v1/vault/vaults/{id}/members (any member): { members: [{ user_id, email, name, role, fingerprint, key_epoch, added_at }] }.
  • GET /v1/vault/vaults/{id}/candidates (owner/manager): organisation members with a vault account who are not in the vault yet, with public_keys and fingerprint.
  • PUT /v1/vault/vaults/{id}/members/{userId} { role, sealed_key, fingerprint } (owner/manager): show the candidate's fingerprint, have it confirmed (out of band, as for emergency contacts), seal the vault key to public_keys and upload it. If the member's keys changed since the fingerprint was read the answer is 409 fingerprint_changed; a user outside the organisation answers 403. The owner row cannot be changed.
  • PATCH /v1/vault/vaults/{id}/members/{userId} { role } changes the role (manager, editor or viewer).
  • DELETE /v1/vault/vaults/{id}/members/{userId} (owner/manager, or the member leaving) deletes the row at once and sets rotation_needed_at. Removing someone from the organisation does the same on every vault of that organisation.

Key rotation

A removed member may still hold an old copy of the vault key, so after any removal the vault shows a banner until an owner or manager rotates it. The client generates a new vault key, re-seals it to every remaining member, re-wraps every item key, and sends everything in one call:

POST /v1/vault/vaults/{id}/rotate { key_epoch, encrypted_meta, members: [{ user_id, sealed_key }], items: [{ id, revision, wrapped_key, ciphertext }] }

key_epoch is the current epoch + 1 and every item revision is its current revision + 1. The request must cover every current member and every live item (at most 2000): otherwise the answer is 409 rotation_incomplete with the missing ids and nothing changes. It runs in one transaction and clears rotation_needed_at.

Quorum approvals

On an org vault the owner can require several people to approve sensitive uses: PUT /v1/vault/vaults/{id}/quorum { m, scope } with m from 2 to 9 and scope critical (critical items only) or high (high and critical); { m: null } turns it off. m cannot exceed the number of owner/manager members (400).

When an agent asks for a t3 or t4 use of a matching item, the approval is created with quorum_m = m and a 24-hour expiry. If fewer than m eligible approvers exist (owners and managers other than the requester, with a vault account), the use is denied at once with reason quorum_unavailable. Every eligible approver gets a push, and GET /v1/vault/approvals lists the request with quorum: { m, approvals, needed, voted }.

  • Vote with POST /v1/vault/approvals/{id}/vote { decision, proof }. Each vote carries its own proof over approve/v4: codai-vault/approve/v4|<approval_id>|<action_hash>|<decision>|once|<approver_user_id>|<device_id>|<preview_digest> signed by one of the approver's own device keys (proof: { type: 'device', device_id, signature }), or as a passkey whose WebAuthn challenge is base64url(SHA-256(the message with device id passkey)). The approver's user id is part of the message, so one approver's proof cannot be replayed as another's. Software keys are refused; t4 needs an attested device or a passkey. There is no match number: approvers are not next to the agent.
  • Any deny ends the request (denied). The m-th distinct approve marks it approved and creates a single once grant for the requester; the audit event approval.approved lists every voter's device. Each vote is logged as approval.vote.
  • Errors: 409 already_voted, 409 not_pending, 403 for a bad proof or an approver who is not eligible. POST /v1/vault/approvals/{id}/decide on a quorum approval answers 409 quorum_required.

What the approver signs (preview v2)

The approval preview now includes the agent's delegation chain (who asked whom, outermost first, joined with >, cut at 300 characters). The canonical text is codai-vault/preview/v2 followed by title, agent, mode, host, action, purpose, task, chain as key=value lines, with the same escaping as v1. A preview object whose chain is a string uses v2; otherwise v1. Hub, desktop and phone show the chain next to the request, and every use event stores it.

Transition: until the next desktop and phone release, the gateway also stores the v1 digest of the same preview (without chain). A v3 proof is checked against the v2 digest first and then against the v1 digest; a v4 proof must use the v2 digest.

Offline approvals by QR

When your phone has no network, the desktop can carry the approval both ways with QR codes:

  1. The desktop shows a request QR (compact JSON, UTF-8, error correction M): { "v": "codai-vault-qr/1", "t": "req", "id", "h": "<action_hash>", "tier": "t3|t4", "u": "<requester user id>", "p": { ...preview... }, "c": ["47","12","83"] }.
  2. The phone refuses a QR whose u is not the signed-in user, recomputes the preview digest from p itself (it never trusts a digest from the QR), shows p and asks you to tap the number the agent shows you (one of c; the number itself is not in the QR).
  3. The phone signs approve/v3 (v2 when there is no number) with its device key and shows a response QR: { "v": "codai-vault-qr/1", "t": "res", "id", "d": "approve|deny", "s": "once|task|1h|8h", "dev": "<device id>", "m": "<number or empty>", "sig": "<base64url>" }.
  4. The desktop reads it with the webcam and posts POST /v1/vault/approvals/{id}/decide { decision, scope, via: 'qr', match_number, proof: { type: 'device', device_id, signature } }. The gateway verifies the device proof exactly as if the phone had posted it.

Policy simulator

Before you change your rules, see what they would have done. POST /v1/vault/policy/simulate { rules?, default_tier?, since?, limit? } replays your past agent uses under a draft: rules replaces your current rules, default_tier your default tier; since defaults to 30 days ago and limit to 200 (at most 500). It is read-only and answers:

{ evaluated, changed, grants: 'none', rows: [{ event_id, at, item_id, title, agent, chain, mode, host, current_tier, simulated_tier, reasons, source }] }

The sources are use.allowed, use.denied and approval.requested events (they store chain, mode, host, action_hash, purpose, task and provenance from this release on, never the raw action) plus the stored requests of older t3/t4 approvals (source: 'approval'). Sensitivity and bound hosts come from each item's current metadata. Grants and anomaly guards are not replayed. The same check is available to agents as the read-only MCP tool vault_policy_simulate.

Signed receipts

Security-relevant events are signed: use.allowed, use.denied, approval.approved, approval.denied, approval.vote and honeytoken. Each carries a DSSE envelope at payload.receipt in the audit log:

  • payloadType: application/vnd.codai.vault-receipt+json;
  • payload: { v: 1, event, user_id, item_id, agent, tier, action_hash, reasons, device_id, approval_id, at } (metadata only, never a value);
  • signatures: [{ keyid, sig }], ECDSA P-256 SHA-256 from a Cloud KMS key.

To verify, fetch GET /v1/vault/receipts/keys ({ keys: [{ kid, alg: 'ES256', pem }] }, cached 1 hour), pick the key whose kid equals keyid, and check the signature over the DSSE pre-authentication encoding DSSEv1 <len(type)> <type> <len(payload)> <payload>. The hub does this in the browser and shows a "signed" badge. If signing is unavailable the event is still stored, unsigned; verification never passes an unsigned or tampered receipt.

Weekly digest

Every Monday at 07:10 (Europe/Bucharest) users with vault activity that week get an email through Kodwen mail: uses, denials, approvals, new devices, items due for rotation. Turn it off with the unsubscribe link, the hub setting or PATCH /v1/vault/settings { digest_opt_out: true }; GET /v1/vault/account shows digest_opt_out.

Scoped API keys

An API key can carry scopes; null (every existing key) is a full key. A scoped key may call only:

ScopeRoutes
vault.readGET /v1/vault/account, GET /v1/vault/vaults, GET /v1/vault/items, GET /v1/vault/catalog, GET /v1/vault/receipts/keys
vault.addPOST /v1/vault/add-requests, GET /v1/vault/add-requests/{id}, DELETE /v1/vault/add-requests/{id}

Anything else answers 403 key_scope_forbidden. Connect browser extension on the hub keys page mints a key with vault.read + vault.add, valid 180 days, after a fresh sign-in, and shows it once. The extension stores it encrypted with a non-extractable AES-GCM key kept in IndexedDB, never in plaintext. GET /v1/vault/account returns key_scopes for the calling key, and the extension warns when it is running on a full key.

Repo honeytokens

codaid vault import --apply also plants 2 decoy items per imported repo (named after --repo or the folder): agent-visible api_key items marked honeytoken, such as aws-prod-legacy, stripe-live-old, github-deploy-old or openai-prod-old, bound to the real provider host and filled with random values in the provider's format (never real). The choice is deterministic per repo and skips slugs you already have; a dry run lists them as decoy. Repo files are never touched. Any use triggers the honeytoken path above (deny, lock, vault.alarm push). Pass --no-honeytokens to skip them.

GET
/v1/vault/account

Authorization

bearerAuth
headerAuthorizationBearer <codai_…>

A Kodwen 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
bundle*

Account bundle: public keys, private keys sealed under the account key, unlock slots (wrapped keys + KDF parameters). No plaintext.

fingerprint*string

Public key fingerprint to compare between devices.

default_tier*string
Value in"t1""t2""t3""t4"
revision*integer
locked_at?string

When the vault was locked (panic or honeytoken); null when unlocked. While locked every agent use is denied with reason vault_locked.

Formatdate-time
lock_reason?string

Why the vault is locked.

Value in"panic""honeytoken"
guards?
key_scopes?array<>

Scopes of the API key that made this call; null = full (unscoped) key. The browser extension warns when it runs on a full key.

digest_opt_out?boolean

True when you turned off the weekly vault digest email.

Example Request
curl -X GET 'https://ai.codai.ro/v1/vault/account'
Example Response
{  "bundle": {},  "fingerprint": "string",  "default_tier": "t1",  "revision": 0,  "locked_at": "2019-08-24T14:15:22Z",  "lock_reason": "panic",  "guards": {    "new_host": true,    "burst_max": 0,    "burst_window_s": 10,    "hour_start": 0,    "hour_end": 1,    "tz": "string"  },  "key_scopes": [    "vault.read"  ],  "digest_opt_out": true}
PUT
/v1/vault/account

Authorization

bearerAuth
headerAuthorizationBearer <codai_…>

A Kodwen 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
bundle*
revision?integer

Response Body

Updated.

application/json
  1. response
[key: string]?any
Example Request
curl -X PUT 'https://ai.codai.ro/v1/vault/account' \  -H 'Content-Type: application/json' \  -d '{  "bundle": {}}'
Example Response
{}
PATCH
/v1/vault/settings

Authorization

bearerAuth
headerAuthorizationBearer <codai_…>

A Kodwen 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
default_tier?string
Value in"t1""t2""t3""t4"
guards?

Any subset of VaultGuards; hour_start and hour_end are both null (off) or both set.

digest_opt_out?boolean

True stops the weekly digest email (Monday 07:10 Europe/Bucharest).

Response Body

Effective settings.

application/json
  1. response
default_tier*string
Value in"t1""t2""t3""t4"
guards*
digest_opt_out?boolean
Example Request
curl -X PATCH 'https://ai.codai.ro/v1/vault/settings' \  -H 'Content-Type: application/json' \  -d '{}'
Example Response
{  "default_tier": "t1",  "guards": {    "new_host": true,    "burst_max": 0,    "burst_window_s": 10,    "hour_start": 0,    "hour_end": 1,    "tz": "string"  },  "digest_opt_out": true}
POST
/v1/vault/lock

Authorization

bearerAuth
headerAuthorizationBearer <codai_…>

A Kodwen 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
via?string
Value in"hub""desktop""phone""wear""extension"

Response Body

Locked.

application/json
  1. response
locked_at*string
Formatdate-time
grants_revoked*integer

Live grants revoked by this lock.

approvals_denied*integer

Pending approvals denied by this lock.

Example Request
curl -X POST 'https://ai.codai.ro/v1/vault/lock' \  -H 'Content-Type: application/json' \  -d '{}'
Example Response
{  "locked_at": "2019-08-24T14:15:22Z",  "grants_revoked": 0,  "approvals_denied": 0}
POST
/v1/vault/unlock/challenge

Authorization

bearerAuth
headerAuthorizationBearer <codai_…>

A Kodwen 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
challenge*string

32 random bytes, base64url. Single use.

expires_at*string

5 minutes after issue.

Formatdate-time
message_template*string

codai-vault/unlock/v1|<challenge>|<device_id or passkey>: the UTF-8 message the proof covers.

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

Authorization

bearerAuth
headerAuthorizationBearer <codai_…>

A Kodwen 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
challenge*string

Challenge from POST /v1/vault/unlock/challenge.

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

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

Response Body

Unlocked.

application/json
  1. response
unlocked*boolean
Example Request
curl -X POST 'https://ai.codai.ro/v1/vault/unlock' \  -H 'Content-Type: application/json' \  -d '{  "challenge": "string",  "proof": {    "type": "device",    "device_id": "string",    "signature": "string"  }}'
Example Response
{  "unlocked": true}
GET
/v1/vault/limits

Authorization

bearerAuth
headerAuthorizationBearer <codai_…>

A Kodwen 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
limits*array<>
Example Request
curl -X GET 'https://ai.codai.ro/v1/vault/limits'
Example Response
{  "limits": [    {      "item_id": "4d8cd62e-a579-4dae-af8c-3172f96f8f7c",      "per_minute": 1,      "per_day": 1,      "units_per_day": 1    }  ]}
PUT
/v1/vault/items/{id}/limits

Authorization

bearerAuth
headerAuthorizationBearer <codai_…>

A Kodwen 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
per_minute*integer
Range1 <= value <= 10000
per_day*integer
Range1 <= value <= 1000000
units_per_day*integer
Range1 <= value <= 1000000

Response Body

OK; limits is null when removed.

application/json
  1. response
limits*
Example Request
curl -X PUT 'https://ai.codai.ro/v1/vault/items/497f6eca-6276-4993-bfeb-53cbbbba6f08/limits' \  -H 'Content-Type: application/json' \  -d '{  "per_minute": 1,  "per_day": 1,  "units_per_day": 1}'
Example Response
{  "limits": {    "item_id": "4d8cd62e-a579-4dae-af8c-3172f96f8f7c",    "per_minute": 1,    "per_day": 1,    "units_per_day": 1  }}
GET
/v1/vault/vaults

Authorization

bearerAuth
headerAuthorizationBearer <codai_…>

A Kodwen 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
vaults*array<>
Example Request
curl -X GET 'https://ai.codai.ro/v1/vault/vaults'
Example Response
{  "vaults": [    {      "id": "497f6eca-6276-4993-bfeb-53cbbbba6f08",      "kind": "personal",      "owner_user_id": "65139110-7c3c-4777-b692-80c218be3b9d",      "org_id": "a40f5d1f-d889-42e9-94ea-b9b33585fc6b",      "encrypted_meta": "string",      "role": "owner",      "sealed_key": {},      "quorum": {        "m": 2,        "scope": "critical"      },      "rotation_needed_at": "2019-08-24T14:15:22Z",      "key_epoch": 1,      "updated_at": "2019-08-24T14:15:22Z"    }  ]}
POST
/v1/vault/vaults

Authorization

bearerAuth
headerAuthorizationBearer <codai_…>

A Kodwen 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
id*string
Formatuuid
encrypted_meta*string
sealed_key*
org_id?string

Create an org vault in this organisation.

Formatuuid

Response Body

Created.

application/json
  1. response
[key: string]?any
Example Request
curl -X POST 'https://ai.codai.ro/v1/vault/vaults' \  -H 'Content-Type: application/json' \  -d '{  "id": "497f6eca-6276-4993-bfeb-53cbbbba6f08",  "encrypted_meta": "string",  "sealed_key": {}}'
Example Response
{}
GET
/v1/vault/items

Authorization

bearerAuth
headerAuthorizationBearer <codai_…>

A Kodwen 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).

Query Parameters

since?string

Only changes after this time. Send an ISO 8601 date-time with a time zone, for example 2026-11-10T12:00:00Z.

Formatdate-time

Response Body

OK.

application/json
  1. response
items*array<>
server_time*string

The since of your next call (see the operation description).

Formatdate-time
has_more*boolean

True: call again now with since = server_time.

Example Request
curl -X GET 'https://ai.codai.ro/v1/vault/items'
Example Response
{  "items": [    {      "id": "497f6eca-6276-4993-bfeb-53cbbbba6f08",      "vault_id": "432b199b-1f71-42bf-ba0b-33d512afa9de",      "revision": 1,      "wrapped_key": "string",      "ciphertext": "string",      "kind": "login",      "slug": "string",      "agent_meta": {},      "honeytoken": true,      "cloud_usable": true,      "deleted_at": "2019-08-24T14:15:22Z",      "last_used_at": "2019-08-24T14:15:22Z",      "created_at": "2019-08-24T14:15:22Z",      "updated_at": "2019-08-24T14:15:22Z"    }  ],  "server_time": "2019-08-24T14:15:22Z",  "has_more": true}
PUT
/v1/vault/items/{id}

Authorization

bearerAuth
headerAuthorizationBearer <codai_…>

A Kodwen 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
vault_id*string
Formatuuid
revision*integer
Range1 <= value
wrapped_key*string

Per-item key wrapped under the vault key (v1.<nonce>.<ciphertext>).

ciphertext*string

The item sealed under its item key.

kind*string
Value in"login""api_key""env""ssh_key""oauth_token""note""card""identity""passkey"
slug?string

Handle slug when the item is exposed to agents.

agent_meta?

Public catalogue entry for agents (title, sensitivity, hosts, env var). Never secret values.

honeytoken?boolean

Decoy flag. Owner or manager only; an update must send the stored value back.

Response Body

Updated.

application/json
  1. response
id*string
Formatuuid
revision*integer
Range1 <= value
Example Request
curl -X PUT 'https://ai.codai.ro/v1/vault/items/497f6eca-6276-4993-bfeb-53cbbbba6f08' \  -H 'Content-Type: application/json' \  -d '{  "vault_id": "432b199b-1f71-42bf-ba0b-33d512afa9de",  "revision": 1,  "wrapped_key": "string",  "ciphertext": "string",  "kind": "login"}'
Example Response
{  "id": "497f6eca-6276-4993-bfeb-53cbbbba6f08",  "revision": 1}
DELETE
/v1/vault/items/{id}

Authorization

bearerAuth
headerAuthorizationBearer <codai_…>

A Kodwen 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

Response Body

Deleted.

application/json
  1. response
deleted*boolean
Example Request
curl -X DELETE 'https://ai.codai.ro/v1/vault/items/497f6eca-6276-4993-bfeb-53cbbbba6f08'
Example Response
{  "deleted": true}
PUT
/v1/vault/items/{id}/cloud

Authorization

bearerAuth
headerAuthorizationBearer <codai_…>

A Kodwen 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
item*

Response Body

OK.

application/json
  1. response
cloud_usable*boolean
Example Request
curl -X PUT 'https://ai.codai.ro/v1/vault/items/497f6eca-6276-4993-bfeb-53cbbbba6f08/cloud' \  -H 'Content-Type: application/json' \  -d '{  "item": {}}'
Example Response
{  "cloud_usable": true}
GET
/v1/vault/rules

Authorization

bearerAuth
headerAuthorizationBearer <codai_…>

A Kodwen 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
rules*array<>
Example Request
curl -X GET 'https://ai.codai.ro/v1/vault/rules'
Example Response
{  "rules": [    {      "id": "497f6eca-6276-4993-bfeb-53cbbbba6f08",      "userId": "2c4a230c-5085-4924-a3e1-25fb4fc5965b",      "itemId": "f11b669d-7201-4c21-88af-d85092f0c005",      "agent": "string",      "mode": "env",      "tier": "t0",      "createdAt": "2019-08-24T14:15:22Z",      "updatedAt": "2019-08-24T14:15:22Z"    }  ]}
POST
/v1/vault/rules

Authorization

bearerAuth
headerAuthorizationBearer <codai_…>

A Kodwen 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
item_id?string
Formatuuid
agent?string
mode?string
Value in"env""http""autofill""ssh_sign""reveal"
tier*string
Value in"t0""t1""t2""t3""t4""deny"

Response Body

Created.

application/json
  1. response
rule*

The stored row as it is: the keys of this object are camelCase.

Example Request
curl -X POST 'https://ai.codai.ro/v1/vault/rules' \  -H 'Content-Type: application/json' \  -d '{  "tier": "t0"}'
Example Response
{  "rule": {    "id": "497f6eca-6276-4993-bfeb-53cbbbba6f08",    "userId": "2c4a230c-5085-4924-a3e1-25fb4fc5965b",    "itemId": "f11b669d-7201-4c21-88af-d85092f0c005",    "agent": "string",    "mode": "env",    "tier": "t0",    "createdAt": "2019-08-24T14:15:22Z",    "updatedAt": "2019-08-24T14:15:22Z"  }}
DELETE
/v1/vault/rules/{id}

Authorization

bearerAuth
headerAuthorizationBearer <codai_…>

A Kodwen 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

Response Body

Deleted.

application/json
  1. response
[key: string]?any
Example Request
curl -X DELETE 'https://ai.codai.ro/v1/vault/rules/497f6eca-6276-4993-bfeb-53cbbbba6f08'
Example Response
{}
GET
/v1/vault/grants

Authorization

bearerAuth
headerAuthorizationBearer <codai_…>

A Kodwen 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
grants*array<>
Example Request
curl -X GET 'https://ai.codai.ro/v1/vault/grants'
Example Response
{  "grants": [    {      "id": "497f6eca-6276-4993-bfeb-53cbbbba6f08",      "userId": "2c4a230c-5085-4924-a3e1-25fb4fc5965b",      "itemId": "f11b669d-7201-4c21-88af-d85092f0c005",      "agent": "string",      "mode": "env",      "host": "string",      "task": "string",      "usesLeft": 0,      "expiresAt": "2019-08-24T14:15:22Z",      "revokedAt": "2019-08-24T14:15:22Z",      "approvalId": "23bbe807-dea1-4601-b208-07fd1aaad2b6",      "createdAt": "2019-08-24T14:15:22Z"    }  ]}
POST
/v1/vault/grants/revoke-task

Authorization

bearerAuth
headerAuthorizationBearer <codai_…>

A Kodwen 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
task*string
Length1 <= length <= 120

Response Body

OK.

application/json
  1. response
revoked*integer
Example Request
curl -X POST 'https://ai.codai.ro/v1/vault/grants/revoke-task' \  -H 'Content-Type: application/json' \  -d '{  "task": "string"}'
Example Response
{  "revoked": 0}
DELETE
/v1/vault/grants/{id}

Authorization

bearerAuth
headerAuthorizationBearer <codai_…>

A Kodwen 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

Response Body

Revoked.

application/json
  1. response
[key: string]?any
Example Request
curl -X DELETE 'https://ai.codai.ro/v1/vault/grants/497f6eca-6276-4993-bfeb-53cbbbba6f08'
Example Response
{}
GET
/v1/vault/events

Authorization

bearerAuth
headerAuthorizationBearer <codai_…>

A Kodwen 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).

Query Parameters

limit?integer

1-500, default 100.

Response Body

OK.

application/json
  1. response
events*array<>
Example Request
curl -X GET 'https://ai.codai.ro/v1/vault/events'
Example Response
{  "events": [    {      "id": 0,      "userId": "2c4a230c-5085-4924-a3e1-25fb4fc5965b",      "kind": "string",      "itemId": "f11b669d-7201-4c21-88af-d85092f0c005",      "agent": "string",      "tier": "string",      "payload": {},      "createdAt": "2019-08-24T14:15:22Z"    }  ]}
GET
/v1/vault/approvals

Authorization

bearerAuth
headerAuthorizationBearer <codai_…>

A Kodwen 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
approvals*array<>
Example Request
curl -X GET 'https://ai.codai.ro/v1/vault/approvals'
Example Response
{  "approvals": [    {      "id": "497f6eca-6276-4993-bfeb-53cbbbba6f08",      "quorum": {        "m": 2,        "approvals": 0,        "needed": 0,        "voted": true      },      "userId": "2c4a230c-5085-4924-a3e1-25fb4fc5965b",      "itemId": "f11b669d-7201-4c21-88af-d85092f0c005",      "agent": "string",      "tier": "t3",      "request": {},      "actionHash": "string",      "reasons": [        "string"      ],      "status": "pending",      "expiresAt": "2019-08-24T14:15:22Z",      "createdAt": "2019-08-24T14:15:22Z"    }  ]}
GET
/v1/vault/approvals/{id}

Authorization

bearerAuth
headerAuthorizationBearer <codai_…>

A Kodwen 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

Response Body

OK.

application/json
  1. response
id*string
Formatuuid
status*string
Value in"pending""approved""denied""expired""cancelled"
tier*string
Value in"t3""t4"
expiresAt*string
Formatdate-time
grantScope?string

Scope picked on approval: once, task, 1h or 8h.

decidedDeviceId?string

Device key, or passkey, that proved the decision.

quorum?
vault_id?string

Vault of the item; present on quorum approvals.

Formatuuid
[key: string]?any
Example Request
curl -X GET 'https://ai.codai.ro/v1/vault/approvals/497f6eca-6276-4993-bfeb-53cbbbba6f08'
Example Response
{  "id": "497f6eca-6276-4993-bfeb-53cbbbba6f08",  "status": "pending",  "tier": "t3",  "expiresAt": "2019-08-24T14:15:22Z",  "grantScope": "string",  "decidedDeviceId": "string",  "quorum": {    "m": 2,    "approvals": 0,    "needed": 0,    "voted": true  },  "vault_id": "432b199b-1f71-42bf-ba0b-33d512afa9de"}
POST
/v1/vault/approvals/{id}/decide

Authorization

bearerAuth
headerAuthorizationBearer <codai_…>

A Kodwen 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

qr: an offline phone signed the decision and the desktop read the response QR.

Value in"hub""desktop""phone""wear""extension""qr"
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
{}
POST
/v1/vault/devices/challenge

Authorization

bearerAuth
headerAuthorizationBearer <codai_…>

A Kodwen 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"}
GET
/v1/vault/devices

Authorization

bearerAuth
headerAuthorizationBearer <codai_…>

A Kodwen 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"    }  ]}
POST
/v1/vault/devices

Authorization

bearerAuth
headerAuthorizationBearer <codai_…>

A Kodwen 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|ios|cli)-[A-Za-z0-9._-]{4,80}$
kind*string
Value in"android_keystore""windows_hello""cli_ed25519""ios_secure_enclave"
name*string
Length1 <= length <= 80
surface*string
Value in"phone""wear""desktop""cli"
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 and for ios_secure_enclave, RSASSA-PKCS1-v1_5 SHA-256 for Windows Hello, raw 64-byte Ed25519 for cli_ed25519), 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. Must be absent for cli_ed25519 (surface cli, stored as software) and for ios_secure_enclave (surface phone, stored as ios-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"  }}
PATCH
/v1/vault/devices/{id}

Authorization

bearerAuth
headerAuthorizationBearer <codai_…>

A Kodwen 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|ios|cli)-[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 Kodwen 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|ios|cli)-[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}
GET
/v1/vault/catalog

Authorization

bearerAuth
headerAuthorizationBearer <codai_…>

A Kodwen 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
[key: string]?any
Example Request
curl -X GET 'https://ai.codai.ro/v1/vault/catalog'
Example Response
{}
POST
/v1/vault/use

Authorization

bearerAuth
headerAuthorizationBearer <codai_…>

A Kodwen 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
handle*string
mode*string
Value in"env""http""autofill""ssh_sign""reveal"
host?string
action*string
purpose?string
provenance?string
Value in"user""agent""untrusted"
chain?array<string>
task?string

Response Body

Decision.

application/json
  1. response
tier*string
Value in"t0""t1""t2""t3""t4""deny"
allowed*boolean
reasons*array<string>

Why this tier. Guard reasons: vault_locked (the vault is locked, tier deny), anomaly:new_host, anomaly:burst, anomaly:hour (tier forced to t4; grants not approved at t4 are ignored).

approval_id?string
Formatuuid
match_number?string

Two digits to show the user with the approval request; they tap the same number on the approving device (number matching). Null when no approval is needed.

Match^[0-9]{2}$
action_hash?string

Hash of the exact request the approver signs.

cloud_usable?boolean
Example Request
curl -X POST 'https://ai.codai.ro/v1/vault/use' \  -H 'Content-Type: application/json' \  -d '{  "handle": "string",  "mode": "env",  "action": "string"}'
Example Response
{  "tier": "t0",  "allowed": true,  "reasons": [    "string"  ],  "approval_id": "15af2260-72db-4fc4-b587-6b841d6a3d55",  "match_number": "string",  "action_hash": "string",  "cloud_usable": true}
POST
/v1/vault/http

Authorization

bearerAuth
headerAuthorizationBearer <codai_…>

A Kodwen 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
handle*string
url*string
Formaturi
method?string
Value in"GET""POST""PUT""PATCH""DELETE""HEAD"
headers?
body?string
purpose?string
provenance?string
Value in"user""agent""untrusted"
chain?array<string>
task?string

Response Body

Upstream response (redacted).

application/json
  1. response
[key: string]?any
Example Request
curl -X POST 'https://ai.codai.ro/v1/vault/http' \  -H 'Content-Type: application/json' \  -d '{  "handle": "string",  "url": "http://example.com"}'
Example Response
{}
POST
/v1/vault/resolve/challenge

Authorization

bearerAuth
headerAuthorizationBearer <codai_…>

A Kodwen 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
challenge*string

32 random bytes, base64url. Single use; sign it into the next POST /v1/vault/resolve proof.

expires_at*string

120 seconds after issue.

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

Authorization

bearerAuth
headerAuthorizationBearer <codai_…>

A Kodwen 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
handles*array<string>
action*string
purpose?string
provenance?string
Value in"user""agent""untrusted"
chain?array<string>
task?string
proof*

Response Body

Decrypted items keyed by handle.

application/json
  1. response
[key: string]?any
Example Request
curl -X POST 'https://ai.codai.ro/v1/vault/resolve' \  -H 'Content-Type: application/json' \  -d '{  "handles": [    "string"  ],  "action": "string",  "proof": {    "device_id": "string",    "challenge": "string",    "signature": "string"  }}'
Example Response
{}
POST
/v1/vault/add-requests

Authorization

bearerAuth
headerAuthorizationBearer <codai_…>

A Kodwen 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
kind*string
Value in"login""api_key""env""ssh_key""oauth_token""note""card""identity""passkey"
title*string
Length1 <= length <= 120
slug?string

Suggested handle slug (the user may change it).

hosts?array<>

Hosts the secret should be bound to.

Itemsitems <= 10
env_var?string
Match^[A-Z_][A-Z0-9_]{0,63}$
fields?array<>

Field names the user should fill in.

Itemsitems <= 10
purpose*string

Why the agent needs this secret; shown to the user.

Length1 <= length <= 500
task?string
Length1 <= length <= 120

Response Body

Created or reused.

application/json
  1. response
id*string
Formatuuid
url*string

Hub link the user opens to add the secret. Null when an equivalent pending request was reused (the link token is not stored).

status*string

A pending request past its expiry reads as expired.

Value in"pending""completed""cancelled""expired"
expires_at*string
Formatdate-time
Example Request
curl -X POST 'https://ai.codai.ro/v1/vault/add-requests' \  -H 'Content-Type: application/json' \  -d '{  "kind": "login",  "title": "string",  "purpose": "string"}'
Example Response
{  "id": "497f6eca-6276-4993-bfeb-53cbbbba6f08",  "url": "string",  "status": "pending",  "expires_at": "2019-08-24T14:15:22Z"}
POST
/v1/vault/add-requests/lookup

Authorization

bearerAuth
headerAuthorizationBearer <codai_…>

A Kodwen 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
token*string
Match^[A-Za-z0-9_-]{43}$

Response Body

OK.

application/json
  1. response
id*string
Formatuuid
status*string

A pending request past its expiry reads as expired.

Value in"pending""completed""cancelled""expired"
agent*string
kind*string
Value in"login""api_key""env""ssh_key""oauth_token""note""card""identity""passkey"
title*string
slug*string
hosts*array<string>
env_var*string
fields*array<string>
purpose*string
task*string
handle*string
item_id*string
Formatuuid
expires_at*string
Formatdate-time
completed_at*string
Formatdate-time
created_at*string
Formatdate-time
Example Request
curl -X POST 'https://ai.codai.ro/v1/vault/add-requests/lookup' \  -H 'Content-Type: application/json' \  -d '{  "token": "string"}'
Example Response
{  "id": "497f6eca-6276-4993-bfeb-53cbbbba6f08",  "status": "pending",  "agent": "string",  "kind": "login",  "title": "string",  "slug": "string",  "hosts": [    "string"  ],  "env_var": "string",  "fields": [    "string"  ],  "purpose": "string",  "task": "string",  "handle": "string",  "item_id": "4d8cd62e-a579-4dae-af8c-3172f96f8f7c",  "expires_at": "2019-08-24T14:15:22Z",  "completed_at": "2019-08-24T14:15:22Z",  "created_at": "2019-08-24T14:15:22Z"}
GET
/v1/vault/add-requests/{id}

Authorization

bearerAuth
headerAuthorizationBearer <codai_…>

A Kodwen 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

Response Body

OK.

application/json
  1. response
id*string
Formatuuid
status*string

A pending request past its expiry reads as expired.

Value in"pending""completed""cancelled""expired"
slug*string
handle*string

{{vault:<slug>}} once completed.

item_id*string
Formatuuid
expires_at*string
Formatdate-time
Example Request
curl -X GET 'https://ai.codai.ro/v1/vault/add-requests/497f6eca-6276-4993-bfeb-53cbbbba6f08'
Example Response
{  "id": "497f6eca-6276-4993-bfeb-53cbbbba6f08",  "status": "pending",  "slug": "string",  "handle": "string",  "item_id": "4d8cd62e-a579-4dae-af8c-3172f96f8f7c",  "expires_at": "2019-08-24T14:15:22Z"}
DELETE
/v1/vault/add-requests/{id}

Authorization

bearerAuth
headerAuthorizationBearer <codai_…>

A Kodwen 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

Response Body

Cancelled.

application/json
  1. response
id*string
Formatuuid
status*string

A pending request past its expiry reads as expired.

Value in"pending""completed""cancelled""expired"
slug*string
handle*string

{{vault:<slug>}} once completed.

item_id*string
Formatuuid
expires_at*string
Formatdate-time
Example Request
curl -X DELETE 'https://ai.codai.ro/v1/vault/add-requests/497f6eca-6276-4993-bfeb-53cbbbba6f08'
Example Response
{  "id": "497f6eca-6276-4993-bfeb-53cbbbba6f08",  "status": "pending",  "slug": "string",  "handle": "string",  "item_id": "4d8cd62e-a579-4dae-af8c-3172f96f8f7c",  "expires_at": "2019-08-24T14:15:22Z"}
POST
/v1/vault/add-requests/{id}/complete

Authorization

bearerAuth
headerAuthorizationBearer <codai_…>

A Kodwen 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
item_id*string
Formatuuid

Response Body

Completed.

application/json
  1. response
id*string
Formatuuid
status*string

A pending request past its expiry reads as expired.

Value in"pending""completed""cancelled""expired"
slug*string
handle*string

{{vault:<slug>}} once completed.

item_id*string
Formatuuid
expires_at*string
Formatdate-time
Example Request
curl -X POST 'https://ai.codai.ro/v1/vault/add-requests/497f6eca-6276-4993-bfeb-53cbbbba6f08/complete' \  -H 'Content-Type: application/json' \  -d '{  "item_id": "4d8cd62e-a579-4dae-af8c-3172f96f8f7c"}'
Example Response
{  "id": "497f6eca-6276-4993-bfeb-53cbbbba6f08",  "status": "pending",  "slug": "string",  "handle": "string",  "item_id": "4d8cd62e-a579-4dae-af8c-3172f96f8f7c",  "expires_at": "2019-08-24T14:15:22Z"}
GET
/v1/vault/emergency

Authorization

bearerAuth
headerAuthorizationBearer <codai_…>

A Kodwen 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
outgoing*array<>
incoming*array<>
Example Request
curl -X GET 'https://ai.codai.ro/v1/vault/emergency'
Example Response
{  "outgoing": [    {      "id": "497f6eca-6276-4993-bfeb-53cbbbba6f08",      "role": "grantor",      "email": "string",      "other_name": "string",      "status": "invited",      "delay_hours": 24,      "vault_ids": [        "ce59860b-d4b4-4beb-86e8-1b6dbe13411f"      ],      "grantee_fingerprint": "string",      "grantee_public_keys": {},      "request_reason": "string",      "requested_at": "2019-08-24T14:15:22Z",      "release_at": "2019-08-24T14:15:22Z",      "granted_at": "2019-08-24T14:15:22Z",      "denied_at": "2019-08-24T14:15:22Z",      "created_at": "2019-08-24T14:15:22Z"    }  ],  "incoming": [    {      "id": "497f6eca-6276-4993-bfeb-53cbbbba6f08",      "role": "grantor",      "email": "string",      "other_name": "string",      "status": "invited",      "delay_hours": 24,      "vault_ids": [        "ce59860b-d4b4-4beb-86e8-1b6dbe13411f"      ],      "grantee_fingerprint": "string",      "grantee_public_keys": {},      "request_reason": "string",      "requested_at": "2019-08-24T14:15:22Z",      "release_at": "2019-08-24T14:15:22Z",      "granted_at": "2019-08-24T14:15:22Z",      "denied_at": "2019-08-24T14:15:22Z",      "created_at": "2019-08-24T14:15:22Z"    }  ]}
POST
/v1/vault/emergency

Authorization

bearerAuth
headerAuthorizationBearer <codai_…>

A Kodwen 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
email*string
Formatemail
Lengthlength <= 320
delay_hours*integer

Hours between an access request and automatic release, unless the grantor denies it.

Value in244872
vault_ids?array<>

Vaults covered; omitted = every vault you own.

Items1 <= items <= 20

Response Body

Invited.

application/json
  1. response
id*string
Formatuuid
Example Request
curl -X POST 'https://ai.codai.ro/v1/vault/emergency' \  -H 'Content-Type: application/json' \  -d '{  "email": "user@example.com",  "delay_hours": 24}'
Example Response
{  "id": "497f6eca-6276-4993-bfeb-53cbbbba6f08"}
POST
/v1/vault/emergency/accept

Authorization

bearerAuth
headerAuthorizationBearer <codai_…>

A Kodwen 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
token*string
Match^[A-Za-z0-9_-]{43}$

Response Body

Accepted.

application/json
  1. response
id*string
Formatuuid
role*string

Your side of the relationship.

Value in"grantor""grantee"
email*string

The invited email (grantor view).

other_name*string

Display name of the other party.

status*string

invited → accepted (the contact has a vault account) → sealed (the grantor sealed the vault keys to the contact) → requested → granted (approved, or the wait elapsed with no denial). A denial returns to sealed.

Value in"invited""accepted""sealed""requested""granted""revoked"
delay_hours*integer

Hours between an access request and automatic release, unless the grantor denies it.

Value in244872
vault_ids*array<>
grantee_fingerprint*string

Fingerprint of the contact public keys the grantor sealed to.

grantee_public_keys*

Contact public keys (grantor view, after acceptance) to seal the vault keys to.

request_reason*string
requested_at*string
Formatdate-time
release_at*string

When a pending request is released automatically.

Formatdate-time
granted_at*string
Formatdate-time
denied_at*string
Formatdate-time
created_at*string
Formatdate-time
Example Request
curl -X POST 'https://ai.codai.ro/v1/vault/emergency/accept' \  -H 'Content-Type: application/json' \  -d '{  "token": "string"}'
Example Response
{  "id": "497f6eca-6276-4993-bfeb-53cbbbba6f08",  "role": "grantor",  "email": "string",  "other_name": "string",  "status": "invited",  "delay_hours": 24,  "vault_ids": [    "ce59860b-d4b4-4beb-86e8-1b6dbe13411f"  ],  "grantee_fingerprint": "string",  "grantee_public_keys": {},  "request_reason": "string",  "requested_at": "2019-08-24T14:15:22Z",  "release_at": "2019-08-24T14:15:22Z",  "granted_at": "2019-08-24T14:15:22Z",  "denied_at": "2019-08-24T14:15:22Z",  "created_at": "2019-08-24T14:15:22Z"}
PUT
/v1/vault/emergency/{id}/seal

Authorization

bearerAuth
headerAuthorizationBearer <codai_…>

A Kodwen 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
grantee_fingerprint*string
Length8 <= length <= 200
sealed_keys*

Vault id → vault key sealed to the contact.

Properties1 <= properties <= 20

Response Body

Sealed.

application/json
  1. response
id*string
Formatuuid
role*string

Your side of the relationship.

Value in"grantor""grantee"
email*string

The invited email (grantor view).

other_name*string

Display name of the other party.

status*string

invited → accepted (the contact has a vault account) → sealed (the grantor sealed the vault keys to the contact) → requested → granted (approved, or the wait elapsed with no denial). A denial returns to sealed.

Value in"invited""accepted""sealed""requested""granted""revoked"
delay_hours*integer

Hours between an access request and automatic release, unless the grantor denies it.

Value in244872
vault_ids*array<>
grantee_fingerprint*string

Fingerprint of the contact public keys the grantor sealed to.

grantee_public_keys*

Contact public keys (grantor view, after acceptance) to seal the vault keys to.

request_reason*string
requested_at*string
Formatdate-time
release_at*string

When a pending request is released automatically.

Formatdate-time
granted_at*string
Formatdate-time
denied_at*string
Formatdate-time
created_at*string
Formatdate-time
Example Request
curl -X PUT 'https://ai.codai.ro/v1/vault/emergency/497f6eca-6276-4993-bfeb-53cbbbba6f08/seal' \  -H 'Content-Type: application/json' \  -d '{  "grantee_fingerprint": "stringst",  "sealed_keys": {    "property1": {      "alg": "xwing-hkdf-xchacha20poly1305",      "ct": "string",      "wrapped": "string"    },    "property2": {      "alg": "xwing-hkdf-xchacha20poly1305",      "ct": "string",      "wrapped": "string"    }  }}'
Example Response
{  "id": "497f6eca-6276-4993-bfeb-53cbbbba6f08",  "role": "grantor",  "email": "string",  "other_name": "string",  "status": "invited",  "delay_hours": 24,  "vault_ids": [    "ce59860b-d4b4-4beb-86e8-1b6dbe13411f"  ],  "grantee_fingerprint": "string",  "grantee_public_keys": {},  "request_reason": "string",  "requested_at": "2019-08-24T14:15:22Z",  "release_at": "2019-08-24T14:15:22Z",  "granted_at": "2019-08-24T14:15:22Z",  "denied_at": "2019-08-24T14:15:22Z",  "created_at": "2019-08-24T14:15:22Z"}
PATCH
/v1/vault/emergency/{id}

Authorization

bearerAuth
headerAuthorizationBearer <codai_…>

A Kodwen 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
delay_hours?integer

Hours between an access request and automatic release, unless the grantor denies it.

Value in244872
vault_ids?array<>

Vaults covered; omitted = every vault you own.

Items1 <= items <= 20

Response Body

Updated.

application/json
  1. response
id*string
Formatuuid
role*string

Your side of the relationship.

Value in"grantor""grantee"
email*string

The invited email (grantor view).

other_name*string

Display name of the other party.

status*string

invited → accepted (the contact has a vault account) → sealed (the grantor sealed the vault keys to the contact) → requested → granted (approved, or the wait elapsed with no denial). A denial returns to sealed.

Value in"invited""accepted""sealed""requested""granted""revoked"
delay_hours*integer

Hours between an access request and automatic release, unless the grantor denies it.

Value in244872
vault_ids*array<>
grantee_fingerprint*string

Fingerprint of the contact public keys the grantor sealed to.

grantee_public_keys*

Contact public keys (grantor view, after acceptance) to seal the vault keys to.

request_reason*string
requested_at*string
Formatdate-time
release_at*string

When a pending request is released automatically.

Formatdate-time
granted_at*string
Formatdate-time
denied_at*string
Formatdate-time
created_at*string
Formatdate-time
Example Request
curl -X PATCH 'https://ai.codai.ro/v1/vault/emergency/497f6eca-6276-4993-bfeb-53cbbbba6f08' \  -H 'Content-Type: application/json' \  -d '{}'
Example Response
{  "id": "497f6eca-6276-4993-bfeb-53cbbbba6f08",  "role": "grantor",  "email": "string",  "other_name": "string",  "status": "invited",  "delay_hours": 24,  "vault_ids": [    "ce59860b-d4b4-4beb-86e8-1b6dbe13411f"  ],  "grantee_fingerprint": "string",  "grantee_public_keys": {},  "request_reason": "string",  "requested_at": "2019-08-24T14:15:22Z",  "release_at": "2019-08-24T14:15:22Z",  "granted_at": "2019-08-24T14:15:22Z",  "denied_at": "2019-08-24T14:15:22Z",  "created_at": "2019-08-24T14:15:22Z"}
DELETE
/v1/vault/emergency/{id}

Authorization

bearerAuth
headerAuthorizationBearer <codai_…>

A Kodwen 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

Response Body

Revoked.

application/json
  1. response
id*string
Formatuuid
status*"revoked"
Value in"revoked"
Example Request
curl -X DELETE 'https://ai.codai.ro/v1/vault/emergency/497f6eca-6276-4993-bfeb-53cbbbba6f08'
Example Response
{  "id": "497f6eca-6276-4993-bfeb-53cbbbba6f08",  "status": "revoked"}
POST
/v1/vault/emergency/{id}/request

Authorization

bearerAuth
headerAuthorizationBearer <codai_…>

A Kodwen 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
reason?string
Lengthlength <= 500

Response Body

Requested.

application/json
  1. response
id*string
Formatuuid
role*string

Your side of the relationship.

Value in"grantor""grantee"
email*string

The invited email (grantor view).

other_name*string

Display name of the other party.

status*string

invited → accepted (the contact has a vault account) → sealed (the grantor sealed the vault keys to the contact) → requested → granted (approved, or the wait elapsed with no denial). A denial returns to sealed.

Value in"invited""accepted""sealed""requested""granted""revoked"
delay_hours*integer

Hours between an access request and automatic release, unless the grantor denies it.

Value in244872
vault_ids*array<>
grantee_fingerprint*string

Fingerprint of the contact public keys the grantor sealed to.

grantee_public_keys*

Contact public keys (grantor view, after acceptance) to seal the vault keys to.

request_reason*string
requested_at*string
Formatdate-time
release_at*string

When a pending request is released automatically.

Formatdate-time
granted_at*string
Formatdate-time
denied_at*string
Formatdate-time
created_at*string
Formatdate-time
Example Request
curl -X POST 'https://ai.codai.ro/v1/vault/emergency/497f6eca-6276-4993-bfeb-53cbbbba6f08/request' \  -H 'Content-Type: application/json' \  -d '{}'
Example Response
{  "id": "497f6eca-6276-4993-bfeb-53cbbbba6f08",  "role": "grantor",  "email": "string",  "other_name": "string",  "status": "invited",  "delay_hours": 24,  "vault_ids": [    "ce59860b-d4b4-4beb-86e8-1b6dbe13411f"  ],  "grantee_fingerprint": "string",  "grantee_public_keys": {},  "request_reason": "string",  "requested_at": "2019-08-24T14:15:22Z",  "release_at": "2019-08-24T14:15:22Z",  "granted_at": "2019-08-24T14:15:22Z",  "denied_at": "2019-08-24T14:15:22Z",  "created_at": "2019-08-24T14:15:22Z"}
POST
/v1/vault/emergency/{id}/deny

Authorization

bearerAuth
headerAuthorizationBearer <codai_…>

A Kodwen 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

Response Body

Denied.

application/json
  1. response
id*string
Formatuuid
role*string

Your side of the relationship.

Value in"grantor""grantee"
email*string

The invited email (grantor view).

other_name*string

Display name of the other party.

status*string

invited → accepted (the contact has a vault account) → sealed (the grantor sealed the vault keys to the contact) → requested → granted (approved, or the wait elapsed with no denial). A denial returns to sealed.

Value in"invited""accepted""sealed""requested""granted""revoked"
delay_hours*integer

Hours between an access request and automatic release, unless the grantor denies it.

Value in244872
vault_ids*array<>
grantee_fingerprint*string

Fingerprint of the contact public keys the grantor sealed to.

grantee_public_keys*

Contact public keys (grantor view, after acceptance) to seal the vault keys to.

request_reason*string
requested_at*string
Formatdate-time
release_at*string

When a pending request is released automatically.

Formatdate-time
granted_at*string
Formatdate-time
denied_at*string
Formatdate-time
created_at*string
Formatdate-time
Example Request
curl -X POST 'https://ai.codai.ro/v1/vault/emergency/497f6eca-6276-4993-bfeb-53cbbbba6f08/deny'
Example Response
{  "id": "497f6eca-6276-4993-bfeb-53cbbbba6f08",  "role": "grantor",  "email": "string",  "other_name": "string",  "status": "invited",  "delay_hours": 24,  "vault_ids": [    "ce59860b-d4b4-4beb-86e8-1b6dbe13411f"  ],  "grantee_fingerprint": "string",  "grantee_public_keys": {},  "request_reason": "string",  "requested_at": "2019-08-24T14:15:22Z",  "release_at": "2019-08-24T14:15:22Z",  "granted_at": "2019-08-24T14:15:22Z",  "denied_at": "2019-08-24T14:15:22Z",  "created_at": "2019-08-24T14:15:22Z"}
POST
/v1/vault/emergency/{id}/approve

Authorization

bearerAuth
headerAuthorizationBearer <codai_…>

A Kodwen 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

Response Body

Granted.

application/json
  1. response
id*string
Formatuuid
role*string

Your side of the relationship.

Value in"grantor""grantee"
email*string

The invited email (grantor view).

other_name*string

Display name of the other party.

status*string

invited → accepted (the contact has a vault account) → sealed (the grantor sealed the vault keys to the contact) → requested → granted (approved, or the wait elapsed with no denial). A denial returns to sealed.

Value in"invited""accepted""sealed""requested""granted""revoked"
delay_hours*integer

Hours between an access request and automatic release, unless the grantor denies it.

Value in244872
vault_ids*array<>
grantee_fingerprint*string

Fingerprint of the contact public keys the grantor sealed to.

grantee_public_keys*

Contact public keys (grantor view, after acceptance) to seal the vault keys to.

request_reason*string
requested_at*string
Formatdate-time
release_at*string

When a pending request is released automatically.

Formatdate-time
granted_at*string
Formatdate-time
denied_at*string
Formatdate-time
created_at*string
Formatdate-time
Example Request
curl -X POST 'https://ai.codai.ro/v1/vault/emergency/497f6eca-6276-4993-bfeb-53cbbbba6f08/approve'
Example Response
{  "id": "497f6eca-6276-4993-bfeb-53cbbbba6f08",  "role": "grantor",  "email": "string",  "other_name": "string",  "status": "invited",  "delay_hours": 24,  "vault_ids": [    "ce59860b-d4b4-4beb-86e8-1b6dbe13411f"  ],  "grantee_fingerprint": "string",  "grantee_public_keys": {},  "request_reason": "string",  "requested_at": "2019-08-24T14:15:22Z",  "release_at": "2019-08-24T14:15:22Z",  "granted_at": "2019-08-24T14:15:22Z",  "denied_at": "2019-08-24T14:15:22Z",  "created_at": "2019-08-24T14:15:22Z"}
GET
/v1/vault/emergency/{id}/vault-data

Authorization

bearerAuth
headerAuthorizationBearer <codai_…>

A Kodwen 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

Response Body

OK.

application/json
  1. response
vaults*array<>
items*array<>
Example Request
curl -X GET 'https://ai.codai.ro/v1/vault/emergency/497f6eca-6276-4993-bfeb-53cbbbba6f08/vault-data'
Example Response
{  "vaults": [    {      "id": "497f6eca-6276-4993-bfeb-53cbbbba6f08",      "encrypted_meta": "string",      "sealed_key": {}    }  ],  "items": [    {      "id": "497f6eca-6276-4993-bfeb-53cbbbba6f08",      "vault_id": "432b199b-1f71-42bf-ba0b-33d512afa9de",      "revision": 0,      "wrapped_key": "string",      "ciphertext": "string",      "kind": "string",      "slug": "string",      "agent_meta": null    }  ]}
GET
/v1/vault/hibp/range/{prefix}

Authorization

bearerAuth
headerAuthorizationBearer <codai_…>

A Kodwen 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

prefix*string

First 5 hex characters of the SHA-1 of the password (k-anonymity).

Match^[0-9A-Fa-f]{5}$

Response Body

SUFFIX:COUNT lines (padded).

response?stringtext/plain
Example Request
curl -X GET 'https://ai.codai.ro/v1/vault/hibp/range/string'
Example Response
"string"
GET
/v1/vault/rotation

Authorization

bearerAuth
headerAuthorizationBearer <codai_…>

A Kodwen 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
items*array<>
Example Request
curl -X GET 'https://ai.codai.ro/v1/vault/rotation'
Example Response
{  "items": [    {      "item_id": "4d8cd62e-a579-4dae-af8c-3172f96f8f7c",      "slug": "string",      "title": "string",      "kind": "string",      "interval_days": 0,      "rotated_at": "2019-08-24T14:15:22Z",      "due_at": "2019-08-24T14:15:22Z",      "overdue": true,      "breached": true,      "breach_count": 0,      "breach_checked_at": "2019-08-24T14:15:22Z",      "snoozed_until": "2019-08-24T14:15:22Z",      "provider": "stripe",      "create_url": "string"    }  ]}
PUT
/v1/vault/items/{id}/rotation

Authorization

bearerAuth
headerAuthorizationBearer <codai_…>

A Kodwen 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
interval_days?integer
Range1 <= value <= 730
provider?string
Value in"stripe""github_pat""openai""google_api_key""other"
snoozed_until?string
Formatdate-time

Response Body

OK.

application/json
  1. response
item_id*string
Formatuuid
interval_days*integer
provider*string
Value in"stripe""github_pat""openai""google_api_key""other"
snoozed_until*string
Formatdate-time
Example Request
curl -X PUT 'https://ai.codai.ro/v1/vault/items/497f6eca-6276-4993-bfeb-53cbbbba6f08/rotation' \  -H 'Content-Type: application/json' \  -d '{}'
Example Response
{  "item_id": "4d8cd62e-a579-4dae-af8c-3172f96f8f7c",  "interval_days": 0,  "provider": "stripe",  "snoozed_until": "2019-08-24T14:15:22Z"}
POST
/v1/vault/items/{id}/rotated

Authorization

bearerAuth
headerAuthorizationBearer <codai_…>

A Kodwen 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

Response Body

OK.

application/json
  1. response
item_id*string
Formatuuid
rotated_at*string
Formatdate-time
Example Request
curl -X POST 'https://ai.codai.ro/v1/vault/items/497f6eca-6276-4993-bfeb-53cbbbba6f08/rotated'
Example Response
{  "item_id": "4d8cd62e-a579-4dae-af8c-3172f96f8f7c",  "rotated_at": "2019-08-24T14:15:22Z"}
POST
/v1/vault/items/{id}/used

Authorization

bearerAuth
headerAuthorizationBearer <codai_…>

A Kodwen 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
via*string

Where the item was used.

Value in"windows_plugin""android_provider""ios_provider""desktop""phone"

Response Body

OK.

application/json
  1. response
id*string
Formatuuid
last_used_at*string
Formatdate-time
Example Request
curl -X POST 'https://ai.codai.ro/v1/vault/items/497f6eca-6276-4993-bfeb-53cbbbba6f08/used' \  -H 'Content-Type: application/json' \  -d '{  "via": "windows_plugin"}'
Example Response
{  "id": "497f6eca-6276-4993-bfeb-53cbbbba6f08",  "last_used_at": "2019-08-24T14:15:22Z"}
POST
/v1/vault/rotation/breach-report

Authorization

bearerAuth
headerAuthorizationBearer <codai_…>

A Kodwen 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
results*array<>
Itemsitems <= 500

Response Body

OK.

application/json
  1. response
recorded*integer
newly_breached*integer
Example Request
curl -X POST 'https://ai.codai.ro/v1/vault/rotation/breach-report' \  -H 'Content-Type: application/json' \  -d '{  "results": [    {      "item_id": "4d8cd62e-a579-4dae-af8c-3172f96f8f7c",      "count": 0    }  ]}'
Example Response
{  "recorded": 0,  "newly_breached": 0}
GET
/v1/vault/derivers

Authorization

bearerAuth
headerAuthorizationBearer <codai_…>

A Kodwen 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
derivers*array<>
Example Request
curl -X GET 'https://ai.codai.ro/v1/vault/derivers'
Example Response
{  "derivers": [    {      "item_id": "4d8cd62e-a579-4dae-af8c-3172f96f8f7c",      "provider": "aws_sts",      "config": {},      "max_ttl_s": 300,      "updated_at": "2019-08-24T14:15:22Z"    }  ]}
PUT
/v1/vault/items/{id}/deriver

Authorization

bearerAuth
headerAuthorizationBearer <codai_…>

A Kodwen 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
provider*string
Value in"aws_sts""gcp_sa""cloudflare""github_app"
config*
max_ttl_s*integer
Range300 <= value <= 43200

Response Body

OK.

application/json
  1. response
deriver*
Example Request
curl -X PUT 'https://ai.codai.ro/v1/vault/items/497f6eca-6276-4993-bfeb-53cbbbba6f08/deriver' \  -H 'Content-Type: application/json' \  -d '{  "provider": "aws_sts",  "config": {},  "max_ttl_s": 300}'
Example Response
{  "deriver": {    "item_id": "4d8cd62e-a579-4dae-af8c-3172f96f8f7c",    "provider": "aws_sts",    "config": {},    "max_ttl_s": 300,    "updated_at": "2019-08-24T14:15:22Z"  }}
DELETE
/v1/vault/items/{id}/deriver

Authorization

bearerAuth
headerAuthorizationBearer <codai_…>

A Kodwen 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

Response Body

Deleted.

application/json
  1. response
deleted*boolean
Example Request
curl -X DELETE 'https://ai.codai.ro/v1/vault/items/497f6eca-6276-4993-bfeb-53cbbbba6f08/deriver'
Example Response
{  "deleted": true}
POST
/v1/vault/derive

Authorization

bearerAuth
headerAuthorizationBearer <codai_…>

A Kodwen 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
handle*string
Length1 <= length <= 80
action*string
Length1 <= length <= 4000
purpose?string
Lengthlength <= 500
provenance?string
Value in"user""agent""untrusted"
chain?array<string>
Itemsitems <= 16
task?string
ttl_s?integer
Range300 <= value <= 43200
proof*

Response Body

Credential.

application/json
  1. response
provider*string
Value in"aws_sts""gcp_sa""cloudflare""github_app"
values*

Short-lived credential fields. For the broker only: inject and redact, never pass to a model.

env*

The same values keyed by environment variable name.

expires_at*string
Formatdate-time
Example Request
curl -X POST 'https://ai.codai.ro/v1/vault/derive' \  -H 'Content-Type: application/json' \  -d '{  "handle": "string",  "action": "string",  "proof": {    "device_id": "string",    "challenge": "string",    "signature": "string"  }}'
Example Response
{  "provider": "aws_sts",  "values": {    "property1": "string",    "property2": "string"  },  "env": {    "property1": "string",    "property2": "string"  },  "expires_at": "2019-08-24T14:15:22Z"}
GET
/v1/vault/vaults/{id}/members

Authorization

bearerAuth
headerAuthorizationBearer <codai_…>

A Kodwen 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

Response Body

OK.

application/json
  1. response
members*array<>
Example Request
curl -X GET 'https://ai.codai.ro/v1/vault/vaults/497f6eca-6276-4993-bfeb-53cbbbba6f08/members'
Example Response
{  "members": [    {      "user_id": "a169451c-8525-4352-b8ca-070dd449a1a5",      "email": "string",      "name": "string",      "role": "owner",      "fingerprint": "string",      "key_epoch": 1,      "added_at": "2019-08-24T14:15:22Z"    }  ]}
GET
/v1/vault/vaults/{id}/candidates

Authorization

bearerAuth
headerAuthorizationBearer <codai_…>

A Kodwen 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

Response Body

OK.

application/json
  1. response
candidates*array<>
Example Request
curl -X GET 'https://ai.codai.ro/v1/vault/vaults/497f6eca-6276-4993-bfeb-53cbbbba6f08/candidates'
Example Response
{  "candidates": [    {      "user_id": "a169451c-8525-4352-b8ca-070dd449a1a5",      "email": "string",      "name": "string",      "fingerprint": "string",      "public_keys": {        "kem": "string",        "sign": "string"      }    }  ]}
PUT
/v1/vault/vaults/{id}/members/{userId}

Authorization

bearerAuth
headerAuthorizationBearer <codai_…>

A Kodwen 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
userId*string

Member user id (uuid).

Formatuuid

Request Body

application/json
  1. body
role*string

owner is never assigned through this route.

Value in"manager""editor""viewer"
sealed_key*
fingerprint*string
Length8 <= length <= 200

Response Body

Added or re-sealed.

application/json
  1. response
member*
[key: string]?any
Example Request
curl -X PUT 'https://ai.codai.ro/v1/vault/vaults/497f6eca-6276-4993-bfeb-53cbbbba6f08/members/497f6eca-6276-4993-bfeb-53cbbbba6f08' \  -H 'Content-Type: application/json' \  -d '{  "role": "manager",  "sealed_key": {    "alg": "xwing-hkdf-xchacha20poly1305",    "ct": "stringstringstri",    "wrapped": "string"  },  "fingerprint": "stringst"}'
Example Response
{  "member": {    "user_id": "a169451c-8525-4352-b8ca-070dd449a1a5",    "email": "string",    "name": "string",    "role": "owner",    "fingerprint": "string",    "key_epoch": 1,    "added_at": "2019-08-24T14:15:22Z"  }}
PATCH
/v1/vault/vaults/{id}/members/{userId}

Authorization

bearerAuth
headerAuthorizationBearer <codai_…>

A Kodwen 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
userId*string

Member user id (uuid).

Formatuuid

Request Body

application/json
  1. body
role*string

owner is never assigned through this route.

Value in"manager""editor""viewer"

Response Body

Updated.

application/json
  1. response
member*
[key: string]?any
Example Request
curl -X PATCH 'https://ai.codai.ro/v1/vault/vaults/497f6eca-6276-4993-bfeb-53cbbbba6f08/members/497f6eca-6276-4993-bfeb-53cbbbba6f08' \  -H 'Content-Type: application/json' \  -d '{  "role": "manager"}'
Example Response
{  "member": {    "user_id": "a169451c-8525-4352-b8ca-070dd449a1a5",    "email": "string",    "name": "string",    "role": "owner",    "fingerprint": "string",    "key_epoch": 1,    "added_at": "2019-08-24T14:15:22Z"  }}
DELETE
/v1/vault/vaults/{id}/members/{userId}

Authorization

bearerAuth
headerAuthorizationBearer <codai_…>

A Kodwen 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
userId*string

Member user id (uuid).

Formatuuid

Response Body

Removed.

application/json
  1. response
removed*boolean
rotation_needed_at?string
Formatdate-time
[key: string]?any
Example Request
curl -X DELETE 'https://ai.codai.ro/v1/vault/vaults/497f6eca-6276-4993-bfeb-53cbbbba6f08/members/497f6eca-6276-4993-bfeb-53cbbbba6f08'
Example Response
{  "removed": true,  "rotation_needed_at": "2019-08-24T14:15:22Z"}
POST
/v1/vault/vaults/{id}/rotate

Authorization

bearerAuth
headerAuthorizationBearer <codai_…>

A Kodwen 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
key_epoch*integer
Range2 <= value
encrypted_meta*string
members*array<>
Items1 <= items
items*array<>
Itemsitems <= 2000

Response Body

Rotated.

application/json
  1. response
key_epoch*integer
[key: string]?any
Example Request
curl -X POST 'https://ai.codai.ro/v1/vault/vaults/497f6eca-6276-4993-bfeb-53cbbbba6f08/rotate' \  -H 'Content-Type: application/json' \  -d '{  "key_epoch": 2,  "encrypted_meta": "string",  "members": [    {      "user_id": "a169451c-8525-4352-b8ca-070dd449a1a5",      "sealed_key": {        "alg": "xwing-hkdf-xchacha20poly1305",        "ct": "stringstringstri",        "wrapped": "string"      }    }  ],  "items": [    {      "id": "497f6eca-6276-4993-bfeb-53cbbbba6f08",      "revision": 2,      "wrapped_key": "string",      "ciphertext": "string"    }  ]}'
Example Response
{  "key_epoch": 0}
PUT
/v1/vault/vaults/{id}/quorum

Authorization

bearerAuth
headerAuthorizationBearer <codai_…>

A Kodwen 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
m*integer
Range2 <= value <= 9
scope?string

Required when m is set.

Value in"critical""high"

Response Body

Effective policy.

application/json
  1. response
quorum*
[key: string]?any
Example Request
curl -X PUT 'https://ai.codai.ro/v1/vault/vaults/497f6eca-6276-4993-bfeb-53cbbbba6f08/quorum' \  -H 'Content-Type: application/json' \  -d '{  "m": 2}'
Example Response
{  "quorum": {    "m": 2,    "scope": "critical"  }}
POST
/v1/vault/approvals/{id}/vote

Authorization

bearerAuth
headerAuthorizationBearer <codai_…>

A Kodwen 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"
proof*|

Proof for a quorum vote, over approve/v4: codai-vault/approve/v4|<approval_id>|<action_hash>|<decision>|once|<approver_user_id>|<device_id>|<preview_digest>. device: one of YOUR enrolled device keys signs the message (software cli_ed25519 keys refused; t4 needs an attested device). passkey: a WebAuthn assertion whose challenge is base64url(SHA-256(the same message with device id passkey)). No match number: approvers are not at the agent.

Response Body

Vote recorded.

application/json
  1. response
status*string
Value in"pending""approved""denied"
quorum*
[key: string]?any
Example Request
curl -X POST 'https://ai.codai.ro/v1/vault/approvals/497f6eca-6276-4993-bfeb-53cbbbba6f08/vote' \  -H 'Content-Type: application/json' \  -d '{  "decision": "approve",  "proof": {    "type": "device",    "device_id": "string",    "signature": "string"  }}'
Example Response
{  "status": "pending",  "quorum": {    "m": 2,    "approvals": 0,    "needed": 0,    "voted": true  }}
POST
/v1/vault/policy/simulate

Authorization

bearerAuth
headerAuthorizationBearer <codai_…>

A Kodwen 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
rules?array<>

Draft rules; omitted = your current rules.

Itemsitems <= 200
default_tier?string
Value in"t1""t2""t3""t4"
since?string

Oldest use to replay (ISO).

Formatdate-time
limit?integer
Range1 <= value <= 500

Response Body

Simulation.

application/json
  1. response
evaluated*integer

Past uses replayed.

changed*integer

Rows whose tier differs under the draft policy.

grants*"none"

Grants and anomaly guards are not replayed.

Value in"none"
rows*array<>
Example Request
curl -X POST 'https://ai.codai.ro/v1/vault/policy/simulate' \  -H 'Content-Type: application/json' \  -d '{}'
Example Response
{  "evaluated": 0,  "changed": 0,  "grants": "none",  "rows": [    {      "event_id": "string",      "at": "2019-08-24T14:15:22Z",      "item_id": "4d8cd62e-a579-4dae-af8c-3172f96f8f7c",      "title": "string",      "agent": "string",      "chain": [        "string"      ],      "mode": "string",      "host": "string",      "current_tier": "t0",      "simulated_tier": "t0",      "reasons": [        "string"      ],      "source": "event"    }  ]}
GET
/v1/vault/receipts/keys

Response Body

OK.

application/json
  1. response
keys*array<>
Example Request
curl -X GET 'https://ai.codai.ro/v1/vault/receipts/keys'
Example Response
{  "keys": [    {      "kid": "string",      "alg": "ES256",      "pem": "string"    }  ]}

On this page