Skip to main content

Identity callables

Base URL:

https://us-central1-axy-ujex.cloudfunctions.net

All functions use Firebase callable JSON. Human-owner calls require a Firebase Auth ID token. Agent calls use the session device-key exchange, then Authorization: Bearer <firebase-id-token>.

FunctionCallerPurpose
sessionunauthenticated body with agentId + deviceKeyMint a Firebase custom token for an agent device key.
createAgenthuman ownerCreate an agent and claim a mailbox local part. Mailbox provisioning is deferred — see below.
identityProvisionMailboxhuman ownerRetry mailbox provisioning for an owned agent, on demand.
deleteAgenthuman ownerSuspend/offboard an owned agent, retire its mailbox, and preserve owner/audit evidence.
rotateDeviceKeyhuman ownerMint a new device key and optionally revoke existing keys.
identityListDeviceKeyshuman ownerList key prefixes and metadata.
revokeDeviceKeyhuman ownerRevoke a device key by prefix.
delegateagentMint a short-lived delegation token for another owned agent.
identityListMandateshuman owner or permitted team memberRead current and historical mandate versions.
identityGrantMandatehuman owner or permitted team memberGrant authority when no active mandate exists.
identityAmendMandatehuman owner or permitted team memberReplace scope/term using optimistic version checking.
identityRevokeMandatehuman owner or permitted team memberRevoke current authority and invalidate sessions.
storeSecrethuman owner or authorized agentStore an encrypted agent secret.
listSecretshuman ownerList secret names.
deleteSecrethuman ownerDelete a secret.
getSecretauthorized agentRetrieve a scoped secret value. Requires sessionId and stamps sensitive_access=true on that session.

See https://ujex.dev/openapi.json for the machine-readable Inbox/session subset.

All protected agent actions require the credential and its time-bounded mandate to remain current. Revocation, expiry, suspension, or a stale session version fails closed.

Agent creation and mailbox provisioning

Creating an agent and provisioning its Ruzayo mailbox are separate steps. createAgent persists the agent, its mailbox claim and its device key first, then attempts provisioning. If Ruzayo is unreachable the agent is still created and the mailbox is provisioned later; the agent is never deleted because mail provisioning failed.

createAgent returns three additional fields (added alongside the existing agentId / deviceKey / prefix / localPart / inboundDomain, which are unchanged):

FieldTypeMeaning
mailState"pending" | "provisioned" | "unavailable"Provisioning truth for the mailbox.
mailStateReasonstring | nullWhy, when not provisioned (e.g. provider_unreachable, address_taken).
mailboxReadybooleantrue only when mailState === "provisioned".
  • pending — Ruzayo was unreachable, returned a transient error, or answered without confirming the mailbox. A durable retry job is queued and drained by the identityRetryMailProvisioning scheduled function every 5 minutes with exponential backoff, for roughly 9.4 days. Mail cannot be sent or received from this address yet, and postboxSend rejects with failed-precondition rather than accepting a message that cannot leave.
  • provisioned — Ruzayo confirmed the mailbox: it returned the address that was requested, marked enabled. This is the only state in which mail works, the only state in which the mailbox document is enabled, and the only state in which a signed agent passport asserts the mail address at all.
  • unavailable — terminal for this address. Either Ruzayo rejected it (address_taken, address_invalid), provisioned a different address (address_mismatch), the deployment is misconfigured (provider_misconfigured), or transient retries were exhausted (retry_exhausted:*). Nothing retries automatically; the owner must act.

A pending mailbox always has convergence scheduled. The retry job is written before the provider call, and identityReconcileMailProvisioning re-queues any mailbox left pending with no live job. mailboxReady: false never means "and nothing is happening".

The same three fields appear on each row of getAgentDetails, and postboxListInboxes reports mailState per inbox. Mailboxes created before provisioning was deferred carry no mailState; readers treat that absence as provisioned, because those mailboxes were provisioned inline.

identityProvisionMailbox

{ "agentId": "<agentId>", "mailboxId": "default" }

Returns {agentId, mailboxId, mailState, mailStateReason, mailboxReady, retryable}. It is idempotent: an already-provisioned mailbox short-circuits without calling Ruzayo, and the underlying provisioning call is an upsert keyed on the address. An unavailable mailbox may be retried through this endpoint once the owner has resolved the conflict.

The retry clears the backoff, not the attempt budget, and runs through the same lease the scheduled drain uses. So it performs exactly one real attempt, it cannot run concurrently with a drain already calling Ruzayo for that mailbox, and repeated retries on a mailbox whose budget is spent report unavailable rather than holding it open indefinitely.