# XKOVA Docs, full text A machine-readable concatenation of every documentation page, for AI tools. See also llms.txt for the page index. --- source: https://docs.xkova.com/404.html # Page Not Found We could not find that page. The page you asked for is not here. It may have moved, or the link may be wrong. ## Find Your Way - [Documentation home](index.html) - [Quickstart](getting-started.html) - [API Reference](api-reference.html) - Press Ctrl K to search the docs. --- source: https://docs.xkova.com/account-holders.html # Account Holders An account holder is the member record your integration acts on behalf of: the person or business that holds a wallet, signs transfers, and appears on the cap table. Every member-facing capability hangs off this record, so it is worth knowing what it carries beyond the wallet. ## Provisioning: explicit or just in time Your backend creates account holders in one of two shapes. Explicit creation registers the member ahead of time. Just-in-time provisioning creates the record on the member's first sign in, which is the natural shape when your members already exist in your own identity system and you only want an XKOVA record once they first touch the rails. Both paths produce the same record; the JIT call is idempotent for the same member, so a race between two first requests resolves to one holder. Verification (KYC for a person, KYB for a business) attaches to the account holder, and the verification gate reads the holder's effective status on every gated action. See [Identity Verification](verification.html). ## The self-service surface A member, authenticated through any of the [member identity patterns](auth.html), can read and act on their own record: - **Balances.** The member's stablecoin balance, maintained from on-chain activity and continuously reconciled. Where the tenant runs internal bank sub-accounts (checking and savings), the member can read those balances and their transaction history, and transfer between their own sub-accounts. - **Contacts.** A personal address book of reused recipients the member can create, update, delete, and search, so a repeat send does not re-enter the destination. - **Consents.** The tenant's policy consent state: what the member has accepted, what is pending, and an accept call your app drives at sign-in or first use. Consent acceptance is recorded append-only, so the trail of who accepted what, and when, is preserved. - **Data export.** A right-to-access export the member can request and poll until the bundle is ready. - **Avatar.** An optional profile image, uploaded through a presign-then-confirm flow (direct-to-storage upload, then a confirm call that persists it) and read back through a short-lived signed URL rather than a permanent public link. Deleting it removes the stored image outright. ## Contacts Contacts are a per-member address book of reused recipients, reached under `/v1/contacts`. The surface is tenant scoped: your backend authenticates with a tenant-scope API key and names the owning member with an `account_holder_id` on every call, because it already knows who is signed in from your own auth flow. Cross-tenant management credentials are not part of this customer surface. In a browser application, use the session-bound member resources from `@xkova/sdk/member-bff` on your server. The helper supplies the authenticated account holder; never accept an account-holder selector from the browser as authority. The explicit selector below describes the tenant server API contract, not a browser authentication mechanism. | Operation | Method and path | Permission | | --- | --- | --- | | List a member's contacts | `GET /v1/contacts` | `contact.read` | | Read one contact | `GET /v1/contacts/{id}` | `contact.read` | | Create a contact | `POST /v1/contacts` | `contact.write` | | Update a contact | `PATCH /v1/contacts/{id}` | `contact.write` | | Delete a contact | `DELETE /v1/contacts/{id}` | `contact.write` | | Look up by email or phone | `POST /v1/contacts/search` | `contact.read` | Create takes a required `display_name` plus optional `email`, `phone`, `wallet_address`, and `notes`, and returns `201` with the stored contact. Update is a partial `PATCH` that returns `200`, and delete returns `204`. List and search are cursor paged and return newest first. Each stored contact carries `id`, `account_holder_id`, `display_name`, `wallet_address`, `notes`, `last_sent_at`, `pending_resolution`, and its created and updated timestamps. Email and phone are hashed at the API boundary with a per-tenant pepper before anything reaches storage, and plaintext is never persisted. Reads return the hashes as `email_hash` and `phone_hash`, not the original values. Because the hash uses a per-tenant pepper, it is stable within your tenant, so lookups work, and divergent across tenants, so a contact cannot be confirmed across a tenant boundary. Look up an existing contact with `POST /v1/contacts/search` rather than a query parameter: the plaintext email or phone travels in the request body, so it never lands in a URL or an access log. The API hashes the supplied value with the same tenant pepper and matches it against the member's stored hashes, with OR semantics across email and phone. Use it for the "do I already have this recipient?" prompt and to dedupe before creating a new row. A contact can also be created by the send flow against a recipient who has not enrolled yet, in which case `wallet_address` is still null and `pending_resolution` is `true`; both backfill once that recipient claims. Create, update, and delete are audited. > A tenant can turn contacts off with a kill switch. While it is off, create and update return `403 contacts_disabled`, while reads and delete still work, so your app can keep rendering a member's existing contacts. ## Staff actions: search, hold, release, close Operators work the same records from the console's Accounts directory (which lists member account holders alongside staff and treasury vaults). A compliance officer can place a hold on an account holder, recording a reason; a held account cannot move value until released. A hold revokes existing member sessions and refresh credentials for that exact membership. Releasing the hold requires a new sign-in and never restores old sessions. Other tenant and workspace memberships remain independent. Closing an account holder is the terminal action. All three are audited. See [Compliance](console-compliance.html) for the operator journey. ## Related - [Wallets and Balances](wallets.html): the wallet each account holder signs with. - [Authentication](auth.html): the three patterns for acting on a member's behalf. - [Identity Verification](verification.html): the KYC and KYB gate on the record. - [Payments](payments.html): what the member signs. > For the account holder, contact, consent, export, and avatar operations, see the [operations list](api-reference.html) or the [interactive reference](reference.html). --- source: https://docs.xkova.com/api-reference.html # API Reference Every public operation in the contract, grouped by area. This page is generated from the OpenAPI spec, so it never drifts from the running system. 229 operations across 9 areas. 0 carry a full description. Tiers: 229 customer. Private XKOVA management Console, partner-management, identity-provider ceremony, and browser capability operations are not shown. The 11 member-auth operations are intentionally published as the confidential server protocol behind @xkova/sdk/member-bff; they are not a browser or raw-token interface. > This is the fast, scannable list. For schemas, examples, and a live try it console, open the [interactive reference](reference.html). Guide pages link into these operations. ## account_holders 30 Concept guides: [Account Holders](account-holders.html), [Wallets and Balances](wallets.html), [Identity Verification (KYC and KYB)](verification.html) GET /account-holders customer Search account holders (staff) POST /account-holders customer Create an account holder (staff) GET /account-holders/{id} customer Get account holder (staff view; broader than /users/{id}) PATCH /account-holders/{id} customer Update mutable account-holder fields GET /account-holders/{id}/history customer Account-holder tx history (staff) GET /account-holders/self customer Get the current account holder's profile GET /account-holders/self/balance customer Get the current account holder's wallet balances GET /account-holders/self/bank-balance customer Get the current account holder's USD bank (savings) balance GET /account-holders/self/bank-transactions customer List the current account holder's internal bank transfers POST /account-holders/self/bank-transfers customer Move USD between the account holder's own share savings and share draft GET /account-holders/self/checking-balance customer Get the current account holder's USD share-draft (checking) balance GET /account-holders/self/consents customer Get the current account holder's consent state POST /account-holders/self/consents customer Accept current tenant policies for the current account holder POST /account-holders/self/data-exports customer Request a right-to-access data export for the current account holder GET /account-holders/self/data-exports/{id} customer Get data export status for the current account holder GET /account-holders/self/product-enrollments/{product} customer Read the member's product enrollment POST /account-holders/self/product-enrollments/{product} customer Record product terms acceptance GET /account-holders/self/sandbox-banking-context customer Get the member's sandbox banking references GET /account-holders/self/transactions customer List the current account holder's transactions GET /account-holders/self/verification customer Get the current member's identity verification status POST /account-holders/self/wallet-auth customer Connect the current member's registered wallet POST /account-holders/self/wallet-provision customer Ensure wallet provisioning for the current member GET /account-holders/self/wallet-readiness customer Get the current member's wallet readiness GET /account-holders/self/wallets customer List the current member's wallets GET /contacts customer List account holder's address book POST /contacts customer Add a contact DELETE /contacts/{id} customer Delete a contact GET /contacts/{id} customer Get a contact PATCH /contacts/{id} customer Update a contact POST /contacts/search customer Look up a contact by email or phone hash ## embedded:avatar 8 Concept guides: [Account Holders](account-holders.html), [Wallets and Balances](wallets.html) DELETE /account-holders/{id}/avatar customer Delete the account holder avatar GET /account-holders/{id}/avatar customer Get a short-TTL presigned GET URL for the holder's avatar POST /account-holders/{id}/avatar/confirm customer Confirm an avatar upload for the account holder POST /account-holders/{id}/avatar/upload-url customer Create a short-lived avatar upload URL DELETE /account-holders/self/avatar customer Delete the account holder avatar GET /account-holders/self/avatar customer Get a short-TTL presigned GET URL for the holder's avatar POST /account-holders/self/avatar/confirm customer Confirm an avatar upload for the account holder POST /account-holders/self/avatar/upload-url customer Create a short-lived avatar upload URL ## embedded:member_auth 11 Concept guides: [Authentication](auth.html), [Wallets and Balances](wallets.html) POST /account-center/handoffs customer Begin an XKOVA Hosted Account Center handoff POST /account-center/handoffs/exchange customer Complete an XKOVA Hosted Account Center handoff POST /member-actions customer Begin exact authorization for a protected member action POST /member-actions/external/complete customer Complete a protected member action with a fresh FI OIDC proof POST /member-auth/hosted/authorizations customer Begin an XKOVA Hosted Auth authorization transaction POST /member-auth/hosted/authorizations/exchange customer Exchange a Hosted Auth result for an XKOVA member session POST /member-auth/proof/challenges customer Issue a one-time nonce for a registered member-auth application POST /member-auth/proof/exchange customer Exchange a verified provider proof for an XKOVA member session POST /member-auth/refresh customer Rotate a BFF-held XKOVA member-session refresh credential GET /member-auth/session customer Resolve the active XKOVA member session POST /member-auth/sign-out customer Revoke the exact XKOVA member session or all current member sessions ## funding 7 Concept guides: [Conversion and Ramps](conversion.html), [Funding and Off-ramp](off-ramp.html) GET /funding-requests customer List funding requests for the tenant (paginated, recent first) POST /funding-requests customer Create a funding request (fiat → stablecoin via [X] partner) GET /funding-requests/{id} customer Get a funding request GET /off-ramp-requests customer List off-ramp requests for the tenant (paginated, recent first) POST /off-ramp-requests customer Create an off-ramp request (stablecoin → fiat via [X] partner) GET /off-ramp-requests/{id} customer Get an off-ramp request GET /off-ramp-requests/burn-address customer The on-chain address to burn stablecoin TO when redeeming (off-ramp) ## payment 22 Concept guides: [Payments](payments.html), [Transfers and Settlement](transfers.html) POST /account-holders/self/payments/{id}/cancel customer Cancel the current member's unsigned payment GET /account-holders/self/payments/{id}/recovery customer Read a member payment and its available actions GET /escrows customer List the calling tenant's escrows (cursor-paginated, newest first) POST /escrows customer Create an escrow (sender-side) GET /escrows/{id} customer Get a single escrow POST /escrows/{id}/cancel customer Validate that an escrow is ready for a sender-cancel action GET /escrows/{id}/events customer Audit-trail timeline for a single escrow GET /payment-requests customer List payment requests POST /payment-requests customer Create a payment request (recipient-initiated; inbound pay ask) GET /payment-requests/{id} customer Get a payment request POST /payment-requests/{id}/approve customer Approve a payment request (spawns a payment on the payer side) POST /payment-requests/{id}/cancel customer Cancel a payment request (verb-noun alias for DELETE /:id) POST /payment-requests/{id}/decline customer Decline a payment request POST /payment-requests/{id}/remind customer Nudge the payer with a reminder GET /payments customer Tenant-wide payment list (cursor-paginated, rich filters, FTS on memo) POST /payments customer Create a payment GET /payments/{id} customer Get a payment POST /payments/{id}/cancel customer Cancel a payment (pre-sign only) GET /payments/{id}/events customer Timeline of state transitions for a payment POST /payments/quote customer Get a fee + compliance quote for a prospective payment POST /payments/simulate customer Simulate a payment's chain feasibility before signing (dry-run) GET /transactions customer List the calling tenant's transactions across every payment-flow kind ## platform 20 Concept guides: [Overview](integrations-overview.html) GET /integration-gateways customer List integration gateways POST /integration-gateways customer Register a deployed integration gateway GET /integration-gateways/{id} customer Get an integration gateway PATCH /integration-gateways/{id} customer Edit gateway registration (name / base_url) POST /integration-gateways/{id}/conformance customer Record a contract-conformance result for the gateway GET /integration-gateways/{id}/events customer List a gateway's invocation events POST /integration-gateways/{id}/pin-cert customer Pin or rotate the gateway's mTLS cert fingerprint POST /integration-gateways/{id}/status customer Enable or disable an integration gateway GET /webhook-deliveries customer List recent webhook deliveries (last 30 days) GET /webhook-deliveries/{id} customer Fetch a single webhook delivery POST /webhook-deliveries/{id}/replay customer Re-fire a past delivery (preserves original Idempotency-Key) POST /webhook-deliveries/bulk-replay customer Bulk re-fire terminal deliveries GET /webhook-endpoints customer List webhook endpoints POST /webhook-endpoints customer Create a webhook endpoint DELETE /webhook-endpoints/{id} customer Delete webhook endpoint GET /webhook-endpoints/{id} customer Get webhook endpoint PATCH /webhook-endpoints/{id} customer Update webhook endpoint POST /webhook-endpoints/{id}/rotate customer Rotate the HMAC signing secret for this endpoint POST /webhook-endpoints/{id}/test customer Send a test event to this endpoint GET /webhook-event-types customer List subscribable webhook event types ## tenant 34 Concept guides: [Tenants and Workspaces](tenancy.html) GET /branding customer Get this tenant's resolved branding + the raw tenant-wide theme PUT /branding customer Upsert this tenant's branding (tenant-wide) GET /branding/email-domains customer List this tenant's custom sending domains + their DNS records POST /branding/email-domains customer Register a custom sending domain DELETE /branding/email-domains/{id} customer Remove a custom sending domain POST /branding/email-domains/{id}/verify customer Trigger + refresh verification for a sending domain DELETE /branding/logo customer Remove the brand logo POST /branding/logo customer Confirm this tenant's brand logo upload POST /branding/logo/upload-url customer Presigned PUT URL for a brand logo upload GET /chains/available customer List every chain automatically available to the calling workspace GET /fee-schedules customer List fee schedules for this tenant POST /fee-schedules customer Propose a new fee schedule (validates floor enforcement) GET /fee-schedules/{id} customer Get a single fee schedule (with effective rules) GET /fee-schedules/platform-fee customer Read-only XKOVA platform-fee rule for this workspace POST /onboarding/setup customer Record onboarding intent and activate the workspace GET /onboarding/status customer Onboarding state for the calling tenant's sandbox workspace GET /public/branding customer Public branding for a hosted claim page (pre-auth) GET /tenant customer Get tenant config GET /tenant/member-auth/allowed-origins customer List allowed browser Origin values for member-auth flows POST /tenant/member-auth/allowed-origins customer Allowlist a new browser Origin for member-auth flows DELETE /tenant/member-auth/allowed-origins/{id} customer Soft-delete an allowed Origin PUT /tenant/member-auth/application customer Register or update the calling backend's BYO OIDC application GET /tenant/member-auth/applications customer List member-auth applications in the active workspace DELETE /tenant/member-auth/applications/{id} customer Disable a member-auth application PATCH /tenant/member-auth/applications/{id} customer Update a member-auth application POST /tenant/member-auth/applications/hosted customer Enable an XKOVA Hosted Auth application GET /tenant/member-auth/redirect-urls customer List redirect URLs for the active member-auth application POST /tenant/member-auth/redirect-urls customer Allowlist a new redirect URL for member authentication DELETE /tenant/member-auth/redirect-urls/{id} customer Soft-delete an allowed redirect URL GET /tenant/policies customer Get tenant policy registry (ToS, privacy, marketing) GET /workspace-tokens customer List Rails-authorized tokens for the calling workspace GET /workspaces customer List the workspace pair under this tenant GET /workspaces/{id} customer Get a single workspace PATCH /workspaces/{id} customer Update mutable workspace fields (name and status) ## tokenization 90 Concept guides: [Overview](tokenization-overview.html), [Token Standards](token-standards.html), [Deploy and Mint](deploy-mint.html), [Access and Policy](token-access.html), [Cap Tables and Distributions](cap-tables.html), [Corporate Actions](corporate-actions.html), [Provenance and Transparency](provenance.html), [Holder Eligibility and Transfers](holder-eligibility.html), [Real-World Assets](rwa.html), [Asset Templates](rwa-templates.html) GET /rwa/asset-schemas customer List the workspace's asset schemas GET /rwa/asset-schemas/{id} customer Get one workspace asset schema and its version history POST /rwa/asset-schemas/{id}/publish customer Publish the workspace schema's open draft POST /rwa/asset-schemas/compile-preview customer Compile a field-palette and report the publish-gate verdict POST /rwa/asset-schemas/draft customer Save a palette as the workspace schema's editable draft POST /rwa/asset-schemas/fork customer Fork a platform template into a workspace draft GET /rwa/asset-schemas/templates customer List the forkable platform templates POST /rwa/asset-schemas/upload-publish customer Publish a raw JSON Schema through the publish gate GET /rwa/contracts/{id}/contract-metadata customer Get a collection's contract metadata (contractURI target) PUT /rwa/holders/{holderRef}/eligibility customer Upsert a holder's global eligibility attributes POST /rwa/schema-rebinds customer Propose an attested schema-evolution rebind ceremony GET /rwa/schema-rebinds/{id} customer Read a rebind ceremony's lifecycle state POST /rwa/schema-rebinds/{id}/attest customer Record an EIP-712 attestation over the rebind proposal digest POST /rwa/schema-rebinds/{id}/commit customer Commit a rebind - move bindings + re-commit metadata POST /rwa/schema-rebinds/{id}/dry-run customer Dry-run a rebind against the target schema (no write) GET /rwa/tokens/{id}/attesters customer List a token's registered attesters POST /rwa/tokens/{id}/attesters customer Register a token-pinned external attester DELETE /rwa/tokens/{id}/attesters/{attesterId} customer Revoke a registered attester (soft) GET /rwa/tokens/{id}/cap-table customer Get a token's cap-table summary POST /rwa/tokens/{id}/corporate-actions customer Open a corporate action on an RWA token GET /rwa/tokens/{id}/corporate-actions/{caId} customer Get a corporate action + its leg progress POST /rwa/tokens/{id}/corporate-actions/{caId}/advance customer Advance a corporate action's state machine POST /rwa/tokens/{id}/distributions customer Create a pro-rata cap-table distribution GET /rwa/tokens/{id}/distributions/{distId} customer Get a distribution + its payouts POST /rwa/tokens/{id}/distributions/{distId}/execute customer Execute a draft distribution (treasury-Safe multiSend payout) POST /rwa/tokens/{id}/document-entitlements customer Grant a recipient access to a token's gated documents DELETE /rwa/tokens/{id}/document-entitlements/{grantee} customer Revoke a recipient's gated-document access GET /rwa/tokens/{id}/documents/{docId} customer Resolve an RWA document to a fetch URL POST /rwa/tokens/{id}/documents/confirm customer Confirm an uploaded RWA document (integrity + provenance) POST /rwa/tokens/{id}/documents/upload-url customer Presign an RWA document upload GET /rwa/tokens/{id}/eligibility/{holderRef} customer Read the (token, holder) transfer-eligibility context GET /rwa/tokens/{id}/fields customer Describe the token's bound-schema fields + current values POST /rwa/tokens/{id}/fields/{fieldPath} customer Commit an operator-authored field value POST /rwa/tokens/{id}/fields/{fieldPath}/attested-entry customer Submit an attested append-log fact GET /rwa/tokens/{id}/holders customer List a token's cap-table holders GET /rwa/tokens/{id}/instances customer List a token's per-instance ownership (NFT gallery) PUT /rwa/tokens/{id}/lockups/{holderRef} customer Set a per-holding lockup on an RWA token GET /rwa/tokens/{id}/log/proof/consistency customer Get an RFC-6962 consistency proof (public transparency log) GET /rwa/tokens/{id}/log/proof/inclusion customer Get an RFC-6962 inclusion proof (public transparency log) GET /rwa/tokens/{id}/log/sth customer Get a token's signed tree head (public transparency log) POST /rwa/tokens/{id}/log/witness customer Submit a third-party witness co-signature for an STH GET /rwa/tokens/{id}/metadata customer Get a token's metadata (ERC-721 tokenURI target) GET /rwa/tokens/{id}/metadata/{tokenId} customer Get per-instance metadata (ERC-1155 uri {id} target) GET /rwa/tokens/{id}/placeholder customer Get a token's branded placeholder image GET /rwa/tokens/{id}/provenance/verify customer Verify a token's metadata provenance chain GET /rwa/tokens/{id}/relationships customer List a token's outgoing relationship edges GET /rwa/tokens/{id}/schema-binding customer Read the token's bound asset schema POST /rwa/tokens/{id}/schema-binding customer Bind a published asset schema to the token POST /rwa/tokens/{id}/witnesses customer Register a trusted third-party transparency witness GET /tokenization/authorities customer List this workspace's token authorities POST /tokenization/authorities customer Register a token authority (server-key EOA or treasury Safe) GET /tokenization/authorities/{id} customer Get one token authority with its role matrix POST /tokenization/authorities/{id}/proof customer Submit a proof-of-possession for a server-key authority POST /tokenization/authorities/{id}/proof-challenge customer Issue a proof-of-possession challenge for a server-key authority POST /tokenization/authorities/control-safes customer Create a token-control Safe and register it as an authority POST /tokenization/erc1155 customer Deploy a customer gasless ERC-1155 (ERC1155X) contract on-chain POST /tokenization/erc20 customer Deploy a customer gasless ERC-20 (ERC20X) on-chain POST /tokenization/erc721 customer Deploy a customer gasless ERC-721 (ERC721X) collection on-chain GET /tokenization/tokens customer List this workspace's customer-deployed tokens GET /tokenization/tokens/{id} customer Get a customer-deployed token POST /tokenization/tokens/{id}/access-list/add customer Add addresses to a customer token's access list (SAFE-PATH) POST /tokenization/tokens/{id}/access-list/remove customer Remove addresses from a customer token's access list (SAFE-PATH) POST /tokenization/tokens/{id}/access-registry customer Attach an access registry to an open token (post-deploy) POST /tokenization/tokens/{id}/access-registry/disable customer Detach the access registry from a token (SAFE-PATH → open transfers) POST /tokenization/tokens/{id}/burn customer Burn a customer token via its admin Safe (SAFE-PATH) GET /tokenization/tokens/{id}/contracts customer List a token's explorable control contracts POST /tokenization/tokens/{id}/deploy/resume customer Resume a failed_resumable deploy POST /tokenization/tokens/{id}/deprecate customer Deprecate a customer token (off-chain retire) POST /tokenization/tokens/{id}/gasless/relay customer Relay a signed gasless holder-transfer ForwardRequest POST /tokenization/tokens/{id}/gasless/transfer-request customer Build a gasless holder-transfer ForwardRequest POST /tokenization/tokens/{id}/mint customer Mint a customer token via its admin Safe (SAFE-PATH) POST /tokenization/tokens/{id}/nft-transfers customer Relay a signed gasless holder NFT transfer POST /tokenization/tokens/{id}/nft-transfers/build customer Build a gasless holder NFT-transfer ForwardRequest GET /tokenization/tokens/{id}/operations customer List a customer token's privileged operations GET /tokenization/tokens/{id}/operations/{opId} customer Get one tokenization operation POST /tokenization/tokens/{id}/operations/{opId}/module-signature customer Submit the authority's ModuleTx signature for a policy mint POST /tokenization/tokens/{id}/operations/{opId}/signatures customer Submit a Safe-owner signature for a tokenization operation POST /tokenization/tokens/{id}/pause customer Pause a customer token via its admin Safe (SAFE-PATH circuit breaker) POST /tokenization/tokens/{id}/policies customer Set a mint-cap policy for a server-key authority (SAFE-PATH) POST /tokenization/tokens/{id}/policies/revoke customer Revoke a server-key authority's mint-cap policy (SAFE-PATH) POST /tokenization/tokens/{id}/policy-mint customer Build a policy-scoped gasless mint (ModuleTx) POST /tokenization/tokens/{id}/raw-safe-tx customer Build a raw governance SafeTx on one of the token's own contracts (SAFE-PATH escape hatch) GET /tokenization/tokens/{id}/read customer Read a view function on a token's control contract POST /tokenization/tokens/{id}/recover customer Recover (claw back) tokens from a blocked account (SAFE-PATH) POST /tokenization/tokens/{id}/roles/grant customer Grant an ERC20X role via the admin Safe (SAFE-PATH) POST /tokenization/tokens/{id}/roles/revoke customer Revoke an ERC20X role via the admin Safe (SAFE-PATH) POST /tokenization/tokens/{id}/treasury-disable customer Remove a customer token from this workspace's treasury readers POST /tokenization/tokens/{id}/treasury-enable customer Enable a customer token for treasury custody + transfers (workspace-scoped) GET /tokenization/tokens/{id}/treasury-status customer Read a customer token's current treasury-eligibility state POST /tokenization/tokens/{id}/unpause customer Unpause a customer token via its admin Safe (SAFE-PATH circuit breaker) ## wallets 7 Concept guides: [Wallets and Balances](wallets.html) GET /account-holders/{id}/wallets customer List wallets for an account holder GET /wallets/{id} customer Get a wallet GET /wallets/{id}/balance customer Materialized running balance for a (wallet, chain, token) POST /wallets/{id}/decommission customer Decommission a wallet (state=decommissioned; not deleted) GET /wallets/{id}/history customer Wallet-scoped payment history (same shape as GET /payments with wallet filter) GET /wallets/{id}/ledger customer Bank-statement timeline for a wallet (atomic debits + credits across every token movement) POST /wallets/{id}/qr customer Generate a receive QR (EIP-681 + XKOVA extensions) for an explicit wallet --- source: https://docs.xkova.com/audit-log.html # Audit Log Every financial or governed mutation on XKOVA writes an audit row before it is considered done. The row records the authority that was exercised: who acted, the permission they used, the roles they held, and for a quorum action the approvers who attested. The trail is queryable for compliance and activity reporting. ## What gets audited Any mutation in the financial or governed domains is audited: payments, escrows and claims, treasury, fees, governance, policies, break glass, ramps, revenue, compliance, verification, tokenization, and real-world assets. The audit row is part of the action, not a side effect added later, so an action that completed left a record. > Audit coverage is enforced, not left to discipline. A governed mutation that did not record its authority would not pass the platform's own checks, so the trail does not have silent gaps. ## What a row records Each row captures the authority exercised and enough context to reconstruct what happened. | Field | Meaning | | --- | --- | | action | The canonical event name, for example a payment created or a claim resolved. | | actor | Who acted, and the actor type: system, staff, account_holder, api_key, or operator. | | permission | The permission used to authorize the action. | | roles | The roles the actor held at the time. | | approvers | For a quorum gated action, the approval request and the approvers who attested. | | outcome | success, failure, or denied. | | occurred_at | When the event happened. | | correlation_id | The request correlation id, also present on errors and webhooks for tracing. | | before and after | Optional snapshots of the changed resource's state before and after the mutation, when captured; many events carry neither. | Quorum approved actions and break glass actions carry extra context: the approval request and its approvers, and for break glass a high severity tag. See [Policies and Quorum Governance](policies-quorum.html). ## Reading the trail The tenant wide audit trail is queryable by staff. Filter by category, action, actor, target, and date range, sort by occurrence time, and page through with a cursor. - **Tenant audit query.** The management Console provides a cursor-paginated audit view with the filters above. It is a private management capability, not a customer SDK operation. - **Per resource timeline.** A resource's lifecycle events provide its transition history, for example an escrow's timeline rendered oldest first. This lifecycle read is distinct from the staff audit query. - **Operator activity (console).** In the hosted console, an operator can also see their own recent authentication events (sign in, sign out, MFA, scope switch) in the activity panel. > Reads are cursor paginated. Use the cursor to walk a long history rather than a page offset. See [Pagination](pagination.html). ## Why this matters The audit trail is how a governed action proves which authority was used. Combined with the approval quorum and separation of duties, it lets you reconstruct not just that an action happened but that it was authorized correctly and by whom. It is the system of record for compliance and incident review. ## Related See [Policies and Quorum Governance](policies-quorum.html) for the approvals an audit row references, [Roles and Permissions](roles-permissions.html) for the permission and role catalog, and [Reporting and Exports](reporting.html) for staff audit views. These staff reads are not customer SDK operations. --- source: https://docs.xkova.com/auth.html # Authentication XKOVA normalizes trusted provider proofs into scoped sessions without forcing every product through one XKOVA-hosted login experience. Authentication UX follows the product and identity owner. The common XKOVA boundary is proof verification, session exchange, scoped authorization, and business APIs. ## Customer server credentials Customer backends authenticate every customer API request with a workspace-scoped bearer credential. ``` Authorization: Bearer $XKOVA_API_KEY ``` | Prefix | Workspace mode | Use | | --- | --- | --- | | `xkv_test_` | Sandbox | Sandbox customer API. | | `xkv_live_` | Production | Production customer API after access approval. | Production mutations also require a customer-held asymmetric request-signing credential. Production reads remain bearer-authenticated by default. See [Key Management](key-management.html) and [SDK Overview](sdk-overview.html). API-key and signing-credential lifecycle operations are private management functions and are not published in `@xkova/sdk`. ## Member identity entry points Member authentication is product-specific. The person authenticates with the identity owner, then a confidential server presents a trusted proof to XKOVA. A visible visit to `auth.xkova.com` is required only when XKOVA owns an explicit authentication or security ceremony. | Product | Who authenticates the person | Proof presented to XKOVA | Visible XKOVA auth page | | --- | --- | --- | --- | | Consumer Xperience, BYO OIDC | The financial institution's IdP | Issuer and audience pinned OIDC proof, exchanged by the customer BFF | No | | Consumer Xperience, Hosted Auth | XKOVA Hosted Auth | XKOVA Hosted Auth proof, exchanged by the customer BFF | Yes, for the Hosted Auth ceremony | | Embedded Solution | Lumin, Q2, Banno, or another approved digital channel | Provider-specific launch or OIDC proof from an approved server-side adapter | No | | Core Rails | The institution's staff IdP and staff platform | Institution staff SSO or a single-use contextual launch | No | | XKOVA management Console | Shared Cognito-managed login or registered institution SSO | Private management session | Separate management login, not a member session | BYO OIDC is the recommended enterprise member-auth model. The configured financial institution identity is authoritative for that tenant. XKOVA never treats email as identity equivalence, never links Hosted Auth and BYO identities automatically, and never accepts a provider proof for a different tenant, workspace, or application. ## The confidential member-session exchange A customer BFF calls the provider-neutral member-auth boundary after the identity provider has authenticated the member. Provider credentials and XKOVA refresh material stay server-side. ``` provider-specific proof -> POST /v1/member-auth/proof/challenges -> POST /v1/member-auth/proof/exchange -> scoped XKOVA member session in the BFF ``` | Server-only helper operation | Purpose | Server credentials | | --- | --- | --- | | `POST /v1/member-auth/proof/challenges` | Issue the one-time nonce bound to the next provider proof. | Workspace bearer plus publishable key | | `POST /v1/member-auth/proof/exchange` | Verify and consume the provider proof, resolve an existing member binding, and create an XKOVA session. | Workspace bearer plus publishable key | | `POST /v1/member-auth/hosted/authorizations` | Start the application-bound Hosted Auth ceremony with exact origin, callback, state, and PKCE binding. | Workspace bearer plus publishable key | | `POST /v1/member-auth/hosted/authorizations/exchange` | Consume the one-time Hosted result and create the provider-neutral member session. | Workspace bearer plus publishable key | | `POST /v1/member-auth/refresh` | Rotate the single-use XKOVA refresh credential. | Workspace bearer plus publishable key | | `GET /v1/member-auth/session` | Validate the exact active session and return a safe member context. | Workspace bearer, publishable application key, and XKOVA member-session ID | | `POST /v1/member-auth/sign-out` | Revoke the exact session by default. | Workspace bearer, publishable application key, and XKOVA member-session ID | These confidential operations are part of the customer manifest specifically so the supported `@xkova/sdk/member-bff` helper can implement the protocol. Customer browser code must not call them directly, and ordinary `XKOVAClient` resources do not expose them. The `X-XKOVA-Publishable-Key` value is a non-secret application registration identifier. It selects the registered tenant, workspace, environment, application, provider kind, issuer, audience, origins, and redirect configuration only when it also matches the authenticated workspace principal. It grants no authority by itself and is not a substitute for the bearer credential. The exchange accepts an exact registered provider proof only once. Ordinary member resource calls do not accept provider tokens, `X-OIDC-ID-Token`, `X-End-User-Id`, raw Hosted Auth sessions, or caller-selected account-holder IDs. They use the server-held XKOVA session through `X-Member-Session-Id` plus `X-XKOVA-Publishable-Key`, and the API rechecks its tenant, workspace, application, policy version, configuration version, and active account-holder binding. Session creation transactionally rechecks the registered application and policy versions after proof verification. A concurrent trust change therefore rejects creation. Refresh validates the caller's exact tenant, workspace, application, publishable key, and API principal before consumed-token replay can revoke a family. Expired one-time proof bindings are retired only after their replay window ends. ## Use the supported BFF helper `@xkova/sdk/member-bff` implements the XKOVA side of authorization transactions, proof exchange, refresh rotation, exact-session logout, safe browser projection, CSRF binding, cookie directives, and framework-neutral session, authorization-transaction, and direct proof-exchange transaction store contracts. The customer supplies its own durable stores, web-framework wiring, FI-specific identity integration, and storage encryption and access controls. ``` import { XKOVAMemberBffClient } from '@xkova/sdk/member-bff'; const memberBff = new XKOVAMemberBffClient({ baseUrl: 'https://sandbox-api.xkova.com', apiKey: process.env.XKOVA_API_KEY, publishableKey: process.env.XKOVA_PUBLISHABLE_KEY, }); const start = await memberBff.beginHostedAuthorization({ browserOrigin: 'https://app.example.com', redirectUri: 'https://app.example.com/api/auth/callback', transactionStore, }); ``` The durable first-party application cookie is host-only through the `__Host-` prefix, Secure, HttpOnly, and `SameSite=Strict`. A separate short-lived `SameSite=Lax` flow cookie may carry OAuth or OIDC transaction state across a callback. Do not weaken the durable cookie for callback convenience. The BFF store must serialize refresh and logout for one session reference across every application instance. Callback recovery without provider-code re-redemption is guaranteed only after the authorization store durably persists `stagedProviderExchange`. From that point, the identical callback reuses the staged proof, stable XKOVA exchange attempt, and application-session reference. A bounded completed transaction also recovers a committed application session after response or store acknowledgement loss. Before durable staging, an ambiguous provider response or staging-store acknowledgement may invoke the provider exchange again. The provider adapter must resolve that ambiguity or fail closed and start a fresh authorization transaction. Preserve the flow cookie and durable transaction for failures known to occur after durable staging. `XKOVAMemberAuthorizationTerminalError` is the helper's authoritative terminal classification for the exact transaction. A confidential proof that does not use the callback helper requires a stable customer-generated `exchangeId` and encrypted `XKOVAMemberProofExchangeTransactionStore`. Reuse the same exchange identifier until the helper returns the application session. ## The browser package `@xkova/browser` is the optional browser companion for Consumer Xperience. It calls only the customer's same-origin BFF and never accepts an XKOVA API key, provider proof, session ID, access token, refresh token, or caller-supplied authorization header. Its initial self-service surface covers session launch and status, enrollment, safe profile and avatar, balances and history, consents, data exports, verification, self-wallet operations, and Account Center handoff. Typed business methods also cover contacts, payment quotes and status, payment and escrow reviews, payment requests, deposits, withdrawals, bank transfers, and receiving QR codes. These browser methods call your BFF. Your backend uses the manifested `@xkova/sdk` operations, enforces account-holder authority, and coordinates the required human authorization. Installing the browser package does not select Hosted Auth or implement those backend routes. ## Hosted Auth, BYO OIDC, and embedded channels ### Hosted Auth XKOVA Hosted Auth is an optional managed identity choice. Managed Hosted email is included for sandbox; production follows the active paid plan or Feature Access Code. Enable it in Console by selecting an existing same-workspace API principal, or by using the guided setup to create a dedicated `member_auth`-only BFF principal. Choose invite-only or open enrollment and register the exact application origin and authorization-result callback. Configure the brand once in the existing Branding settings. The generated publishable key is a non-secret application selector. The reusable tenant-branded XKOVA screens use the tenant's registered Cognito pool. Native sign-in supports email codes and passkeys, with password and TOTP paths where enabled by the registered policy. Enrolled-factor and fresh passkey requirements still apply. Your BFF starts the ceremony with `beginHostedAuthorization` and completes the one-time result with `completeHostedAuthorization`. XKOVA manages the provider registration and sender configuration; Cognito sends sign-in codes through its configured email service. You do not configure another Hosted OAuth client ID or secret, issuer, JWKS URL, authorization URL, or token URL. A customer login domain is optional. Login email addresses are limited to128 characters. Hosted members manage passkeys, Hosted sessions, recovery-code readiness, and applicable wallet readiness in XKOVA's tenant-branded Account Center. The customer BFF starts a one-use, application-bound handoff. The handoff is context only, so the expected Hosted subject must authenticate before an Account Center session is issued. Lost-all-factor recovery requires one saved recovery code plus a fresh verified email code, replacement of provider factors, acknowledgement of a new code set, security notification acceptance, and a 24-hour restriction before fresh sign-in can restore authority. Do not recreate these ceremonies on the customer application origin. Account Center also manages enabled password and TOTP settings. Institution-only pools use the institution's login and do not offer native password or email-code fallback. Separate tenant pools keep customer identities confined to their tenant; the shared management Console uses Cognito-managed login. Hosted Auth is optional for direct enterprise OIDC, Embedded Solution and Core Rails; their registered identity and protected-action requirements still apply. ### BYO OIDC The customer BFF owns state, nonce, PKCE, and the provider callback when using the supported BFF proof exchange pattern. A brokered flow may also be approved. Provider client secrets and refresh credentials never enter browser JavaScript. Issuer, audience, provider namespace, tenant, workspace, application, and callback are pinned to registered server configuration. Plover Credit Union demonstrates Plover-branded Hosted email sign-in, a banking dashboard, and explicit stablecoin-product enrollment with a 5 dUSDC welcome credit. Sign-in completes a one-time Hosted result and confidential BFF exchange without exposing an identity-provider credential or XKOVA member-session credential to browser JavaScript. FI/BYO OIDC remains a supported enterprise integration, but it is not a Plover customer-facing path. ### Embedded Solution Lumin, Q2, and Banno authenticate the member inside the institution's existing digital banking session. A thin platform adapter verifies the provider-specific launch or OIDC result and exchanges it for the same downstream XKOVA member-session model. Banno OIDC can use the normalized JWT proof boundary. Lumin and Q2 signed launches fail closed until their exact vendor signature, key discovery, replay, audience, and return contracts are registered. The shared embedded application is not required to adopt the external `@xkova/browser` abstraction. ## Staff and management users Institution staff using Core Rails are not XKOVA management Console users. Core Rails uses institution staff SSO, a managed sidecar, or a signed contextual launch to create a server-managed staff context. The existing XKOVA management Console keeps its private authentication architecture. No authentication-host, issuer, JWKS, cookie, or redirect migration is implied by the member-session work. ## Fresh Human Authorization For Member Actions Payment signing, escrow signing, and member approval attestations freeze the exact operation and require a new, short-lived human proof before the controlled wallet-signing surface opens. Hosted Auth uses the member's Account Center passkey. FI/BYO OIDC uses a fresh provider proof that satisfies the application's pinned issuer, subject, maximum age, ACR, and AMR policy. XKOVA does not force the FI user through Hosted Auth or require an XKOVA passkey. The BFF exchanges the one-use proof result for an opaque signing session. A returned `signing_url` is only a locator. It is not bearer authority, a member session, or customer request signing. Claim and verification capabilities remain separate purpose-bound flows. ## Related See [Member Browser and BFF Integration](embedded-member.html), [SDK Overview](sdk-overview.html), [Key Management](key-management.html), and the [interactive customer reference](reference.html). --- source: https://docs.xkova.com/authz.html # Authorization and RBAC Authentication proves who is calling. Authorization decides what they may do. XKOVA has one authorization control plane: route permissions come from a shared registry, and a role is a named bundle of permissions resolved per request. ## How a request is authorized A principal, either a staff session or an API key, carries one or more roles. At request time the platform resolves those roles to a permission set and checks the permission the route requires. There is no separate permission catalog for each feature. Provider, member-session, product, and governance checks apply alongside route permissions. Some ceremonies also require an exact subject or the literal owner role, such as proposing break-glass. - `owner` and `admin` are wildcards: they hold every non-platform permission. - Other built-in roles are explicit bundles scoped to a domain. - A tenant may define custom roles, bounded by anti-escalation: you can only grant a role whose permissions you already hold. ## Built-in roles | Role | Authority | | --- | --- | | `owner` | Full tenant authority. Required to arm break-glass. | | `admin` | Full tenant authority, not the literal owner. May opt out of a domain (for example no treasury). | | `compliance_officer` | Compliance and verification oversight. Reads the approval trail but cannot attest. | | `customer_service` | Read payments and escrows, manage contacts, place and release holds. | | `treasury_admin` | Full treasury authority over vaults, signers, thresholds, and transfers. | | `treasury_signer` | Treasury reads plus transfer draft, approve, and execute. | | `treasury_viewer` | Read-only treasury. | | `tokenize_admin` | Full digital-asset and real-world-asset authority: deploy, mint, burn, schemas, distributions. | | `tokenize_minter` | Token read plus mint only. | The permissions group into domains: treasury and money, identity and keys and governance, firewall and compliance and config, tenant and branding and reporting, and digital assets. For the complete role model and the generated permission catalog, see [Roles and Permissions](roles-permissions.html). ## Tenant scope An API key carries a scope as well as roles. Every integrator credential is tenant scope: it acts within the single tenant the key is bound to, and that tenant is fixed by the key. A typical caller is an integrator backend acting for its own institution. At creation, choose explicit resource families or deliberately select the supported `*` wildcard. Resource scopes limit which permission families the key may use. Treasury and tokenization roles remain a second, independent authority check, and are never added implicitly to a first or founding key. There is also an internal platform scope that sits above tenants. It is used only by XKOVA platform operations and is not a surface integrators call. > Platform-only permissions, such as approving production access, are reachable only by a platform principal. They are never granted to a tenant role. ## Separation of duties and quorum High-blast actions are gated by an approval quorum rather than a single permission. The request creator is recorded and cannot self-approve whenever the approval clause can be satisfied without them. Break-glass is the only bypass: two-of-two to arm, time-boxed, with every action under it tagged and audited at high severity. See [Policies and Quorum Governance](policies-quorum.html) for the governance model and [Audit Log](audit-log.html) for what each authorized action records. ## Related See [Authentication](auth.html) for how a credential proves itself, [Roles and Permissions](roles-permissions.html) for the full catalog, and the [Errors](errors.html) page for the `role_denied` response. --- source: https://docs.xkova.com/branding.html # Branding Branding controls your tenant application screens, recipient claim and signing pages, and application notification emails. These surfaces render from a single tenant theme: a brand name, a logo, a color palette, copy overrides, and a verified from-address. This page covers reading and editing that theme, the logo upload flow, verifying a custom sending domain, and the pre-auth public read that lets a hosted page paint your brand before the member has authenticated. ## What Branding Controls A tenant has one application branding theme. It carries the values used by its member-facing screens and notices: the brand and legal names, the from, reply-to, and support email addresses, a logo, a light and dark color palette, email-specific colors, page copy, and links to your terms and privacy pages. The console [Settings and Branding](console-settings.html) page edits the same theme this API exposes. Reads return two views of the theme so you can tell what you set apart from what actually renders: - **Resolved.** The values that render after layering. Any field you leave unset falls back to the platform default (the `XKOVA` brand, `no-reply@xkova.com`, no logo). Every resolved field is present. - **Theme.** The raw tenant-wide row exactly as you set it. A `null` field here means you have not set it and it inherits from the platform default. Resolution runs most-specific first: a surface-specific override, then your tenant-wide theme, then the platform default. Editing sets your tenant-wide theme; anything you leave `null` keeps inheriting. Customer API calls always read and write the authenticated tenant's theme. ## Read and Edit the Theme `GET /v1/branding` returns the resolved bundle plus the raw theme. Any tenant-scoped caller can read it. `PUT /v1/branding` upserts the tenant-wide theme and requires the `branding.manage` permission (an admin or owner). The write is a patch: it updates the fields you send and leaves every other field untouched, so a partial body is the normal case. The logo is set through the separate upload flow below, not through this call. The editable theme fields: | Field | Shape | Renders as | | --- | --- | --- | | `brand_name`, `legal_name` | Text, up to 120 and 160 chars | The display name and legal entity name across pages and email. | | `from_name`, `from_email`, `reply_to_email`, `support_email` | Name text plus valid email addresses | The sender identity and support contact on transactional email. | | `primary_color`, `secondary_color`, `accent_color` | Strict `#RRGGBB` hex | The light-mode page palette. | | `primary_color_dark`, `secondary_color_dark`, `accent_color_dark` | Strict `#RRGGBB` hex | The dark-mode palette pages use under `prefers-color-scheme: dark`; `null` inherits the light value. | | `email_button_color`, `email_bg_color`, `email_text_color` | Strict `#RRGGBB` hex | Email-specific colors. | | `app_title`, `footer_text` | Text | The page title and footer copy. | | `terms_url`, `privacy_url` | `https` URLs | Links surfaced on member pages. | | `subject_override`, `intro_override` | Text | Override the email subject line and intro copy. | | `display_term_preset` | One of `member`, `customer`, `user`, `client` | The noun used for the end user across member-facing copy. | | `copy_overrides` | Object with `singular`, `plural`, `possessive` | Override the end-user noun forms directly instead of picking a preset. | > Validation is strict. Colors must be a full six-digit `#RRGGBB` hex; named, three-digit, and eight-digit forms are rejected. Free-text fields reject newlines and angle brackets, and `terms_url` and `privacy_url` must be `https`. A rejected field returns `validation_failed`. The write is idempotent under an `Idempotency-Key`; see [Idempotency](idempotency.html). ## Upload a Logo A logo is set in three steps so the image bytes never pass through the API request body. 1. **Request a target.** `POST /v1/branding/logo/upload-url` with the `content_type` (one of `image/png`, `image/jpeg`, `image/webp`, `image/gif`) and the `content_length` in bytes (up to 5 MiB, 5,242,880 bytes). It returns an `upload_url`, an `object_key`, and an `expires_at`. 1. **Upload the bytes.** `PUT` the image directly to the `upload_url` before it expires, using the requested content type. Uploading alone does not change your saved logo. 1. **Confirm.** `POST /v1/branding/logo` as the same tenant, using the exact `object_key` returned for this upload. Treat that key as opaque. The API checks the stored size and fully decodes the image, including every animation frame; the client-declared content type is not trusted. Invalid, incomplete, oversized or foreign uploads return `validation_failed`. On success a separate saved image is associated with the theme and the updated branding is returned. Later writes through the original upload URL cannot change that saved image. A saved logo URL or key cannot be confirmed as a new upload. Read `logo_upload_limits` from `GET /v1/branding` for current upload rules. New logos allow at most 4096 × 4096 pixels and 32 million decoded pixels across all frames, including animated PNG, GIF and WEBP. Count image width × image height × frame count, including any separate poster image. For example, twenty 1000 × 250 frames use 5 million pixels. Saved originals remain unchanged. Complex images that exceed validation processing limits are rejected; export a simpler image or shorter animation. For a crisp logo, start with a transparent PNG around 1000 × 250 pixels for a wordmark or 512 × 512 for an icon. These are design recommendations. Leave clear space and check readability on light and dark backgrounds. The Console lets you preview the original image before choosing Upload Logo. Failed validation or publication leaves your saved branding unchanged. `has_logo_override` tells you whether your tenant has its own tenant-wide logo. `DELETE /v1/branding/logo` clears only that override, including a legacy URL. Existing partner/platform inheritance applies, so a resolved logo can remain; surface-specific overrides are unchanged. Stored originals are retained and previously issued image URLs remain usable. All three logo writes require `branding.manage`. ## Verify a Sending Domain A branded `from_email` only delivers reliably from a domain you have verified. Registering a custom sending domain returns the DNS records to publish, and verification checks that they have propagated. - `GET /v1/branding/email-domains` lists your registered domains, each with its DNS records and live status. The envelope also carries `provider_configured`, which is `false` when the platform's domain-capable email provider is not set up; domain management remains unavailable until XKOVA completes that configuration. - `POST /v1/branding/email-domains` registers a domain (a fully-qualified name such as `mail.example.com`) and returns the record set to publish. Re-registering an existing domain returns `email_domain_conflict`; a domain the provider rejects returns `email_domain_invalid`. - `POST /v1/branding/email-domains/{id}/verify` asks the provider to re-check the published records and returns the refreshed status. - `DELETE /v1/branding/email-domains/{id}` removes the domain. Each domain response carries an `id`, the `domain`, its `status`, the `region`, the `dns_records` to publish (each with a `record`, `name`, `value`, `type`, `ttl`, and `priority`), a `verified_at`, a `last_synced_at`, and a `created_at`. The status moves through five states: | Status | Meaning | | --- | --- | | `not_started` | Registered; the DNS records have not been published or checked yet. | | `pending` | Verification is in progress while the records propagate. | | `verified` | The records are live; the domain can send. `verified_at` is set. | | `failed` | Verification did not succeed; re-check the published records. | | `temporary_failure` | A transient provider error; retry verification. | The mutations require the `branding.email_domain.manage` permission. A verify or delete against an id that does not exist for your tenant returns `email_domain_not_found`. ## Public Capability Branding Read Purpose-bound hosted pages such as claims need to paint your brand before the person has authenticated. `GET /v1/public/branding` serves that read with no API key: it takes a `claim_token` query parameter, and the token itself is the capability, exactly like the recipient [claim](escrow.html) endpoints. It resolves the token to a tenant and returns that tenant's public brand bundle, a subset with the brand name, support email, logo, palette (including the dark-mode colors), app title, footer text, and terms and privacy links. Internal sender-domain fields are omitted. An invalid, expired, unknown, or missing token returns the platform-default bundle with a `200`, never a `404`, so token validity cannot be fingerprinted by status. The endpoint is rate-limited by client IP. Use it only to render a hosted page whose claim token the page already holds. XKOVA Hosted Auth does not ask customer code to call this endpoint or place a tenant selector in the URL. The reusable Hosted screens and Account Center resolve the brand from the application-bound ceremony. Cognito sign-in codes use the pool's configured sender and template; application notices use XKOVA's tenant-aware mail path. The former signed authentication-email hook is retired. Basic Cognito-managed login branding is configured separately from the tenant's application theme. ## Authorization and Errors Reads (`GET /v1/branding` and `GET /v1/branding/email-domains`) are open to any tenant-scoped caller. Every mutation requires a permission and is recorded in the [audit log](audit-log.html) as a configuration change. | Operation | Permission | | --- | --- | | Upsert theme, logo upload / confirm / delete | `branding.manage` | | Add, verify, remove a sending domain | `branding.email_domain.manage` | The error codes each surface most commonly returns: | Surface | Codes | | --- | --- | | Theme upsert, logo | `validation_failed`, `role_denied` | | Sending domains | `email_domain_invalid`, `email_domain_conflict`, `email_domain_not_found` | Provider-configuration and provider-runtime diagnostics are private operational errors and are not part of the customer error catalog. See [Authorization](authz.html) for how permissions resolve from roles and [Errors and Correlation IDs](errors.html) for the full catalog. ## Related - [Settings and Branding](console-settings.html): editing the same theme from the operator console. - [Escrow and Claims](escrow.html): the claim page the public branding read paints. - [Tenants and Workspaces](tenancy.html): the tenant a theme belongs to. > For the exact request and response shapes, see [the operations list](api-reference.html) or try the calls live in [the interactive reference](reference.html). --- source: https://docs.xkova.com/break-glass.html # Break-Glass Emergency Access Governance gates high impact actions behind an approval quorum. That is the right default until the quorum itself cannot be reached, for example when an approver group has fewer active members than its threshold and the request would strand forever. Break-glass is the one sanctioned way out: a time-boxed session, armed by two different principals, that lets governed actions bypass the quorum for a bounded window while every bypass is counted and audited. It is deliberately not routed through the quorum it bypasses, because its whole purpose is to work when that quorum is unreachable. ## When You Need It A governed action is normally staged as an approval request that authorized approvers attest until the clause threshold is met. Governance refuses to open a request whose clause can never reach quorum, because that request would sit pending forever. When it detects an unsatisfiable clause (the approver group has fewer active members than the clause threshold), it stops the action and directs an owner to the management Console's emergency-access workflow instead of stranding the request. > Break-glass is a private XKOVA management Console capability. Its routes are intentionally absent from the customer API reference and `@xkova/sdk`. The blocked action is not retryable until a session is armed for the workspace. Break-glass is workspace-scoped, so arming it affects only the workspace whose quorum is stuck. ## The Two-Principal Arming Ceremony Arming a session takes two different principals, so no single person can open the valve. The owner proposes the session, and a second, named staff member confirms it. Confirmation is what arms it. 1. **Propose.** An owner posts a session with a `reason` and a `co_signer_staff_id` naming a different, active staff member of the tenant. The session lands in the `proposed` state. The proposer must hold the literal `owner` role; naming yourself as the co-signer is rejected. 1. **Confirm.** The named co-signer confirms the proposed session. Only that co-signer can confirm it. On confirmation the session becomes `active` and its time-box starts. The co-signer sets the window at this step by passing `duration_minutes` (default 60, maximum 480). Both endpoints require the `break_glass.arm` permission, which is held by `owner` and `admin` roles. On top of that, propose additionally requires the caller to be a literal owner, and confirm additionally requires the caller to be the named co-signer. See [Authorization and RBAC](authz.html) for how a request is authorized and [Policies and Quorum Governance](policies-quorum.html) for the quorum this session bypasses. | Console step | Who | Permission | Effect | | --- | --- | --- | --- | | Propose | Owner | `break_glass.arm` plus a literal owner role | Proposes a session naming a different co-signer. State becomes `proposed`. | | Confirm | The named co-signer | `break_glass.arm` | Arms the session and starts the time-box. State becomes `active`. | | Revoke | Owner, proposer, or co-signer | `break_glass.arm` | Ends a proposed or active session early. State becomes `revoked`. | | Review history | Any authorized reader | `break_glass.read` | Lists the workspace's sessions, newest first. | The propose and confirm calls accept an idempotency key, so a retried arm does not open a second session. See [Idempotency](idempotency.html). ``` flowchart TD N[Governed action cannot reach quorum] --> A[Owner proposes a session] A --> P[Proposed] P -- named co-signer confirms with a time-box --> V[Active, armed] P -- owner or participant revokes --> RV[Revoked, terminal] V -- time-box elapses --> EX[Expired, terminal] V -- owner or participant revokes --> RV V -- governed action runs --> BY[Bypass counted and audited] BY --> V ``` ## Time-Boxed and Auto-Expiring A break-glass session is an emergency, not a standing posture, so it is bounded in time and cannot stay open. The co-signer sets the window when they confirm, in minutes, with a default of 60 and a hard ceiling of 480 (eight hours). An omitted value defaults to 60; a value below 1 or above 480 is rejected with a validation error rather than clamped. Expiry is enforced two ways, so a lagging background job never extends the window: - **Fail-safe at read time.** The security path that governance consults treats an `active` session whose `expires_at` is in the past as already inactive. Once the time-box elapses, no further bypass is granted, regardless of the recorded state. - **Durable sweep.** A background job periodically flips over-due `active` rows to `expired` so the recorded state and the list view stay accurate. This is bookkeeping, not the control: the read-time check has already closed the window. ## What Happens While Armed While a session is `active` and unexpired, a governed action for that workspace that would otherwise be held for quorum proceeds instead. Each time that happens the session records the bypass: - **Counted.** The session's `bypass_count` increments and `last_bypass_at` is set, so the durable record shows exactly how many governed actions ran under the valve and when the last one did. - **Tagged and audited.** Every bypass emits an audit event naming the action that skipped its quorum and the resource it targeted, alongside the arm, confirm, and revoke events for the session itself. Break-glass leaves the loudest possible trail. See [the Audit Log](audit-log.html). > Arming break-glass does not loosen the policy firewall or waive the member signature on a transfer. It bypasses only the governance approval quorum, only while armed, and only for the workspace it was armed in. There is no other bypass path. ## Ending a Session A session ends in one of two terminal states. - **Revoked.** An owner, the proposer, or the co-signer can revoke a `proposed` or `active` session early. Revoking an `active` session closes the bypass window immediately rather than waiting for the time-box. - **Expired.** An `active` session that reaches its time-box expires on its own. No call is needed. Both states are terminal: a revoked or expired session cannot be re-armed. If emergency access is still needed, an owner proposes a fresh session. ## Reading Sessions List the workspace's sessions to see current and past emergency access, newest first. Each session carries its state, the reason given, both principals, the ceremony timestamps, and the bypass tally. | Field | Meaning | | --- | --- | | `state` | One of `proposed`, `active`, `expired`, or `revoked`. | | `reason` | Why emergency access was requested, given at propose time. | | `proposed_by_staff_id`, `co_signer_staff_id` | The two principals in the ceremony. | | `proposed_at`, `armed_at`, `expires_at`, `revoked_at` | The lifecycle timestamps. `armed_at` and `expires_at` are set at confirm; `revoked_at` is set on early revoke. | | `bypass_count`, `last_bypass_at` | How many governed actions ran under the session and when the last one did. | ## Related - [Policies and Quorum Governance](policies-quorum.html): the approval quorum break-glass bypasses when authorized through the management Console. - [Audit Log](audit-log.html): how the arm, confirm, revoke, and per-action bypass events are recorded. - [Treasury](treasury.html): the N of M signer flow that is the most common quorum-gated action. - [Security Model](security.html): the overall control posture break-glass sits inside. --- source: https://docs.xkova.com/cap-tables.html # Cap Tables and Distributions Every issued token has a cap table: who holds it and how much. XKOVA rebuilds that cap table from the chain, so it always reflects on-chain reality rather than a hand-kept ledger. From the same cap table you can run a pro-rata distribution that pays holders out of your treasury Safe, without XKOVA ever taking custody of the funds. ## The cap table is rebuilt from chain You never write cap-table rows by hand. XKOVA rebuilds the holders and positions from the token's on-chain balance log. The rebuild is reorg-safe, so a late chain reorganization corrects the projection rather than corrupting it. What you read is a materialized snapshot of that rebuild, tagged with the block it reflects. - **Summary**: total supply, distinct holder count, and the block the snapshot reflects. - **Holders**: one position per holder, largest balance first, with each holder's fraction of total supply. For an ERC1155X share class a position is per (holder, sub-id). - **Instances**: per-instance ownership for an ERC721X or ERC1155X token, one row per (on-chain token id, holder). A fungible ERC20X token has no instance view. These are authenticated reads, scoped to your workspace. A token outside the workspace returns 404. For the exact response shapes, see the [interactive reference](reference.html). ### Freshness: reading as_of_block The summary read carries `as_of_block`: the block height the snapshot reflects. A cap table is a read model with an explicit vantage point, not a live on-chain query. A freshly settled transfer, mint, or burn appears shortly after it confirms; until then the snapshot reflects the earlier block. - Treat `as_of_block` as the snapshot's as-of point. If it is lower than the block of a transfer you just settled, re-read after a moment. - Holder rows instead carry `last_movement_block`, the block of that holder's most recent balance change. That is a per-holder field, not the snapshot's as-of point. - `rebuilt_at` is the wall-clock time the snapshot was materialized, useful for display alongside the block height. ## Distributions pay holders pro-rata A distribution splits a single payout amount across the current cap table in proportion to each holder's balance. The payout is denominated in a stablecoin or other ERC-20, not in the asset token itself, so a dividend, interest payment, redemption proceeds, royalty, or capital return all flow as ordinary value to each holder's address. Distributions run against any token standard. The proportion basis is the holder's balance in that standard: total token balance for a fungible ERC20X, summed balance across sub-ids for an ERC1155X share class (so each holder receives one payout, however many sub-ids they hold), and instance count for an ERC721X collection. In every case the split is pro-rata over the same chain-rebuilt cap table described above. The flow has two steps on purpose. Creating a distribution freezes the math but moves no money; executing it moves the money out of a treasury Safe you choose. ### Step one: create (freeze the math) You supply the payout token and the total amount. XKOVA snapshots the latest cap table and splits the total across holders by balance. Floor-division dust is assigned to the largest holder so the per-holder payouts sum exactly to the total. The distribution lands in a `draft` state with its payout set frozen. No transfer is drafted yet. ### Step two: execute (pay out of the treasury Safe) At execute time you choose which workspace treasury Safe funds the payout. The funds remain in the issuer's on-chain Safe until the configured owner quorum authorizes execution; XKOVA never holds the money. Before any transfer is drafted, every holder is re-screened, and any sanctioned holder's leg is excluded (that payout is marked failed). The remaining clean legs are batched into one N of M treasury transfer (a multiSend), which your existing treasury signers approve and the XKOVA Relayer submits. Each payout flips to `settled` once the transfer confirms on-chain, and the distribution itself then lands `completed`. > Execute requires a step-up authenticated session, the token admin role, and that the caller is an active signer of the chosen Safe. It is resumable and idempotent: re-running after the transfer is already drafted returns the current state rather than drafting a second transfer. ## End to end ``` flowchart TD A[Mint to holders] --> B[XKOVA observes balances on chain] B --> C[Replay rebuilds positions] C --> D[Create distribution draft] D --> E[Execute, screen and exclude] E --> F[Batched multiSend from Safe] F --> G[Treasury signers approve] G --> H[Relayer submits] H --> I[Payouts settled on chain] ``` ## Screen and exclude, not block Sanctions screening at execute time removes a flagged holder's single leg rather than failing the whole distribution. The clean holders still get paid in the same batched transfer; the excluded leg is recorded as failed and surfaces in the payout set. This keeps one bad address from stalling a payout to everyone else. ## Related - [Treasury](treasury.html): the Safe vaults and N of M signer model the payout rides. - [Corporate Actions](corporate-actions.html): splits, redemptions, and other lifecycle events that also move positions. - [Holder Eligibility and Transfers](holder-eligibility.html): who may hold and move a token. - [Non-custodial Model](non-custodial.html): why the issuer's funds, not XKOVA's, move. > For the cap-table, distribution create, and execute operations, see the [operations list](api-reference.html) or the [interactive reference](reference.html). --- source: https://docs.xkova.com/changelog.html # Changelog Release notes for the XKOVA API. Each entry records what changed in the public surface, so you can tell at a glance whether an upgrade needs work on your side. Breaking changes never land quietly here. ## Current Versions | Component | Version | Stage | | --- | --- | --- | | API service | `v0.1.1-alpha` | Pre-launch alpha | | `@xkova/sdk` | `0.1.0-alpha.6` | Alpha | | `@xkova/browser` | `0.1.0-alpha.6` | Alpha | The HTTP namespace is `/v1`. OpenAPI document metadata and workspace package metadata do not identify a deployed API release. See [Versioning](versioning.html). During pre-launch, contracts may change in place. Keep the API reference and both SDK packages aligned with your environment. Review the [SDK changelog](https://github.com/XKOVA/typescript-sdk/blob/main/CHANGELOG.md) and [repository releases](https://github.com/XKOVA/typescript-sdk/releases) when upgrading. This table identifies versions in the current source. npm distribution tags determine which package version an installation command selects. The table does not prove which build a particular environment has deployed or that a package version has completed npm publication. ## How to read an entry Every release entry groups its changes under fixed headings so you can scan for the parts that affect you. Each entry should state the affected component and whether an integrator must take action. | Heading | What it lists | | --- | --- | | Added | New endpoints and new webhook events, each with a short purpose. | | Changed | Behavior changes. A breaking change here is rare and is called out as such. | | Deprecated | Surfaces scheduled for removal, with their sunset date. See [Deprecations](deprecations.html). | | Fixed | Corrected behavior in existing surfaces. | | Security | Security relevant changes. | | Migration notes | What you must do to adopt the release, or a clear statement that nothing is required. | ## Related See [Versioning](versioning.html) for the compatibility policy, and [Deprecations](deprecations.html) for how a surface is retired with notice. --- source: https://docs.xkova.com/concepts.html # Platform Overview XKOVA is digital asset management for regulated institutions. This page shows how the pieces fit: the two product pillars, the orchestration layer they run on, the non-custodial model underneath, and the tenancy that isolates each institution. ## Two product pillars XKOVA ships two products that share one set of rails. - [XKOVA SPS](rails-overview.html), the Stablecoin Payment System, moves value: payments, escrow and claims, treasury, settlement, and off-ramp. - [XKOVA TAMS](tokenization-overview.html), the Tokenized Asset Management System, issues and manages digital assets: programmable tokens, real-world assets, cap tables, distributions, and corporate actions. ## The orchestration layer XKOVA owns the orchestration: policy, ledger, audit trail, trusted identity-proof verification, scoped session authorization, and settlement state. The financial institution or authenticated digital channel remains authoritative for member identity. Around that core, every external connection is a swappable adapter, so an institution plugs in the providers it already trusts and is never locked into one stack. ``` flowchart TB subgraph CORE["XKOVA orchestration"] direction LR P["Policy and firewall"] L["Ledger and audit"] I["Identity proof and scoped session"] S["Settlement state"] end CB["Core banking"] --- CORE SC["Screening, KYT and BSA"] --- CORE KV["Identity, KYC and KYB"] --- CORE CV["Conversion and ramps"] --- CORE WP["Wallet providers"] --- CORE NW["Blockchain networks"] --- CORE ``` See [Integrations and Orchestration](integrations-overview.html) for each connection and the vendors it supports. ## Wallet custody and signed settlement XKOVA never receives or exports human MPC private-key material. Every value transfer still needs a valid signature from the configured owner address, or the configured N of M quorum on a multisig Safe, before the XKOVA Relayer can submit and pay gas. Thirdweb MPC human wallets use a protected custom-JWT issuer that can bootstrap a general provider signing session, so XKOVA also freezes the business payload and verifies the exact signer and signature before acceptance. Externally controlled EOAs sign independently of that issuer. See the [Non-custodial Model](non-custodial.html). ## Tenants, workspaces, and actors A tenant is an institution using XKOVA. A workspace is the operational scope inside a tenant where payments, treasury, tokens, and fees live. Every customer request is restricted to its authorized tenant and workspace. See [Tenants and Workspaces](tenancy.html). | Actor | Role | | --- | --- | | Member or account holder | An end user who holds balances and signs their own transfers. | | Institution staff | A teller, operations, treasury, compliance, or commercial user entering through institution SSO or contextual launch. | | Customer backend | A confidential server that calls customer APIs with a workspace credential. | | XKOVA management user | A platform-management user of the private XKOVA Console. | > For a hands on path, start with the [Quickstart](getting-started.html). For the full surface, see the [API Reference](api-reference.html). --- source: https://docs.xkova.com/console-compliance.html # Compliance The Compliance area is the operator triage queue. When a payment or a ramp trips a screening review, a case lands here for a compliance officer to work. The console lets you review the queue, move a case through its lifecycle, and place or lift a hold on an account holder. ## Where cases come from A case is created when a screening decision asks for review. That happens when a payment, an on-ramp, or an off-ramp is flagged by transaction screening. XKOVA orchestrates screening through interchangeable providers, so the decision that creates a case comes from the screening pipeline. For the model, see [Screening](screening.html). ## Work the queue The queue lists cases for the active tenant. Filter by state to focus on what needs attention, then click a case to open its detail. The detail panel shows the case type, priority, evidence, and the transitions that are valid from its current state. | Action | What it does | | --- | --- | | Filter by state | Narrow the queue to open, investigating, escalated, SAR filed, closed, or all. | | Open a case | Expand the detail panel with its transitions, comments, and evidence. | | Transition state | Move the case along its lifecycle using the buttons valid for its current state. | | Add a note | Attach evidence or rationale as part of a transition. | | Escalate | Move the case to the escalated (manager review) state, optionally reassigning and attaching a note. | | Close | Reach a terminal state with a recorded reason. | ## The case lifecycle A case moves through a defined set of states, and only the transitions valid from the current state are offered. A typical path runs from open, through investigating or escalated, to closed. Each transition is recorded in the audit trail with the actor and the rationale. ## Hold and release an account holder Triage can also act on the member behind a case. From the Accounts directory, a compliance officer can place a hold on an account holder, recording a reason, which blocks further activity for that member. The same surface lifts the hold with a release. Both actions emit audit events so a later reviewer can see what happened. > The Accounts page is a unified directory: it lists end-user account holders, staff operators, and treasury vaults together, each row opening that kind's own detail page. Holds and releases apply only to end-user account holders in that list. > Holds and releases are deliberate, audited actions. The reason you record travels with the event, so a later reviewer can see why a member was held and by whom. ## Verification gates fail closed Where an action depends on a member's verification status, an unknown or missing status is treated as not verified, never as a pass. Identity verification itself is orchestrated through interchangeable providers; see [Identity Verification](verification.html). ## Related For the audit trail behind every action here, see the [Audit Log](audit-log.html). Case triage and holder hold/release are institution staff Console operations; their private routes are not customer SDK methods. The [customer API reference](api-reference.html) describes the operations available to customer servers. To receive published lifecycle events in your own systems, see [Webhooks](webhooks.html). --- source: https://docs.xkova.com/console-payments.html # Payments The Payments area is where an operator reviews direct sends and follows each one from creation to a confirmed on chain state. It is the console view over the same payments API your backend can call directly. ## What this area covers The Payments list shows direct sends for the active workspace. Escrowed payments live in a sibling area, because they hold funds until a recipient claims them. For the concepts behind both, see [Payments](payments.html) and [Escrow and Claims](escrow.html) in the XKOVA SPS section. ## Before you can send A payment will not move until the workspace has the right setup in place. If you have just signed up, the dashboard checklist walks you through it. You need a compatible asset from the automatic catalog, a committed fee schedule, and an account holder to act as the sender. - **An eligible and certified asset.** Read the automatic asset catalog and select one whose exact direct-send flow is rails eligible and runtime certified. - **A committed fee schedule.** The payments path is rejected until a fee schedule prices the send. Commit one in [settings](console-settings.html). - **An account holder.** The member who signs the transfer. Your backend can provision these on member sign in. ## How a payment moves Sending is never a single write that moves money. The application creates and freezes an intent, the configured account-holder wallet produces its signature, XKOVA verifies the exact signer and payload, the XKOVA Relayer submits the accepted instruction and pays gas, and XKOVA confirms the result on chain. XKOVA never receives or exports the human wallet's MPC private-key material. The full trust boundary is in the [Non-custodial Model](non-custodial.html). 1. The payment is created and runs the compliance pipeline; once allowed, it enters a pending sign state. 1. The holder authorizes the transfer intent and permit through the configured wallet ceremony. 1. The XKOVA Relayer submits the signed call on chain. 1. XKOVA observes the result on chain and the payment becomes confirmed. A webhook fires on state change. > A payment that is mined on chain is not automatically a success. A reverted transaction can still be mined. XKOVA confirms the on chain success flag before marking a payment confirmed, so trust the status the console shows, not the raw mined event. ## Working the list The list shows each payment with its status and counterparties. Click a row to open the payment detail, where you can see its current state, the amount, the parties, and its progress through signing, relay, and confirmation. Use the detail view to confirm a payment reached a terminal state rather than guessing from the list alone. ## Status and what it means The console groups the payment lifecycle into four display states; the detail view shows the underlying step within each. | State | Meaning | | --- | --- | | Queued | Created and working through pre-flight: quoting, the compliance screen, and any required approval. | | Pending | Cleared pre-flight and in motion: waiting for the holder's signature, or signed, relayed, and awaiting on chain settlement. | | Success | Settled on chain with a verified success flag. The terminal success state. | | Failed | Did not complete: blocked by compliance, cancelled, or the transaction did not succeed on chain. The amount did not move. | ## The value-movement firewall Every payment (and treasury transfer) is checked against the workspace's committed authorization policy before it can move: a default-deny rule set built from closed rule types such as destination allow and deny lists, amount caps, velocity limits, counterparty risk, and required-approval rules. The Firewall area is where an operator views the active policy, builds a draft, simulates sample transactions against it, and commits it; a committed policy is immutable, so changing the rules means drafting and committing a new one (the commit itself can be governance held). Every allow or deny decision the firewall makes is recorded in a decision log the operator can review. ## Related For the exact request and response shapes, see the [operations list](api-reference.html) or the [interactive reference](reference.html). To send programmatically rather than from the console, see the [XKOVA SPS overview](rails-overview.html) and the [SDK workflows](sdk-workflows.html). To get notified when a payment changes state, see [Webhooks](webhooks.html). --- source: https://docs.xkova.com/console-settings.html # Settings and Branding Settings is where an operator configures the workspace itself: who is on the team, which API keys exist, how payments are priced, and how your brand appears to members. It opens with the four areas you will use most, then covers webhooks, integration gateways, production access, and billing. ## Team The team section lists every operator in the active tenant. Admins can invite a new operator, which sends a Login Link by email, resend an invite that has not yet been redeemed, and revoke access. Revoke is a soft action that flips the operator's state and is reversible by re-inviting. You cannot revoke your own access from the console; that guard is enforced by the API. > Revoking an operator can cascade. When a staff member is revoked, role grants they authorized for API keys are revoked too. Plan team changes with that in mind. ## API keys The API keys section issues and revokes the tenant's keys. A newly created key shows its secret exactly once, so copy it into your secret manager immediately. Sandbox keys are prefixed `xkv_test_`. Revoke is the normal way to retire a key; the audit trail of who revoked what and when is preserved. - **Create.** Name the key, optionally set an expiry, and optionally mark it for actions requiring step-up authentication. The secret appears once. On a fresh workspace the first creation is held for owner approval: the form shows a held notice, the owner attests under Governance, Approvals, and re-submitting the same form mints the key and shows the secret. - **Scope and roles.** Choose the exact resource families the key needs, or deliberately select the supported `*` wildcard. Treasury and tokenization roles are separate optional authority. No first or founding key receives an implicit role. Role grants can be attached to an existing key without rotating its secret. - **Revoke.** Retire a key; it stops authorizing immediately. For the auth model behind keys, see [Authentication](auth.html) and [Authorization and RBAC](authz.html). Never ship an API key to a browser; keep it server side. ## Fee schedules A fee schedule prices the payments that flow through a workspace. A payment is rejected until a committed schedule exists for it, which is why committing one is on the onboarding checklist. Each schedule is per workspace, so the active workspace (sandbox or production) determines which schedule applies. The XKOVA platform fee is applied automatically to the enabled transaction types and is shown read only. 1. Build a draft schedule with rules for the transaction types you support. 1. Set the beneficiary for earned fees; a treasury vault can be picked directly. 1. Commit the schedule. Creating and committing are admin only. > A schedule that carries earned-fee rules (your own institution, external, or issuer fees) cannot commit unless the workspace has an active revenue treasury, because earned fees are routed to that treasury vault. Set up that vault first in [Treasury](console-treasury.html). ## Branding Branding gives a tenant owner self-service control over how their brand appears on the member facing surfaces: sign-in emails, and the claim and signing pages. The console itself stays XKOVA branded; only the editor lives in settings. You set a brand name, colors for light and dark, a logo, and the term you use for members, then preview each surface live before saving. Saving is admin or owner gated. | Surface | What branding controls | | --- | --- | | Sign-in emails | The from name, brand name, and colors on the emails members receive. | | Claim page | The look of the page a recipient uses to claim funds. | | Signing page | The look of the page where a member previews and signs a transfer. | ## Approving held actions Some actions are held pending a quorum of staff sign-off (the seeded defaults in [Policies and Quorum Governance](policies-quorum.html), plus any requirements the owner adds). The Approvals inbox under Governance lists every open request with its live signature count per clause and an owner-attestation flag where one is required. Approving means connecting your in-app signing wallet and signing an approval message; there is no separate password or code. Once enough staff have signed, the request flips to ready and the original held action is re-submitted from its own page to complete. Cancelling a request abandons the pending approval only; it does not undo or clear the resource that was waiting on it. ## Webhooks The Webhooks area manages the endpoints XKOVA delivers events to. Create an endpoint with its URL and the event types it subscribes to; the signing secret is shown exactly once at creation and once at each rotation, so store it immediately. From the same page you can enable or disable an endpoint, send a signed test event to confirm the wiring, and delete an endpoint (irreversible). Each endpoint has a delivery log with per-delivery diagnostics, and failed deliveries can be replayed one at a time or in bulk. Secret rotation keeps the old secret valid through a short overlap window so you can cut over without dropping events. For the delivery format and verification, see [Webhooks](webhooks.html). > Webhooks are included in every current plan in both sandbox and production. ## Integration gateways A tenant that runs its own backend gateways (for example the core-banking gateway from [Core Banking](core-banking.html)) registers them here by name and base URL, and pins the gateway's TLS certificate fingerprint so XKOVA trusts only that exact certificate; rotate the pin when the certificate changes. A gateway can be enabled or disabled, shows a conformance status from the automated conformance run, and keeps a per-gateway log of the calls it has made. ## Production access Moving from sandbox to production is a gated request, not a toggle. The Production access section shows a checklist of gates: a manual approval on XKOVA's side, and a production network that is live for payments on this XKOVA environment. Both are platform-side gates you cannot action yourself; contact XKOVA if either is blocking. Submit the request from this page and track it to approval or rejection; the decision is recorded with a note in the audit trail. ## Billing and usage The Billing section is the read side of what the workspace owes and uses: the current subscription and its plan, invoice history, and a link out to a hosted portal for managing payment methods. It also reports the on-chain platform fees the tenant has paid per token (with a downloadable accrual log) and a USD ramp volume aggregate for a date range that mirrors what invoicing bills against, with production-billable activity separated from sandbox activity. This surface is read-only reporting; what a tenant charges on its own payments is the fee schedule above. ## Member authentication The Member Auth settings let you enable optional XKOVA Hosted Auth for an application. Managed Hosted email is included in sandbox; production follows the active paid plan or Feature Access Code. Select an existing API principal from the same workspace, or let the guided setup create a dedicated `member_auth`-only BFF principal with no Treasury or tokenization role. Then name the application, choose invite-only or open enrollment, register the exact origin and authorization-result callback, and choose whether it is the default. Console displays the non-secret publishable key and the fixed XKOVA-managed security profile. Custom sending domains remain a Scale or Feature Access Code capability. When unavailable, the Console shows the upgrade path without issuing a denied request to the gated endpoint. Changing an origin, callback, enrollment policy, or enabled state invalidates affected sessions. Console never exposes identity-provider service keys, provider tokens, signing keys, or internal issuer controls. Configure the member-facing logo, colors, copy, and legal links once in Branding. The preview uses the same brand bundle as the controlled Hosted page and transaction-bound email. ## Related Settings also covers workspaces. Network and asset catalogs are automatic and are not configured here. For the exact operations, see the [operations list](api-reference.html) or the [interactive reference](reference.html). For the roles that gate these actions, see [Roles and Permissions](roles-permissions.html). --- source: https://docs.xkova.com/console-tokens.html # Tokens and Assets The console's token surface spans two areas. Tokenize is where an operator launches tokens and works the on-chain contract: supply, roles, access, and signing. Digital Assets is where the workspace manages an asset's data: metadata, cap table, documents, provenance, and corporate actions. A guided wizard deploys a token and the relayer pays for the deploy. ## What this area covers The Tokenize area lists the tokens this workspace has deployed and is the entry to deploying a new one. It also hosts the building blocks for real-world assets: authorities and asset schemas. The data that describes a tokenized asset (metadata, cap table, documents, provenance, corporate actions) lives in the Digital Assets area, on the asset's own page. For the concepts behind all of this, see the [XKOVA TAMS overview](tokenization-overview.html). ## Deploy a token The create token wizard is a guided form that ends in a relayer paid deploy. There is no in-browser wallet ceremony for the deploy itself; the XKOVA Relayer submits it and pays gas. The wizard moves through a few steps, then a progress view that polls until the token is active. | Step | What you provide | | --- | --- | | Network | An active chain selected from the workspace's automatic available-chain catalog. | | Basics | The token name and symbol. | | Admin Safe | An existing Safe that will hold the admin, minter, and pauser roles. The platform does not create the Safe; you point at a deployed one, typically a treasury vault. | | Access | Optional on chain transfer gating: none, an allowlist, or a denylist. | | Review | Confirm the configuration and submit. | After you submit, the progress view tracks the deploy until the token reaches an active status and its on chain address is available, or it reports a failed status with a reason. ## Token standards Tokens follow the gasless meta transaction model so holders can act without holding native gas. The engine supports a fungible standard for digital dollars and non-fungible and multi-token standards for assets and collections. For the differences, see [Token Standards](token-standards.html) and [Deploy and Mint](deploy-mint.html). ## Manage access and policy Once deployed, a token's transfer access is governed by allow and deny lists, roles, mint-cap policies, and a pause control. The console exposes these so an operator can change who may hold a token and who may mint or burn it. For the model, see [Access and Policy](token-access.html). ## Real-world assets and schemas For tokenized real-world assets, the engine separates the asset's data from its code. You publish an asset schema (the shape of the metadata) and then bind a token to it, so the data is config not code and immutable fields stay immutable by system design. From the console you can build or upload a schema, publish it, bind a token, and edit a token's fields within the rules the schema sets. The concepts are in [Real-World Assets](rwa.html), with cap tables, distributions, corporate actions, provenance, and holder eligibility covered in their own pages in the XKOVA TAMS section. ## Related For the full surface, see the [XKOVA TAMS overview](tokenization-overview.html). For the exact operations, see the [operations list](api-reference.html) or the [interactive reference](reference.html). --- source: https://docs.xkova.com/console-treasury.html # Treasury Treasury is where an operator manages institution held funds. Funds sit in a multisig vault, and every transfer out needs a quorum of signers to approve it before the XKOVA Relayer submits it. The console is the surface for creating vaults, managing signers, and approving transfers. ## What a vault is A treasury vault is a multisig Safe (for example a Gnosis Safe) that holds the institution's funds. Unlike a member wallet, a vault is moved by a quorum, not a single signer. A transfer needs N of M signers to each sign the Safe transaction before it can be submitted. The concepts behind this are in [Treasury](treasury.html) in the XKOVA SPS section. ## Set up signing first Each operator who acts as a treasury signer needs an in-app signing wallet provisioned before they can sign. The treasury overview surfaces your own setup status, so you see the gate before you hit it. Complete your signer setup from the overview if it is not yet done. ## Create a vault From the treasury overview, start a new vault. The form asks for a name, the staff signers, and the threshold, and target network. Choose the network from the workspace's automatic active-chain catalog. Creating a vault selects a chain for that vault; it does not pin or configure the workspace. - **Signers.** Pick from the active staff in your tenant. The default is the current operator alone, which is the common case for a first vault. - **Threshold.** The N in N of M. A vault survives a lost key only when it has more signers than its threshold; raising the threshold protects against a stolen key but makes key loss harder to survive. Any vault that could not survive two key losses (fewer than threshold + 2 signers) asks you to acknowledge the reduced redundancy, with a single-signer vault being the extreme case where no key loss is survivable. > Choose the threshold deliberately. A higher threshold protects against a stolen or compromised key but needs more signers present to move funds, and it makes a lost key harder to survive. A one signer vault is convenient but has no redundancy. ## Move funds A transfer out of a vault follows the quorum pattern. The flow is created, then each required signer signs the Safe transaction in their own wallet, and once the threshold is met the transfer becomes ready to execute; an operator then runs Execute, after which the XKOVA Relayer submits it on chain. 1. An operator creates a transfer from a vault, naming the asset, amount, and destination. 1. Each required signer reviews and signs the Safe transaction in their wallet. 1. When the signature count reaches the threshold the transfer becomes ready to execute; an operator then clicks Execute, after which the XKOVA Relayer submits the transfer on chain and pays gas. 1. XKOVA confirms settlement on chain and the transfer reaches a terminal state. > A mined transaction is not automatically a success. XKOVA verifies the on chain success flag before marking a treasury transfer confirmed, so a reverted transfer is never recorded as settled. ## Beyond transfers The treasury area also covers counterparties (named, reusable destinations), per signer dashboards, and a tax export. A revenue treasury is a designated vault that receives the tenant's earned fees (its institution, external, and issuer fees); relinking it to a different vault goes through a quorum approved ceremony rather than a single edit. > On a fresh workspace, signer changes and counterparty additions are governance held: the console shows a held notice with the pending approval, the owner attests it under Governance, Approvals, and re-submitting the same form completes the change. This is the seeded secure default described in [Policies and Quorum Governance](policies-quorum.html). ## Related For the underlying model, see [Treasury](treasury.html) and the [Non-custodial Model](non-custodial.html). For approval quorums and governance, see [Policies and Quorum Governance](policies-quorum.html). Treasury staff operations run through the contextual Core Rails session in Console and are not customer SDK operations. --- source: https://docs.xkova.com/conversion.html # Conversion and Ramps The rails move stablecoin value. Conversion is how that value connects to fiat. A conversion partner turns fiat into stablecoin on the way in and stablecoin back into fiat on the way out. Production partners are swappable and configured per tenant. Every sandbox workspace receives XKOVA's sandbox conversion partner automatically, without changing how its integration calls the rails. ## The conversion partner A conversion partner is the vendor that holds reserves and exchanges fiat for stablecoin. XKOVA talks to the partner through a stable interface, quoting a rate, requesting a mint or a burn, and reading the request to settlement. The orchestration layer presents the same conversion shape regardless of which partner is active, which is the model from [Integrations and Orchestration](integrations-overview.html) applied to ramps. ## The two ramps | Ramp | Direction | What the partner does | | --- | --- | --- | | On ramp | Fiat to digital | Takes fiat and mints or releases stablecoin onto the rails. | | Off ramp | Digital to fiat | Takes stablecoin and pays fiat out to the bank. | The request side of both ramps, how you create and follow a funding or off-ramp request, is on [Funding and Off-ramp](off-ramp.html). This page covers the partner that sits behind those requests. ## Quote, then act A conversion starts with a quote at the partner's current rate. From a quote, the partner issues a mint for an on ramp or accepts a burn for an off ramp, returning a request you read to completion. Because settlement depends on the partner, and for an off ramp also on an on chain transfer confirming and on the receiving bank confirming the fiat credit has posted, the request is asynchronous: create it, then poll it or subscribe to events to learn when it settles or fails. See [Webhooks](webhooks.html) for event delivery. > For an off ramp, the member's wallet burns the stablecoin to the partner first. That outbound transfer uses the same owner-signature, XKOVA acceptance, and relayer path as other rail movements. See [Transfers and Settlement](transfers.html) and the custody and signing model on [Non-custodial Model](non-custodial.html). ## Example vendors The conversion adapter is interchangeable. Examples of the kind of partner that plugs in here include Coinbase, Paxos, and Anchorage. Each production tenant runs one configured partner; moving to another changes nothing about how the rails fund value in or cash value out. Every sandbox workspace instead receives XKOVA's sandbox conversion partner automatically. It runs the same funding and off-ramp lifecycle against on-chain `dUSDC` on Avalanche Fuji and Base Sepolia, including deterministic happy, partial, and failure scenarios. This keeps the developer path production-shaped without pretending demo funds are issuer-controlled USDC or fiat-backed value. Circle Testnet `USDC` is also listed on both chains as a separate supported asset. Acquire it from Circle's faucet or a pre-funded wallet. The sandbox conversion partner does not mint or substitute it. ## Related - [Funding and Off-ramp](off-ramp.html): creating and following ramp requests. - [Core Banking](core-banking.html): where the fiat side settles against the institution's ledger. - [Payments](payments.html): spending the value once it is on the rails. > For the exact conversion and ramp operations and their request and response shapes, see [the operations list](api-reference.html) or try them live in [the interactive reference](reference.html). --- source: https://docs.xkova.com/core-banking.html # Core Banking Core banking is where an institution's members, accounts, and ledger live. XKOVA connects to that core through the integration gateway, a small service that speaks one stable contract to XKOVA and a core specific adapter to the bank. The core is swappable: the gateway presents the same shape whether the core is Symitar, Corelation, or Fiserv. ## What the gateway connects The integration gateway is a deployable unit that sits between XKOVA and an institution's core banking system. XKOVA calls a fixed set of operations on the gateway, and the gateway translates each one into the core's own protocol through a banking provider adapter. The product code above the gateway does not change when the core does, which is what keeps the integration open. This is the orchestration model from [Integrations and Orchestration](integrations-overview.html) applied to the bank ledger. ## The operations it exposes The gateway exposes a compact contract for the actions a money movement flow needs against the core ledger. At a high level: | Operation | What it does | | --- | --- | | Resolve member | Look up a member or account in the core from a reference. | | Check funds available | Confirm an account can cover an amount before acting. | | Place hold | Reserve funds against an account. | | Release hold | Free a previously placed hold. | | Capture hold | Settle a hold into a debit. | | Credit member | Credit funds to an account. | | List operations | Read the gateway's operation stream for reconciliation. The gateway keeps at least 30 days of history, so an integrator can reconcile its own ledger against the core's state. | | Notify member | Optional member notification hook. | Errors from the gateway carry a code, a message, and a correlation id. A code from the gateway maps to an HTTP status; a failure reaching the core is passed through so the caller can tell the two apart. See [Errors and Correlation IDs](errors.html) for the envelope and how to trace a request. ## Example cores The adapter behind the gateway is interchangeable. Examples of the kind of core that plugs in here include Symitar, Corelation, and Fiserv. An institution runs one core, and moving from one to another is an adapter change rather than a change to how XKOVA orchestrates the flow above it. > Core banking is an operator configured integration, not a self-serve API. The gateway is deployed and wired to the institution's core as part of onboarding, with the credentials and network path the core requires. There is no public endpoint to point at your own core on your own; the connection is set up with you. ## How it fits the rails The gateway is how the rails touch fiat at the institution. It backs the hold, capture, and credit steps that funding and off-ramp depend on, so a member crediting a savings account or an off-ramp paying fiat out resolves against the real core ledger. See [Funding and Off-ramp](off-ramp.html) for the request side of those flows and [Conversion and Ramps](conversion.html) for how fiat and stablecoin convert. > For the exact public operations XKOVA exposes to integrators, see [the operations list](api-reference.html) or the [interactive reference](reference.html). The gateway contract itself is an institution-deployed surface configured during onboarding. --- source: https://docs.xkova.com/corporate-actions.html # Corporate Actions A corporate action is a structured change to an issued token: a split, a redemption, an additional issuance, and so on. XKOVA runs each one through a lifecycle engine that opens the action, freezes transfers, executes the on-chain legs one at a time, reconciles the cap table, and unfreezes. The action is the unit of record, so each step is auditable and resumable. ## The kinds A corporate action is opened with a kind, plus the parameters that kind needs. The supported kinds are: | Kind | What it does | | --- | --- | | `split` | Multiplies each holder's balance by a ratio. A 2:1 split is numerator 2, denominator 1; each new balance is floor(old times numerator over denominator). | | `consolidation` | The reverse of a split, applying a ratio that reduces balances (a reverse split). | | `redemption` | Prices out holders at a per-unit price in a payout token, settling the cash leg through the payments chokepoint. | | `additional_issuance` | Mints new units to one or more named recipients, as mint-only legs through the same lifecycle engine. Works on any token standard; the recipient shape is standard-specific (a fungible amount, an NFT instance id, or both for a multi-token). | | `capital_call` | Collects a per-unit obligation from holders. Not yet available: opening one is rejected with a clear error, because collecting from holders requires a holder-authorized payment flow. | | `mandatory_conversion` | Converts holdings into a target token. Not yet available: opening one is rejected with a clear error. | Opening an action validates and freezes the kind-specific parameters, then lands the action in `draft`. No chain leg fires at open time. You drive it forward with a separate advance step. For the exact parameter fields per kind, see the [interactive reference](reference.html). > The money-moving kind that executes today (a redemption price-out) settles through the same [payments](payments.html) chokepoint every other value movement uses. It references an ERC-20 or stablecoin payout token and a funded issuer wallet to draw on. ### Token standard constraints The balance-rewriting kinds (`split`, `consolidation`, `redemption`) apply only to fungible `ERC20X` tokens, and the token must have an access registry attached (allowlist or denylist access control, chosen at deploy): the mechanism that claws back and re-issues holder balances requires both. Opening one of these kinds on an `ERC721X` or `ERC1155X` collection, or on a token without a registry, is rejected up front with a clear error rather than freezing a token the action cannot execute on. NFT corporate actions are a planned capability. `additional_issuance` carries no standard or registry constraint: it mints to named recipients on any standard, with the recipient shape validated per standard up front (a fungible amount for `ERC20X`, a unique token id for an `ERC721X` instance, an id and amount for `ERC1155X`). `capital_call` and `mandatory_conversion` are not yet available: opening either is rejected at draft with a clear error, so an action that cannot execute is never opened. ## The lifecycle engine Advancing an action drives a re-entrant state machine one step forward. Re-entrant means you can call advance again after a transient failure to resume from where it stopped, rather than restarting. The states run in order: 1. **draft**: parameters are frozen; nothing has executed. 1. **frozen**: the transfer freeze commits before any on-chain leg fires, so positions cannot move underneath the action while it runs. 1. **executing_on_chain**: the on-chain legs run one at a time. Each leg rides the shared tokenization build, sign, relay, and confirm lifecycle. 1. **reconciling**: the cap-table replay confirms the new positions against the chain. 1. **completed**: the freeze is lifted and the action is closed. There are two failure outcomes. If the relayer or RPC layer fails transiently and the transaction never landed, or an on-chain leg reverts without diverging supply, the action lands `failed_resumable`: calling advance re-enters the machine from the failed step. The one terminal failure is `reverted`, a supply divergence where a mint reverts after its paired burn already confirmed, so balances were clawed back but not re-issued; it needs operator reconciliation. A reverted on-chain transaction is never treated as success. ``` flowchart TD D[draft] --> F[frozen: transfers locked] F --> E[executing_on_chain: legs run one at a time] E --> R[reconciling: cap table checked against chain] R --> C[completed: freeze lifted] E -- mint reverts after a confirmed burn --> V[reverted, terminal: supply divergence] E -- transient relayer or RPC failure, or a non-divergent revert --> FR[failed_resumable] FR -- advance --> E ``` ## Reading state and progress Reading an action returns its current state, the frozen parameters, the snapshot block and freeze timestamp, and the ordered legs, each linked to its tokenization operation id. This is the auditable record of what the action did and how far it has progressed. Opening and advancing require a step-up authenticated session and the token admin role, and both are audited. ## Constraints to know - The recover-orchestration kinds (those that rebuild balances on-chain) apply to the fungible ERC20X share class and require an access registry on the token; without one the open is rejected. - Corporate actions are gated by the real-world-asset entitlement. A tenant without it is rejected before any work happens. ## Related - [Cap Tables and Distributions](cap-tables.html): how positions are read and how a pro-rata payout works. - [Holder Eligibility and Transfers](holder-eligibility.html): the transfer freeze and the eligibility door that gate movements. - [Token Standards](token-standards.html): the ERC20X, ERC721X, and ERC1155X models the legs act on. - [Provenance and Transparency](provenance.html): the append-only record behind each change. > For the open, advance, and read operations, see the [operations list](api-reference.html) or try them in the [interactive reference](reference.html). --- source: https://docs.xkova.com/demos.html # Plover Credit Union Demo Plover Credit Union demonstrates a member banking app powered by XKOVA. Sign in with your own email to explore the banking experience. ## Enter the Demo Open [Plover Credit Union](https://pcu.xkova.com/), select Sign In with Email, verify the emailed code on Plover’s branded sign-in page, and arrive at `https://pcu.xkova.com/dashboard`. Complete existing MFA when required. The [demo introduction](https://console.xkova.com/demo) links only to the member app. There is no public management Console demo, shared-admin login, or automatic reset. ## The Member Journey 1. Sign in with email through XKOVA Hosted Auth. 1. Explore the banking dashboard and its demo checking and savings accounts. 1. Choose the stablecoin account product, review its terms, accept explicitly, and complete the required passkey ceremony. 1. Wait for the fixed `5.00 dUSDC` sandbox welcome credit to confirm before using stablecoin features. Signing in alone does not grant stablecoin access or fund a wallet. 1. Send a payment and complete its separate signing ceremony. Plover's BFF keeps provider credentials, XKOVA member-session IDs, and refresh credentials on the server. The browser receives an opaque application cookie. Product acceptance, wallet readiness, a confirmed funding credit, and authorization to sign a specific payment are distinct steps. Plover demonstrates Hosted email sign-in. FI/BYO OIDC remains supported through the provider-neutral API and SDK with its own integration flow. See [Embedded Member Xperience](embedded-member.html) and [Authentication](auth.html). Sandbox credits are test assets with no monetary value. ## Build Your Own Integration Follow the [Quickstart](getting-started.html) for a workspace you control, then use [Sandbox Scenarios](sandbox-scenarios.html) to test failure and review outcomes. See [Non-custodial Model](non-custodial.html) for the signing boundary. --- source: https://docs.xkova.com/deploy-mint.html # Deploy and Mint Deploying a token is a short saga, not a single call. You name the token and supply its admin Safe, the platform relayer deploys the contracts and pays gas, XKOVA confirms the result on-chain, and supply changes (mint and burn) run through the Safe's signer quorum afterward. ## Deploy You deploy a token by its standard: an `ERC20X` fungible token, an `ERC721X` collection, or an `ERC1155X` multi-token contract. Deploying requires the `tokenize_admin` role (tenant admins inherit it) and a step-up authenticated session, and is gas-paid by the platform relayer. The request returns immediately with a token in `pending_deploy`. You provide the token's admin authority as a multisig Safe you already control. That Safe becomes the contract's administrator: it holds the minter, pauser, and admin roles. The deploy does not provision a Safe for you; supply the address of one you own. ### What happens after you call deploy 1. A token factory deploys the token proxy, a roles contract bound to the admin Safe, and, when access control is configured, an access registry (allow list or deny list). All are relayed and gas-paid. 1. XKOVA observes the new contract on-chain and records a deployment for it, with no extra call from you. A fungible `ERC20X` is additionally auto-registered onto the payment and treasury rails; an `ERC721X` or `ERC1155X` collection is recorded as a deployment only and is not added to those rails. 1. XKOVA marks the token `active` only after both deploy transactions confirm on-chain success. A reverted-but-mined transaction is never treated as success. 1. You poll the token until it resolves to `active` and carries its on-chain proxy address. ``` flowchart TD A[Call deploy] --> B[Token factory deploys proxy] B --> C[XKOVA observes the contract on chain] C --> D[Records a deployment, ERC20X auto registers onto the rails] D --> E[XKOVA confirms on chain success] E --> F[Token active] F --> G[Mint to holders, supply updated] ``` > Deploys are idempotent. Send an `Idempotency-Key` so a retried deploy returns the original operation rather than starting a second one. See [Idempotency](idempotency.html). ## Explorer source verification After a confirmed tokenization deployment, XKOVA submits contract source verification and the proxy-to-implementation association to the supported network explorers. This is asynchronous and depends on explorer availability. An active token or an accepted verification submission does not establish that an explorer has completed source verification. Inspect the deployed token's source and implementation association. A verified factory does not prove that its deployed children are verified. Token proxies and access registries need their own source and implementation association; roles clones must resolve to the verified mastercopy. Explorer source verification publishes readable contract code and does not move value on chain. ## Provisioning the admin Safe The admin Safe is a required input to deploy, not something the deploy call creates. You pass the address of a multisig Safe you already control, and it must be a Safe already deployed on the token's chain. The service checks that the address is a valid checksummed address with on-chain code and rejects a plain wallet address or an undeployed counterfactual Safe. The Safe you supply becomes the contract's administrator, holding its admin, minter, and pauser roles, and it is the quorum that signs every later mint, burn, and recovery. If you do not yet have a Safe, provision one through treasury. Creating a treasury vault deploys a Gnosis Safe multisig on-chain with the staff signers and threshold you choose; once it is active you can use its address as the admin Safe for a deploy. Using a treasury vault also means the same signer quorum and the same draft, collect, relay, settle path govern both your treasury transfers and your token's supply changes. See [Treasury](treasury.html) for creating a vault, managing signers, and how a quorum-signed transaction settles. > The admin Safe is chosen at deploy time and becomes the authority for the token's supply. Pick a Safe whose signers and threshold match how you intend to govern mint, burn, and recovery, all of which run through that Safe's quorum. See [Token Authorities](token-authorities.html) for the roles a token grants and who holds them. ## Mint Minting creates new supply and credits it to a recipient. A mint is built against the token's admin Safe, then the Safe's N of M owners sign the returned typed data and submit signatures until the threshold is met. At that point the platform relayer submits the mint and pays gas, and XKOVA marks the operation `confirmed` once it confirms on-chain success. - The amount is a smallest-unit integer (the raw on-chain value, not a decimal). - The token must be `active` before it can be minted. - One mint per token is in flight at a time. Wait for it to confirm or fail before starting another. ## Burn and supply Burning reduces supply. A burn runs the same Safe-signed path as a mint and reduces both the executing Safe's balance and the total supply. The token's total supply is therefore always the net of confirmed mints and burns, reconstructed from on-chain state rather than tracked off chain. There is also a compliance recovery action that moves a blocked account's balance to the admin Safe. It requires a dedicated recovery role on the Safe and is covered alongside the access controls on [Access and Policy](token-access.html). > Mint, burn, and recover all go through the Safe quorum and the relayer. The Safe contract requires its configured owner signatures, the relayer submits, and the chain settles. Each owner wallet retains its own custody and signing-session trust boundary. See the [Non-custodial Model](non-custodial.html). ## For exact shapes The deploy request bodies, the mint and burn payloads, and the signing flow are in the [interactive reference](reference.html) and the [operations list](api-reference.html) under the tokenization tag. --- source: https://docs.xkova.com/deprecations.html # Deprecations During pre-launch alpha, contract changes are cutovers. Earlier request and response shapes do not have a compatibility window. ## Current Stage Do not assume a twelve-month overlap, an earlier alpha endpoint remaining available, or a locked `/v1` shape. Keep your integration aligned with the current API and SDK contracts. XKOVA has not established a production deprecation schedule on this page. ## Upgrade Steps 1. Review the [current versions and release notes](changelog.html). 1. Compare your operations with the current [API Reference](api-reference.html). 1. Update SDK packages and any affected request construction or response parsing. 1. Exercise success, review, rejection, retry, and timeout paths in sandbox before production. ## Deprecation Signals `Deprecation` and `Sunset` are useful when a published retirement schedule exists. This page does not claim the alpha API emits these headers or guarantees an overlap period. A future production retirement policy must state affected operations, replacements, and dates. See [Versioning](versioning.html) for the distinction between a namespace, a component release, and an immutable on-chain deployment. --- source: https://docs.xkova.com/embedded-member.html # Member Browser and BFF Integration The browser talks only to your same-origin backend. Your BFF owns provider callbacks, the XKOVA session, refresh rotation, and the tenant API credential. The browser receives a host-only application cookie and safe member projections. XKOVA member-session and identity-provider credentials remain server-side. ## Package boundaries | Surface | Runs in | Responsibility | | --- | --- | --- | | `@xkova/sdk` | Customer server | Customer business APIs, including money movement, using bearer authorization and production mutation signatures. | | `@xkova/sdk/member-bff` | Customer BFF | Provider-proof transactions, Hosted Auth start and completion, XKOVA session exchange and rotation, exact logout, typed context, cookies, CSRF, and store contracts. | | `@xkova/browser` | Browser | Optional same-origin Consumer Xperience client for member self-service. | `@xkova/browser` is not the foundational abstraction for Embedded Solution vendor adapters. Lumin, Q2, and Banno integrations may share internal client primitives while preserving the hosting, launch, bridge, and lifecycle rules of each platform. ## The security boundary ``` browser -> first-party application cookie -> customer BFF -> provider-specific proof exchange -> server-held XKOVA member session -> XKOVA member APIs ``` The browser never receives the workspace API key, XKOVA member-session ID, XKOVA refresh token, provider proof, provider access token, or provider refresh token. Resource endpoints do not accept raw provider tokens, `X-OIDC-ID-Token`, `X-End-User-Id`, or arbitrary account-holder selectors. The durable application cookie must be named with the `__Host-` prefix and use Secure, HttpOnly, `Path=/`, and `SameSite=Strict`. A separate short-lived `SameSite=Lax` flow cookie is permitted only where a cross-site OAuth or OIDC callback needs it. Use exact Origin checks and a session-bound CSRF token on same-origin mutations. ## Start and complete Hosted Auth Enable Hosted Auth in Console, select an existing same-workspace API principal, choose invite-only or open enrollment, and register your exact origin and callback. The BFF helper creates state and S256 PKCE material, stores the transaction, and asks XKOVA for the controlled customer-branded ceremony URL. Your application never handles the email code, TOTP code, or provider token. ``` import { XKOVAMemberBffClient, serializeXKOVAMemberSessionCookie, } from '@xkova/sdk/member-bff'; const auth = new XKOVAMemberBffClient({ baseUrl: process.env.XKOVA_API_URL, apiKey: process.env.XKOVA_API_KEY, publishableKey: process.env.XKOVA_PUBLISHABLE_KEY, requestSigner, }); const start = await auth.beginHostedAuthorization({ browserOrigin: 'https://app.example.com', redirectUri: 'https://app.example.com/api/auth/callback', transactionStore, }); // At the registered callback: const established = await auth.completeHostedAuthorization({ callbackUrl: request.url, expectedState: flowCookieState, transactionStore, sessionStore, applicationData: { memberAuthPublishableKey: process.env.XKOVA_PUBLISHABLE_KEY }, }); ``` Redirect only to `start.authorizationUrl`. At callback, the helper validates exact state, callback, and PKCE binding, consumes the one-time result, and stores the provider-neutral session. Keep `established.session` and `established.reference` server-side. ## Start and complete FI OIDC The BFF helper creates state, nonce, and PKCE material and stores the transaction by a one-way state hash. Callback completion serializes one winner, stages the confidential provider proof with a stable XKOVA exchange attempt and application-session reference, and retains a bounded completed record. Retrying the identical callback can therefore recover after an ambiguous response or store acknowledgement without reusing the provider authorization code. Your code performs the FI-specific authorization-code exchange and returns the proof to the helper. XKOVA verifies it against the registered application's pinned issuer, audience, provider kind, and namespace. ``` const start = await auth.beginAuthorization({ proofKind: 'oidc', browserOrigin: 'https://app.example.com', redirectUri: 'https://app.example.com/api/auth/callback', transactionStore, }); ``` Preserve the flow cookie and durable transaction when a retriable infrastructure failure escapes callback completion. Only `XKOVAMemberAuthorizationTerminalError` marks that exact flow unusable and permits the BFF to clear the cookie and start again. The publishable key is safe to include in public application configuration because it grants no authority. The BFF still presents the confidential workspace bearer credential, and production mutations still require asymmetric request signing. ## Read security setup before starting an action After resolving the confidential session, read `resolved.resources.self.actionReadiness()`. This uses the existing session endpoint and performs no enrollment, provider request, action creation, or grant consumption. Hosted members receive distinct states for first-passkey setup, recovery-code acknowledgement, lost-credential recovery, and ready setup. FI-managed members are reported as externally managed; do not send them to Hosted enrollment. The browser companion exposes `getActionReadiness()` through your same-origin BFF. Keep dependent action controls disabled while readiness is unknown and show the next setup step. Refresh after Account Center return, credential changes, and session rotation. A ready projection is not an authorization grant: every sensitive action still needs fresh proof, current permission, wallet readiness, policy checks, and its actual settlement result. Provider health is explicitly `not_checked`, not assumed healthy. ## Session storage and rotation Implement `XKOVAMemberSessionStore` and the authorization transaction store with your existing durable database or shared session service. Its per-reference exclusivity method must serialize refresh and logout across every BFF instance. A process-local lock is suitable only for one-process development. Encrypt and access-control refresh material and authorization transactions because a pending transaction can briefly contain a provider proof. For an embedded or other confidential proof that does not use the browser callback helper, call `establishSession` with a stable customer-generated `exchangeId` and an encrypted `XKOVAMemberProofExchangeTransactionStore`. Persist that identifier before the first attempt and reuse it for recovery so the same XKOVA operation attempt and application-session reference are retained. Refresh credentials are single-use. The API retains append-only lineage so replay or concurrent reuse revokes the refresh family. Session validation also enforces idle and absolute expiry, exact tenant, workspace, application, and account-holder binding, application configuration version, session policy version, membership status, and explicit revocation. Logout revokes the exact session unless the caller deliberately requests the broader server-side scope. ## The browser client ``` import { createMemberClient } from '@xkova/browser'; const member = createMemberClient({ basePath: '/api/member' }); const session = await member.getSession(); const wallets = session ? await member.listWallets() : []; if (session && wallets.length === 0) { await member.ensureWalletProvisioning(); } ``` The initial browser package covers auth launch and status, enrollment, safe profile and avatar, balances, transaction history, consents, data exports, verification, wallet readiness and self-wallet operations, and Account Center handoff. It intentionally excludes contacts, payments, payment requests, escrows, bank transfers, ramps, treasury, tokenization, and compliance decisions. Those excluded business operations remain available to a Consumer Xperience backend through the customer-server SDK when they are present in the reviewed customer operation manifest. Excluding them from browser JavaScript does not remove the product capability. ## Self-wallet safety Member self-wallet routes derive account-holder and provider identity from the active XKOVA session. The browser can list its wallets, read readiness, and send a bodyless request that wakes or resumes durable provisioning. It cannot name another account holder or provider account, submit an address or chain, mint a raw provider credential, or register an arbitrary wallet. > Member signing still occurs in the controlled signing UI after a separate exact human-action ceremony. XKOVA gives that page a public Thirdweb client selector and one fresh 60-second custom JWT for the canonical account-holder wallet. The provider session is memory-only and disposed immediately, but Thirdweb does not restrict it to the XKOVA business purpose. XKOVA freezes the payload and accepts or relays only the exact authorized signer, action, and signature. This bootstrap is never exposed as an ordinary member or customer API. ## Hosted Auth and BYO OIDC Keep the entry points visibly distinct. BYO OIDC is the primary enterprise model and redirects to the financial institution's IdP. Hosted Auth is an optional XKOVA-managed alternative. Its controlled page presents tenant branding, six-digit email-code sign-in, and an existing TOTP challenge when required. Customer applications must not recreate those ceremonies on their own origins. Hosted members use the tenant-branded Account Center for passkey lifecycle, Hosted session management, recovery-code readiness, and approved lost-all-factor recovery. Both identity models converge into the same provider-neutral XKOVA member-session model without exposing durable credentials to browser JavaScript. ## Purpose-Bound Browser Flows Payment signing, escrow signing, claims, approvals, and verification are narrow flows. For wallet signing, the BFF freezes the exact action first. Hosted members prove a fresh Account Center passkey. FI/BYO OIDC members prove the configured fresh external assurance profile. The one-use result becomes an opaque signing session, never URL bearer authority or a reusable member session. - **Payment and escrow signing.** Render the exact typed data returned by the signing session, establish one ephemeral provider ceremony for the canonical member wallet, and submit the resulting signature through the purpose-bound flow. - **Claims.** The path token selects one claim and the recipient completes the required recipient-bound verification before release. - **Approvals and verification.** Follow the controlled handoff and return contract. Do not reinterpret these capabilities as general API credentials. ## From a Pending Payment to Member Signing Creating a payment does not grant its creator control of the payer's wallet. `POST /v1/payments` intentionally returns a pending resource, not a bearer `signing_url`. Changing `channel` cannot grant signing authority. 1. In Console, open Settings, Member Auth (`/settings/member-auth`). Register a Hosted application on your workspace, bound to your confidential BFF API principal. Register the exact HTTPS application origin and separate login, Account Center and member-action callbacks. Grant only the permission families needed by the server operations, including `member_auth` and `payment` for this flow. A payment-only key cannot establish member sessions. Add wallet or funding permissions only when those operations are needed. 1. Use `beginHostedAuthorization` and `completeHostedAuthorization` above. In open enrollment, the verified subject creates its exact workspace membership. Invite-only enrollment requires the configured invitation process first. Take `established.session.accountHolderId` from the server result. Do not create an unrelated bare holder and attach an email later. 1. Provision and fund that member's wallet, accept required policies, and use that holder and wallet when creating the payment. Keep the payment ID for the same authenticated payer. A machine can initiate the payment, but cannot impersonate the payer to sign it. 1. Hosted members complete passkey enrollment and save recovery codes in Account Center. A lost code display can be resumed with the existing passkey. `member_security_setup_required` means finish that setup, not repeatedly log in. 1. The BFF starts the public member-action API through the helper: Preserve the original business review before a setup detour. The browser companion's `createMemberActionReference()`, `saveActionDraft()` and `getActionDraft()` retain an opaque review reference. The BFF uses `resolveActionIntents()` with its encrypted, replica-serialized intent store. Its `create()` freezes reviewed input without a business mutation; explicit `prepare()` uses one stable idempotency key and persists the exact payment or escrow resource. Repeated initiation does not create another resource. Changed input is a conflict. Review expiry is one hour, capped by the application session; an expired or missing review never silently starts a replacement action. ``` const action = await auth.beginMemberAction({ resourceType: 'payment', resourceId: paymentId, action: 'sign', browserOrigin: 'https://app.example.com', returnUri: 'https://app.example.com/api/member-actions/callback', applicationReturnUri: 'https://app.example.com/payments/complete', sessionReference: authenticatedApplicationSessionReference, sessionStore, transactionStore, }); // Keep action.state in a short-lived Secure, HttpOnly, SameSite=Lax flow cookie. // Hosted applications redirect to action.authorizationUrl when // action.authorizationMethod === 'hosted_passkey'. // At the registered member-action callback: const completed = await auth.completeMemberAction({ callbackUrl: request.url, expectedState: flowCookieState, sessionStore, transactionStore, }); const exchange = new URL('/api/member-action/exchange', completed.signingUrl); exchange.searchParams.set('code', completed.signingExchangeCode); if (completed.applicationReturnUri) { exchange.searchParams.set('return_to', completed.applicationReturnUri); } // Clear the flow cookie and return an HTTP 303 to exchange with // Cache-Control: no-store and Referrer-Policy: no-referrer. ``` The helper calls `POST /v1/member-actions` with the registered principal, application selector and exact server-held member session. The controlled signing surface exchanges the one-use result, obtains the frozen payload and submits the wallet signature. These are not guessed `/members`, `/apps` or per-payment `/sign` endpoints. Integrations must not substitute internal signing APIs for the controlled ceremony. FI OIDC uses the same member-action helper with its registered external proof and `exchangeProviderCode` callback instead of a Hosted passkey. Subscribe the webhook endpoint in the same tenant and workspace as the payment. A payment in a separate demonstration tenant cannot prove delivery to your tenant's endpoint. Verify the signature on raw request bytes before parsing JSON, using `verifyWebhookSignature` from `@xkova/sdk/webhook`. Its inputs are `rawBody`, the `XKOVA-Webhook-Signature` header and the endpoint secret. Do not reserialize a captured JSON object or try alternate signing schemes. Check `payment.confirmed` only after this exact payment actually reaches confirmed. ## Embedded Solution note In Embedded Solution, the institution's digital channel already authenticated the member. Lumin and Q2 launch proofs remain rejected until an exact vendor contract is registered and implemented. Banno's standards-based OIDC proof can converge through the registered JWT proof exchange. The Integration Gateway, not the browser or identity adapter, retains core credentials, raw core mappings, and authoritative fiat posting responsibility. ## Related See [Authentication](auth.html), [SDK Overview](sdk-overview.html), [Wallets and Balances](wallets.html), and the [interactive customer reference](reference.html). --- source: https://docs.xkova.com/entitlements.html # Plan and Add-on Entitlements Not every feature is on for every tenant. Core value movement (stablecoin payments, escrow, wallets) is always available, but capabilities like treasury, full compliance screening, and the tokenization module are add-ons that a tenant enables. A workspace's effective feature set is the union of what its plan includes, any mode-specific inclusion, and any overlay grants layered on top. When a request hits a feature it does not have, the API returns a single, predictable error so your integration can react. This page covers how entitlements resolve, how to read them, how to redeem a Feature Access Code, and the feature_not_enabled contract. ## How Entitlements Resolve A workspace's effective feature set is a union of three sources. There is no precedence to reason about: a feature is active if any source grants it. | Source | What it is | How it changes | | --- | --- | --- | | Plan baseline | The features included in the tenant's assigned plan. | Changes when the tenant's plan changes. | | Workspace mode | Sandbox includes managed OTP and email without broadening the tenant's production grant. | Fixed by the authenticated workspace mode. | | Overlay grants | Per-tenant add-on grants layered on top of the plan, from a redeemed Feature Access Code or a direct grant by XKOVA staff. | Changes when a code is redeemed, or a grant is added or revoked. | An overlay grant that duplicates a plan feature is a no-op: the plan already grants it. Revoking an overlay grant only drops the add-on, not a feature the plan itself includes. To drop a plan feature, change the plan. ## The Feature Catalog Feature gating is binary and module-level. There is one feature key per top-level capability, not a read/write split. Either the tenant has the key, or it does not. The add-on keys are: | Feature key | Gates | | --- | --- | | `treasury` | The treasury surface: multi-signature wallets, signers, and N-of-M approved transfers. See [Treasury](treasury.html). | | `revenue_controls` | Revenue and fee controls, including the revenue treasury link and fee schedules. See [Fee Schedules](fee-schedules.html). | | `screening_full` | The upper-tier compliance screening provider used during the pre-sign pipeline. See [Screening and Sanctions](screening.html). | | `byo_oidc` | Bring-your-own OIDC identity provider configuration for member sign-in. | | `rwa_tokenization` | The ERC-721 and ERC-1155 tokenization module and the entire real-world-asset metadata surface. See [Real-World Assets](rwa.html). | | `billing_webhooks` | Customer webhook endpoint and delivery management. Included in every current plan for sandbox and production. | | `managed_messaging` | XKOVA-managed OTP and member email, including optional Hosted Auth. Included in sandbox; production follows the paid-plan matrix or an explicit grant. | | `custom_domains` | Verified customer sending domains. Available through Scale or an explicit grant. | > Stablecoin tokens (ERC-20) are the base product and are never gated by an entitlement. Only the NFT and real-world-asset surface sits behind `rwa_tokenization`. > Production access is separate from feature entitlements. A tenant submits a production-access request, and XKOVA's approval is the production entitlement. No Feature Access Code grants production access. ## Reading Effective Entitlements Fetch the caller-tenant's plan and full feature catalog with a single read. The call is tenant-scoped: it reports the plan and, for every feature in the catalog, whether it is active for this tenant and how it became active. The inactive features are returned too, so a settings surface can show the add-ons a tenant has not enabled. The response carries a `plan` object (`key` and `name`) and a `features` array. Each feature row reports its `key`, `name`, `description`, `category`, an `active` boolean, a `granted_via` provenance value, and a `granted_at` timestamp. | `granted_via` | Meaning | `granted_at` | | --- | --- | --- | | `plan` | Active because the tenant's plan includes it. | `null` (plan grants are implicit, with no per-tenant timestamp). | | `sandbox` | Active because the authenticated workspace is sandbox. | `null` (workspace-mode inclusion is implicit). | | `activation_code` | An overlay grant from a redeemed Feature Access Code. | ISO-8601 grant time. | | `admin_grant` | An overlay grant set directly by XKOVA staff. | ISO-8601 grant time. | | `null` | The feature is not active for this tenant. | `null`. | This read is what the console uses to drive sidebar gating and the Plan and Add-ons settings section. See [Settings and Branding](console-settings.html) for the operator view. ## Redeeming a Feature Access Code An add-on is enabled by redeeming a Feature Access Code. Redemption is an admin staff action: it requires the `entitlement.redeem` permission, a step-up MFA session (AAL2), and it is audited. The request body carries only the plaintext `code`. XKOVA hashes the code before lookup and never logs the plaintext. A code references a named grant template, a fixed bundle of feature keys it enables. The templates are: | Template | Enables | | --- | --- | | `all_addons` | Every add-on feature. | | `treasury_only` | `treasury`. | | `rwa_only` | `rwa_tokenization`. | The response reports what changed. `granted_features` lists the keys this redemption newly activated, `already_active` lists keys the tenant already held (nothing to do), and `redemption_id` is the audit row id to quote in a support ticket. Two idempotency properties matter here, and they are different: - **The redemption is idempotent at the feature-set layer.** Re-granting a feature the tenant already has is a no-op, reported in `already_active` rather than raising an error. - **The code itself is one-time-use across all tenants.** Once a code has been redeemed, a second redemption of that same code is rejected. To grant the same features again, issue a fresh code. Codes may be scoped to a single tenant. A scoped code presented by a different tenant is rejected, and support can distinguish that condition from a mistyped or unknown code. Codes issued via the redeem endpoint cannot be created there; code issuance is an XKOVA management function, not a customer API surface. ## The feature_not_enabled Contract When a request reaches a surface gated by an add-on the tenant does not have, the API returns `403 feature_not_enabled`. The check runs after authentication and before the handler, so the request never touches the gated resource. The error body's `details.feature` carries the exact feature key that was missing, so your integration (or the console) can render a targeted "redeem a code to enable *X*" prompt rather than a generic denial. `feature_not_enabled` is not retryable. Retrying the same call changes nothing; the tenant must first gain the feature (redeem a code, or have staff grant it). This is distinct from an authorization role gap: a role failure is `role_denied` (you are not allowed to perform the action), while `feature_not_enabled` means the capability is not turned on for the tenant at all. The customer-facing errors you may see across entitlement reads and gated calls are: | Code | Status | When | | --- | --- | --- | | `feature_not_enabled` | 403 | The tenant lacks the entitlement a gated surface requires. `details.feature` names the key. | | `role_denied` | 403 | The caller is not an admin staff member, so cannot redeem. | | `step_up_required` | 403 | The admin caller does not hold a step-up MFA (AAL2) session. | Feature Access Code ceremony failures belong to the private management surface and are intentionally absent from the customer error catalog. See the [error catalog](errors.html) for the full envelope shape and correlation IDs. ## Related - [Authorization and RBAC](authz.html): how permissions and roles decide who may act, the layer above the entitlement check. - [Settings and Branding](console-settings.html): the Plan and Add-ons view where operators read entitlements and redeem codes. - [Treasury](treasury.html), [Real-World Assets](rwa.html), and [Screening and Sanctions](screening.html): surfaces that individual add-ons enable. > For the exact request and response shapes, see [the operations list](api-reference.html) or try the calls live in [the interactive reference](reference.html). --- source: https://docs.xkova.com/errors.html # Errors and Correlation IDs XKOVA returns a single, stable error envelope on every customer API failure. This catalog lists the 112 reviewed customer error codes by HTTP status. It is generated from the explicit customer manifest and canonical error metadata. > Every error response carries the same envelope: a stable `code`, a human readable `message`, an optional `details` object, and a `correlation_id` to quote when contacting support. The `retry` column states whether retrying can change the outcome. ## Retry semantics | Value | Meaning | | --- | --- | | `none` | Not retryable. Retrying will not change the outcome. | | `immediate` | Retry right away, for example after a transient network blip. | | `header` | Wait the seconds given in the Retry-After response header. | | `details` | Wait the seconds given in details.retry_after_seconds. | ## 400 3 | Code | Retry | When it occurs | | --- | --- | --- | | `ramp_profile_not_allowed` | no | The requested funding profile is not allowed for this tenant. | | `sandbox_ramp_chain_not_supported` | no | Sandbox dUSDC funding is not available on the requested chain. | | `sandbox_ramp_token_not_supported` | no | The sandbox conversion partner only funds XKOVA dUSDC and never substitutes issuer USDC. | ## 401 12 | Code | Retry | When it occurs | | --- | --- | --- | | `member_auth_refresh_replay_detected` | no | An already rotated refresh credential was replayed, so its complete session family was revoked. | | `member_auth_session_revoked` | no | The exact member session is revoked or its authoritative binding is no longer valid. | | `member_auth_token_invalid` | no | The identity proof, exchange token, refresh credential, or ceremony token is invalid or mismatched. | | `request_content_digest_mismatch` | no | The signed content digest does not match the exact request body bytes. | | `request_signature_coverage_invalid` | no | The signature does not cover every required request component. | | `request_signature_expired` | yes (immediate) | The signature is expired, too far in the future, or requests an excessive validity window. | | `request_signature_invalid` | no | The request signature is malformed, unsupported, or failed verification. | | `request_signature_replayed` | no | The request signature nonce has already been accepted for this signing credential. | | `request_signature_required` | no | This production mutation requires an HTTP Message Signature. | | `request_signing_credential_inactive` | no | The signing credential is not active for the authenticated customer principal. | | `unauthorized` | yes (immediate) | The request does not contain a valid authentication credential. | | `unauthorized_account_holder` | no | The authenticated member context does not resolve to an authorized account holder. | ## 402 1 | Code | Retry | When it occurs | | --- | --- | --- | | `relayer_exposure_limit_reached` | no | The tenant has reached the limit for XKOVA-sponsored production writes. | ## 403 20 | Code | Retry | When it occurs | | --- | --- | --- | | `authorization_denied` | no | The workspace authorization policy denied this value movement. | | `compliance_blocked` | no | Compliance policy blocked the requested value movement. | | `contacts_disabled` | no | The tenant has disabled the contacts capability. | | `feature_not_enabled` | no | The tenant does not have the requested capability enabled. | | `first_use_required` | yes (immediate) | The member must accept the current required policies before this operation can proceed. | | `human_authorization_invalid` | no | The exact human action authorization is expired, consumed, or does not match the actor, session, scope, origin, or request. | | `human_authorization_required` | no | A fresh exact human authorization is required before this request can be retried. | | `member_auth_account_holder_inactive` | no | The verified identity resolves to a member account that is not active. | | `member_auth_origin_not_allowlisted` | no | The request origin is not registered for this member-auth application. | | `member_security_setup_required` | no | Complete initial passkey and saved recovery-code setup in Account Center before starting this Hosted member action. Login MFA alone does not complete setup. | | `not_a_treasury_signer` | no | The acting institution user is not an active signer for the selected treasury wallet. | | `production_access_denied` | no | The tenant or workspace is not approved and ready for this production operation. | | `role_denied` | no | The authenticated principal does not have the role required for this operation. | | `rwa_document_not_entitled` | no | The requested grantee is not entitled to access this gated document. | | `step_up_required` | yes (immediate) | This operation requires a customer credential created with the required higher assurance. | | `tenant_account_disabled` | no | Customer authentication is disabled for this tenant. | | `tenant_read_only` | no | The tenant is currently read-only and cannot accept this mutation. | | `tokenization_not_authorized` | no | The customer credential is not authorized for this tokenization mutation. | | `treasury_role_denied` | no | The acting institution user lacks the required treasury role. | | `verification_required` | no | The member must complete the required identity verification before this operation can proceed. | ## 404 5 | Code | Retry | When it occurs | | --- | --- | --- | | `email_domain_not_found` | no | The requested sending domain does not exist or is not visible to this tenant. | | `not_found` | no | The requested resource does not exist or is not visible to this caller. | | `tokenization_access_registry_not_set` | no | The token does not have an access registry. | | `tokenization_authority_not_found` | no | The requested token authority does not exist in this workspace. | | `treasury_wallet_not_found` | no | The requested treasury wallet does not exist or is not visible to this tenant. | ## 409 23 | Code | Retry | When it occurs | | --- | --- | --- | | `asset_schema_fork_conflict` | no | The requested asset schema key is already in use in this workspace. | | `attester_already_registered` | no | An active attester is already registered for the same scope and address. | | `attester_revoked` | no | The requested attester registration has been revoked. | | `authorization_requires_approval` | no | The workspace policy requires an approval before this value movement can proceed. | | `compliance_case_open` | yes (immediate) | An active compliance review currently prevents this transaction from proceeding. | | `conflict_state` | no | The requested transition is not valid from the resource's current state. | | `earned_beneficiary_unset` | no | The workspace fee beneficiary is not ready for this value movement. | | `email_domain_conflict` | no | The sending domain is already registered for this tenant. | | `idempotency_conflict` | no | The idempotency key was already used with a different request body. | | `insufficient_funds` | no | The source account does not have enough funds for the requested debit. | | `invalid_state` | no | The resource exists but is not in a state that permits this action. | | `permit_nonce_in_flight` | yes (header) | Another payment from this wallet is still using the same token approval sequence. | | `rebind_invalid_state` | no | The schema-rebind ceremony is not in the state required for this step. | | `signer_mpc_not_provisioned` | yes (details) | One or more selected signers have not completed secure wallet provisioning. | | `token_already_bound` | no | The token is already bound to a different published asset schema. | | `token_transfer_frozen` | no | The token is under an active transfer freeze. | | `tokenization_access_registry_exists` | no | The token already has an access registry. | | `tokenization_authority_already_verified` | no | This token authority has already completed proof of control. | | `tokenization_invalid_state_transition` | no | The requested token lifecycle transition is not valid from its current state. | | `tokenization_operation_already_relayed` | no | The tokenization operation has already been submitted for execution. | | `tokenization_operation_pending` | no | A conflicting tokenization operation is already pending. | | `treasury_safe_nonce_exhausted` | no | XKOVA could not safely reserve the next control-wallet transaction sequence. | | `workspace_setup_incomplete` | no | Complete workspace setup before calling this chain-bound operation. | ## 410 1 | Code | Retry | When it occurs | | --- | --- | --- | | `payment_expired` | no | The requested time-bounded operation expired. Check its current state before starting a new request. | ## 413 1 | Code | Retry | When it occurs | | --- | --- | --- | | `payload_too_large` | no | The request body exceeds the supported size limit. | ## 422 36 | Code | Retry | When it occurs | | --- | --- | --- | | `account_frozen` | no | The linked account cannot currently accept this operation because it is frozen, locked, or closed. | | `attester_scope_mismatch` | no | The attester is not registered for the requested token scope. | | `attester_signature_mismatch` | no | The submitted attestation signature does not match the registered attester. | | `chain_not_allowed` | no | The requested chain is not available for this workspace or conflicts with a referenced resource. | | `email_domain_invalid` | no | The submitted sending domain is not valid. | | `flow_not_certified` | no | The exact asset and rail flow is not certified for this installation. | | `funding_rejected` | no | The conversion request was declined under the applicable account or transaction policy. | | `member_auth_redirect_url_not_allowlisted` | no | The callback or redirect URL is not registered for this member-auth application. | | `off_ramp_burn_unverified` | no | The supplied burn transaction could not be verified for the withdrawal. | | `rebind_attestation_invalid` | no | The stored schema-rebind attestation no longer verifies against the proposal. | | `rebind_not_forward_only` | no | A schema rebind must move to a later published version of the same asset schema. | | `role_not_allowed_for_service_account` | no | The requested token role cannot be assigned to a service-account authority. | | `rwa_document_hash_mismatch` | no | The uploaded document does not match the declared content hash. | | `rwa_relationship_cycle` | no | The requested asset relationship would create a cycle. | | `rwa_relationship_external_ref_invalid` | no | The external asset relationship reference does not satisfy the schema policy. | | `rwa_relationship_target_not_found` | no | The requested asset relationship target could not be resolved. | | `rwa_schema_unsafe` | no | The asset schema did not pass the publication safety checks. | | `tokenization_authority_kind_mismatch` | no | This proof operation is not valid for the selected authority kind. | | `tokenization_authority_not_verified` | no | The selected token authority has not completed proof of control. | | `tokenization_operation_not_signable` | no | The submitted signature is not valid for the current tokenization operation. | | `tokenization_policy_exceeded` | no | The requested token mint exceeds the configured policy limit. | | `tokenization_policy_not_set` | no | No active mint policy exists for this token and authority pair. | | `tokenization_proof_invalid` | no | The submitted token-authority proof of control is invalid or expired. | | `tokenization_raw_call_invalid` | no | The requested advanced token-control call is outside the allowed contract and function boundary. | | `tokenization_recover_target_active` | no | Token recovery cannot target an account that still has active access. | | `tokenization_relay_expired` | no | The gasless token transfer request has expired. | | `tokenization_relay_invalid_signature` | no | The gasless token transfer signature does not match the request sender. | | `tokenization_relay_wrong_target` | no | The gasless token transfer request targets a different token contract. | | `tokenization_unknown_role` | no | The requested token role is not supported. | | `tokenization_unsupported_decimals` | no | The requested token decimal configuration is not supported. | | `transfer_eligibility_denied` | no | The token transfer did not pass the configured holder eligibility checks. | | `treasury_chain_unsupported` | no | Secure institution wallet control is not supported on the requested chain. | | `treasury_signer_count_below_minimum` | no | The requested signer set is too small for the selected approval threshold. | | `treasury_threshold_invalid` | no | The requested approval threshold is invalid for the signer set. | | `unknown_event` | no | The requested webhook event type is not supported. | | `validation_failed` | no | The request body, path, or query parameters failed validation. | ## 429 2 | Code | Retry | When it occurs | | --- | --- | --- | | `rate_limited` | yes (header) | The customer request rate limit was exceeded. | | `sandbox_ramp_daily_cap_exceeded` | no | The destination wallet has reached its daily sandbox funding limit. | ## 500 1 | Code | Retry | When it occurs | | --- | --- | --- | | `internal_error` | no | XKOVA encountered an unexpected failure. Use the correlation ID when contacting support. | ## 502 1 | Code | Retry | When it occurs | | --- | --- | --- | | `integration_gateway_error` | yes (immediate) | The institution's Integration Gateway could not complete the requested operation. | ## 503 5 | Code | Retry | When it occurs | | --- | --- | --- | | `core_banking_outage` | yes (header) | The institution's authoritative account service is temporarily unavailable. | | `relayer_exposure_evidence_unavailable` | yes (immediate) | XKOVA could not establish the current evidence needed to authorize a sponsored production write. | | `service_unavailable` | yes (header) | XKOVA is temporarily unavailable. Retry according to the response metadata. | | `tenant_suspended` | no | The tenant is currently suspended. | | `wallet_monitoring_not_ready` | yes (header) | The member wallet exists, but its external transaction monitoring is still catching up. Retry after the response interval. | ## 504 1 | Code | Retry | When it occurs | | --- | --- | --- | | `core_banking_timeout` | yes (header) | The institution's authoritative account service did not respond in time. | --- source: https://docs.xkova.com/escrow.html # Escrow and Claims Escrow lets a sender commit value before the recipient is ready to receive it. The sender signs an escrow that holds the funds, the recipient gets a claim link, and the recipient proves they are the intended party with a one-time code before choosing how to receive. Use escrow when the recipient is not yet onboarded, not yet known by wallet address, or when you want the recipient to choose the destination. ## When to use escrow instead of a direct send A direct send needs a recipient wallet to push value to. Escrow does not. The sender commits the funds and the recipient resolves where they go later, from a link. Reach for escrow when: - The recipient has no wallet yet, or has not onboarded. - You know the recipient by email or phone, not by address. - You want the recipient to choose the destination at claim time. If the recipient is a known, onboarded member with a wallet, a [direct send](payments.html) is simpler. ## The flow end to end An escrow is created and authorized by the sender through the same signed-intent wallet ceremony as a payment. That produces a claim link delivered to the recipient. The recipient verifies a one-time code, then resolves the claim by choosing a path. Resolution drives settlement, which releases the held value. ``` flowchart TD A[Sender creates escrow] --> B[Sender signs intent] B --> C[Funds held in escrow] C --> D[Claim link sent to recipient] D --> E[Recipient verifies one-time code] E --> F[Recipient resolves a path] F --> G[Settlement releases funds] ``` ## Creating an escrow The sender's application creates and freezes the escrow intent. The configured sender wallet produces one signature over that intent. The signing session returns the exact typed data and a fresh 60-second Thirdweb custom JWT when the sender uses XKOVA's MPC wallet. Once submitted, the API verifies and relays the signed intent and the escrow holds the funds. The custody and signing-session mechanics are covered in the [Non-custodial Model](non-custodial.html). ## The recipient claim The recipient lands on the claim flow from the claim link. The link carries a token that authenticates the recipient side without an API key, so this is a browser surface the recipient drives, not a backend call. The claim runs in these steps. 1. **Fetch context.** The recipient opens the link and sees the claim context, what is being claimed and from whom. 1. **Send code.** A one-time code is issued to the recipient's chosen channel, email or phone. 1. **Verify code.** The recipient enters the code. Verifying proves the recipient controls that channel before any value moves. 1. **Resolve.** The recipient chooses a path. They can resolve to an external wallet, or wait, which leaves the escrow open until it expires. > A claim token can be refreshed into a fresh token for the same underlying escrow, which is how an expired or stale link is reissued without creating a new escrow. > Every resolution path requires a verified one-time code first, including choosing to wait. If the code session has expired by the time the recipient resolves, they are asked to request a new code and verify again before the claim can complete. ## Release and expiry Resolving to a destination drives the corresponding settlement flow, which releases the held value to the recipient. Choosing to wait leaves the escrow open until its expiry. The release itself settles on chain like any other transfer, so the same confirmation rules apply, see [Transfers and Settlement](transfers.html). > Only the sender can cancel an escrow, and only before it expires. Once the expiry passes, the held value returns to the sender, not the recipient, and recovering it no longer needs the sender's signature. Treat expiry as the sender getting their funds back, not as the recipient's claim eventually completing on its own. ## Reading state List your tenant's escrows newest first with cursor pagination, fetch a single escrow for its current state, or read its event timeline to see each transition. Direct sends and escrows also appear together in a unified transactions view when you want both in one feed. ### Errors worth handling per step The [error catalog](errors.html) documents every code; these are the ones each step most commonly returns. | Step | Codes | | --- | --- | | Create | `validation_failed`, `flow_not_certified`, `chain_not_allowed`, `compliance_blocked`, `authorization_denied` | | Sign and submit | `unauthorized` (an expired or invalid signing token), `invalid_state`, `authorization_denied`, `compliance_case_open` | | Claim journey | `not_found` (an unknown or superseded claim token), `invalid_state` (already resolved or expired), `unauthorized` (the one-time code is not verified yet), `validation_failed` | | Cancel | `invalid_state` (already claimed, cancelled, or past expiry), `not_found`, `validation_failed` | ## Related - [Payments](payments.html): direct sends when the recipient is already onboarded. - [Transfers and Settlement](transfers.html): how the release confirms on chain. - [Webhooks](webhooks.html): events that tell you when a claim is resolved and settled. > For the exact request and response shapes of the escrow and claim operations, see [the operations list](api-reference.html) or try them live in [the interactive reference](reference.html). --- source: https://docs.xkova.com/faq.html # FAQ and Troubleshooting The failures integrators hit most often, and what to do about each. Always start from the response `code`, not the HTTP status alone, and keep the `correlation_id` from the response. That id is the fastest path to a root cause. ## Always start from the code Every error response carries a stable `code` and a `correlation_id`. Branch your handling on the code, and log the correlation id. See [Errors and Correlation IDs](errors.html) for the full envelope and the catalog of live codes. ## Authentication and scope | Symptom | What it means and how to fix it | | --- | --- | | `401 unauthorized` | No valid credential. Check the `Authorization: Bearer xkv_...` header is present and that the key has not been revoked. See [Authentication](auth.html). | | `401 active_scope_unavailable` | An XKOVA management Console session pointed at a scope it cannot act in, usually a stale cached pointer. Clear the active scope header and re-pick a scope you are authorized for. | | `403` on a mutation you expected to work | Your role lacks the required permission. Check the permission the operation needs against [Roles and Permissions](roles-permissions.html). | ## Member Authentication The confidential member-session exchange returns member-auth-specific errors. See [Member Browser and BFF Integration](embedded-member.html) for the flow and [Errors and Correlation IDs](errors.html) for the full catalog. | Symptom | What it means and how to fix it | | --- | --- | | `401 member_auth_token_invalid` | The provider proof, XKOVA refresh credential, or session reference is invalid or was already consumed. Restart provider authorization for a proof failure. For refresh replay, discard the local session family and require fresh authentication. | | `401 member_auth_session_revoked` | The XKOVA session is revoked, expired, or no longer matches current application or member policy. Delete the first-party application session and begin a new authentication flow. | | `401 member_auth_refresh_replay_detected` | A single-use refresh credential was reused or two refreshes raced. XKOVA revoked the family. The BFF store must serialize refresh for one application-session reference across all instances. | ## Payments and signing Use the error code to distinguish a signature recovery mismatch from other `422` validation failures. The signed bytes did not recover to the wallet the server expected. The most common causes are: - The wallet identity used to derive the address did not match the one the signing session expects. - A numeric (`uint256`) field was not restored to a big integer before signing, so it signed as a different value. - The wrong field name was signed. The transfer intent uses `recipient`, not `to`, and the field set differs across intent types. After correcting the integration, obtain fresh authorization for the same pending resource and sign exactly its returned `typed_data`, restoring big integers for every numeric field. See [Non-custodial Model](non-custodial.html) for how signing and submission fit together. | Symptom | What it means and how to fix it | | --- | --- | | `401` fetching a signing session | Signing authority expired. Read `client.payments.recovery(paymentId)` through the authenticated BFF. If `can_continue` is true, begin a fresh member action for the same payment ID and complete its registered authorization ceremony. Do not create another payment to resume this one. If cancellation is available, require an explicit choice before cancelling. | | Payment stuck in `pending_sign` | The member has not signed yet. Recover the existing payment from activity and obtain fresh authorization through the BFF. A signing URL is a locator, not bearer or member-session authority. | | Payment `failed` with `failure_reason_code: on_chain_revert` | The transaction landed on chain but the contract reverted. There is no `reverted` status; the payment moves to `status: failed` with `failure_reason_code: on_chain_revert`, so branch on `failed` plus the failure reason code. This is terminal. Note that inclusion in a block is not success; confirmation checks the on chain status, not just that the transaction was mined. See [Transfers and Settlement](transfers.html). | ## Idempotency and retries - **`409` idempotency conflict.** You reused an `Idempotency-Key` with a different body. Use a fresh key for a new logical write, and reuse the same key only to retry the identical request. See [Idempotency](idempotency.html). - **When to retry.** Branch on the error's `code`, not the bare HTTP status: the [error catalog](errors.html) documents each code's retry strategy. A transient upstream `502` or `503` is generally safe to retry with backoff (a rate-limited response carries a `Retry-After` header); a `403` or `422` is not, retrying will not change the outcome. See [Rate Limits](rate-limits.html) for backoff guidance. ## Treasury - **`409` executing a transfer.** The signer quorum is not yet met. Collect more signer attestations until the N of M threshold is reached, then execute. See [Treasury](treasury.html). - **Self-approval rejected.** The request creator cannot approve their own transfer when the approval clause can be satisfied without them. A different signer must attest. See [Policies and Quorum Governance](policies-quorum.html). ## Webhooks - **Signature verification fails.** Verify against the webhook signature headers over the raw request body. Do not re-serialize the JSON before verifying, or the bytes will not match. See [Webhooks](webhooks.html). - **Duplicate deliveries.** Retries can re-deliver an event. Handle events idempotently using `XKOVA-Webhook-Event-Id` (the envelope `id`) so a repeated event is a no-op. Use `XKOVA-Webhook-Delivery-Id` for delivery-level tracing. ## Still stuck Capture the `correlation_id` from the response and the timestamp of your request. Those two values are the fastest path to a root cause. See [Support](support.html) for where to take them. --- source: https://docs.xkova.com/fee-schedules.html # Fees and Pricing A fee schedule is the pricing your workspace applies to payments. It is a versioned set of rules, each one addressed to a beneficiary and tagged with the transaction types it prices. A schedule is authored as a draft, reviewed, then committed to become the active pricing. Committing supersedes the prior schedule atomically, so the workspace always has exactly one active schedule and a full version history behind it. This page covers the lifecycle, the rule shape, the transaction type registry, the revenue treasury a schedule needs before it can commit, and the read-only accessor for the XKOVA platform fee. ## The Draft to Commit Lifecycle A fee schedule moves through three states. You create it as a draft, commit the draft to make it active, and a schedule is superseded automatically the moment a newer draft for the same workspace commits. | State | Meaning | | --- | --- | | `draft` | Authored but not yet active. Editable only by replacement: to change a draft, create a new one. A draft can sit un-committed indefinitely with no effect on pricing. | | `committed` | The active pricing for the workspace. Exactly one committed schedule exists per workspace at a time. A committed schedule is immutable. | | `superseded` | A schedule that was committed and then replaced when a newer draft committed. Retained for history, never active again. | Because a committed schedule is immutable, there is no rule-editing endpoint. To change pricing you author a new draft carrying the full rule set you want, then commit it. Committing looks up the workspace's current committed schedule, marks it superseded, and flips the new draft to committed in one transaction. Version numbers are assigned by the server, starting at 1 and incrementing per workspace, so the version on a schedule tells you where it sits in that workspace's history. ``` flowchart LR A[Create draft] --> B[draft] B --> C[Commit] C --> D[committed, active pricing] D -- newer draft commits --> E[superseded] ``` ### The Endpoints | Call | What it does | | --- | --- | | `GET /v1/fee-schedules` | List the workspace's schedules, cursor-paginated and filterable by `status` (`draft`, `committed`, or `superseded`). Each schedule includes its full `rules` array. | | `GET /v1/fee-schedules/{id}` | Fetch one schedule with its rules and commit metadata. | | `POST /v1/fee-schedules` | Create a new draft from a `rules` array. Returns the drafted schedule. | | `POST /v1/fee-schedules/{id}/commit` | Commit a draft. Returns the committed schedule. A `justification` is captured into the audit log. | | `GET /v1/fee-schedules/platform-fee` | Read the XKOVA platform fee rule that applies to the workspace. Read-only. | Fee schedules are gated behind the revenue controls entitlement; a tenant without it gets `feature_not_enabled` on every call here. See [Entitlements](entitlements.html) for how entitlements are enabled. The list is a standard cursor page: it returns `data`, `next_cursor`, and `has_more`. See [Pagination](pagination.html). ## Rules and Beneficiaries A schedule is a list of rules. Each rule directs one fee share to one beneficiary, priced over one or more transaction types. A rule carries these fields. | Field | Meaning | | --- | --- | | `beneficiary` | Who the fee share is for. One of the typed roles (`institution`, `custody_provider`, `screening_provider`, `issuer`, `integrator`) or `external`, a tenant-named slot. | | `applies_to_tx_types` | Which transaction types this rule prices. At least one required. See the registry below. | | `basis` | `flat`, `bps`, or `percent`. | | `value` | Decimal string interpreted per the basis, for example `"30"` for 30 basis points or `"0.25"` for 0.25 percent. | | `beneficiary_address` | The on-chain destination for this share. Required for paid rules, and must be omitted for earned rules (see below). | | `label` | A display name. Required and non-empty when `beneficiary` is `external`; optional for the typed roles. | | `min_amount`, `max_amount` | Optional decimal bounds on the computed fee. | ### Earned Rules Versus Paid Rules Beneficiaries split into two classes, and the split decides where the `beneficiary_address` comes from. Earned fees are revenue the tenant keeps; they route to the workspace's revenue treasury, whose address the server stamps at commit time. Paid fees pass through to a provider or integrator destination the tenant controls, so the tenant supplies the address on the rule. | Class | Beneficiaries | Address | | --- | --- | --- | | Earned | `institution`, `external`, `issuer` | Omit it. The server stamps the workspace revenue treasury Safe address at commit. | | Paid | `custody_provider`, `screening_provider`, `integrator` | Required. The tenant-controlled destination for the share. | Sending an address on an earned rule, or omitting one on a paid rule, is rejected at create with `conflict_state`. An `external` rule needs a label and, being earned, omits the address. You cannot add a rule for the `xkova` beneficiary: that is the platform fee, covered below, and it is not tenant-settable. ## The Transaction Type Registry Every rule tags the transaction types it prices in `applies_to_tx_types`. The allowed values are a fixed registry, published as the `FeeScheduleTxType` enum in the API spec and the SDK. Each type is either live or preparing. The API accepts rules tagged with a preparing type so pricing can be authored ahead of the flow going live, but no fee is charged for a preparing type until its payment flow ships. | Value | Prices | Status | | --- | --- | --- | | `p2p_send` | A member-to-member transfer inside the same tenant. | Live | | `external_send` | A member sending to an external wallet. | Live | | `escrow_claim` | An escrow as a whole. One fee per escrow lifecycle, realized at release, not separate fees on create and release. | Live | | `ecommerce_payment` | A member paying a merchant through a checkout session. | Preparing | A rule with an empty `applies_to_tx_types`, or one naming a value outside the registry, is rejected at create with `conflict_state`. See [Payments](payments.html) and [Escrow and Claims](escrow.html) for the flows these types price. ## The Revenue Treasury Prerequisite A schedule that carries any earned rule cannot be committed unless the workspace has an active revenue treasury. The reason is direct: an earned fee's destination is the revenue treasury Safe, and there is nothing to route the fee to without one. When you commit a schedule that carries earned rules, the server resolves the workspace's active revenue treasury Safe and stamps its address onto every earned rule in the same transaction that flips the schedule to committed. The draft holds no address for those rules until then. If the workspace has no active revenue treasury, commit is rejected and the schedule stays a draft. The fix is to establish a treasury Safe and designate it as the workspace's revenue treasury first, then commit. Paid rules do not depend on this: their addresses are tenant-supplied and present from create. See [Treasury](treasury.html) for establishing a Safe and designating the revenue treasury. ## The Platform Fee The XKOVA platform fee is applied to a workspace's payments alongside the tenant's own rules. It is not tenant-adjustable: only XKOVA sets it, and it is not writable through this API. Attempting to include a rule for the `xkova` beneficiary in a create is rejected. Read it with `GET /v1/fee-schedules/platform-fee`. The response is an envelope, `{ "rule": }`, or `{ "rule": null }` when the workspace has no platform fee configured yet. Surfacing it read-only lets an operator see the full fee stack that applies to every transaction, tenant rules and platform fee together. The platform fee is preserved across commits automatically. When you commit a new schedule, the platform fee rule from the prior committed schedule is carried onto the new one and widened to cover every live transaction type, so committing your own pricing never drops the platform fee. ## Authorization Fee schedules are tenant-scoped; the platform scope above tenants does not manage them. Reading schedules and the platform fee is available to any tenant-scope caller with the revenue controls entitlement. Authoring is more restricted: creating a draft requires the `fee_schedule.create` permission and committing requires `fee_schedule.commit`, both held by the admin and owner roles. A commit is recorded in the audit log as a configuration change, including the `justification` supplied on the call. See [Authorization and RBAC](authz.html) for roles and permissions, and [the audit log](audit-log.html) for what a commit records. ## Errors The [error catalog](errors.html) documents every code; these are the ones each step most commonly returns. | Step | Codes | | --- | --- | | Create | `validation_failed`, `conflict_state` (a reserved `xkova` rule, an `external` rule with no label, an earned rule carrying an address, a paid rule missing one, an empty or unknown `applies_to_tx_types`), `role_denied`, `feature_not_enabled`, `idempotency_conflict` | | Commit | `not_found`, `invalid_state` (the schedule is not a draft), `conflict_state` (no rules defined or no active revenue treasury), `role_denied`, `feature_not_enabled` | | Read | `feature_not_enabled`, `not_found` | Create accepts an `Idempotency-Key` header so a retried create does not draft a duplicate. See [Idempotency](idempotency.html). ## Related - [Treasury](treasury.html): establishing the revenue treasury an earned schedule needs. - [Payments](payments.html): the flows a fee schedule prices. - [Reporting](reporting.html): reading back the earned and platform-fee sides of settled fees. - [Entitlements](entitlements.html): enabling the revenue controls entitlement. > For the exact request and response shapes, see [the operations list](api-reference.html) or try the calls live in [the interactive reference](reference.html). --- source: https://docs.xkova.com/getting-started.html # Quickstart Start with an authenticated sandbox call, then prepare the payer and complete a signed payment. The API Reference defines exact request and response shapes. > All examples target the sandbox base URL `https://sandbox-api.xkova.com/v1`. The production base URL is `https://api.xkova.com/v1`. Sandbox API keys are prefixed `xkv_test_`. ## 1. Get a sandbox API key Sign up in the management Console to create your sandbox workspace, then create an API key with the permission families your server needs. Production access requires separate approval. Sandbox keys carry the `xkv_test_` prefix and authorize tenant scoped calls. ## 2. Make your first authenticated call Every authenticated request sends the key as a bearer token. Confirm your key and scopes: ``` curl -s https://sandbox-api.xkova.com/v1/me/scopes \ -H "Authorization: Bearer $XKOVA_API_KEY" ``` Read the authenticated automatic chain and asset catalogs. They contain the active testnet networks and assets available to this sandbox workspace: ``` curl -s https://sandbox-api.xkova.com/v1/chains/available \ -H "Authorization: Bearer $XKOVA_API_KEY" curl -s https://sandbox-api.xkova.com/v1/workspace-tokens \ -H "Authorization: Bearer $XKOVA_API_KEY" ``` ## 3. Follow the path to a first payment A single call does not move money. A confirmed payment depends on a small amount of one time setup, then a signed transfer that the XKOVA Relayer submits and XKOVA confirms on chain. The Console checklist helps prepare the workspace; permission, compliance, asset eligibility, and signing checks still apply. | Step | What it does | | --- | --- | | Create an API key | A sandbox key for server to server calls. | | Review fees and policy | Use workspace pricing and the sandbox screen-and-cap policy. Custom fee schedules require revenue controls; earned rules also require a revenue treasury. | | Provision an account holder | Prepare the session-bound payer, meet the committed identity and consent policy, provision the wallet, and confirm sandbox funding. | | Create the payment | The member signs an EIP-712 intent, the XKOVA Relayer submits it, and XKOVA confirms it on chain. | When you create the payment, choose a rails-eligible asset whose direct-send flow is certified from `GET /v1/workspace-tokens`. This is an operation choice, not workspace configuration. > When API key creation is governance held: the call returns `status: pending_approval` with a `pending_approval_request_id`, the workspace owner attests it in the console under Governance, Approvals (or via the attestation operations), and re-submitting the same call mints the key. Committing the value-movement authorization policy is held the same way; committing a fee schedule is not. See [Policies and Quorum Governance](policies-quorum.html) for the approval baseline. > The signing step is what makes the system non-custodial. The member signs the transfer in their own wallet. XKOVA never holds member keys. See the [Platform Overview](concepts.html) for the model. ## 4. Where to go next - [API Reference](api-reference.html) for the exact shape of every operation. - [Developer Guide](auth.html) pages for authentication, idempotency, pagination, errors, rate limits, and webhooks. - [SDK Reference](sdk-overview.html) for the TypeScript client and the workflow methods. --- source: https://docs.xkova.com/glossary.html # Glossary The words that recur across these docs have precise meanings on XKOVA. This page collects them in one place, grouped by area and cross-linked to the guide that covers each in depth. If a term reads ambiguously elsewhere, this is the definition it means. For how the pieces fit together, start with the [Platform Overview](concepts.html). ## Actors and Scopes These terms describe who acts on XKOVA and the boundaries their access runs inside. The two isolation scopes, tenant and workspace, are covered in [Tenants and Workspaces](tenancy.html). | Term | Definition | | --- | --- | | **Tenant** | An institution using XKOVA, for example a bank, a credit union, or a fintech. The institution remains authoritative for consumer identity. The tenant scope groups its identity-provider configuration, billing, branding, staff authorization, and compliance policy. Each tenant receives sandbox access at signup and a production workspace after access approval. | | **Workspace** | The operational scope inside a tenant where day to day operations live. Payments, escrows, treasury, wallets, fees, and member sessions are scoped to a workspace. Sandbox receives active testnets and production receives active mainnets in every installation. | | **Member** | An end user of a tenant, also called an account holder, who holds balances and signs their own value transfers in their own wallet. See [Account Holders](account-holders.html). | | **XKOVA management/Console user** | A platform-management user of XKOVA's private management Console. This is distinct from an institution staff member using Core Rails through institution SSO and from a member using a consumer experience. | | **Institution staff** | A teller, operations, treasury, compliance, or commercial user authenticated by the institution and launched into Core Rails with scoped staff context. Institution staff do not enter through member authentication or the XKOVA management Console. | | **Integrator** | A backend system that calls the XKOVA API with a tenant API key. The key is scoped to a workspace, so an integrator's calls operate inside that one workspace without pinning it to a network. See [Authentication](auth.html). | ## Money Movement These terms describe how value moves on the rails, XKOVA SPS. Every transfer is signed by its owner and submitted by the relayer, never by the platform holding a key. | Term | Definition | | --- | --- | | **Treasury / vault** | An institution's own operational funds held in a multisig vault, a Gnosis Safe owned by a set of staff signers. Value leaves the vault only when an N of M signer quorum has each attested to the transfer. See [Treasury](treasury.html). | | **Revenue treasury** | A treasury vault designated to receive a workspace's earned fees, the institution, external, and issuer fees the tenant collects into its own pockets. A fee schedule carrying an earned fee rule cannot commit unless the workspace has an active revenue treasury, and re-linking to a different vault is a quorum-gated attestation ceremony. See [Fee Schedules](fee-schedules.html). | | **Escrow** | Held value that a sender commits before the recipient is ready to receive it. Use escrow when the recipient has no wallet yet, is known by email or phone rather than by address, or should choose the destination later. See [Escrow and Claims](escrow.html). | | **Claim** | The recipient side of an escrow. The recipient lands on a claim flow from a claim link that carries a token, verifies a one-time code to prove they control the channel, then resolves where the held value goes. | | **Relayer** | The XKOVA Relayer, the on-chain submitter of accepted transfers. It takes an instruction bearing the required owner-address or Safe-quorum signatures, submits it, and pays gas. It is separate from both human MPC signing-session authority and external signer-key custody. See [Transfers and Settlement](transfers.html). | | **Meta-transaction** | A transfer authorized by an off-chain signature that someone else submits. The owner signs EIP-712 typed data, the XKOVA Relayer wraps it in the contract's meta-transaction entrypoint, and the contract recovers and verifies the signer on chain before it moves value. Key custody and provider-session authority remain properties of the signer wallet. See the [Non-custodial Model](non-custodial.html). | ## Digital Assets These terms describe the tokenized asset side of the platform, XKOVA TAMS. | Term | Definition | | --- | --- | | **Tokenization** | Issuing and managing programmable tokens on the rails: deploying ERC-20, ERC-721, and ERC-1155 token contracts, minting and burning, and setting on-token policies. See [Tokenization Overview](tokenization-overview.html). | | **RWA** | A real-world asset, an off-chain asset represented by a token, together with the metadata plane that describes it: published schemas, committed fields, provenance, and its cap table. See [Real-World Assets](rwa.html). | ## Controls and Governance These terms describe the controls that stand in front of a high impact action. Authorization decides who may attempt an action; governance decides whether it proceeds. See [Policies and Quorum Governance](policies-quorum.html). | Term | Definition | | --- | --- | | **Policy firewall** | The screen every transfer passes through before the XKOVA Relayer submits it. It applies the tenant's compliance and risk policy, including transaction screening and sanctions checks, so a transfer that fails a check is denied rather than relayed. | | **Break-glass** | The only sanctioned bypass of the normal approval quorum, for genuine emergencies. It is armed two of two (the owner plus one other principal), time boxed and auto expiring, and every action taken while it is armed is tagged and recorded as a high severity audit event. See [Break-glass Access](break-glass.html). | | **Fee schedule** | The set of fee rules a workspace applies to its payments. Fees are recomputed at write time rather than trusted from an earlier quote, and a schedule carrying an earned fee rule requires an active revenue treasury before it can commit. See [Fee Schedules](fee-schedules.html). | ## API Mechanics These terms describe how a request behaves at the API surface, independent of what it does. | Term | Definition | | --- | --- | | **Gateway** | The integration gateway, a deployable service that sits between XKOVA and an institution's core banking system. It speaks one stable contract to XKOVA and a core specific adapter to the bank, and it backs the hold, capture, and credit steps that funding and off-ramp depend on. See [Core Banking](core-banking.html). | | **Idempotency key** | The `Idempotency-Key` header required on every mutating request. It lets you retry a write safely: the first request does the work, and every retry with the same key returns the original result. See [Idempotency](idempotency.html). | | **Cursor** | An opaque token that marks a position in a list. List operations return one page at a time and hand you a cursor for the next page, so pagination stays correct while the underlying data changes. See [Pagination](pagination.html). | | **Correlation id** | A per-request identifier echoed on the `X-Correlation-Id` response header of every response, including pre-auth errors, and carried in the error envelope. Include it when you report an issue so a request can be traced end to end. See [Support](support.html). | ## Related - [Platform Overview](concepts.html): how the two product pillars, the orchestration layer, and the actors fit together. - [Tenants and Workspaces](tenancy.html): the two isolation scopes in depth. - [Non-custodial Model](non-custodial.html): the sign, relay, confirm mechanics behind every transfer. - [Payments](payments.html): the money movement flow these terms describe end to end. --- source: https://docs.xkova.com/holder-eligibility.html # Holder Eligibility and Transfers A regulated asset cannot move to just anyone. XKOVA keeps an eligibility store of accreditation and jurisdiction per holder and lockups per holding, then runs a graded transfer door that evaluates every move against that store, the cap table, and any lifecycle freeze, before the relayer ever spends gas. ## The eligibility store Two kinds of facts govern who may hold and move a token: attributes about the holder, and lockups on a specific holding. ### Holder attributes A holder's global attributes are accreditation status and jurisdiction, keyed by (workspace, holder). There is one row per holder, and a repeat write overwrites it. Accreditation status is one of `none`, `self_attested`, `verified`, or `expired`, with an optional expiry timestamp. The source records where the attribute came from: a KYC provider, an operator attestation, or an integrator-supplied value. A holder is identified either by an account-holder id or a raw wallet address (case-insensitive; an address is lowercased server-side). > Accreditation is read back as an EFFECTIVE value. A holder who was verified but is now past their expiry reads back `expired`, not `verified`, so an out-of-date accreditation never silently passes a transfer check. ### Per-holding lockups A lockup pins a release instant on a specific (token, holder) pair: the holder cannot move that token until the lockup expires. There is one lockup row per (token, holder); a repeat write overwrites it. A lockup carries an operator-supplied reason and is recorded as a compliance action. ### Reading the context Reading the (token, holder) eligibility context returns exactly what the transfer door consumes: the holder's effective accreditation, their jurisdiction, and the lockup release instant for that holding. When no attribute or lockup row exists, accreditation reads `none` and the other fields are null. Setting holder attributes and lockups requires the token admin role. ## The graded transfer door Every governed transfer of a real-world-asset token passes through one decision point before any gas is spent. This is the graded transfer-eligibility door. It evaluates a set of predicates over the eligibility store, the cap table, and any lifecycle freeze, and returns a binary allow or deny verdict. Two transfer paths both run it: - **Gasless holder transfer**: the holder signs an ERC-2771 forward request to move their own NFT without paying gas. The api builds the request, the holder signs it, and on submission the api recovers the signer, runs the door, then runs a separate fail-closed sanctions screen of the recipient, and only then has the XKOVA Relayer submit the move and pay gas. - **Treasury Safe transfer**: an issuer moves an asset the treasury Safe owns, via the N of M signer flow. The door runs at draft time, before signers approve. ### What the door checks | Predicate | Reads | | --- | --- | | Accreditation / jurisdiction | The effective holder attributes for the recipient. | | Lockup | The per-holding release instant; a move before it is denied. | | Holder cap | The cap table, to enforce a maximum distinct-holder count where a template defines one. | | Lifecycle freeze | Whether the token or instance is frozen by a running corporate action. | If a predicate rejects the move, the transfer is denied with a clear reason (an eligibility denial for a holder-cap, lockup, or jurisdiction rule, or a transfer-frozen denial for a lifecycle freeze) and no gas is spent. The door runs before the relayer, so an ineligible move costs nothing on-chain. Sanctions screening of the recipient address is a separate, fail-closed gate that runs after the door and before the relayer submits. It denies with its own screening reason, distinct from an eligibility denial, so a blocked move tells you which control stopped it. ``` flowchart TD A[Signed transfer request] --> B{Transfer eligibility door} B -- holder cap, lockup, jurisdiction, accreditation --> X[Denied with an eligibility reason, no gas spent] B -- lifecycle freeze active --> X2[Denied as transfer frozen] B -- allow --> C{Sanctions screen of the recipient} C -- match, or screen cannot complete --> X3[Denied, fail closed] C -- clear --> D[XKOVA Relayer submits the move and pays gas] ``` > The door fails closed. A missing or unknown accreditation status is treated as not eligible, never as a pass. The same posture governs the sanctions screen: a screen that cannot complete denies rather than allows. ## Where the rules come from Some eligibility rules are not set per holder at all but baked into the asset schema. A share-class template can declare a transfer-eligibility rule (for example, that a transfer requires the recipient be accredited), which the schema compiler turns into a predicate the door evaluates. That keeps the policy with the asset definition rather than scattered across individual writes. See [Real-World Assets](rwa.html) for the schema model. ## Related - [Cap Tables and Distributions](cap-tables.html): the positions the holder-cap predicate reads. - [Corporate Actions](corporate-actions.html): the lifecycle freezes the door honors. - [Treasury](treasury.html): the N of M Safe flow the treasury transfer path uses. - [Screening (KYT and BSA)](screening.html): the sanctions providers behind the egress check. - [Non-custodial Model](non-custodial.html): why the holder signs and the relayer submits. > For the eligibility, lockup, and transfer operations, see the [operations list](api-reference.html) or the [interactive reference](reference.html). --- source: https://docs.xkova.com/idempotency.html # Idempotency Network calls can fail after the server has already acted. An idempotency key lets you retry a write without performing it twice. The first request does the work, and every retry with the same key returns the original result. ## How it works Send an `Idempotency-Key` header on any write request. Use a unique value per logical operation, for example a UUID you generate before the first attempt. The server records the key with the result of the first request. If you send the same key again, the server returns the stored result instead of acting a second time. ``` curl -s https://sandbox-api.xkova.com/v1/payments \ -H "Authorization: Bearer $XKOVA_API_KEY" \ -H "Idempotency-Key: $IDEMPOTENCY_KEY" \ -H "Content-Type: application/json" \ -d '{ "...": "..." }' ``` ## What counts as the same request An idempotency key is tied to the request it first completed. Reusing the same key with a different body is a conflict, rejected with `409` and the `idempotency_conflict` error code. Keep one key for one intended operation, and generate a fresh key for a genuinely new operation. > Keys are remembered for a retention window, then expire. Retries within that window are safe. After it, a reused key is treated as a new operation. ## Telling a replay apart A replayed request returns the same cached response as the original, plus two headers so your logging and monitoring can tell it apart: `Idempotent-Replay: true` marks the response as a replay, and `X-Original-Correlation-Id` carries the correlation id of the original request the cached response came from. ## When to use it Always send an idempotency key on writes that move money or create resources, so a timeout or a dropped connection cannot cause a double send. Reads do not need one, because they do not change state. ## Related See [Errors and Correlation IDs](errors.html) for how to tell a retryable failure from a permanent one, and the [interactive reference](reference.html) for which operations accept an idempotency key. --- source: https://docs.xkova.com/index.html # XKOVA Documentation XKOVA is digital asset management for regulated institutions. Two products, XKOVA SPS (the Stablecoin Payment System) and XKOVA TAMS (the Tokenized Asset Management System), sit on an orchestration layer that plugs into the vendors you already trust. Human MPC private-key material remains non-exportable, on-chain movement requires valid owner signatures, and the platform is API first with management and member apps. This portal is built for both people and AI coding agents. > New here? Start with the [Quickstart](getting-started.html), then read the [Platform Overview](concepts.html) to see how the pieces fit. Browse every endpoint in the [API Reference](api-reference.html) or try calls live in the [interactive reference](reference.html). ## Two products Both products run on the same signed-settlement pattern: the chain requires a valid owner-address signature or Safe quorum, the XKOVA Relayer submits and pays gas, and XKOVA confirms the result. ### XKOVA SPS The Stablecoin Payment System. Move stablecoin value: payments, escrow and claims, treasury, settlement, and off-ramp. ### XKOVA TAMS The Tokenized Asset Management System. Issue and manage digital assets: programmable tokens, real-world assets, cap tables, distributions, and corporate actions. ## On an open orchestration layer XKOVA owns the orchestration: policy, ledger, audit trail, trusted identity-proof verification, scoped session authorization, and settlement state. The financial institution or authenticated digital channel remains authoritative for member identity. Every vendor connection is a swappable adapter, so an institution plugs in the providers it already trusts and is never locked into one stack. Closed competitors bundle wallets, networks, compliance, and ramps from a single provider, so switching means rebuilding. ### Core Banking Symitar, Corelation, Fiserv, and other cores, through the integration gateway. ### Screening BSA and KYT through Chainalysis, TRM, Elliptic, and others. ### Identity KYC and KYB through Persona, Alloy, Onfido, and others. ### Conversion and Ramps Fiat to digital through Coinbase, Paxos, Anchorage, and others. ### Wallet Providers Thirdweb MPC infrastructure, external wallets, custody boundaries, and future provider adapters. ## Browse by area ### Quickstart Make your first authenticated sandbox call and reach a confirmed payment. ### Platform Overview The pillars, the orchestration layer, the non-custodial model, tenants and networks. ### API Reference Every operation, generated from the contract, plus a live try it console. ### Developer Guide Authentication, authorization, idempotency, pagination, errors, rate limits, webhooks. ## Built for people and agents Every page exposes a Copy page for LLM action, and the portal publishes an [llms.txt](llms.txt) index so AI coding agents can discover and read the documentation directly. The reference is generated from the OpenAPI contract, so it never drifts from the running system. ## Explore Plover The Plover Credit Union member app demonstrates banking powered by XKOVA. It offers branded email sign-in through its confidential BFF and scoped XKOVA member session. Members accept the stablecoin product terms before a 5 dUSDC welcome credit funds the stablecoin account. See [Plover Demo](demos.html) to get started. --- source: https://docs.xkova.com/integrations-overview.html # Integrations and Orchestration Beyond the two product pillars, XKOVA is an orchestration layer. It owns the policy, ledger, audit trail, trusted identity-proof verification, scoped session authorization, and settlement state. The financial institution or authenticated digital channel remains authoritative for member identity. XKOVA connects to the rest of an institution's stack through swappable adapters, so you can use the vendors you already trust. ## Why orchestration matters Closed competitors bundle wallets, blockchain networks, compliance, stablecoins, and ramps from a single provider. Switching any one piece means rebuilding. XKOVA takes the opposite stance: each connection is an adapter behind a stable interface, so a vendor can be swapped without touching the product surface above it. ## The connections Each category lists the vendor live today and examples of the interchangeable vendors the adapter seam is designed for. An institution chooses one per category; sandbox provider availability does not authorize a production connection. Production adapters require approved configuration, credentials, and integration validation. Example names do not mean every adapter ships. | Connection | Vendors | Where it surfaces | | --- | --- | --- | | [Core Banking](core-banking.html) | The institution's own core (Symitar, Corelation, Fiserv, and similar) behind its integration gateway | Integration gateway | | [Screening (KYT and BSA)](screening.html) | Chainalysis (live); TRM Labs, Elliptic (integration examples) | Compliance pipeline | | [Identity (KYC and KYB)](verification.html) | Persona, Alloy, Onfido (integration examples; sandbox provider available) | Verification flows | | [Conversion and Ramps](conversion.html) | Coinbase, Paxos, Anchorage (integration examples; sandbox provider available) | Funding and off-ramp | | [Wallet Providers](wallet-providers.html) | Thirdweb (live); Turnkey, Fireblocks, Privy, Crossmint (integration examples) | Configured per tenant | | Blockchain Networks | Active EVM networks from the chain catalog | See [Supported Networks](networks.html) | | Treasury Custody | Safe (Gnosis) (live); Fireblocks, Anchorage (integration examples) | See [Treasury](treasury.html) | ## The adapter model Each category sits behind a provider interface. Screening, verification, conversion, and core-banking providers have sandbox simulations. Wallet signing infrastructure and EVM testnet settlement still use real provider and on-chain mechanics. Production connections require approved configuration. The product code above the interface does not change when the vendor does. This is what keeps the platform open. > The sandbox implementations are simulated providers, not production vendors. Screening, identity verification, conversion, and core-banking calls in sandbox return deterministic simulated results so you can build and test end to end without vendor credentials. They are not connected to a real compliance, KYC, or banking system and must not be relied on for production decisions. When you move to production, XKOVA binds each interface to your chosen vendors as part of production onboarding; you bring the vendor relationship and credentials, and the wiring is a separate production step. > The wallet provider surface is not a self-serve API yet. Wallet vendor selection, custodial or non-custodial, is configured per tenant on a case by case basis. See [Wallet Providers](wallet-providers.html) for what is supported today. ## Managing an Integration Gateway The Integration Gateway that connects XKOVA to an institution's core is deployed and wired to the core during onboarding, as described in [Core Banking](core-banking.html). Once a gateway is deployed, an admin in your tenant manages its registration, security posture, health, and call history through the management API under `/v1/integration-gateways`. These operations manage the deployed gateway's record with the platform; they do not point XKOVA at a new core on their own, and they are workspace scoped, so a tenant only ever sees and manages its own gateways. | Operation | Method and path | What it does | | --- | --- | --- | | List | `GET /v1/integration-gateways` | List the gateways the tenant has registered, each with its status, pinned certificate fingerprint, and latest conformance result. | | Register | `POST /v1/integration-gateways` | Register a deployed gateway with a `name` and a `base_url`. The new gateway starts in the `registered` status. | | Get | `GET /v1/integration-gateways/{id}` | Read one gateway's full record. | | Edit registration | `PATCH /v1/integration-gateways/{id}` | Update the `name` or `base_url`. Only the fields you send are written. | | Pin or rotate cert | `POST /v1/integration-gateways/{id}/pin-cert` | Pin the gateway's mTLS certificate `fingerprint` for the mutually authenticated channel. | | Enable or disable | `POST /v1/integration-gateways/{id}/status` | Set the `status` to `active`, `disabled`, or `registered`. | | Record conformance | `POST /v1/integration-gateways/{id}/conformance` | Record a contract conformance result of `passing`, `failing`, or `unknown`. | | Read events | `GET /v1/integration-gateways/{id}/events` | Read the log of calls XKOVA made to the gateway, newest first. | Gateway management is admin only. It is tenant configuration rather than an access grant, so the operations are held by the admin permission set (`gateway.read` for the reads, `gateway.register`, `gateway.repoint`, `gateway.pin_cert`, `gateway.activate`, and `gateway.conformance` for the writes) and are not part of any lesser operator role. Every mutation is recorded in the audit trail as a configuration change. The write operations accept an idempotency key so a retried call is safe; see [Idempotency](idempotency.html). ### Lifecycle and status A gateway carries a `status` of `registered`, `active`, or `disabled`. Registration creates the record in `registered`; enabling moves it to `active` so the platform routes core banking calls to it; disabling stops the platform routing to it without removing the registration, so it can be re-enabled later. In a tenant that gates gateway activation behind a governance approval, an enable request is held for a quorum: the gateway is unchanged and the response carries a `pending_approval_request_id`, and you re-issue the enable once the approval clears. ### Certificate pinning and rotation The mTLS channel to the gateway is pinned to a certificate fingerprint. Pinning is done with a public sha256 digest, never key material. Pinning a new fingerprint rotates the current one into `previous_cert_fingerprint` and stamps `cert_rotated_at`, so a rotation keeps a record of the fingerprint it replaced. The pinned and previous fingerprints are readable on the gateway record. ### Conformance A conformance result attests that the deployed gateway matches the expected adapter contract, the same contract the operations in [Core Banking](core-banking.html) describe (resolve member, check funds available, place hold, release hold, capture hold, credit member, list operations, and notify member). Recording a result sets `conformance_status` to `passing`, `failing`, or `unknown` and stamps `conformance_checked_at`, so the gateway record carries its most recent conformance state. ### The event stream Every call XKOVA makes to a gateway is logged, and you read that log through the events endpoint, newest first with cursor pagination (pass a prior response's `next_cursor`; `limit` defaults to 25 and caps at 100). Each event records the operation (`op`), the HTTP `method`, whether the call succeeded (`ok`), the `upstream_status` and `error_code` on failure, the `latency_ms`, and the `idempotency_key` if one was sent. This is distinct from the gateway's own operation stream in [Core Banking](core-banking.html), which an integrator reads for core ledger reconciliation: the event stream here is XKOVA's outbound call log for observing gateway health. ## Where to go next Read each connection page for the vendors it supports and how it is configured: [Core Banking](core-banking.html), [Screening](screening.html), [Identity Verification](verification.html), [Conversion and Ramps](conversion.html), and [Wallet Providers](wallet-providers.html). --- source: https://docs.xkova.com/key-management.html # Key Management There are several kinds of key on XKOVA, and they are deliberately separated. Member wallet keys move value and remain non-exportable in the configured MPC provider, or remain with an external owner. Treasury Safe moves require the configured owner quorum. The XKOVA Relayer submits accepted signatures and pays gas. API keys provide bearer authorization. A separate customer-held asymmetric key proves possession for production mutations. ## The kinds of key | Key | Held by | What it can do | | --- | --- | --- | | Thirdweb MPC human-wallet key material | Non-exportable in Thirdweb's MPC infrastructure. | Produces member or staff signatures after a custom-JWT provider session is established. | | External EOA key | The external owner. | Produces signatures independently of XKOVA. | | Treasury Safe owner keys | Each owner wallet under its own custody and session model. | Each signs a Safe transaction; the configured N of M quorum is needed to move treasury funds. | | Thirdweb custom-JWT signing key | XKOVA's protected issuer. | Bootstraps a general Thirdweb provider signing session for one canonical human-wallet subject. It is signing-critical authority. | | Integrator API key | The integrator, on a server. | Bearer authorization for customer API calls scoped to one workspace. | | Request-signing key | The integrator's KMS, HSM, or key provider. | Proof of possession for production mutations. It grants no authority without the bound bearer API key. | ## Human-wallet key material stays non-exportable Thirdweb's MPC service keeps human-wallet private-key material non-exportable. XKOVA receives the public address and returned signatures, not the private-key material. Externally controlled EOAs keep their private keys with the external owner. A Safe holds value at the contract and enforces signatures from its configured owner quorum. See [the Non-custodial Model](non-custodial.html) and [Wallet Providers](wallet-providers.html). > MPC key non-exportability does not remove signing-session authority. XKOVA's custom-JWT issuer plus the public Thirdweb client can establish a general provider session for the named wallet. Protect the issuer like signing authority. Thirdweb does not enforce the XKOVA business purpose on that session. ## Relayer authority is separate from wallet-signing authority The XKOVA Relayer is the on-chain sender and pays gas, but the contract still requires a valid configured owner signature or Safe quorum. Compromising only the relayer does not provide those signatures. The broader platform threat model also includes the custom-JWT issuer used for Thirdweb MPC wallets. XKOVA limits that path with owner-specific authorization, a frozen payload, exact signature acceptance, short-lived memory-only browser ceremonies, and on-chain verification. See the [Security Model](security.html). ## API keys: issued once, then rotated - **Shown once.** A new API key's secret is returned only at creation, and a rotated key's replacement secret is returned only by the rotate call, matching the create-time behavior. Only a prefix (visible in the console) and a hash are stored, so the full secret is never shown again or logged. On a fresh workspace the first creation is governance held pending owner attestation; the approved re-submission is the call that returns the secret. See [Policies and Quorum Governance](policies-quorum.html). - **Test versus live.** Test keys are valid only against the sandbox; live keys are valid only against production after production access is approved. - **Rotation.** Rotating a key returns a single replacement, and the old secret stops verifying immediately; there is no grace window for API keys (webhook signing secrets keep one, API keys do not). Rotation is a step up action, so it requires a fresh authentication proof. - **Revocation.** Revoking a key makes it invalid on the next verification. Deleting a key is a cleanup step that only works after it has been revoked. - **Scoped grants.** A key carries roles, and additional role grants can be attached or revoked without rotating the secret, with a cascade on revoke. See [Authentication](auth.html). ## Production request-signing credentials A production customer mutation requires two distinct credentials: the workspace API key as bearer authorization and an active asymmetric signing credential bound to the same API principal. The SDK sends `Signature-Input` and `Signature` using XKOVA's strict RFC 9421 profile, plus the RFC 9530 `Content-Digest` field when a body is present. Production reads remain bearer-only. Sandbox mutations may use the same signing profile when configured, but do not require it. The customer keeps the private key in its own KMS, HSM, or key provider. The SDK supports async signing callbacks for P-256 P1363, Ed25519, and RSA-PSS with SHA-256, MGF1 SHA-256, and a 32-byte salt, and never receives exportable private-key material. XKOVA pins the algorithm to the credential resolved by `keyid` and omits the RFC 9421 `alg` parameter, because RFC 9421 does not register an `rsa-pss-sha256` value. Signing-credential registration, rotation, revocation, and public-key updates are private management actions, not methods in the customer `@xkova/sdk` package. See the [SDK Overview](sdk-overview.html) for the transport profile. Rotation registers one successor and requires proof of possession before activation. Only one live successor may reference a predecessor. Activating the successor starts a bounded five-minute overlap so the customer can switch signers without widening signature freshness. Either credential can still be revoked immediately. ## Webhook signing secrets Each webhook endpoint has a signing secret used to sign deliveries. Rotating it issues a new secret while a grace window keeps the old one valid, so you can verify with both during the cutover and switch over without dropping events. Secret rotation is a step up action. See [Webhooks](webhooks.html). ## Secrets are not exposed - Human MPC private-key material is never exposed to XKOVA or to integrators. - The 60-second Thirdweb custom JWT is a transient signing-session bootstrap, not private-key material. It remains memory-only in the controlled signing page and is disposed immediately after the ceremony. - API key secrets are shown once and never returned again; only a prefix and a hash persist. - Request-signing private keys stay in the customer's KMS, HSM, or key provider; XKOVA stores only the registered public key and credential metadata. - The Authorization header is redacted from logs; mirror that practice in your own code. - Rotate immediately if any secret is exposed, using the rotate and reissue flow. > Never commit API keys to source and never log the Authorization header. Keep tenant keys on a server or trusted worker, not in a browser. ## Related See [Authentication](auth.html) for key formats, rotation, and the step up flow, [Wallet Providers](wallet-providers.html) for how member wallets are provisioned, and the [Security Model](security.html) for how these keys fit the threat model. --- source: https://docs.xkova.com/local-dev.html # Local Development XKOVA is hosted, so there is nothing to install or run locally. Developing your integration means pointing your own app at the hosted sandbox with a test key and building the way you normally would. ## Point your app at the sandbox The sandbox API, management Console, and demo tenant all run on XKOVA's side, so you only need two values to start: the sandbox base URL and a sandbox API key (`xkv_test_` prefix, issued from the console under Settings). Keep the key server-side, in your environment config. ``` XKOVA_BASE_URL=https://sandbox-api.xkova.com/v1 # Set XKOVA_API_KEY to your sandbox key on the server only ``` The SDK takes the same two values; see the [SDK Overview](sdk-overview.html). Sandbox keys only work against the sandbox, so local work can't reach production. Request signing is optional for sandbox mutations. Configure a sandbox signing credential only when you want to exercise the same RFC 9421 transport profile that production mutations require. ## Develop a member experience through your backend Consumer Xperience browser applications use your backend or BFF as the security boundary. Register the application and keep the resulting XKOVA and identity-provider session material on the server. A publishable key identifies the registered application and is not a secret or a source of member authority. ``` XKOVA_PUBLISHABLE_KEY=xkova_pk_sandbox_... # public application identifier # Server code import { XKOVAMemberBffClient } from '@xkova/sdk/member-bff'; ``` The helper handles the XKOVA transaction, proof exchange, session refresh and rotation, exact-session logout, CSRF integration points, and safe member projections. Your application supplies its session store, web-framework wiring, and FI-specific identity integration. Use a host-only, `Secure`, `HttpOnly`, `SameSite=Strict` durable application cookie. If an OAuth or OIDC callback requires cross-site navigation, keep that state in a separate short-lived `SameSite=Lax` flow cookie. Never place provider tokens or XKOVA access or refresh credentials in browser JavaScript. BYO OIDC is the recommended enterprise member-authentication model. XKOVA Hosted Auth is an optional managed alternative. For Hosted Auth, configure the application, branding, exact origin, and callback in Console, then call `beginHostedAuthorization` and `completeHostedAuthorization` from your BFF. XKOVA owns the controlled email-code ceremony and any required additional factor, provider endpoints, and signing metadata. Embedded Solution integrations accept authenticated Lumin, Q2, Banno, or another approved channel proof through a server-side adapter and do not require a universal XKOVA login page. See [Authentication](auth.html) and [Build a Member Experience](embedded-member.html). ## Receive webhooks on your machine XKOVA needs to reach your webhook endpoint, and a plain `localhost` URL isn't reachable from the internet. Two ways to develop the receiving side locally: - **Test sends and replay.** Register your endpoint in the console's Webhooks area, send a signed test event to it on demand, and replay any failed delivery. This covers most receiver development without extra tooling. - **A public tunnel.** To exercise live event flow end to end, expose your local receiver through a tunneling tool and register the tunnel URL as the endpoint. Verify the signature on each delivery; the SDK ships a verifier. See [Webhooks](webhooks.html). ## Test value and scenarios - **Test tokens.** Active test networks and test assets appear automatically. Read `GET /v1/chains/available` and `GET /v1/workspace-tokens`, then select a compatible asset for the operation. No real funds are involved. - **Screening scenarios.** The sandbox screening provider is deterministic: specific inputs produce allow, review, and block outcomes, so you can exercise each compliance path. See [Screening](screening.html). - **The Plover demo.** Explore the member banking app through branded email sign-in and explicit stablecoin-product acceptance. See [Plover Demo](demos.html). ## A few habits that help - Send an `Idempotency-Key` on writes that accept it; a re-run of a crashed script won't double-create. See [Idempotency](idempotency.html). - Exercise rate-limited sandbox scenarios, so you can build in back-off early. See [Rate Limits](rate-limits.html). - Log the `correlation_id` from each response and webhook; it ties your logs to XKOVA's audit trail if you need support. See [Errors](errors.html). ## Next Follow the [Quickstart](getting-started.html) to your first confirmed payment, or open [Plover Demo](demos.html) for the Plover member banking app. --- source: https://docs.xkova.com/networks.html # Supported Networks A workspace receives every active chain applicable to its mode. Networks are not pinned at onboarding. Each chain-bound resource still identifies the exact network used for that operation. ## Mode mapping across every installation Workspace mode alone determines whether the automatic catalog contains testnets or mainnets. Installation selects where the service and credentials run, never the network tier. | Installation | Sandbox mode | Production mode | | --- | --- | --- | | Local or local VM | Testnets | Mainnets | | Staging | Testnets | Mainnets | | Production | Testnets | Mainnets | Active Avalanche Fuji and Base Sepolia deployments can appear in every sandbox catalog. Active Avalanche and Base deployments can appear in every production-mode catalog. A deployed contract or ready registry entry does not appear until the chain is active. Mainnet catalog availability does not bypass production access, billing, compliance, authorization, or runtime certification. For sandbox behavior, see the [Quickstart](getting-started.html) and the operations under [All Operations](api-reference.html). ## Discovering chains `GET /v1/chains` is the global ready and active registry. An authenticated client uses `GET /v1/chains/available` for the active chains actually applicable to its workspace mode. Each row carries a stable `chain_ref`, an `is_testnet` flag, and the native gas symbol. - `chain_ref` is the stable, environment portable identifier you key on. The internal chain UUID is per environment and not portable, so it is never the public identity. - `is_testnet` identifies the chain tier already selected by workspace mode. - `native_symbol` is the native gas token, for example AVAX or ETH. ## The chain descriptor Every chain bound resource carries an embedded chain descriptor, so a consumer always knows which chain a resource settled on, and whether it was testnet or mainnet, without a second call. A resource always carries exactly one chain, resolved from the wallet, token, or explicit operation-level selection. ``` "chain": { "ref": "43113", "kind": "evm", "name": "Avalanche Fuji", "is_testnet": true, "native_symbol": "AVAX" } ``` ## Operation-level selection Some requests accept `chain_ref` or `chain_id` because the operation must choose one available network. Tokenization deploys are one example. Payment and escrow requests must also agree with the referenced wallet and token deployment. A conflict or an inapplicable chain returns `chain_not_allowed`. Onboarding accepts no chain selection. > Adding and activating another supported chain makes it appear automatically. Integrations should read the available catalog and chain descriptors instead of hardcoding Avalanche or Base. ## Related See [Tenants and Workspaces](tenancy.html) for automatic asset availability, and [Wallets and Balances](wallets.html) for the assets a member holds on it. --- source: https://docs.xkova.com/non-custodial.html # Non-custodial Model XKOVA never receives or exports human MPC private-key material. Contracts still require a valid signature from the configured owner address, or the required Safe quorum, before the XKOVA Relayer can settle value. A Thirdweb custom-JWT wallet adds a separate signing-authority trust boundary that externally controlled wallets do not have. ## Sign, relay, confirm A transfer on XKOVA is never a single write that moves money. It is a three step pattern that separates who authorizes a transfer from who pays gas to submit it. 1. **Sign.** The application builds an intent, and the authorized person signs it through the configured wallet as EIP-712 typed data. The resulting owner-address signature is the on-chain authorization. Nothing has moved yet. 1. **Relay.** The XKOVA Relayer takes the signed instruction, wraps it in the contract's meta transaction entrypoint, submits it on chain, and pays the gas. 1. **Confirm.** XKOVA observes the on chain result and the resource transitions to a confirmed state. A webhook fires when state changes. ``` flowchart TD M["Member signs intent (EIP-712)"] --> R["XKOVA Relayer submits and pays gas"] R --> C["Chain settles transaction"] C --> I["XKOVA confirms result on chain"] I --> W["Webhook on state change"] ``` ## Key custody and signing authority are different XKOVA never receives or exports a human wallet's private-key material. That statement does not mean every wallet has the same trust model. An externally controlled EOA signs independently of XKOVA. A Safe requires the configured owner quorum. A Thirdweb MPC wallet keeps its private-key material non-exportable in the provider, while a valid XKOVA custom JWT plus the public Thirdweb client can establish a general provider signing session for that wallet. | Wallet shape | Signing boundary | | --- | --- | | Externally controlled EOA | The external owner controls the key and signs independently of XKOVA. | | Thirdweb MPC human wallet | Thirdweb keeps MPC private-key material non-exportable. XKOVA protects the custom-JWT authority and accepts a signature only for the frozen authorized purpose. | | Multisig Safe | The Safe contract requires its configured quorum. Each owner wallet retains its own custody and signing-session model. | | XKOVA Relayer | The relayer submits accepted signatures and pays gas. It is not an asset owner. | > Thirdweb does not limit a connected custom-JWT session to one XKOVA business purpose. XKOVA authorizes and freezes the intended payload before bootstrap, uses a fresh memory-only browser ceremony, verifies the returned signer and exact payload, and disposes the provider session immediately. Protect the custom-JWT issuer as signing-critical authority. ## The mechanics The pattern is built from public standards, so the cryptographic property is verifiable on chain. | Piece | What it does | | --- | --- | | EIP-712 typed data | The structured message presented for authorization. The contract recovers the signer with `ecrecover` and checks it matches the expected address. | | EIP-2612 permit | A signed token approval, so a direct send approves and transfers in one relayed transaction without a separate on chain approval step. | | Signed-intent meta transaction | The relayer pattern. The relayer is the on chain sender and pays gas, while the contract recovers and validates the original signer from the signed instruction before acting on it. | ## Direct sends and treasury A member to member direct send follows the pattern above: the configured member wallet produces a transfer-intent signature plus an EIP-2612 permit, and the XKOVA Relayer submits both in one relayed call. Treasury movements use a Safe quorum. Institution-held funds sit in a multisig Safe, and a transfer needs the configured N of M owner signatures before the relayer submits it. If an owner is a Thirdweb MPC wallet, that owner's signing session has the custom-JWT trust boundary described above. For the money movement product on top of this model, see [XKOVA SPS](rails-overview.html) and [Transfers and Settlement](transfers.html). For treasury specifics see [Treasury](treasury.html), and for the threat model see the [Security Model](security.html). ## Related See [Wallets and Balances](wallets.html) for how member wallets are provisioned, and the [interactive reference](reference.html) for the signing session and signature submission operations. --- source: https://docs.xkova.com/off-ramp.html # Funding and Off-ramp The rails move stablecoin value. Funding and off-ramp are the two ends that connect that value to the bank. A funding request brings fiat in and turns it into stablecoin. An off-ramp request turns stablecoin back into fiat and pays it out. Both run through a conversion partner. Production partners are interchangeable and configured for the tenant. Every sandbox workspace is pre-enabled with XKOVA's sandbox conversion partner. ## The two directions | Direction | Request | What it does | | --- | --- | --- | | In | Funding request | Converts fiat to stablecoin and credits it onto the rails. | | Out | Off-ramp request | Converts stablecoin back to fiat and pays it out to the bank. | Each direction is a request you create and then read to completion. Both list with pagination, newest first, and each can be fetched by id to follow its progress. ## Funding value in A funding request represents fiat coming in to be converted into stablecoin. You create the request, the conversion partner moves the fiat and mints or releases the stablecoin, and the value lands on the rails ready to send. From there it flows through [Payments](payments.html), [Escrow](escrow.html), or [Treasury](treasury.html) like any other balance. ## Cashing out An off-ramp request converts stablecoin back to fiat and pays it out. Off-ramp is member burns first: the member's wallet sends the stablecoin to the conversion partner before the request completes. The API exposes the on chain destination address to send to, resolved from the active conversion profile, so the client knows exactly where to sign the outbound transfer. Once the partner has the stablecoin, it dispatches the fiat; the request settles when the receiving bank confirms the credit has posted to the member's account, so expect a window between the partner sending the wire and the request reading as settled. > Because off-ramp burns first, the outbound stablecoin transfer uses the same signed-intent path as a payment. The configured member wallet produces the required signature, XKOVA verifies the exact signer and payload, and the relayer submits it. See [Transfers and Settlement](transfers.html). ## Interchangeable conversion partners Funding and off-ramp do not bind the public API to a single provider. In production, the conversion partner is a swappable adapter configured for the tenant, so XKOVA can move an institution without changing its integration. Examples include Coinbase, Paxos, and Anchorage. In sandbox, XKOVA automatically uses a pre-enabled conversion partner for `dUSDC` on supported testnets. The same request lifecycle therefore exercises the shape a production integration uses, while no fiat or issuer-controlled USDC moves. The full conversion model is on [Conversion and Ramps](conversion.html). ## Following a request to completion Both funding and off-ramp are asynchronous because settlement depends on the partner and, for off-ramp, on an on chain transfer confirming. Create the request, then either poll it by id or subscribe to events to learn when it completes or fails. See [Webhooks](webhooks.html) for event delivery and [Errors and Correlation IDs](errors.html) for distinguishing a retryable failure from a permanent one. Conversion availability failures use `service_unavailable` with a bounded `Retry-After`. Check the existing request before retrying with the same idempotency key; a timeout does not prove that nothing happened. A declined conversion returns `funding_rejected`, and an expired quote returns `payment_expired`. Error responses and resource reads omit private upstream diagnostics. Keep the `correlation_id` for support. > Sandbox supports deterministic funding and off-ramp failure scenarios so you can test the unhappy paths before going live. Drive them with the sandbox scenario header on ramp writes. ## Related - [Conversion and Ramps](conversion.html): the conversion model and partner configuration. - [Payments](payments.html): spending the value once it is on the rails. - [Transfers and Settlement](transfers.html): how the off-ramp burn confirms on chain. > For the exact funding and off-ramp operations and their request and response shapes, see [the operations list](api-reference.html) or try them live in [the interactive reference](reference.html). --- source: https://docs.xkova.com/onboarding.html # Operator Onboarding The console takes a new operator from signup to a first confirmed payment along one path: sign up, follow the Login Link, pick how you want to use XKOVA, then work a short checklist. Networks and assets appear automatically. This page walks that path in order so you know what each screen asks and why. ## The onboarding path Onboarding is a sequence, not a settings page. Each step enables the next, and the last step lands you on a working integration. The flow looks like this. ``` flowchart TD S["Sign up"] --> L["Login Link or one-time code"] L --> F["Intent fork: pick a direction"] F --> C["Four item checklist"] C --> Fu["Fund the member wallet"] Fu --> P["First confirmed payment"] ``` ## 1. Sign up Create a workspace from the console signup form. You provide an email, an organization name and type, and accept the terms. Signup provisions a sandbox workspace and a first operator. XKOVA creates the production workspace only after approving the separate production-access request. New workspaces start in a pending setup state until you record the product intent on the next screen. This lifecycle does not select a network. > Sandbox is the default. Sandbox API keys are prefixed `xkv_test_`. You move to production later through a separate production access step in settings. > The sandbox firewall starts in screen-and-cap mode. A newly approved production workspace starts completely locked and cannot send value until institution staff commit explicit production rules. ## 2. Follow the Login Link The first operator must sign in with the Login Link or the one-time code sent by email before doing anything else. This proves control of the email and establishes the operator session. After sign in, the console routes a pending setup workspace to the intent fork rather than the dashboard. ## 3. The intent fork The first screen asks one question: what do you want to do with XKOVA? Pick one to continue. You can change direction later. | Choice | What it sets up | Where it lands you | | --- | --- | --- | | Move money | The rails product: payments, treasury, and escrow. | The dashboard. | | Create my own token | The tokenization engine, ready to deploy a token. | The create token wizard. | | Just exploring | An empty sandbox workspace to look around. | The dashboard. | Choosing one of these records your intent and flips the workspace from pending setup to active. The call is idempotent, so a retry is safe. It accepts no network or token selection. ## 4. Review automatic availability `GET /v1/chains/available` lists every active network applicable to the workspace, and `GET /v1/workspace-tokens` lists qualified external assets and its active workspace-issued assets. External assets appear after direct-send and escrow qualification for that exact deployment. There is no onboarding choice or settings toggle. Workspace-issued assets retain their own grants and eligibility; check `rails_eligible` before selecting one for a payment. ## 5. The four item checklist The dashboard shows a checklist of the four backend objects and milestones that stand between a fresh workspace and a confirmed `POST /v1/payments`. Each row links to the page that completes it, and the card disappears once all four are done. After the checklist clears, the dashboard becomes an at-a-glance summary: KPI tiles, a recent-activity feed, and the open compliance queue, each drilling through to its own area. | Item | What it is | Where | | --- | --- | --- | | Create an API key | A test prefix key for your backend's server to server calls. | [Settings, API keys](console-settings.html) | | Commit a fee schedule | Payments are rejected until a committed schedule prices the send. | [Settings, fee schedules](console-settings.html) | | Provision an account | An account holder your members act as. Your backend can create these just in time on member sign in. | Account holders | | First payment | The milestone: a payment signed by the holder, relayed by the XKOVA Relayer, confirmed on-chain. | [Payments](console-payments.html) | > One checklist item is governance held on a fresh workspace: the first API key creation returns a pending approval rather than executing immediately. The console shows a held notice; the owner attests the request under Governance, Approvals, and re-submitting the same form completes the step. (Committing the value-movement authorization policy, a separate configuration step outside this checklist, is held the same way; committing a fee schedule is not.) This is the seeded secure default described in [Policies and Quorum Governance](policies-quorum.html). ## 6. Fund the member wallet Provisioning an account holder and deriving that member's wallet records an on chain address but moves no value. A freshly derived wallet is empty, so before the member can send anything it needs a stablecoin balance to send. Gas is not the blocker: the XKOVA Relayer pays gas for every signed transfer, so a fresh wallet never needs a native balance of its own. What stands between a derived wallet and a first send is funding it with the payment asset selected for that operation. This is the provision to fund to pay seam. A successful member binding creates or resumes the canonical wallet lifecycle described in [Wallets and Balances](wallets.html). Once readiness returns the wallet address, you fund that address. Only then can the member sign a payment that settles. Fund the wallet with a funding request. `POST /v1/funding-requests` credits stablecoin to the member's wallet, keyed by `account_holder_id` with the wallet as its `destination_address`, and returns a request you follow to settlement. In sandbox, request `dUSDC` on Avalanche Fuji or Base Sepolia. XKOVA automatically routes the request through its sandbox conversion partner, mints real on-chain demo tokens to the destination, and settles the normal funding lifecycle. Do not send a ramp-profile header. The partner is capped per destination wallet per day. Like every funding request, the call takes an `Idempotency-Key` so a retry never double funds. Sandbox also exposes Circle Testnet `USDC` on each supported testnet. It is a separate issuer-controlled token and is never substituted for `dUSDC`. Acquire it from [Circle's faucet](https://faucet.circle.com/) or use a pre-funded test wallet. Circle publishes the canonical contracts in its [USDC contract-address list](https://developers.circle.com/stablecoins/usdc-contract-addresses). Both assets use six decimals; `GET /v1/workspace-tokens` returns their issuer and asset identity so your integration can distinguish them by chain and contract address. For `dUSDC`, `sandbox_faucet_contract_ref` returns the exact public faucet contract for the selected chain. A funding request moves through `pending_compliance`, `pending_issuance`, and `settled`, or `failed` if it cannot complete. Once it settles, the funded balance appears on the member's wallet, which XKOVA keeps in sync with the chain by reconciliation rather than a live read on every request. The wallet now has value to send. See [Funding and Off-ramp](off-ramp.html) for the funding request in full and [Sandbox Scenarios](sandbox-scenarios.html) for test funding and the deterministic ramp outcomes. ## 7. Your first payment Once a fee schedule is committed, select a rails-eligible asset whose direct-send flow is certified and create a payment. The holder signs the intent in their own wallet, the XKOVA Relayer submits it and pays gas, and XKOVA confirms the result on-chain. When the first confirmed payment lands, the checklist clears and onboarding is complete. For the mechanics of the signed transfer, see the [Non-custodial Model](non-custodial.html). ## Related For the developer path to the same first call, see the [Quickstart](getting-started.html). For the console areas you will use next, see [Payments](console-payments.html), [Treasury](console-treasury.html), [Tokens and Assets](console-tokens.html), [Compliance](console-compliance.html), and [Settings and Branding](console-settings.html). --- source: https://docs.xkova.com/pagination.html # Pagination List operations return one page at a time and hand you an opaque cursor for the next page. Cursor pagination uses the last sort position instead of an offset. It does not freeze a snapshot of rows between page reads. ## The list envelope Every paginated list returns the same envelope. The `data` array holds the page, and the two sibling fields tell you whether and how to continue. ``` { "data": [ { "...": "..." } ], "next_cursor": "eyJpZCI6...", "has_more": true } ``` | Field | Meaning | | --- | --- | | `data` | The items on this page, in the operation's defined order. | | `next_cursor` | An opaque token to fetch the next page. It is `null` on the final page. | | `has_more` | True when another page exists, false when this is the last page. | ## Request parameters | Parameter | Type | Behavior | | --- | --- | --- | | `cursor` | string | The opaque token from a prior response's `next_cursor`. Omit it (or pass null) for the first page. | | `limit` | integer | Maximum items per page. Default 25, maximum 100. | > Treat the cursor as opaque. Do not parse, build, or modify it. It encodes the operation's sort position, and a hand-made or altered value is not honored. Always pass back exactly the token the previous response gave you. ## Walking every page Request the first page without a cursor, process `data`, then keep passing the returned `next_cursor` back as the `cursor` parameter until `has_more` is false (or `next_cursor` is null). ``` // first page GET /v1/payments?limit=50 // each subsequent page GET /v1/payments?limit=50&cursor= // stop when has_more is false ``` You can combine the cursor with an operation's own filters, such as a token or a date range. Keep the filters identical across pages; changing a filter mid-walk invalidates the cursor's position. ## Bounded lists A small number of genuinely-bounded lists, where the result set is naturally short and complete, return a `{ data }` envelope with no cursor. The interactive reference documents which response shape each operation returns. ## Related See the [operations list](api-reference.html) for which endpoints are lists, the [interactive reference](reference.html) for each operation's exact parameters and response shape, and [Rate Limits](rate-limits.html) before paging through a large result set quickly. --- source: https://docs.xkova.com/payments.html # Payments A payment moves stablecoin value from one member to another. The configured sender wallet must produce a valid signature, so a payment is never a single write. The application creates the payment, the sender authorizes an intent, the XKOVA Relayer submits it, and the chain settles. This page covers the two shapes a payment takes (a direct send and a payment request) and the lifecycle each one follows. ## Two shapes The payments surface covers two directions of value movement. - **Direct send.** The sender pushes value to a recipient. The sender's application creates the payment, then the sender signs it. This is the core money movement primitive. - **Payment request.** A recipient asks a payer for value. The request is created first, the payer is notified, and when the payer approves, a direct send is spawned on the payer side and signed as usual. Both end in the same place: a signed transfer that the XKOVA Relayer submits and XKOVA confirms on chain. See [Transfers and Settlement](transfers.html) for how a signed transfer reaches a confirmed on chain state. ## Funding the sender wallet first A member's wallet is durably provisioned from the canonical account-holder identity, and a newly provisioned wallet holds nothing. A balance is a running total XKOVA maintains from the wallet's on chain activity, so a wallet that has never received value reads as a zero balance. Before a member can make their first payment, value has to arrive in that wallet. The seam is derive, then fund, then pay. 1. **Provision.** The durable account-holder lifecycle resolves the canonical Thirdweb MPC address independently of the member's authentication source. XKOVA never receives or exports its MPC private-key material. See [Wallets and Balances](wallets.html). 1. **Fund.** Bring value into the wallet. Create a funding request with `POST /v1/funding-requests`, which converts fiat to stablecoin and credits the minted stablecoin to the wallet named in `destination_address`. The call returns `202` and the request advances asynchronously through `pending_compliance`, `pending_issuance`, and `settled`; poll `GET /v1/funding-requests/{id}` to follow it. In sandbox, request `dUSDC` on Avalanche Fuji or Base Sepolia. The workspace's pre-enabled sandbox conversion partner credits real on-chain demo tokens without moving fiat. Circle Testnet `USDC` remains a separate issuer-controlled supported asset acquired through Circle's faucet or a pre-funded wallet. 1. **Pay.** Once the funding request settles and the balance is on chain, create the payment. `POST /v1/payments` takes a `from_wallet_id`, and the member signs the intent through the lifecycle below. You can confirm the wallet is ready before asking the member to sign. Run the feasibility check (`POST /v1/payments/simulate`): on a zero balance wallet it returns `would_succeed: false` with `balance_sufficient: false` and a `revert_reason` indicating insufficient funds, so you catch an unfunded wallet before creating a row. The full funding and cash-out surface, including the interchangeable conversion partners, is on [Funding and Off-ramp](off-ramp.html). For where this step fits in the operator setup path, see [Operator Onboarding](onboarding.html). ## The direct send lifecycle A direct send moves through four steps. Quote and create are server calls your backend makes. Sign is an authorized browser ceremony for the canonical member wallet. Submit hands the signature back to the API, which builds the calldata and relays it. 1. **Quote.** Ask for a fee quote for a prospective payment. The quote breaks fees out by component so you can show the payer an estimate and catch a likely compliance block early. A quote is advisory: create always recomputes fees and re-runs compliance at write time, so treat the create response, not the quote, as the source of truth for the fees that apply. 1. **Create.** Create the payment. The payment lands in a pending sign state and the API returns a signing URL pointing at the member signing experience. 1. **Sign.** The member opens the signing session and reviews the exact typed-data intent. XKOVA issues a fresh 60-second custom JWT for the canonical wallet, and Thirdweb MPC produces the signature through a fresh memory-only browser session. XKOVA never receives or exports the MPC private-key material. 1. **Submit.** The signed intent is submitted back to the API. The API recovers the signer, builds the meta transaction calldata, and the XKOVA Relayer submits it on chain and pays gas. ``` flowchart TD Q[Quote, optional and advisory] --> A[Create payment] A --> B{Compliance screen} B -- blocked --> X[Blocked, terminal] B -- review --> R[Held for operator triage] R -- released --> C B -- allowed --> C[Pending sign] C --> D[Member signs typed data in their wallet] D --> E[Submit signatures to the API] E --> F[XKOVA Relayer submits on chain, pays gas] F --> G{On chain result verified} G -- success --> H[Confirmed] G -- reverted --> I[Failed, no value moved] ``` > Before a member signs, you can run a feasibility check that reads the sender's on chain balance and the token's permit capability and runs the fee and compliance pipeline without creating a row or submitting anything. Use it to catch an insufficient balance or a compliance block early. ## Signing and acceptance are separate boundaries The contract requires the configured owner-address signature before it moves anything, and the relayer only carries the accepted signature. For Thirdweb MPC wallets, the custom-JWT issuer can bootstrap a general provider signing session, so XKOVA separately freezes the payment intent, verifies the exact signer and payload, and disposes the provider session after the ceremony. The full mechanics are in the [Non-custodial Model](non-custodial.html). ## Payment requests A payment request is recipient initiated. The recipient creates the request, which represents an inbound ask for value. The payer can approve, decline, or be reminded. Approving spawns a payment on the payer side, which the payer's member then signs through the same quote, create, sign, submit flow as a direct send. | Action | Who | Effect | | --- | --- | --- | | Create | Recipient | Opens the ask and notifies the payer. | | Approve | Payer | Spawns a payment on the payer side to be signed. | | Decline | Payer | Closes the request without paying. | | Remind | Recipient | Nudges the payer with a reminder. | | Cancel | Recipient | Withdraws the ask. | ## Reading state Every payment carries a state and a timeline of transitions you can read back. Fetch a single payment to see its current state, or read its event timeline to see each transition in order. A tenant wide list with cursor pagination and filters lets you reconcile across many payments, and a unified transactions read merges direct sends and escrows into one timeline when you want both in a single feed, covered next. ## The unified transactions read Direct sends and escrows each have their own list, but reconciliation usually wants both in one feed. `GET /v1/transactions` returns a single timeline that merges direct sends and escrows into one list for the calling tenant, newest first. Each row carries a `kind` discriminator of `direct` or `escrow`, so a client branches on `kind` to decide which fields are meaningful. The endpoint is read only at the list level: to act on a row, follow its `kind` to the detail endpoint, `GET /v1/payments/{id}` for a direct send or `GET /v1/escrows/{id}` for an escrow, each of which carries its own state machine and actions. Every row carries the fields common to both kinds: the chain descriptor, gross and net amount, token symbol, a raw kind-specific `status`, a canonical status, and timestamps. Escrow-only fields (`recipient_ref`, `claim_state`, `claim_path`, `on_chain_payment_id`, `expires_at`) are null on direct rows. ### Filters | Filter | Effect | | --- | --- | | `kind` | Narrows to one side. `direct` or `escrow`; any other value returns `validation_failed`. | | `status_canonical` | The shared four state bucket applied across both kinds: `queued`, `pending`, `success`, or `failed`. Each kind's own states fold into these buckets, so `failed` spans a cancelled or expired escrow as well as a failed direct send. | | `date_from`, `date_to` | Bound the created-at window on both sides. | ### Cursor pagination The list is cursor paginated like every other list on the API, returning `{ data, next_cursor, has_more }`. Pass `next_cursor` back as the `cursor` query parameter to page forward, and stop when `has_more` is false and `next_cursor` is null. `limit` defaults to 25 and is capped at 100. Ordering is by created-at descending across both kinds, which keeps the read stable for a reconciliation sweep that walks every transaction a tenant produced. The read is tenant scoped, the scope integrators use; the internal platform scope that sits above tenants does not reach it. See [Pagination](pagination.html) for the cursor contract in full. ## Failure and retry A payment that fails on chain is marked retryable when the failure is one a retry can clear. You can cancel a payment only before it has been signed. After signing, the transfer is in flight and the right move is to wait for settlement or retry if it is marked retryable. See [Errors and Correlation IDs](errors.html) for telling a retryable failure from a permanent one, and [Idempotency](idempotency.html) for making create safe to retry without double sending. ### Errors worth handling per step The [error catalog](errors.html) documents every code; these are the ones each step of the lifecycle most commonly returns. | Step | Codes | | --- | --- | | Quote | `validation_failed`, `flow_not_certified`, `chain_not_allowed`, `compliance_blocked`, `first_use_required` | | Create | `validation_failed`, `idempotency_conflict`, `flow_not_certified`, `workspace_setup_incomplete`, `compliance_blocked`, `authorization_denied` | | Signing session / member JWT | `unauthorized` (an expired or invalid signing token), `verification_required`, `compliance_case_open`, `not_found` | | Submit signatures | `invalid_state` (already signed or past signing), `verification_required`, `service_unavailable` | ## Related - [Transfers and Settlement](transfers.html): the relayer lifecycle and on chain confirmation. - [Escrow and Claims](escrow.html): when the recipient is not yet known or onboarded. - [Wallets and Balances](wallets.html): the member wallet a payment signs from. > For the exact request and response shapes, see [the operations list](api-reference.html) or try the calls live in [the interactive reference](reference.html). --- source: https://docs.xkova.com/policies-quorum.html # Policies and Quorum Governance Authorization decides who may attempt an action. Governance decides whether a high impact action proceeds. A policy firewall screens every transfer before it relays, a quorum gates the highest blast actions, separation of duties keeps one person from approving their own request, and break glass is the only audited bypass. ## The policy firewall Every transfer passes through a policy firewall before the XKOVA Relayer submits it. The firewall applies the tenant's compliance and risk policy, including transaction screening and sanctions checks, so a transfer that fails a check is denied rather than relayed. Because the firewall sits in front of the relayer, a denied action never reaches the chain. Payment, escrow, and funding-request creation each require their registered create permission before request-body validation. The firewall and the member signature are additional controls. For the screening providers behind it, see [Screening](screening.html). > The tenant send-initiation lock is an emergency stop. When armed, it blocks payment, escrow, and funding-request creation for every role, including administrators. Disarming it does not grant a missing create permission. New sandbox workspaces use a screen-and-cap policy. New production workspaces start with an empty committed policy and reject outbound value operations until institution staff commit explicit rules. ## Approval quorums High blast actions are gated by an approval quorum rather than a single principal. The action is staged as a request, a defined number of authorized approvers must attest, and only then does the action execute. Treasury movement is the canonical example: a transfer out of a Safe needs N of M signers to each sign the Safe transaction before the relayer submits it. - The request is created and the creator is stamped on it. - Authorized approvers attest until the clause's threshold is met. - The action executes only once quorum is reached. See [Treasury](treasury.html) for the N of M signer model and [Cap Tables and Distributions](cap-tables.html) for quorum approved distributions. ## Fresh workspace governance baseline A fresh workspace does not start ungoverned. Provisioning seeds an owner-approval requirement on six high impact actions, so the secure posture is the default rather than something you must remember to configure: - Changing a governance requirement itself (the meta gate; this one cannot be removed). - Transferring workspace ownership. - Committing the value-movement authorization policy (the firewall that screens transfers). - Creating an API key. - Adding or removing a treasury signer, or changing a Safe threshold. - Adding a treasury counterparty. The first call to a seeded action does not execute. It returns a held response carrying `status: pending_approval` and a `pending_approval_request_id`. The owner attests the request (in the console under Governance, Approvals, or through the attestation operations), and then the same call is re-submitted and executes from the approved snapshot. The owner can loosen, tighten, or remove any of these requirements through governance, except the meta gate on governance changes, which stays in place so the matrix can never be rewritten silently. ``` flowchart TD A[Call a governed action] --> B{Approval requirement?} B -- none --> E[Executes immediately] B -- quorum required --> C[Held: pending_approval with a request id] C --> D[Approvers attest by signing in their own wallet] D --> F{Quorum met?} F -- not yet --> D F -- yes --> G[Request ready] G --> H[Re-submit the same call] H --> I[Executes from the approved snapshot] ``` ## Separation of duties The request creator cannot approve their own request when the clause is satisfiable without them. If the threshold can be met by other authorized approvers, a self attestation by the creator is rejected. The check is conditional, not absolute: a solo or owner clause that can only be satisfied by that one principal still resolves (for example an owner attesting a one of one clause). Separation of duties is never silently bypassed. ## Break glass: the only bypass Break glass is the single sanctioned way to act without the normal quorum, for genuine emergencies. - **Armed two of two.** Arming requires the owner plus one other principal. No single person can arm it. - **Time boxed.** It is bounded in time and auto expires, so it cannot stay open. - **Tagged and audited.** Every action taken while armed is tagged as a break glass action and recorded as a high severity audit event with an alert. - **Only bypass.** It bypasses the approval requirement only while armed, and there is no other bypass path. > Break glass leaves the loudest possible trail. Use it only for emergencies; every action under it is high severity audited and alerted. See [the Audit Log](audit-log.html). ## Who may do what The roles that can initiate, approve, and execute these actions, and the permissions each role carries, are defined in the permission registry. See [Roles and Permissions](roles-permissions.html) for the role model and the catalog, and [Authorization and RBAC](authz.html) for how a request is authorized. ## Related For how the controls in this page are recorded, see [the Audit Log](audit-log.html). For the overall posture, see the [Security Model](security.html). For the exact governance and treasury operations, see the [interactive reference](reference.html). --- source: https://docs.xkova.com/provenance.html # Provenance and Transparency Real-world-asset metadata is immutable by system design: you can append new facts, but you cannot quietly rewrite old ones. Three mechanisms enforce that claim. Every change is appended to a hash chain, external parties sign their own facts, and a public transparency log lets any holder verify the history without trusting XKOVA's database. ## An append-only history Each metadata change earns a provenance version and a leaf in a per-token hash chain. Nothing is edited in place; a correction is a new appended version, not a rewrite of an old one. That makes the history tamper-evident: if a past version were altered, the chain would no longer recompute. You verify a token's provenance chain on demand. The verify check recomputes the metadata hash chain, confirms the latest signed checkpoint still matches (catching a rewritten version), and recovers the checkpoint's EIP-712 signature against the live isolated-signer address (catching a forged checkpoint; the row's stored signer address is not trusted). The verdict is one of: | Verdict | Meaning | | --- | --- | | ok, no reason | The chain is intact and covered by a checkpoint. | | ok, unanchored | Intact, but no checkpoint exists yet. | | chain_broken | A version's leaf does not recompute. | | head_mismatch | A covered version was rewritten. | | signature_invalid | The checkpoint signature does not recover to the live signer. | | signer_unreachable | Transient; retry. | ## Multi-attester facts Some facts come from outside the issuer: an auditor's opinion, an appraiser's valuation, a custodian's confirmation. Rather than the issuer typing those in, the external party signs them with their own key. 1. The issuer registers a trusted external attester against the token, pinned to that token, with a role label such as auditor, appraiser, or custodian. The attester's address is validated and stored. 1. The attester signs an EIP-712 fact over the token reference, the field path, and a digest of the payload, then submits it to an append-log field. 1. XKOVA recomputes the digest, recovers the signer, and rejects the fact if the recovered address is not the named attester's registered address. No row is written on a mismatch. An accepted attested fact commits through the same single ingestion path as any other change, earning a provenance version and chain leaf, with its provenance source recorded as attested. An attester can be soft-revoked, after which their key can no longer sign accepted facts. Only append-log fields can be attested. > The attester's signature proves who asserted the fact. The provenance chain proves the fact was never silently changed afterward. The two together let a reader trust a third-party fact without trusting the issuer or XKOVA. ## The public transparency log Each token's metadata history is also committed to an RFC-6962 transparency log, the same Merkle-tree structure used by certificate transparency. The log read routes are public, with no api key, because the log carries only hashes, never gated content. That lets a holder or auditor confirm two things without trusting XKOVA's database: - **Inclusion**: a specific metadata version was logged. Fetch a signed tree head, fetch an inclusion proof for the version, and verify the proof against the tree head's root offline. - **Consistency**: the log was only ever appended to, never rewritten between two points in time. Fetch a consistency proof between two tree sizes and verify that the earlier tree is a prefix of the later one. The XKOVA TypeScript SDK ships verifiers for these proofs, so a holder can check them in their own code. A signed tree head, once published, is append-only and always served. ### Third-party witnesses For stronger assurance, an issuer can register a trusted witness (an auditor, custodian, or oracle) whose key co-signs the published tree heads. Only a co-signature that recovers to a registered, active witness is accepted and recorded on the tree head. A witness that independently co-signs the log makes it harder for any single party to rewrite history unnoticed. ## Verified against the chain XKOVA re-verifies the stored record against ground truth on a nightly schedule: stored documents against their content hashes, and projected positions and token supply against on-chain state. Most detected drift is logged and alerted for an operator to reconcile against the chain, for example by re-indexing a token; the on-chain supply mirror is additionally re-replayed from chain state automatically, while tampered document bytes can only be flagged, never silently restored. The transparency log above lets you prove the history was never rewritten. ## Related - [Real-World Assets](rwa.html): the schema and metadata model these facts attach to. - [Holder Eligibility and Transfers](holder-eligibility.html): how attested attributes feed transfer decisions. - [Corporate Actions](corporate-actions.html): lifecycle changes that also append history. - [Security Model](security.html): the isolated signer behind the checkpoints. > For the verify, attester, and transparency-log operations, see the [operations list](api-reference.html) or the [interactive reference](reference.html). --- source: https://docs.xkova.com/rails-overview.html # XKOVA SPS XKOVA SPS, the Stablecoin Payment System, is the money movement product. It moves stablecoin value between members, into treasury, and out to the bank. XKOVA never receives or exports human MPC private-key material. Every transfer still requires the configured owner-address signature or Safe quorum before the XKOVA Relayer submits and the chain settles. ## What SPS covers - [Payments](payments.html): direct sends between members and payment requests. - [Escrow and Claims](escrow.html): hold funds until a recipient claims them. - [Treasury](treasury.html): institution held funds in a multisig Safe, moved by an N of M signer quorum. - [Transfers and Settlement](transfers.html): how a signed transfer reaches a confirmed on chain state. - [Funding and Off-ramp](off-ramp.html): funding value in and cashing out to the bank. ## One pattern underneath A transfer is never a single write. The application creates and freezes an intent, the configured wallet produces the required signature, XKOVA verifies it, the XKOVA Relayer submits and pays gas, and XKOVA confirms the result on chain. Thirdweb MPC wallets and externally controlled wallets have different signing-session trust boundaries. See the [Non-custodial Model](non-custodial.html) for the mechanics. > For the exact operations, see the [API Reference](api-reference.html) or try them live in the [interactive reference](reference.html). For a hands on path, follow the [Quickstart](getting-started.html). --- source: https://docs.xkova.com/rate-limits.html # Rate Limits The API limits how fast a caller can send requests so one integrator cannot crowd out another or overwhelm a value-moving path. Every response tells you where you stand, and a request over the limit is rejected with a clear signal to wait and retry. ## How limits are applied Limits use a fixed-window counter model: your quota allows a set number of requests per time window and resets as a whole when the window rolls over. Customer-server requests without an attached member session share a per-API-key request quota set by the tenant's plan tier, so every endpoint, reads and value-moving writes alike, draws from the same window, and a burst of reads can consume the quota that writes would otherwise use. Unauthenticated requests are held to a separate per-IP abuse baseline. - **Base**: 60 requests per minute per API key. - **Growth**: 600 requests per minute per API key. - **Scale**: 3000 requests per minute per API key. - **Unauthenticated**: about 100 requests per 10 seconds per IP, an abuse baseline that applies before an API key is resolved. A higher plan tier raises the per-minute request ceiling for your API key. Value-moving writes, such as creating a payment or a funding or off-ramp request, count against this same per-API-key quota as any other request. The unauthenticated sign-in, session, and sign-up endpoints are the exception: they are limited separately per route by IP to resist brute force. Member-session and Console staff traffic use separate burst and sustained profiles. Member traffic is limited per session, including when its BFF also supplies an API key; it does not consume the ordinary per-key plan bucket. Current member and staff profiles allow 600 requests per 10 seconds and 3000 per minute. Local and test environments disable this tier guard. Sandbox API-key quotas follow the plan tier; exercise [forced throttling scenarios](sandbox-scenarios.html) to test backoff. ## Response headers Every non-rejected response carries the current state of the relevant bucket, so you can pace yourself without ever hitting the limit. | Header | Meaning | | --- | --- | | `X-RateLimit-Limit` | The request quota for your API key's plan tier. | | `X-RateLimit-Remaining` | Requests you can still make before the bucket empties. | | `X-RateLimit-Reset` | The Unix timestamp in seconds when the bucket window resets. | ## When you exceed a limit A request over the limit is rejected with `429` and the `rate_limited` error code. The response carries a `Retry-After` header with the number of seconds to wait before trying again. ``` HTTP/1.1 429 Too Many Requests Retry-After: 2 Content-Type: application/json { "code": "rate_limited", "message": "API rate limit exceeded.", "correlation_id": "..." } ``` ## Backoff Handle a `429` the same way every time: 1. Read `Retry-After` and wait at least that long. 1. Retry the request. If it fails again, increase the wait (exponential backoff) and add a small random jitter so parallel workers do not all retry on the same beat. 1. Cap the total retries and surface a clear failure rather than looping forever. > Pair backoff with idempotency on writes. A value-moving request that timed out may have succeeded server-side, so retry it with the same idempotency key to avoid a double send. See [Idempotency](idempotency.html). > Two account-state signals are not throttles and must not be treated as a transient `429`. A suspended tenant (`tenant_suspended`) is short-circuited on every request and returns `503` Service Unavailable. A read-only tenant (`tenant_read_only`) returns `403` Forbidden, and only on outbound money-movement routes, so reads and non-money-moving writes still succeed. Neither is retryable and neither carries a `Retry-After` header. ## Related See [Errors and Correlation IDs](errors.html) for the full error envelope and which codes are retryable, [Idempotency](idempotency.html) for safe write retries, and [Pagination](pagination.html) when paging quickly through a large result set. --- source: https://docs.xkova.com/recipes.html # Recipes and Examples Most outcomes on XKOVA are a short, ordered sequence of operations where one step's output feeds the next. These recipes walk the common flows end to end and identify which steps belong to the customer backend, a purpose-bound hosted experience, or a private institution-staff workflow. ## How to read these Every recipe is workflow shaped. A single write rarely finishes the job: a payment still needs a member signature, a treasury transfer still needs a signer quorum, and settlement is observed on chain rather than returned inline. The off chain steps (a member's EIP-712 signature in their own wallet, relayer submission and on chain settlement) are noted where they occur, and terminal status is reached by polling the relevant read. For why the API is shaped this way, see [the Non-custodial Model](non-custodial.html). Customer-server API steps use the sandbox base URL `https://sandbox-api.xkova.com/v1` and a tenant key `$XKOVA_API_KEY` that you keep server side. Purpose-bound member ceremonies and private institution-staff workflows are identified separately and are not called with that tenant key. ## Accept your first payment 1. Preview feasibility and fees (optional; create re-prices): `POST /v1/payments/quote`. 1. Create it: `POST /v1/payments`. The response carries the resource and its `pending_sign` status. 1. From your BFF, start an exact member action with `beginMemberAction`. Hosted members complete a fresh passkey ceremony in the branded Account Center. FI/BYO OIDC members complete the fresh assurance profile registered for that application. 1. Complete the action through the BFF helper. It exchanges the one-use result and opens the controlled signing surface without putting a provider token, XKOVA member credential, or signing authority in browser JavaScript. 1. After the hosted ceremony completes, continue polling the customer-server payment resource. 1. Poll `GET /v1/payments/{id}` until `status: confirmed`. > Use `client.payments` for the customer-server quote, create, and status steps. Member signing remains a separate exact fresh-action ceremony. See [SDK Workflows](sdk-workflows.html) for the composed workflows the SDK actually publishes. ## Send to someone without an account (escrow plus claim) 1. Create the held payment: `POST /v1/escrows`. A claim token is issued. 1. The sender completes the same purpose-bound hosted signing ceremony as above. 1. The recipient opens the claim link and completes its hosted verification and resolution ceremony. The claim path uses a capability token, not an API key, so the recipient never needs credentials. See [Escrow and Claims](escrow.html). ## Stand up a treasury vault and move funds 1. An institution administrator creates the Safe, signers, and threshold through Core Rails. 1. An authorized institution staff member drafts the transfer in its contextual Core Rails session. 1. Each signer completes the institution-staff signing ceremony until the Safe quorum is met. 1. Core Rails executes the approved transfer and presents its settlement status. The request creator cannot self approve when the clause is satisfiable without them. See [Treasury](treasury.html) and [Policies and Quorum Governance](policies-quorum.html). ## Issue and distribute a real-world asset token 1. Deploy: `POST /v1/tokenization/erc1155`, then poll `GET /v1/tokenization/tokens/{id}` until `status: active`. 1. Bind metadata: publish an asset schema, `POST /v1/rwa/tokens/{id}/schema-binding`, then commit fields with `POST .../fields/{fieldPath}`. 1. Mint to holders, then create and execute a distribution: `POST /v1/rwa/tokens/{id}/distributions` then `POST .../{distId}/execute`, then poll until the distribution reads `completed`. The batched payout leaves the treasury Safe as one quorum approved transfer; sanctioned holders are excluded server side. See [Real-World Assets](rwa.html) and [Cap Tables and Distributions](cap-tables.html). ## Receive events reliably 1. Subscribe to the event types you care about: `POST /v1/webhook-endpoints`. 1. On each delivery, verify `XKOVA-Webhook-Signature` plus its Ed25519 companion over the raw body, then handle idempotently by the event id (`XKOVA-Webhook-Event-Id`, equal to the envelope `id`); reserve the delivery id (`XKOVA-Webhook-Delivery-Id`) for delivery-level tracing. 1. Rotate the secret with `POST .../{id}/rotate`. A grace window keeps the old secret valid during the cutover. The SDK ships `verifyWebhookSignature` for step two. See [Webhooks](webhooks.html). ## Set up your workspace Setup is console-first, then API. In the console: sign up, follow the emailed Login Link, pick a direction on the intent screen, then work the checklist: commit a fee schedule and create an API key (held for owner approval on a fresh workspace; attest under Governance, Approvals, then re-submit). From your backend: poll `GET /v1/onboarding/status` until setup reads complete, read the automatic chain and asset catalogs, provision account holders, and make the first payment with a rails-eligible, certified asset. The intent choice does not pin a network. See [Operator Onboarding](onboarding.html) for the console path and the [Quickstart](getting-started.html) for the first call. ## Related For the exact request and response shapes of customer-server operations referenced here, see the [interactive reference](reference.html) or the [operations list](api-reference.html). For supported composed customer-server helpers, see [SDK Workflows](sdk-workflows.html). --- source: https://docs.xkova.com/reporting.html # Reporting and Exports Reporting pulls records back out of the platform for reconciliation, compliance, and tax filing. The management Console provides tenant-scoped audit views, earned-revenue views, and treasury tax exports. These management capabilities are not currently published as customer API or @xkova/sdk operations. ## Three Reporting Views The Console reporting views are read-only and split by what they answer. The audit view tells you what happened, the revenue reads tell you what a workspace earned, and the tax exports assemble what a workspace paid out to its counterparties for the year. | Surface | Reads | Formats | Access | | --- | --- | --- | --- | | Audit reports | Recorded actions across the tenant | JSON | The `report.read` permission | | Revenue reads | Fee revenue the active workspace earns | JSON and CSV | The `revenue_controls` feature | | Treasury tax exports | Confirmed treasury transfers to counterparties | JSON and CSV | A treasury role plus the `treasury` feature | > These views are available to authorized management Console users. Their private routes are intentionally absent from the customer API reference and `@xkova/sdk`. See [Authorization and RBAC](authz.html) for how roles map to permissions, and [Entitlements and Feature Gates](entitlements.html) for how the feature flags above are resolved. ## Audit Reports The management Console provides a cursor-paginated view of recorded tenant actions and a filter set for narrowing to the events a report needs. | Filter | Effect | | --- | --- | | `category` | The event family (for example a payment, treasury, or compliance action). | | `action` | The specific action recorded. | | `actor_id` | Who took the action. | | `target_type` and `target_id` | The resource the action touched. | | `correlation_id` | The full audit footprint of one request, for tracing an incident. | | `date_from` and `date_to` | An ISO-8601 time window. | Results sort by `occurred_at`, newest first by default, and you can flip `order` to `asc`. Paging uses the standard cursor contract with `next_cursor` and `has_more`; see [Pagination and Cursors](pagination.html). Reading this surface requires the `report.read` permission, which owner and admin roles hold, along with the compliance officer role. > This is the query surface over the audit trail. For the event model itself, what an audit event records, which actions are audited, and how the authority exercised is stamped on each row, see [Audit Log](audit-log.html). ## Revenue Reads The revenue namespace reads the fee revenue a workspace earns. Earned revenue is the fee flowing to the workspace's own beneficiaries; it is disjoint from the platform fee a tenant pays, which lives under a separate billing surface. The fee mechanics behind these totals are covered in [Fee Schedules](fee-schedules.html). This namespace is gated by the `revenue_controls` feature and is tenant-scoped. | Console view or export | Returns | | --- | --- | | Setup readiness | Whether revenue is wired: an active designated revenue treasury and a committed earned-fee schedule must both be present before earned revenue flows. | | Revenue summary | Per-token earned totals for the workspace, with an optional `date_from` and `date_to` window. | | Revenue log | One row per earned fee event, cursor-paginated, filterable by `token` and date range, sortable by `date` or `amount`. | | Revenue CSV export | The same filtered rows as the log query, but the full dataset with no pagination, as a CSV download. | Each log row carries the settlement details of a single earned fee: the date, the transaction hash, the originating payment, the token and its beneficiary, and the amount. The summary rolls those rows up to one earned total per token. Both are backed by the on-chain fee events scoped to the workspace, so the numbers reconcile against settlement rather than an internal estimate. ## Treasury Tax Exports The treasury tax surface assembles the confirmed treasury transfers a workspace sent to its counterparties into year-end tax files. It reads only `confirmed` transfers of kind `transfer` inside the requested window, using UTC year boundaries. Only stablecoin assets contribute to the USD totals, since the export assumes one-to-one stablecoin parity; a non-stable asset transfer is recorded but excluded from the tax total. The surface is gated by the `treasury` feature. | Console export | Shape | Role | | --- | --- | --- | | 1099 summary | JSON summary of reportable counterparties for a year, each with a USD total and a transaction count. | Treasury admin | | 1099 CSV | CSV, one row per reportable counterparty, with legal name, display name, tax id, tax id type, classification, total, and count. | Treasury admin | | Transactions CSV | CSV, one row per confirmed transfer in a date range, optionally filtered to a single counterparty. No aggregation. | Treasury viewer or higher | ### Who Is Reportable A counterparty appears in the 1099 summary and the 1099 CSV only when its stablecoin transfers for the year total at least 600 USD, the federal 1099-MISC reporting floor for non-employee compensation. A transfer that is not mapped to a counterparty, and any counterparty below the floor, is omitted from the 1099 files. Those transfers still appear in the transactions CSV, which is the unfiltered per-transfer record. Populate a counterparty's legal name, tax id, and classification so the 1099 rows carry the fields a filing needs; the tax id type is one of `ein`, `ssn`, `foreign`, or `none`. See [Treasury](treasury.html) for how counterparties are managed. ### Date Windows The 1099 routes take a single `year` and derive the UTC year boundary themselves. The transactions CSV takes an explicit `from` and `to`, where `from` is inclusive and `to` is exclusive. To pull the full 2026 calendar year, pass `from=2026-01-01` and `to=2027-01-01`. ## Exports and Scope The CSV exports are recorded actions in their own right: downloading a revenue log CSV, a 1099 CSV, or a transactions CSV writes a data-access audit event, so a report pull is itself auditable. Every reporting capability is tenant-scoped and returns only that tenant's data. A Console user without the required role or enabled feature is rejected rather than handed a filtered view. ## Related - [Audit Log](audit-log.html): the event model behind the audit report query. - [Fee Schedules](fee-schedules.html): how earned revenue is priced and accrued. - [Treasury](treasury.html): the treasury transfers and counterparties the tax exports read. - [Entitlements and Feature Gates](entitlements.html): how the revenue and treasury features are resolved. > Programmatic reporting is not part of the current customer operation manifest. Do not infer a customer endpoint from the Console capability described here. --- source: https://docs.xkova.com/roles-permissions.html # Roles and Permissions One permission registry governs every guarded action. This page lists the built in tenant roles, what each one grants, and the 134 permissions a tenant role may hold. It is generated from the registry, so it cannot drift. > XKOVA has one permission registry. Every guarded action checks a `resource.action` permission. Roles are named bundles of permissions. A tenant can also define custom roles from the same catalog. ## Least-privilege API keys In Console Team > Roles, define a custom role containing the exact operations an integration needs, then select it when creating an API key. For example, a payment and webhook integration can use `payment.read`, `payment.create`, `webhook.read`, `webhook.create`, and `webhook.delete`, with the `payment` and `webhook` resource scopes. Scopes narrow role permissions; they never grant permissions alone. A key with wildcard scope and no roles cannot read payments, escrows, or account holders. Keys receive no implicit administrator authority. Only roles owned by the current tenant and delegable by the issuer can be assigned. If quorum approval is required, changing a custom role before application requires fresh approval. ## Built in roles | Role | What it does | Grants | | --- | --- | --- | | `owner` | Full control of the tenant. Holds every non platform permission. | all non platform permissions | | `admin` | Administers the tenant. Holds every non platform permission. | all non platform permissions | | `compliance_officer` | Reviews compliance cases, holds and releases account holders, reads the audit and approval trail. | 26 permissions | | `customer_service` | Front line support. Manages account-holder profiles and contacts, reads wallets, payments and escrows, and can place and release holds. | 12 permissions | | `treasury_admin` | Configures treasury wallets and signers and runs the full transfer lifecycle. | 15 permissions | | `treasury_signer` | Drafts and approves treasury transfers and signs the on chain move. | 6 permissions | | `treasury_viewer` | Read only view of treasury wallets and activity. | 1 permissions | | `tokenize_admin` | Deploys tokens and manages the tokenization surface. | 18 permissions | | `tokenize_minter` | Mints from existing tokens. | 2 permissions | ## Role grants ### `compliance_officer` `policy.read` `policy.decision.read` `compliance_case.read` `compliance_case.flag` `compliance_case.transition` `compliance_case.assign` `compliance_case.update` `compliance_case.escalate` `compliance_case.comment` `compliance_case.sar_file` `compliance_case.close` `verification_policy.read` `verification_policy.update` `verification.holder.read` `verification.holder.approve` `verification.holder.reject` `account_holder.read` `wallet.read` `account_holder.hold` `account_holder.release` `account_holder.offboard` `report.read` `audit.read` `governance.read` `approval_group.read` `approval_request.read` ### `customer_service` `account_holder.read` `account_holder.create` `account_holder.update` `wallet.read` `payment.read` `escrow.read` `contact.read` `contact.write` `compliance_case.read` `compliance_case.flag` `account_holder.hold` `account_holder.release` ### `treasury_admin` `treasury.read` `treasury.create` `treasury.decommission` `treasury.signer.change` `treasury.threshold.change` `treasury.transfer.draft` `treasury.transfer.approve` `treasury.transfer.execute` `treasury.transfer.cancel` `treasury.counterparty.add` `treasury.counterparty.remove` `treasury.revenue.designate` `treasury.revenue.relink` `treasury.revenue.relink.sign` `treasury.tax.export` ### `treasury_signer` `treasury.read` `treasury.transfer.draft` `treasury.transfer.approve` `treasury.transfer.execute` `treasury.transfer.cancel` `treasury.revenue.relink.sign` ### `treasury_viewer` `treasury.read` ### `tokenize_admin` `token.read` `token.deploy` `token.mint` `token.burn` `token.recover` `token.role.change` `token.policy.set` `token.pause` `token.distribution.create` `token.distribution.execute` `token.authority.register` `schema.publish` `schema.rebind` `schema.bind` `rwa.field.commit` `rwa.corporate_action.create` `rwa.holder.eligibility.set` `rwa.attester.register` ### `tokenize_minter` `token.read` `token.mint` ## Permission catalog 134 tenant-grantable permissions across 33 resources. Private platform-management permissions are intentionally omitted. ### account_holder 6 `account_holder.create` `account_holder.hold` `account_holder.offboard` `account_holder.read` `account_holder.release` `account_holder.update` ### api_key 6 `api_key.create` `api_key.read` `api_key.revoke` `api_key.role_grant` `api_key.role_revoke` `api_key.rotate` ### approval 2 `approval.attest` `approval.cancel` ### approval_group 6 `approval_group.create` `approval_group.delete` `approval_group.member_removal` `approval_group.membership_change` `approval_group.read` `approval_group.update` ### approval_request 1 `approval_request.read` ### audit 2 `audit.export` `audit.read` ### billing 3 `billing.portal.manage` `billing.read` `billing.subscription.manage` ### branding 2 `branding.email_domain.manage` `branding.manage` ### break_glass 2 `break_glass.arm` `break_glass.read` ### compliance_case 9 `compliance_case.assign` `compliance_case.close` `compliance_case.comment` `compliance_case.escalate` `compliance_case.flag` `compliance_case.read` `compliance_case.sar_file` `compliance_case.transition` `compliance_case.update` ### contact 2 `contact.read` `contact.write` ### entitlement 1 `entitlement.redeem` ### escrow 3 `escrow.cancel` `escrow.create` `escrow.read` ### fee_schedule 3 `fee_schedule.commit` `fee_schedule.create` `fee_schedule.read` ### gateway 6 `gateway.activate` `gateway.conformance` `gateway.pin_cert` `gateway.read` `gateway.register` `gateway.repoint` ### governance 2 `governance.read` `governance.requirement_change` ### payment 6 `payment.cancel` `payment.confirm` `payment.create` `payment.force_cancel` `payment.read` `payment.send_lock` ### policy 4 `policy.commit` `policy.create` `policy.decision.read` `policy.read` ### production_access 1 `production_access.request` ### ramp 2 `ramp.offramp.create` `ramp.onramp.create` ### report 1 `report.read` ### role 4 `role.create` `role.delete` `role.read` `role.update` ### rwa 4 `rwa.attester.register` `rwa.corporate_action.create` `rwa.field.commit` `rwa.holder.eligibility.set` ### schema 3 `schema.bind` `schema.publish` `schema.rebind` ### staff 7 `staff.federation.manage` `staff.federation.read` `staff.invite` `staff.read` `staff.revoke` `staff.role_change` `staff.transfer_ownership` ### tenant 4 `tenant.create` `tenant.delete` `tenant.onboard` `tenant.update` ### token 11 `token.authority.register` `token.burn` `token.deploy` `token.distribution.create` `token.distribution.execute` `token.mint` `token.pause` `token.policy.set` `token.read` `token.recover` `token.role.change` ### treasury 15 `treasury.counterparty.add` `treasury.counterparty.remove` `treasury.create` `treasury.decommission` `treasury.read` `treasury.revenue.designate` `treasury.revenue.relink` `treasury.revenue.relink.sign` `treasury.signer.change` `treasury.tax.export` `treasury.threshold.change` `treasury.transfer.approve` `treasury.transfer.cancel` `treasury.transfer.draft` `treasury.transfer.execute` ### verification 3 `verification.holder.approve` `verification.holder.read` `verification.holder.reject` ### verification_policy 2 `verification_policy.read` `verification_policy.update` ### wallet 2 `wallet.decommission` `wallet.read` ### webhook 7 `webhook.create` `webhook.delete` `webhook.read` `webhook.replay` `webhook.rotate` `webhook.test` `webhook.update` ### workspace 2 `workspace.read` `workspace.update` --- source: https://docs.xkova.com/rwa-templates.html # Asset Templates You do not have to design an asset schema from scratch. The platform ships 13 curated templates, each a published schema for a real asset vertical: its fields, their mutability, and the validation rules the vertical needs. Fork one into your workspace, adjust it, and publish, or bind a token to the platform version as is. ## How templates work A template is a published, platform-owned schema version. Forking it copies the field palette into an editable draft in your workspace; you can add, remove, or tighten fields, preview the compiled result, and publish your own version. Every template has already passed the same publish gate your own schemas go through, so a fork starts from a known-good shape. The mutability and field-type model behind these is on [Real-World Assets](rwa.html). ## The gallery | Template | Models | Notable rules | | --- | --- | --- | | Residential Real Estate | A single residential property: address, parcel id, valuation, occupancy, deed, appraisal history. | Valuation must be positive; occupancy is a closed state set; the deed document is entitlement-gated. | | Fractional / Commercial Real Estate | A commercial property split into fractional units with rent-roll income and a holder cap table. | The sum of all fractional holders' balances must exactly equal the declared total units; net operating income is a derived, read-only value. | | Private Credit | A direct loan: principal, rate, repayment schedule, payment ledger, covenant state. | Outstanding principal can never exceed principal; covenant state is a closed set (performing, watch, default, workout, repaid). | | Trade Invoice | A factored trade-finance invoice: amount, currency, debtor, due schedule, settlement ledger. | The debtor is a relationship to an identity token; currency and status are closed sets. | | Carbon Credit | A registry-issued offset: project, registry, vintage, serial range, tonnage, retirement status. | Registry is a closed set of recognized registries; vintage year is bounded; the MRV report is entitlement-gated. | | Fine Art | A collectible artwork: artist, title, year, condition imagery, authentication, provenance history. | Nearly everything is frozen at issuance; provenance is an append-only log; authentication documents are entitlement-gated. | | Luxury Collectible | A watch, handbag, or similar: maker, serial range, grade, custody state, appraised value. | Grade and custody state are closed sets; appraised value must be positive. | | Music / IP Royalty | A royalty stream: work, ISWC, rights type, territory, rate schedule, earnings history, split holders. | The royalty term exposes a derived current value from its schedule; the royalty-split cap table must sum above zero. | | Fund Shares / Private Equity | Units in a private fund: status, NAV per share, NAV history, capital-call schedule, required accreditation, holders. | The only template with a schema-level transfer rule: every platform-relayed transfer is denied unless the recipient is currently accredited. See the note below. | | Debt Instrument (Bond / Muni) | A bond: issuer, CUSIP, face value, coupon schedule, maturity, rating, redemption status. | Rating and redemption status are closed sets; face value must be positive. | | Commodity / Warehouse Receipt | Stored physical commodity: type, quantity, unit, grade, warehouse, custody status, inspections. | The warehouse is a relationship to an identity token; unit of measure, grade, and custody status are closed sets. | | Precious Metals | Bullion: metal, refiner, serial range, weight, fineness, assay certificate, custody status. | Fineness is bounded at parts-per-thousand (at most 1000); the assay certificate is required and entitlement-gated. | | Generic RWA | A minimal catch-all for asset types without a vertical: name, description, class, valuation, status, one document. | Deliberately unopinionated and data-only (no image field), the baseline to extend when nothing else fits. | ## The template that gates transfers > Fund Shares / Private Equity declares a transfer-eligibility rule in the schema itself: a transfer of a fund-share token is denied by the [transfer-eligibility door](holder-eligibility.html) unless the recipient's accreditation is current (verified or self-attested, and not expired). This models a Reg-D-style resale restriction, and it travels with the schema: any token bound to the template enforces it with no per-holder configuration. It is also the pattern to copy when your own asset class needs a transfer rule. ## What the field types give you Across the gallery you can see the field palette doing vertical-specific work: money and number fields carry range constraints, state and enum fields enforce closed vocabularies, schedule fields expose a derived current value, relationship fields bind to other tokens (a debtor, a warehouse), document and media fields carry entitlement-gated visibility, append-log fields hold histories that can never be rewritten, and holder-positions fields tie the record to the on-chain cap table. Each field's mutability (frozen, versioned, append-log) is enforced on every write, as described in [Real-World Assets](rwa.html). ## Related - [Real-World Assets](rwa.html): the schema model, mutability, and the public resolver. - [Tokens and Assets](console-tokens.html): forking and publishing from the console's schema builder. - [Holder Eligibility and Transfers](holder-eligibility.html): how the transfer rule in a template is enforced. - [Cap Tables and Distributions](cap-tables.html): the holder positions several templates track. > For the template list, fork, and publish operations, see the [operations list](api-reference.html) or the [interactive reference](reference.html). --- source: https://docs.xkova.com/rwa.html # Real-World Assets A real-world asset is a token plus a rich, structured record: an appraisal, a deed reference, a holder roster, a stream of readings. The engine adds a metadata plane on top of ERC20X, ERC721X, and ERC1155X where that record is defined by configuration rather than custom code, and is immutable by system design. ## Asset schemas: config, not code An asset schema describes the fields an asset carries: their types, whether they are required, and the rules they must satisfy. You build a schema from a field palette (or fork a curated template), preview the compiled result, and publish it. There is no custom code to write or deploy for a new asset type. - Start from a platform template or build a palette of fields. - Compile-preview shows the result and whether it would pass the publish gate, without persisting. - Publish turns the draft into a fixed, versioned schema. A published version is frozen. A token is then bound to a published schema version. Once bound, the token's metadata reads against that schema. To move a token to a later schema version, you go through an attested rebind, so a schema change is deliberate and recorded rather than silent. ``` flowchart TD A[Fork a curated template or build from the field palette] --> B[Compile preview, no write] B --> C[Publish: a frozen, versioned schema] C --> D[Bind the token to the published version] D --> E[Commit field values under each field's mutability rule] E --> F[Public resolver serves the tokenURI metadata] C -. a later version ships .-> G[Attested rebind ceremony] G --> D ``` ## Field mutability Each field declares how it may change after it is first set. The engine enforces that rule on every write, through one ingestion path that every writer (operator edit, external attestation, automated feed) goes through. | Mutability | Behavior | Fits | | --- | --- | --- | | `frozen` | Set once, then locked. A later change is rejected. | A deed reference, an issue date, a fixed identifier. | | `versioned` | Each change appends a new version. Old versions remain readable. | An appraisal value, a status that updates over time. | | `append-log` | Each write adds an entry to an append-only log. Nothing is overwritten. | An inspection history, a stream of facts or attestations. | A write is validated against the field's compiled schema fragment and its constraints before it commits. A value that fails validation, or a change to a frozen field, is rejected. > Not every field kind is written through the generic field-commit call. Media attachments and time-series data points each have their own dedicated write operation (a document confirm and an observation append, respectively); committing one through the generic path is rejected with a pointer to the correct operation. Per-holder position snapshots are not written at all: they are on-chain, rebuilt from the cap-table replay. Relationships to other tokens or holders do commit through the generic field path; the dedicated relationships surface is read-only. ## Immutable by design The record is immutable by system design, not by convention. A published schema version is frozen, a frozen field cannot be rewritten, and versioned and append-log history is never overwritten in place. The result is an asset record where the full history is reconstructable and tamper-evidence is built in. The provenance and reconciliation surfaces sit on top of this; see [Provenance and Transparency](provenance.html). ## The public resolver and tokenURI The engine serves a public metadata document, the target a marketplace or explorer fetches off the token's `tokenURI` (and the per-instance `uri` for an ERC-1155 id). It needs no API key: the per-field public or gated policy is itself the access control. - The document carries the standard fields (name, description, image, attributes) at the top level, with the rich asset data nested underneath. - A gated field is projected only as a reference (a content hash and a document reference), never as the underlying bytes, so a private field stays private while still being verifiable. - It never returns not-found for a token that ever existed. A burned token returns its last record marked as burned. - When a token has no committed image and no collection-default image, and its schema's image field opts into generated placeholders, the resolver serves a deterministic, brand-aware placeholder image; otherwise the image field is omitted. > Beyond metadata, a real-world asset can carry a cap table, distributions, corporate actions, and holder eligibility. See [Cap Tables and Distributions](cap-tables.html), [Corporate Actions](corporate-actions.html), and [Holder Eligibility and Transfers](holder-eligibility.html). For the exact operations, see the [interactive reference](reference.html) or the [operations list](api-reference.html). --- source: https://docs.xkova.com/sandbox-scenarios.html # Sandbox Scenarios Your integration's hardest code paths are the failure paths, and the sandbox lets you force every one of them deterministically: a screening block, a compliance review, a failed verification, a stalled ramp, a partner outage, a held approval. Every lever on this page works only in sandbox; production rejects them outright. ## Screening: allow, review, block Value-moving create calls accept an optional `sandbox_scenario` body field with the value `allow`, `review`, or `block`. The compliance pipeline runs exactly as it does in production; only the screening decision is forced. | Value | What happens | | --- | --- | | `allow` | The screen passes and the payment proceeds to signing. | | `review` | The payment holds in a pending compliance state and a compliance case opens for an operator to triage in the console, exactly the flow your team will work in production. | | `block` | The screen blocks (a sanctions-style hit) and the payment lands in the terminal `blocked` state (a compliance block). No value moves and nothing reaches the chain. The granular `status` is `blocked` while `status_canonical` rolls up to `failed`, so failure handling that keys on `status === 'failed'` will not catch compliance blocks. | The field is accepted on payment create, escrow create, and the gasless token-transfer operations. Sending it in production returns a validation error, so a copy-pasted test request can never influence a live compliance decision. ## Verification: pass, fail, manual review Creating a verification session returns a hosted flow URL. In sandbox that flow is a simulator offering named demo subjects, each with a fixed outcome, so you can drive every verdict end to end, including the signed webhook back to XKOVA and the resulting status change on the account holder: | Flow | Subject | Outcome | | --- | --- | --- | | KYC | `avery-approved` | Verified. | | KYC | `riley-rejected` | Rejected (unreadable document). | | KYC | `morgan-manual` | Manual review (name mismatch); the subject reads in progress until an operator decides. | | KYB | `acme-llc` | Verified. | | KYB | `shell-co` | Rejected (sanctions hit). | | KYB | `gamma-partners` | Manual review (incomplete documents). | ## Ramps: stalls, rejections, outages Funding and off-ramp requests use the workspace's conversion partner. Sandbox workspaces are automatically bound to XKOVA's sandbox conversion partner, with no profile header or partner setup. The `X-Sandbox-Scenario` header forces that partner's behavior: | Scenario | What happens | | --- | --- | | absent or `success` | The happy path. A mint settles synchronously; a burn walks submitted, pending settlement, fiat in flight, settled on short timers so you can watch each state. | | `scenario-a-pre-issuance-fail` | The partner rejects the request synchronously with a compliance block, before any value moves. | | `scenario-b-awaiting-institution-credit` | An off-ramp stalls at fiat in flight and never settles, modeling a bank credit that does not post. Use it to test your stuck-request handling. | | `x-partner-outage` | Every call touching the partner returns a partner outage error, modeling a full provider outage. | Production rejects `X-Sandbox-Scenario`; a sandbox request never lets the caller select a production partner. ## Scenario Outcomes by Value Each `X-Sandbox-Scenario` value drives a distinct, deterministic outcome. Values are case insensitive, and an unrecognized value is rejected with a validation error rather than silently ignored. The table names the observable outcome and HTTP status each value produces. | Header value | Outcome | Status | Customer result | | --- | --- | --- | --- | | absent or `success` | The happy path. A funding request accepts and settles; an off-ramp walks its states through to settled. | `202` on create | none | | `scenario-a-pre-issuance-fail` | A funding request is rejected synchronously with a compliance block, before any value moves. | `403` | `compliance_blocked` | | `scenario-b-awaiting-institution-credit` | An off-ramp is accepted, then stalls while waiting for institution credit and never settles. | `202` on create and on subsequent visibility checks | Accepted but intentionally not settled | | `x-partner-outage` | Every call touching the partner fails, modeling a full provider outage. | `503`, retryable, honors the `Retry-After` header | Retryable provider-unavailable response | ## Testnet assets and funding Every supported testnet exposes two distinct six-decimal assets: | Asset | Issuer | How to acquire it | | --- | --- | --- | | `dUSDC` | XKOVA demo token | Create a normal funding request with `stablecoin: "dUSDC"`, or call the chain's public demo-faucet contract directly. The sandbox conversion partner mints real on-chain tokens and observes the normal request lifecycle. | | `USDC` | Circle Testnet | Use [Circle's faucet](https://faucet.circle.com/) or a pre-funded test wallet. XKOVA lists the token as supported but cannot mint it. Verify the exact chain address against Circle's [canonical USDC contract list](https://developers.circle.com/stablecoins/usdc-contract-addresses). | The sandbox partner accepts only `dUSDC`. It never wraps or substitutes Circle Testnet `USDC`. Select the exact asset by chain, symbol, and contract address from `GET /v1/workspace-tokens`; the `dUSDC` row also returns its exact `sandbox_faucet_contract_ref`. The partner's `dUSDC` funding is capped per destination wallet per day; exceeding the cap returns `sandbox_ramp_daily_cap_exceeded`. ## Held approvals No lever needed: every fresh workspace seeds owner-approval requirements on its six highest impact actions, so performing one (creating the first API key, committing the authorization policy, changing a treasury signer, adding a counterparty) returns a held response you can drive through the attest and re-submit flow. See [Policies and Quorum Governance](policies-quorum.html) for the full journey. ## Related For the base sandbox setup, see the [Quickstart](getting-started.html). For what screening and verification enforce, see [Screening](screening.html) and [Identity Verification](verification.html). For ramp mechanics, see [Funding and Off-ramp](off-ramp.html). --- source: https://docs.xkova.com/screening.html # Screening (KYT and BSA) Every transaction on the rails is screened before it is signed. An always-on Bank Secrecy Act (BSA) sanctions baseline runs on every transfer, and know your transaction (KYT) risk scoring plugs in as an upper tier through the same swappable provider adapter, so the institution screens with the vendor it already trusts and the vendor can change without changing the flow above it. ## What screening gates Screening sits in the compliance pipeline in front of money movement. Before a transfer is signed and relayed, the addresses and the transaction are screened, and the result determines whether the transfer proceeds, pauses for review, or is blocked. This is the orchestration model from [Integrations and Orchestration](integrations-overview.html) applied to compliance: the provider is an adapter behind a stable interface, and the pipeline above it does not change when the provider does. ## The two checks | Check | What it asks | | --- | --- | | BSA sanctions | Does an address match a sanctions list (for example an OFAC hit)? Always on, for every transfer. | | KYT | How risky is this transaction, given the counterparties and on chain history? An upper-tier check available to tenants with the full-screening feature; the vendor is provisioned by XKOVA. | ## Decision outcomes A screen returns a decision with a risk score and reason codes. The pipeline acts on the decision: | Decision | Effect | | --- | --- | | Allow | The transaction proceeds to signing and settlement. | | Review | The transaction is held for an operator to triage in the console. | | Block | The transaction is stopped, for example on a sanctions match. | A review outcome surfaces as a compliance case an operator triages, holds, or releases. How held actions clear, and how high blast actions are gated, is covered in [Policies and Quorum Governance](policies-quorum.html). ## Sanctions list updates Sanctions lists change, and screening always reflects the provider's current list: a list update takes effect immediately, so an address screened moments before the update is re-evaluated against the new list on its next screen. As defense in depth, a screening result is also never trusted beyond a short lifetime, so even a missed update cannot leave a stale result standing for long. ## Example vendors The screening adapter is interchangeable. The always-on sanctions baseline runs on Chainalysis screening with an OFAC list fallback; upper-tier KYT vendors such as TRM Labs plug in through the same adapter when configured. The institution screens with one provider, and moving to another is an adapter change, not a change to how the rails enforce screening. In the sandbox, deterministic scenarios produce allow, review, and block outcomes so you can exercise each compliance path before going live; see [Sandbox Scenarios](sandbox-scenarios.html). ## Related - [Policies and Quorum Governance](policies-quorum.html): how held and high blast actions clear. - [Identity Verification](verification.html): the KYC and KYB side of compliance. - [Transfers and Settlement](transfers.html): where a passing screen leads next. --- source: https://docs.xkova.com/sdk-overview.html # SDK Overview The TypeScript SDK is the supported server-side client for XKOVA customer operations. Its generated types, resource methods, API reference, and package artifact share one exact customer operation manifest. The reviewed manifest entries are authoritative. ## Installation ``` npm install @xkova/sdk@alpha ``` Node 22 or newer is required. Use the package from a trusted backend, BFF, or server-side worker. Never put a tenant API key in browser code. ## Build with a coding assistant Both packages include a `llms.txt` integration guide. Give your coding assistant that file, the package README, and the installed declarations. The installed version determines available methods; live documentation may describe a newer release. The guide maps capabilities to resources and covers production signing, confidential member sessions, durable stores, idempotency, and settlement. For more context, use the [documentation index](llms.txt), [full documentation text](llms-full.txt), and [customer OpenAPI](data/openapi-customer.json). Console concepts do not imply customer SDK methods. ## Thirdweb and dependencies The server SDK's only direct runtime dependency is `viem`, used for offline RWA signature verification. The browser companion has no runtime dependencies. Neither package imports Thirdweb. XKOVA uses Thirdweb in its MPC wallet infrastructure and controlled signing application. Integrators do not need to install Thirdweb or supply its credentials to use these packages. ## Create a tenant client ``` import { XKOVAClient } from '@xkova/sdk'; const client = XKOVAClient.forTenant({ baseUrl: 'https://sandbox-api.xkova.com', apiKey: process.env.XKOVA_API_KEY, timeoutMs: 10_000, }); const chains = await client.chains.listAvailable(); const assets = await client.workspaceTokens.list(); ``` The API key determines tenant, workspace, and environment. It is the bearer authorization credential for every customer API request. Construct a separate client for each workspace key. The SDK does not publish partner-management, management Console, API-key lifecycle, controlled browser credential steps, provider administration, or browser capability operations. The customer-manifest member-auth operations are the supported confidential server protocol used by `@xkova/sdk/member-bff`; they are not exposed as a browser or raw-token interface. ## Bearer authorization and production request signing Every production workspace mutation requires both the bearer API key and an active asymmetric signing credential bound to the same API principal. The signing credential adds proof of possession and never replaces bearer authorization. Configure the credential UUID, registered algorithm, and an async callback that delegates to your KMS, HSM, or key provider. The private key never enters the SDK. ``` import { XKOVAClient } from '@xkova/sdk'; const client = XKOVAClient.forTenant({ baseUrl: 'https://api.xkova.com', apiKey: process.env.XKOVA_API_KEY, requestSigner: { credentialId: process.env.XKOVA_SIGNING_CREDENTIAL_ID, algorithm: 'ecdsa-p256-sha256', async sign(input) { return customerKeyProvider.sign(input); }, }, }); ``` P-256 callbacks return 64-byte IEEE P1363 signatures. Ed25519 callbacks return 64 bytes. RSA callbacks use RSA-PSS with SHA-256, MGF1 SHA-256, and a 32-byte salt. XKOVA pins the algorithm to the credential resolved by `keyid` and omits the RFC 9421 `alg` parameter, because RFC 9421 does not register an `rsa-pss-sha256` value. Signature lifetimes may be 30 through 60 seconds and default to 45. The SDK emits `Signature-Input` and `Signature` using XKOVA's strict HTTP Message Signatures profile (RFC 9421). For a request body it emits the SHA-256 `Content-Digest` field defined by RFC 9530. The signature covers the exact final URL authority, encoded path and query, bearer authorization, body digest and content type, idempotency key, and all present routed or member-context headers. It serializes a JSON body once, signs those UTF-8 bytes, and sends the same string. The API canonicalizes the incoming `Host` authority and requires it to match the configured public API origin before signature verification. A request delivered through another authority is rejected even when every other covered component is unchanged. | Request | Required credentials | | --- | --- | | Sandbox read or mutation | Bearer API key. Mutations are additionally signed only when a signer is configured. | | Production read | Bearer API key only. | | Production mutation | Bearer API key plus RFC 9421 asymmetric request signature. | A production mutation without a signer fails before fetch. Each call uses a random nonce and a fresh short-lived signature. Explicit retries reuse the same caller-owned idempotency key and receive a new signature. Signing-credential registration and lifecycle remain private management operations and are not exposed by the customer SDK. ## Customer namespaces The client groups supported calls under `accountHolders`, `branding`, `chains`, `contacts`, `escrows`, `feeSchedules`, `integrationGateways`, `onboarding`, `paymentRequests`, `payments`, `ramps`, `rwa`, `tenant`, `tokenization`, `transactions`, `wallets`, `webhooks`, `workspaceTokens`, and `workspaces`. ``` const payments = await client.payments.list({ limit: 20 }); const contacts = await client.contacts.list({ limit: 20 }); const capTable = await client.rwa.capTable('ctk_...'); ``` ### Hosted application administration The tenant resource exposes the customer-controlled Hosted application lifecycle. XKOVA owns the fixed identity-provider security profile. Your server selects an existing same-workspace API principal, application name, enrollment mode, exact origins and callbacks, default status, and enabled state. ``` const application = await client.tenant.createHostedApplication({ name: 'Acme Member App', api_key_id: 'key_...', enrollment_mode: 'invite_only', is_default: true, }); await client.tenant.createAllowedOrigin({ application_id: application.id, origin: 'https://app.example.com', }); await client.tenant.createRedirectUrl({ application_id: application.id, purpose: 'authorization_result', url: 'https://app.example.com/api/auth/callback', }); const applications = await client.tenant.listMemberAuthApplications(); await client.tenant.updateHostedApplication(application.id, { enrollment_mode: 'open', }); await client.tenant.disableHostedApplication(application.id); ``` The response includes the non-secret publishable application selector. These methods never expose identity-provider administrative configuration, provider tokens, provider client secrets, or signing keys. ## BFF member context Use the server-only `@xkova/sdk/member-bff` entry point for authorization transactions, provider-proof exchange, secure refresh rotation, exact-session logout, safe browser projections, CSRF, cookie directives, and framework-neutral session, authorization-transaction, and direct proof-exchange transaction store contracts. Account-holder `self` methods require an opaque XKOVA member-session ID and the exact non-secret publishable application key recovered from the BFF's server-side session. The one-time provider proof is consumed only during exchange and is never sent downstream. The session stays in the customer BFF, and the methods never accept an arbitrary account-holder ID. ``` import { XKOVAMemberBffClient } from '@xkova/sdk/member-bff'; const memberAuth = new XKOVAMemberBffClient({ baseUrl: 'https://sandbox-api.xkova.com', apiKey: process.env.XKOVA_API_KEY, publishableKey: process.env.XKOVA_PUBLISHABLE_KEY, }); const start = await memberAuth.beginHostedAuthorization({ browserOrigin: 'https://app.example.com', redirectUri: 'https://app.example.com/api/auth/callback', transactionStore, }); ``` ``` const balance = await client.accountHolders.selfBalance({ memberSessionId: session.sessionId, memberAuthPublishableKey: session.applicationData.memberAuthPublishableKey, }); await client.accountHolders.ensureSelfWalletProvisioning({ memberSessionId: session.sessionId, memberAuthPublishableKey: session.applicationData.memberAuthPublishableKey, }); ``` The provisioning request has no body and accepts no account-holder, provider identity, address, chain, wallet, credential, or registration selector. Raw wallet derivation challenges and provider credentials are not part of the customer SDK. The customer owns its application-session persistence, framework wiring, FI-specific identity integration, deployment environment, and the encryption and access controls for stored session and pending proof material. Callback recovery without provider-code re-redemption is guaranteed only after the authorization store durably persists `stagedProviderExchange`. From that point, callback recovery reuses the stable XKOVA operation attempt and application-session reference. Before durable staging, an ambiguous provider response or staging-store acknowledgement may invoke the provider exchange again and must be resolved by the provider adapter or a fresh authorization transaction. Direct proof exchange durably stages the supplied proof with its stable operation attempt before the first XKOVA exchange. Preserve callback state for failures known to occur after durable staging. Durable application cookies use Secure, HttpOnly, host-only `__Host-` naming and `SameSite=Strict`. A separate short-lived `SameSite=Lax` flow cookie is used only where an OAuth or OIDC callback requires it. See [Authentication](auth.html). ## Reliability and helpers | Feature | Use | | --- | --- | | `timeoutMs` and `AbortSignal` | Bound transport calls and cancel workflow polling. | | `withRetry()` | Apply caller-owned retry policy without automatic write retries. | | `withIdempotency()` | Create a stable key to reuse for the same logical write. | | `paginateCursor()` | Walk a cursor-paginated list. | | `verifyWebhookSignature()` | Verify a webhook signature over the raw request body. | ## Errors and types Non-success responses throw `XKOVAApiError`, including the public error code and correlation ID. Import request and response types from `@xkova/sdk/types`. See the [interactive customer reference](reference.html) for exact request and response schemas. --- source: https://docs.xkova.com/sdk-workflows.html # Workflows Workflow methods compose customer-server operations and preserve the same typed errors, idempotency, deadlines, and cancellation behavior as their underlying resource methods. ## Member Payment and Escrow Signing Member signing starts in `@xkova/sdk/member-bff`, not a server workflow. Use `beginMemberAction` and `completeMemberAction` with an authenticated member session and the exact pending resource. See the [member-action walkthrough](embedded-member.html#member-actions) for application registration, payer setup, fresh authorization, callback handling, and webhook verification. Use the documentation host matching your deployment when validating an unreleased staging build. ## Token deploy and mint `tokenDeploy` creates an ERC-20, ERC-721, or ERC-1155 token and polls until the token reaches a terminal state. `tokenMint` submits a mint and polls the token record. ``` const token = await client.workflows.tokenDeploy( 'erc721', deployRequest, { idempotencyKey: crypto.randomUUID(), poll: { attempts: 30, intervalMs: 2_000 }, }, ); ``` ## Off-ramp initiation `offRampInitiate` requires the selected chain reference, resolves that chain's burn address, then creates the off-ramp request. Use the numeric EVM chain ID as a string, such as `43114`, from the chain catalog. It returns before the member's out-of-band burn settles. ``` const { request, burnAddress } = await client.workflows.offRampInitiate( '43114', offRampRequest, { idempotencyKey: crypto.randomUUID() }, ); ``` ## Sandbox wallet funding `client.ramps.fundSandboxWallet` creates a normal funding request for `dUSDC` and preserves the same idempotency, compliance, and settlement behavior as `POST /v1/funding-requests`. The workspace automatically selects the sandbox conversion partner; callers do not choose a ramp profile. ``` const request = await client.ramps.fundSandboxWallet( { account_holder_id: accountHolderId, fiat_amount: '100.00', fiat: 'USD', chain: { kind: 'evm', chain_id: 84532 }, source_bank_account_id: sourceBankAccountId, pool_bank_account_id: poolBankAccountId, destination_address: walletAddress, }, { idempotencyKey: crypto.randomUUID() }, ); ``` This method cannot fund Circle Testnet `USDC`. Acquire issuer-controlled Testnet `USDC` through Circle's public faucet or a pre-funded wallet, then use its exact chain and contract identity from `GET /v1/workspace-tokens`. ## Cancellation Pass an `AbortSignal` to stop a request or poll loop during server shutdown or when the owning application request is cancelled. ``` const controller = new AbortController(); await client.workflows.tokenMint(tokenId, mintRequest, { idempotencyKey: crypto.randomUUID(), signal: controller.signal, }); ``` ## Production transport Workflow mutations use the tenant client's bearer API key and the same RFC 9421 request signer as direct resource mutations. A production workflow fails before its first mutation when no signer is configured. Keep one idempotency key for each logical mutation across an explicit retry; every new request invocation receives a fresh nonce and signature. Production reads remain bearer-only. ## Signing boundaries The SDK workflow client does not drive browser signing sessions, accept member keys, or publish purpose-bound signing capabilities. Use the documented hosted or companion member flow for member signing. Customer-server money movement remains available through typed resource methods. ## Failure handling A terminal failed, reverted, cancelled, expired, or rejected status stops polling and throws. Exhausting the configured attempts also throws. Use `withRetry` only when your application has an explicit retry policy, and reuse the same idempotency key for the same logical write. --- source: https://docs.xkova.com/security.html # Security Model XKOVA is an orchestration layer, not a custodian. Human wallet private-key material remains non-exportable in the configured MPC provider, and the XKOVA Relayer can submit a transfer only after the contract receives a valid owner signature. The Thirdweb custom-JWT signing authority is still security-critical because it can bootstrap a general provider signing session. XKOVA enforces business purpose at its capability, frozen-intent, signature-acceptance, and on-chain boundaries. ## The non-custodial property Every value transfer requires a valid signature from the configured owner address or Safe quorum. In the normal product flow, the authorized person reviews and signs an EIP-712 intent through the configured wallet, and the XKOVA Relayer wraps the signed instruction in the contract's meta transaction entrypoint and pays gas to submit it. The contract recovers the signer on chain and rejects anything that does not match. Because the signature is the on-chain authorization, the platform never receives or exports the MPC private-key material. Thirdweb custom-JWT authentication can nevertheless establish a provider session capable of producing signatures. XKOVA therefore authorizes a frozen purpose before bootstrap, keeps the browser session ephemeral, and accepts only a signature that matches the exact intent and signer. See [the Non-custodial Model](non-custodial.html) for the mechanics. > The relayer is the on-chain sender and pays gas, but it cannot originate a transfer. Without the valid owner-address or Safe-quorum signatures required by the contract, the call reverts. The relayer moves nothing on its own. ## What the platform can and cannot do | The platform can | The platform cannot | | --- | --- | | Build and freeze an intent, then present it for signing. | Receive or export the MPC private-key material. | | Submit an accepted owner-address or Safe-quorum instruction and pay gas. | Move funds without the signatures required by the contract. | | Apply policy and screening before relaying. | Extract a member's private key. | | Reject signatures outside the authorized purpose. | Make Thirdweb enforce XKOVA's business purpose on a connected custom-JWT session. | ## Threat model - **Platform compromise.** XKOVA never receives or exports MPC private-key material. However, compromise of the Thirdweb custom-JWT signing authority plus use of the public client can establish a general provider signing session and produce owner signatures. Protect that authority as signing-critical. XKOVA purpose limits are separate acceptance-layer controls, so incident response must stop JWT issuance, reject affected signatures, revoke capabilities, and disconnect provider sessions where supported. - **Relayer compromise.** The relayer submits signed instructions and pays gas. Compromising the relayer alone does not provide the owner-address or Safe-quorum signatures the contract requires. - **Stolen API key.** A workspace API key is bearer authorization for customer server-to-server calls. A production mutation also requires an RFC 9421 signature from the active asymmetric credential bound to that API principal, so the bearer key alone cannot submit a production write or mint the separate Thirdweb custom-JWT bootstrap. Revoke and rotate immediately if a key is exposed. - **Stolen request-signing key.** The asymmetric credential adds proof of possession but grants no authority by itself. The server requires the separately bound bearer API key and rejects a signing credential registered to another principal. Revoke and rotate the signing credential independently if its private key is exposed. - **Stolen application cookie.** The browser cookie names only a customer BFF session and carries no reusable XKOVA or provider credential. The BFF validates Origin and CSRF, enforces idle and absolute expiry, and can revoke the exact XKOVA session. The wallet key remains separate. - **Replayed request.** Idempotency keys make a retried write safe: a key reused with a different body is rejected with a conflict error, so a replay can never submit a different operation under a retried key. See [Idempotency](idempotency.html). ## Customer API request credentials The bearer API key authorizes every customer API request. Production mutations additionally use XKOVA's strict RFC 9421 HTTP Message Signatures profile and RFC 9530 `Content-Digest` for a body. Production reads remain bearer-only; sandbox mutations are signed only when configured. The customer-held signing key can be P-256 P1363, Ed25519, or RSA-PSS with SHA-256, MGF1 SHA-256, and a 32-byte salt, and stays in the customer's KMS, HSM, or key provider. XKOVA selects the registered credential algorithm by `keyid`, not from a client-controlled RFC 9421 `alg` parameter. See [SDK Overview](sdk-overview.html) and [Key Management](key-management.html). ## Authorization and governance Beyond the signature, every mutation is authorized against a single permission registry, and high blast actions are gated by an approval quorum with separation of duties. Every financial or governed mutation records the authority exercised. These controls run in front of the relayer, so a denied or unapproved action never reaches the chain. The posture is secure by default: a fresh workspace is seeded with owner-approval requirements on six high impact actions (governance changes, ownership transfer, policy commits, API key creation, signer changes, counterparty additions), and the gate on governance changes itself cannot be removed. See [Roles and Permissions](roles-permissions.html), [Policies and Quorum Governance](policies-quorum.html), and [the Audit Log](audit-log.html). ## Auth boundaries The customer server authenticates with its workspace API key. A member-initiated BFF request adds an exact XKOVA member session created from a verified provider proof. Provider credentials and XKOVA session material stay server-side. Certain high impact actions additionally require a fresh purpose-bound step-up proof. The full product-specific entry points are in [Authentication](auth.html). ## Secrets hygiene - Never commit API keys to source, and never log the Authorization header. Keep tenant keys on a server or trusted worker, not in a browser. - Each key is shown once at creation; only a prefix and a hash are stored. Rotation returns a replacement once and revokes the old key. - Never send provider proofs, XKOVA session IDs, or refresh credentials to browser JavaScript. Store them behind the customer's first-party application session. - Member MPC private-key material remains non-exportable at the wallet provider and is never exposed to the platform or integrators. Treat custom-JWT signing authority as signing-critical because it can bootstrap a general provider session. See [Key Management](key-management.html). ## Compliance posture Items defined by the business rather than by the API (formal certifications, data retention windows, support tiers, and service levels) are available on request and are defined by the business. See [Support](support.html) for how to reach the team. ## Related See [Key Management](key-management.html) for how keys are held and rotated, [Screening](screening.html) and [Identity Verification](verification.html) for the compliance controls that run before a transfer, and the [interactive reference](reference.html) for the operations behind each control. --- source: https://docs.xkova.com/support.html # Support XKOVA support helps developers and startups integrate and operate stablecoin rails. Capture the error code, correlation ID and request timestamp when reporting a problem. ## Get Help Visit [XKOVA Support](https://support.xkova.com/) to ask the AI assistant, open a ticket, or request a person. The assistant uses public documentation and the XKOVA website and cites its sources. Check [XKOVA System Status](https://status.xkova.com/) for current service observations and incident updates. For staging integration work, use [staging support](https://support.staging.xkova.com/) and [staging system status](https://status.staging.xkova.com/). Confirm the installation before sharing diagnostics. ## Prepare a Support Request Include the installation, workspace mode, affected product, request timestamp with timezone, error `code`, `correlation_id`, and steps to reproduce the problem. Add the chain ID and transaction hash when a public-network transaction is involved. A transaction hash helps identify an operation; it does not prove account ownership or settlement success. Use the support portal or the support contact available in your workspace. Check the assistant's cited source and request a person when the answer does not resolve the issue. Support does not require you to give another person your API key or wallet credentials. ## Installation and Workspace Mode Installation identifies where the software runs. Workspace mode identifies the network tier. Every sandbox workspace uses supported testnets. Every production workspace uses supported mainnets, including workspaces on the staging installation. The two modes have separate keys and data. | Workspace Mode | Use | What to Record | | --- | --- | --- | | Sandbox | Integration testing with supported testnet assets and deterministic provider scenarios. | Sandbox endpoint, scenario if used, chain ID, error code and correlation ID. | | Production | Approved live operations on supported mainnets. | Production endpoint, timestamp, error code, correlation ID and transaction hash if available. | Production access requires approval. Sandbox availability does not grant production execution authority. Data is protected in both workspace modes. A sandbox or testnet label does not make records disposable. ## Share Diagnostics Safely Never include passwords, API keys, authorization headers, private keys, seed phrases or payment-card details. Remove personal information from screenshots and logs unless it is necessary for the request. Describe the expected and observed behavior, and include the smallest useful reproduction. ## Platform and End-User Support XKOVA support covers the integration and platform behavior. People using an application built on XKOVA contact that application's support team for account questions. Support conversations do not authorize account impersonation, policy changes, transaction signing or movement of funds. ## Service Status and Response Commitments See [System Status](system-status.html) for how service observations and incident updates should be interpreted. A healthy service observation does not establish that a particular transaction settled. This guide does not promise a response deadline, 24-hour staffing, refunds, fund recovery or a service-level agreement. Any applicable response commitment must be explicitly included in your published plan or agreement. ## Related See [FAQ and Troubleshooting](faq.html), [Errors and Correlation IDs](errors.html), [Networks and Tokens](networks.html), and [Changelog](changelog.html). --- source: https://docs.xkova.com/system-status.html # System Status Service status describes recent observations of a service. It is separate from your workspace permissions, production access and the outcome of an individual transaction. Open [XKOVA System Status](https://status.xkova.com/) for the production installation or [staging system status](https://status.staging.xkova.com/) when testing that installation. ## Read the Observation | State | Meaning | | --- | --- | | Operational | Recent checks met the configured success threshold. | | Verifying | Checks are collecting enough observations to confirm a state. | | Degraded | At least one service check met its failure threshold, or an incident is open. | | Unknown | A current observation is missing, stale or unavailable. | | Blocked | A check cannot run because an explicit prerequisite is missing. | Unknown does not establish an outage, and it must not be treated as operational. Check the observation timestamp and incident updates. A page behind a sign-in gate does not prove the protected application is healthy. ## Separate Installations and Workspace Modes Staging observations describe the staging installation. Production observations describe the production installation. Each installation can serve sandbox workspaces on testnets and production workspaces on mainnets. Confirm the installation, workspace mode and chain when investigating a problem. ## Availability Is Not Settlement A responding API does not prove payment execution, indexing, webhook delivery or on-chain settlement. Follow the operation's status and verify its final result. A transaction included in a block can still revert. Service status never grants production access, billing entitlement or authorization to execute a flow. ## Incident Updates and Email Subscribe on the status page and confirm the email subscription before receiving incident updates. Each notification includes an unsubscribe link. Incident updates describe what the team has verified; they do not promise a recovery deadline or that an individual account is unaffected. ## Report a Problem Use [Support](support.html) and include the error code, correlation ID, timestamp, installation and workspace mode. Avoid repeating a value-moving request until you have checked its outcome and the documented [idempotency behavior](idempotency.html). --- source: https://docs.xkova.com/tenancy.html # Tenants and Workspaces XKOVA isolates each institution at two levels. A tenant is the institution itself, and a workspace is an operational scope inside it. Every customer request is restricted to its authorized tenant and workspace. ## Tenant: the institution A tenant is the institution using XKOVA, for example a bank, a credit union, or a fintech. The tenant scope owns the institution's verified member bindings and directory, billing, staff, branding, and compliance policy. The institution or its authenticated channel remains the identity authority. Each tenant receives a sandbox workspace at signup and a separate production workspace after access approval, so every institution can keep test and live operations isolated. See [Supported Networks](networks.html) for how those environments map to chains. ## Workspace: the operational scope A workspace is the scope inside a tenant where day to day operations live. Operational data such as payments, escrows, treasury, wallets, fees, and member sessions is scoped to a workspace. A workspace is not a network or token configuration boundary. Its available networks and assets are derived automatically from the installation and workspace mode. A fresh workspace is also provisioned with a seeded governance baseline: owner approval is required on its highest impact actions until the owner tunes those requirements. See [Policies and Quorum Governance](policies-quorum.html). | Scope | What lives here | | --- | --- | | Tenant | Verified member bindings, billing, staff, branding, compliance policy. | | Workspace | Payments, escrows, treasury, wallets, fees, member sessions. | ## Automatic network and asset catalogs A tenant does not enable networks or tokens. XKOVA derives the workspace catalog from mode, the active chain registry, and the active asset registry. It also includes the workspace's own active TokenFactory ERC-20, ERC-721, and ERC-1155 deployments. An external asset becomes available automatically wherever its tier applies after its exact deployment's direct-send and escrow qualification passes. | Call | Returns | | --- | --- | | `GET /v1/chains/available` | Every active chain applicable to this installation and workspace mode. | | `GET /v1/workspace-tokens` | Qualified external assets on those chains plus this workspace's active TokenFactory assets. | Each asset row identifies its `chain_ref`, `source`, `asset_standard`, `permit_strategy`, `rails_eligible`, and certification state. External assets require both direct-send and escrow certification. Workspace-issued assets retain their own payment grants and eligibility checks. NFT assets appear in the catalog for holdings and asset operations even when `rails_eligible` is false. > Both catalog endpoints are read only. There are no network or token enable and disable calls, onboarding choices, or settings toggles. ## Tenant and workspace isolation Every customer request runs under its authorized tenant and workspace, and access outside that scope is rejected. A workspace-scoped key always gets strict workspace isolation, and the tenant boundary always holds on top of that, so one customer cannot use its credential to read or change another customer's data. XKOVA management and partner-management principals use separate private surfaces that are not published by the customer SDK. > Tenant and workspace scope is an authorization invariant, not a caller-supplied filter. ## How keys map to scope An integrator authenticates with a tenant API key scoped to a workspace, so its calls operate inside one workspace without being pinned to a network. Customer applications do not receive a cross-tenant management credential. XKOVA management users sign in to the private Console, and private partner-management functionality remains outside `@xkova/sdk`. - [Authentication](auth.html): API keys, bearer sessions, and federated or bring your own identity. - [Authorization and RBAC](authz.html): customer roles and permissions. ## Related For where these scopes come from when you first set up, see [Customer Onboarding](onboarding.html). For the actors that operate within a tenant, see the [Platform Overview](concepts.html). --- source: https://docs.xkova.com/token-access.html # Access and Policy A token's controls are part of the contract, not an off-chain layer. Who may hold it, who may act on it, how much may be minted, and whether it is paused are all enforced on chain. Every change runs through the token's admin Safe, so high-impact actions require its signer quorum. ## Access registry: allow and deny lists An access registry gates who may send or receive a token. A token runs in one of two modes: - **Allow list:** only listed addresses may transact. Adding an address permits it. This is the closed model for a permissioned asset. - **Deny list:** everyone may transact except listed addresses. Adding an address blocks it. A token may be deployed with a registry, or deployed open and have a registry attached later. Membership is changed through the admin Safe (add or remove addresses), and the on-chain registry reflects the change once the operation confirms. A token without a registry is open: any address may hold it. ## Roles Named roles govern who may perform privileged contract actions. The admin Safe holds the top admin role and is the administrator of every named role, so it grants and revokes the rest. The named roles come from a fixed set, for example a minter role, a burner role, a pauser role, and a recovery role. - The deploy grants the admin Safe its core roles (admin, minter, pauser). - Some actions need a role the deploy does not grant. Burning needs a burner role; the compliance recovery action needs a recovery role. Grant it through the Safe before using that action. - On chain is the source of truth. The off-chain role record is updated only after a grant or revoke confirms. ## Recovery: clawing back a blocked balance The compliance recovery action moves a blocked account's balance to the admin Safe. It is the strictest privileged action and the contract enforces three prerequisites, all of which must hold or the action reverts on chain: - The admin Safe must hold the recovery role. The deploy does not grant it, so grant it through the Safe first, as noted above. - The token must have an access registry attached. Recovery is defined only against a registry, so a token with no registry cannot recover. - The target account must currently be denied by that registry. Recovery works on a blocked account, not an active one, so recovering an account that still has access reverts. > Because recovery reverts unless all three hold, block the account first in whichever mode the registry runs: on a deny-list token, add the account to the deny list; on an allow-list token, remove the account from the allow list. Then recover. ## Policies: capped, unattended minting A mint-cap policy lets a server-controlled key mint gaslessly within a per-call amount cap, without a Safe signature each time. This is the unattended capped-minting path for automation that issues supply on a schedule or on demand. - The Safe sets the policy once: it scopes a verified server key to mint up to a fixed cap. - After that, the server key submits capped mints gaslessly. The relayer submits them; a mint above the cap reverts on chain. - The Safe can revoke the policy at any time, which removes the server key's scoped permission. > A policy never widens what a key may do beyond its cap. The cap is enforced on chain, so a compromised server key still cannot mint above the policy limit. ### How a policy mint is signed `POST /v1/tokenization/tokens/{id}/policy-mint` builds the operation and returns `module_typed_data` and `module_tx_hash`. The scoped server key signs `module_typed_data` with `eth_signTypedData` (EIP-712). Do not sign `module_tx_hash` with `personal_sign` (EIP-191): personal_sign prefixes the digest, recovery produces a different address, and the submission is rejected with a signer mismatch. This is a deliberately different shape from a Safe-path mint. A Safe mint signs a Safe transaction (`typed_data`, keyed by `safe_tx_hash`) against the admin Safe's signer quorum. A policy mint signs a module transaction against the token's on-chain policy contract (a Zodiac Roles module, the Safe-ecosystem permissions component), whose EIP-712 domain is the policy contract itself; the module enforces the amount cap during execution, so no Safe quorum is needed per mint. `module_tx_hash` is the EIP-712 digest and the operation's replay guard. Submit the signature to `POST /v1/tokenization/tokens/{id}/operations/{operation_id}/module-signature`; the relayer then executes the mint gaslessly. The generic `.../signatures` endpoint is the Safe-path (SafeTx) signature route and rejects a policy-mint operation, because a policy mint's ModuleTx signature carries only the raw signature (no Safe-owner `signer` field) and is verified against the scoped authority's key, not the Safe owner set. For the exact typed-data fields, see the [interactive reference](reference.html). ## Pause: the circuit breaker Pausing flips a global circuit breaker on the token. While paused, every transfer, mint, and burn reverts on chain. Pause and unpause run through the admin Safe and require the pauser role (granted at deploy). Use it to freeze a token during an incident, then unpause to restore normal operation. ## Everything routes through the Safe Attaching a registry, changing membership, granting or revoking a role, setting or revoking a policy, and pausing all build a Safe transaction. The Safe's N of M owners sign the typed data, the platform relayer submits it, and XKOVA records success only after the transaction confirms on chain. A reverted-but-mined transaction is never treated as success. > For the exact request shapes, the role names, and the policy parameters, see the [interactive reference](reference.html) or the [operations list](api-reference.html) under the tokenization tag. For the quorum and governance model, see [Policies and Quorum Governance](policies-quorum.html). --- source: https://docs.xkova.com/token-authorities.html # Token Authorities A token's roles (minter, burner, pauser, admin) are held by addresses, not by users in a database. A token authority is a registered address the workspace trusts to hold those roles. This page covers the two axes that describe an authority (its kind and its purpose), how you prove control of a server key before the platform trusts it, the capped minter-only service account, and the read and write surfaces for a token's control contracts. ## The Two Axes: Kind and Purpose Every authority is described by two independent choices. Its **kind** is what the address is, and it fixes how control is proven. Its **purpose** is what the address is allowed to hold. An authority is **workspace scoped**: you register it once, and it is usable across every token in the workspace. Managing authorities is tenant scoped, requires a step-up (AAL2) authentication for mutations, and requires the `tokenize_admin` role (tenant admins inherit it). | Kind | What it is | Control | Verification | | --- | --- | --- | --- | | `server_key` | A customer-controlled EOA (a single key, 1-of-1). | The private key. | Registered unverified, then proven by signing a server challenge (proof-of-possession). | | `safe` | A Gnosis Safe whose N-of-M owner set is its control. | The owner quorum. | Verified at registration; there is no proof step. | | Purpose | Meaning | May hold | | --- | --- | --- | | `operator` (default) | A controller of the token. | Any token role, including admin. | | `service_account` | An integrator-held automation EOA for unattended minting. | Only a policy-capped minter, never admin. | The two axes are not fully independent: a `service_account` must be a `server_key` (a Safe cannot be a service account). Registering a `service_account` with any other kind is rejected with `validation_failed`, and a database constraint backs the rule. ## Registering an Authority You register an authority with a single call to `POST /v1/tokenization/authorities`. What the body carries depends on the kind. A `server_key` supplies the EOA `address` (its EIP-55 checksum is re-validated server side) and is registered unverified. A `safe` supplies a `safeRef` and is verified immediately. The `safeRef` field is polymorphic. It resolves the Safe from any of three forms, so you can point at a Safe XKOVA manages for you or at an external one you assert. | safeRef form | Resolves to | | --- | --- | | `msf_...` | A managed Safe by its id. | | `vlt_...` | The same managed Safe under its treasury-vault id (reusing a treasury Safe as a token controller). | | `0x...` (40 hex) | An external Safe you assert for this workspace. It is stored as a bare address with no managed link. | Registering an authority grants nothing on its own. The admin Safe's N-of-M signature still gates every role grant on chain, so trusting an operator's assertion of an external Safe is safe: the assertion only records who may later be granted a role, not the grant itself. If you want a fresh controller rather than a reference to an existing Safe, use `POST /v1/tokenization/authorities/control-safes`. It deploys a new Safe dedicated to token control (with the staff signers and N-of-M threshold you supply, the same quorum flow as a treasury vault) and registers it as a verified `safe` authority in one step. The Safe deploys asynchronously; the returned address is the deterministic (counterfactual) address, and a role grant against it waits for the Safe to be live on chain. > Registration is keyed on the pair of workspace and address. When a role is granted to an address, XKOVA auto-creates a lightweight authority row for it on chain confirmation. Registering that same address later enriches the existing row (its label, kind, and Safe linkage) rather than creating a duplicate. The address is the join key in both directions. The authority record returns a `tka_` prefixed `id`, its `kind` and `purpose`, the checksummed `address`, a `verified_at` timestamp (null for an unproven server key), and, for a Safe, its `safe_purpose` and a `managed_safe_id` (`msf_`). Fetch one authority with `GET /v1/tokenization/authorities/{id}` to also read its active role matrix: each role it holds, the token it holds it on, and when it was granted. ## Proving Control of a Server Key A `server_key` is registered unverified. Before the platform trusts it to hold a role, the customer must prove they control the key. Proof-of-possession is a two-call flow, and it does not apply to a `safe` (a Safe's control is its on-chain owner set, verified at registration; calling the proof endpoints on a Safe returns `tokenization_authority_kind_mismatch`). 1. **Request a challenge.** Call `POST /v1/tokenization/authorities/{id}/proof-challenge`. It returns an opaque `challenge` token, the exact `message` the authority's EOA must sign, and an `expires_at`. The challenge is a stateless signed token; no row is stored to remember it. The message binds a fixed domain string, the authority id, the workspace id, and a random nonce, so a signature collected for it cannot be replayed for another authority or purpose. 1. **Sign and submit.** The EOA signs the message with a standard EIP-191 personal-sign (`account.signMessage({ message })`), producing a 65-byte signature. Submit the `challenge` and the `signature` to `POST /v1/tokenization/authorities/{id}/proof`. The api verifies the challenge and its expiry, recovers the signer from the signature over the exact message, and accepts only if the recovered address equals the authority's registered address. On success it stamps `verified_at`. A server key is provable exactly once. Its first successful proof is permanent, so a proof submitted for an already-verified authority returns `tokenization_authority_already_verified`. A forged or expired challenge, or a signature that does not recover to the registered address, returns `tokenization_proof_invalid` (the same code for both, so a caller cannot distinguish a bad challenge from a bad signature). The proof endpoints are an authenticated integrator or operator route, not a capability link: the recovered signature is the cryptographic gate, on top of the authenticated, step-up session. ## Capped Minter-only Service Accounts A service account lets an integrator's backend mint unattended, without holding an admin key. It is a `server_key` authority registered with `purpose: service_account`. After you prove control of it, you do not grant it a role through the raw grant path. Instead you scope it with a mint-cap policy. - **Set the policy.** `POST /v1/tokenization/tokens/{id}/policies` scopes the authority on the token's per-token roles modifier so it may mint gaslessly within an on-chain per-call amount cap. This is enforced on chain, not just in the mirror. - **Mint within the cap.** `POST /v1/tokenization/tokens/{id}/policy-mint` mints gaslessly through that scoped role. A request above the confirmed on-chain cap is rejected with `tokenization_policy_exceeded` before it is relayed. The raw grant path is deliberately closed to a service account. Granting an ordinary role would produce an uncapped on-chain minter, so a grant that targets a `service_account` is rejected with `role_not_allowed_for_service_account` regardless of which role was named. A service account can hold a capped minter and nothing else, by construction. See [Access and Policy](token-access.html) for how mint-cap policies work. ## Reading a Token's Control Surface Every token exposes a generic, read-only contract explorer over its own control contracts. It is scoped strictly to the token's own contracts: an arbitrary address is never reachable. The reads require the `tokenize_minter` role and carry no step-up, because a view call is not a signed mutation. - `GET /v1/tokenization/tokens/{id}/contracts` lists the token's three control contracts (the `token` itself, its `access_registry`, and its `roles_modifier`) with each resolved on-chain address and an `abi_name` key (`ERC20XGasless`, `AccessRegistry`, or `Roles`). The client reads the full function list from the ABI barrel the SDK ships, so it never hardcodes an address. The `roles_modifier` entry is omitted until its proxy address is resolvable. - `GET /v1/tokenization/tokens/{id}/read` calls one view or pure function on one of those three contracts (a `contract`, `function`, and JSON `args` query). The result is ABI-decoded with every integer stringified, so it is JSON-safe. An unknown contract, an unknown function, or a state-mutating (non-view) function is rejected with `validation_failed`. ## The Raw Safe Transaction Escape Hatch The typed lifecycle (mint, burn, roles, access list, pause, policy) covers the common admin actions. The frozen token, access-registry, and roles contracts also expose admin functions those verticals do not wrap, such as updating the contract URI, changing the trusted forwarder, or a roles scope tweak. Rather than ship a bespoke endpoint per function, `POST /v1/tokenization/tokens/{id}/raw-safe-tx` lets an admin compose any of them. The escape hatch is arbitrary function on a fixed contract, not an arbitrary call. The `contract` is one of the token's three own control contracts by enum, and the target address is server-resolved from that enum, so an arbitrary address can never be reached. The inner calldata is encoded from the committed ABI, so a function not in that contract's ABI, or arguments whose count or shape do not match its inputs, is rejected with `tokenization_raw_call_invalid` before any transaction is built. Because it is composed by an admin, it is the most privileged tokenization act: it requires the `tokenize_admin` role and a step-up authentication. It is otherwise a normal signing operation. It lands in `pending_sign` and cannot broadcast without the admin Safe's N-of-M owner signatures (submitted to the shared operation signatures endpoint) and the XKOVA Relayer, so on-chain authorization is the unchanged Safe-quorum gate. Each owner wallet still retains its own custody and signing-session trust boundary. It moves no value generically, so it sits outside the value-movement compliance gate, and it writes no mirror rows: the operator chose a raw call, so on chain is the source of truth. Every raw call is recorded in the audit log. See [Deploy and Mint](deploy-mint.html) for the shared signing and settlement flow every tokenization operation follows, and [Non-custodial Model](non-custodial.html) for the Safe, signer-wallet, and relayer boundaries behind every write. ### Errors Worth Handling The [error catalog](errors.html) documents every code; these are the ones this surface most commonly returns. | Code | HTTP | When | | --- | --- | --- | | `tokenization_not_authorized` | 403 | The caller is in the right tenant but lacks the `tokenize_admin` role. | | `tokenization_authority_not_found` | 404 | An unknown authority id in the caller's workspace (a genuine miss and a cross-workspace id are indistinguishable by design). | | `tokenization_authority_kind_mismatch` | 422 | A proof endpoint was called on a `safe`, which has no proof-of-possession step. | | `tokenization_proof_invalid` | 422 | A forged or expired challenge, or a signature that does not recover to the registered address. | | `tokenization_authority_already_verified` | 409 | A proof was submitted for an authority that is already verified (a server key is provable exactly once). | | `role_not_allowed_for_service_account` | 422 | A raw role grant targeted a `service_account` authority. Use a mint-cap policy instead. | | `tokenization_raw_call_invalid` | 422 | A raw Safe transaction named an unknown contract or function, or arguments whose shape does not match. | ## Related - [Access and Policy](token-access.html): named roles, mint-cap policies, and the access registry an authority acts on. - [Deploy and Mint](deploy-mint.html): the deploy saga and the signing flow every operation follows. - [Treasury and Safes](treasury.html): the N-of-M Safes an authority can point at. - [Non-custodial Model](non-custodial.html): how owner signatures, provider sessions, Safe quorum, and relaying are separated. > For the exact request and response shapes, see [the operations list](api-reference.html) or try the calls live in [the interactive reference](reference.html). --- source: https://docs.xkova.com/token-standards.html # Token Standards Every token you deploy through the engine follows a familiar Ethereum standard with one addition: a gasless meta-transaction path. A configured owner wallet produces the required signature, the XKOVA Relayer submits it and pays gas, and the chain settles. That is what the X suffix marks. ## The three standards The engine supports the three common token shapes. Pick the one that matches what you are issuing. | Standard | Shape | Typical use | | --- | --- | --- | | `ERC20X` | Fungible. One supply, divisible into units. | A stablecoin, a points balance, a fungible share class. | | `ERC721X` | Non-fungible (NFT). Each token id is unique. | A one of one asset, a deed, a unique instrument. | | `ERC1155X` | Multi-token. Many ids in one contract, each with its own supply. | A fund with several share classes, a fractional asset, batched instruments. | ## What the X suffix means The X variant is the XKOVA gasless meta-transaction model. A standard ERC-20 or ERC-721 requires the caller to hold native gas and sign and broadcast its own transaction. The X variant instead accepts a signed instruction relayed by a trusted forwarder (the ERC-2771 pattern). The owner never needs native gas and never broadcasts a transaction directly. - The configured owner wallet authorizes an action by signing typed data. External keys remain with their owners; Thirdweb keeps human MPC private-key material non-exportable. - The XKOVA Relayer submits the signed instruction on chain and pays the gas. - The contract recovers the original signer and acts on their behalf. This is the same signed-intent and relayer pattern the rails use. The signer wallet's custody and session model still matters. See the [Non-custodial Model](non-custodial.html) for the mechanics that apply across both pillars. > The X variant is interface-compatible with the base standard. A wallet, explorer, or marketplace that reads ERC-20, ERC-721, or ERC-1155 sees an ordinary token. The gasless path is additive, not a different interface. ## Owner controls are part of the contract An X token is programmable. Access lists, named roles, and a global pause are built into the contract, not bolted on off chain. The contract's admin authority is a multisig Safe you provide, so high impact actions (minting, pausing, granting roles) require the Safe's signer quorum. Those controls are covered on [Access and Policy](token-access.html). ## Where each standard fits - [Deploy and Mint](deploy-mint.html) walks the lifecycle: deploy a token, then mint, burn, and manage supply. - [Real-World Assets](rwa.html) adds a configurable metadata plane on top of ERC20X, ERC721X, and ERC1155X for assets that carry rich, structured data. - [Cap Tables and Distributions](cap-tables.html) reconstructs holders and positions and pays them pro rata. > For the deploy request shapes and every token operation, see the [interactive reference](reference.html) or the [operations list](api-reference.html) under the tokenization tag. --- source: https://docs.xkova.com/tokenization-overview.html # XKOVA TAMS XKOVA TAMS, the Tokenized Asset Management System, is the digital asset product. It issues and manages programmable tokens and real-world assets, from deployment through the full lifecycle: minting, access policy, metadata, cap tables, distributions, corporate actions, and provenance. It is the largest surface in the platform. ## What TAMS covers - [Token Standards](token-standards.html): ERC20X, ERC721X, and ERC1155X, the gasless meta-tx token model. - [Deploy and Mint](deploy-mint.html): deploy a token, then mint, burn, and manage supply. - [Access and Policy](token-access.html): allow and deny lists, roles, mint-cap policies, and pause. - [Real-World Assets](rwa.html): asset schemas and metadata, configured not coded, immutable by design. - [Cap Tables and Distributions](cap-tables.html): holders, positions, and pro-rata distributions. - [Corporate Actions](corporate-actions.html): splits, redemptions, and the lifecycle engine. - [Provenance and Transparency](provenance.html): attestation, the transparency log, and reconciliation. - [Holder Eligibility and Transfers](holder-eligibility.html): eligibility attributes and transfer policy. ## Programmable and signature-gated Tokens are programmable: access lists, roles, mint-cap policies, and pause are part of the contract. They are also gasless and signature-gated. The configured owner address or Safe quorum signs, the XKOVA Relayer submits, and the chain settles. XKOVA never receives or exports human MPC private-key material, but a Thirdweb custom-JWT signer has a different session-authority boundary from an externally held EOA. Real-world assets add a configurable metadata plane on top, so an asset's schema and data are defined by configuration rather than custom code, and the record is immutable by system design. > For the exact operations, see the [API Reference](api-reference.html) or try them live in the [interactive reference](reference.html). --- source: https://docs.xkova.com/transfers.html # Transfers and Settlement Every money movement on the rails ends the same way, regardless of whether it started as a payment, an escrow release, or a treasury transfer. A configured owner address or Safe quorum signs, the XKOVA Relayer submits, the chain mines the transaction, and XKOVA confirms the result on chain. This page explains that shared lifecycle and the one rule that matters most: mined is not the same as confirmed. ## One settlement path Payments, escrow releases, and treasury transfers all converge on the same back end. The differences are in which wallet signs and how many signatures are needed. After XKOVA accepts the exact required signatures, the path is identical: the instruction is handed to the relayer, submitted on chain, and watched to confirmation. The relayer carries signatures and pays gas; it does not replace the owner address or Safe quorum the contract requires. ``` flowchart TD A[Signed] --> B[Relayed] B --> C[Mined] C --> D{On chain success} D -- success --> E[Confirmed] D -- reverted --> F[Failed] D -- unknown --> G[Retry next tick] G --> D ``` ## The lifecycle states | State | Meaning | | --- | --- | | Signed | XKOVA has accepted the required owner-address or Safe-quorum signature for the frozen intent. Nothing is on chain yet. | | Relayed | The XKOVA Relayer has submitted the signed instruction on chain and paid gas. It is now in the mempool. | | Mined | The transaction has been included in a block. This is necessary but not sufficient. A mined transaction can still have reverted. | | Confirmed | The transaction was mined and verified to have succeeded on chain. Only now is the transfer real. | ## Mined is not success The single most important rule in settlement is that being included in a block does not mean the transfer worked. A transaction that reverts still consumes gas and is still mined. The on chain success or revert result is a separate signal. XKOVA reads that signal and flips a transfer to confirmed only when the chain reports success. A reverted transaction is marked failed, never confirmed. > Do not treat mined as done. Wait for confirmed. If you only watch for inclusion in a block, you will count reverted transfers as successful. The platform never flips a transfer to a successful terminal state without verifying on chain success first. ## When the result is not yet readable Right after a transaction is mined, the success or revert result can take a few seconds to become readable from chain peers. When that happens the result is treated as unknown, not as success. XKOVA defers and re-checks on its next pass. Unknown is never resolved optimistically, so a momentary read gap cannot promote a transfer to confirmed before the chain has actually confirmed it. ## How to track settlement You have two ways to follow a transfer to its terminal state. - **Poll.** Read the resource (a payment, an escrow, or a treasury transfer) and watch its state move to a terminal value. Payments and escrows also expose an event timeline you can read for the full sequence of transitions; a treasury transfer has no event timeline, so read the transfer resource itself for its current state. - **Subscribe.** Register a webhook and receive an event when the transfer reaches a terminal state. This avoids polling and is the recommended way to react to settlement. See [Webhooks](webhooks.html). ## Why settlement can take time Settlement is asynchronous because the chain is asynchronous. A signed transfer is accepted by the API quickly, but reaching confirmed depends on block production and on the success result becoming readable. Design your integration to treat create and confirm as two separate moments, and to act on the confirmed signal rather than assuming the create call settled the money. ## Related - [Payments](payments.html): the sign step that produces a signed transfer. - [Treasury](treasury.html): N of M signing before a vault transfer is relayed. - [Non-custodial Model](non-custodial.html): why the owner signs and the relayer only submits. - [Webhooks](webhooks.html): the events that fire on a terminal settlement state. --- source: https://docs.xkova.com/treasury.html # Treasury Treasury holds an institution's own operational funds in a multisig vault, a Gnosis Safe owned by a set of staff signers. Moving value out of the vault is not a single click. A transfer is drafted, a quorum of signers attests to it, and only then does the XKOVA Relayer submit the assembled transaction on chain. The Safe contract enforces the configured signature threshold. ## The vault model A treasury vault is a Gnosis Safe multisig deployed for the tenant. Each vault has a set of owners (the staff signers) and a threshold, the number of signatures required to execute a transfer. This is the N of M model: any N of the M owners must sign before a transfer can settle. The vault, not an XKOVA ledger account, holds value on chain. XKOVA never receives or exports human MPC private-key material, and the Safe verifies the configured owner quorum. - **Create a vault.** Deploying a vault deploys the Safe multisig on chain with its initial owners and threshold. - **Manage signers.** Adding or removing a signer, or changing the threshold, is gated at two levels. On a fresh workspace the change is first governance held for owner attestation (the seeded baseline in [Policies and Quorum Governance](policies-quorum.html)); the approved change is then itself a Safe owner-management transaction that the signer quorum must approve. Pending signer changes are visible alongside active signers. - **Use available assets.** Active supported tokens for the selected network appear automatically. Tenant-issued ERC20 tokens appear for their owning workspace when treasury-eligible. ## How a transfer settles Moving value out of a vault follows a draft, collect, relay, settle path. The draft reserves the Safe nonce and computes the exact typed data hash each signer will sign. Signers submit their signatures one at a time. Once the threshold is met, the assembled transaction is executed through the XKOVA Relayer, which submits the Safe execution and pays gas. The chain settles the transfer. ``` flowchart TD A[Draft transfer] --> B[Reserve Safe nonce and compute hash] B --> C[Collect N of M signatures] C --> D{Threshold met} D -- no --> C D -- yes --> E[Relay execTransaction] E --> F[Chain settles transfer] ``` The steps map to discrete operations. | Step | Who | What happens | | --- | --- | --- | | Draft | Operator or backend | Reserves the Safe nonce and computes the EIP-712 hash to sign. | | Sign | Each signer | A staff signer signs the Safe hash in their own wallet. Repeats until the threshold is met. | | Execute | Operator or backend | Submits the assembled execTransaction through the XKOVA Relayer. | | Settle | Chain and XKOVA | The transfer confirms on chain and XKOVA records the settlement. | A transfer can be cancelled off chain before it executes. After execution it is in flight and settles like any other transfer, see [Transfers and Settlement](transfers.html). ### Failures worth handling per step Treasury signing and execution are private institution-staff workflows. Customer applications should handle their outcomes through the Core Rails or management experience that launched the workflow, not by depending on private error identifiers. | Step | Failure conditions | | --- | --- | | Draft | The caller lacks the treasury role or signer authority, the wallet or asset is not registered, or compliance blocks the transfer. | | Sign | The signature does not recover to a Safe owner, was already submitted, or the transfer is no longer signable. | | Execute | The signature threshold is not met, the transfer is no longer executable, an earlier draft holds the Safe nonce, authorization is denied, or relaying is temporarily unavailable. | ## Signature production is browser-side Each eligible staff signer uses the Console to authorize the frozen Safe transaction. XKOVA issues a fresh 60-second custom JWT for that exact staff identity, the browser connects a fresh memory-only Thirdweb MPC ceremony, and Thirdweb produces the EIP-712 signature. The Console submits the signature, disconnects the provider session, and clears ceremony memory. XKOVA then rechecks staff authorization and Safe-owner eligibility before persistence, quorum evaluation, and execution. > XKOVA never receives or exports the staff MPC private-key material. The custom-JWT issuer is nevertheless signing-critical because Thirdweb does not restrict the connected session to the frozen Safe transaction. XKOVA's capability, payload freeze, signer verification, authorization rechecks, and Safe quorum are separate required controls. See the [Non-custodial Model](non-custodial.html). > Treasury mutations require a recently authenticated AAL2 session (a fresh MFA step). A signer cannot draft or execute a treasury move from a stale session. See [Authorization and RBAC](authz.html) for the role and step-up model. ## Beyond transfers The treasury surface also covers reading on chain vault balances and confirmed-transfer history, a counterparty registry for naming and reusing destinations, tax exports for reportable counterparties, and NFT custody and moves for vaults that hold tokenized assets. On a fresh workspace, adding a counterparty is governance held the same way as a signer change: the call returns a pending approval, the owner attests, and the re-submitted call creates the entry. Changing which vault receives platform revenue is a separate quorum-gated re-link ceremony. ## Related - [Transfers and Settlement](transfers.html): how the executed transfer confirms on chain. - [Policies and Quorum Governance](policies-quorum.html): approval quorums and break glass. - [Roles and Permissions](roles-permissions.html): who can draft, sign, and execute. > Treasury transport is private to Core Rails institution-staff workflows. The customer API operations list and interactive reference intentionally do not publish those routes. --- source: https://docs.xkova.com/verification.html # Identity Verification (KYC and KYB) Identity requirements follow the tenant's committed KYC/KYB policy. XKOVA runs know your customer (KYC) and know your business (KYB) checks through a swappable identity provider. The subject completes a hosted flow at the provider, the provider posts a signed result back, and the verification gate moves the subject to verified, in progress (held for manual approval), or rejected. ## The hosted flow plus callback model Verification does not run inside XKOVA's own UI. The subject is handed a link to the provider's hosted flow, where they complete the identity steps the provider requires. When they finish, the provider posts a signed result back to XKOVA, and that callback drives the gate. This keeps sensitive identity capture at the provider and keeps XKOVA's surface free of personal data it does not need to hold. ### The path a verification takes 1. The subject is handed a link to the provider's hosted flow. 1. The subject completes the identity steps the provider requires. 1. The provider posts a signed callback to XKOVA with the result. 1. XKOVA verifies the signature and moves the subject to verified, in progress (when the result is held for manual approval), or rejected. ## The two checks | Check | Subject | | --- | --- | | KYC | An individual member. | | KYB | A business entity. | ## The signed callback The provider's result is delivered as a signed webhook so XKOVA can trust it came from the provider and was not tampered with. XKOVA verifies the signature before acting on the verdict; an unverified callback is rejected. The verdict normalizes to one of three states, verified, in progress, or rejected, which the gate then enforces. A result awaiting manual approval reads as in progress on the subject; `verified` and an explicit policy-derived `not_required` pass the gate. A missing status does not mean verification is optional. Provider callbacks use their configured verification scheme. Customer webhook delivery is a separate contract, described in [Webhooks](webhooks.html). > Verification gates fail closed. A missing or unknown verification status is treated as not verified, never as a pass, so a gated action cannot slip through on an absent result. ## Example vendors The identity adapter is interchangeable. Examples of the kind of provider that plugs in here include Persona, Alloy, and Onfido. The institution verifies with one provider, and switching is an adapter and configuration change rather than a change to how the gate behaves. This is the orchestration model from [Integrations and Orchestration](integrations-overview.html) applied to identity. Production verification fails closed until an approved real provider template is configured. Example vendor names do not mean every adapter ships or is self-serve. XKOVA ships a sandbox identity provider so the full member to verified loop runs in local development, with demo subjects that map to pass, fail, and review verdicts. ## Related - [Screening (KYT and BSA)](screening.html): the transaction side of compliance. - [Policies and Quorum Governance](policies-quorum.html): how a held verification is cleared. - [Operator Onboarding](onboarding.html): where verification sits in setup. ## Read Member Verification State `GET /v1/account-holders/self/verification` returns verification state for the session-bound member. In your BFF, call `resolved.resources.self.verification()` after resolving the application session. Keep the XKOVA member-session credential on the server. See [Embedded Member Xperience](embedded-member.html). > Sandbox uses the XKOVA Demo Verifier selected by workspace mode. Its pass, fail, and review outcomes test the flow and are not real KYC/KYB decisions. --- source: https://docs.xkova.com/versioning.html # Versioning The /v1 prefix identifies the HTTP namespace. It is separate from the API service release and the semantic versions of the SDK packages. ## Current Compatibility Expectations XKOVA is in pre-launch alpha. Request and response shapes, validation, identifiers, and behavior may change in place under `/v1`. Earlier alpha contracts do not have a compatibility overlap. Update your integration against the current [API Reference](api-reference.html) and keep SDK packages aligned with the environment you use. Removing an implementation must not silently remove a supported product capability. Immutable on-chain deployments and third-party provider contracts have their own versioning constraints. ## The Version Dimensions | Identity | What it identifies | | --- | --- | | `/v1` | The HTTP API namespace. | | API service version | The reviewed API component release. | | SDK package version | A release of `@xkova/sdk` or `@xkova/browser`. | | Exact build | The source revision and immutable artifacts deployed to an environment. | | Contract deployment | The chain, address, and deployed bytecode provenance. | See [Changelog](changelog.html) for current component versions. The SDKs already have semantic versions and are available as alpha packages. ## Writing Clients That Tolerate Additions Ignore response fields your application does not use. Handle documented status values and errors explicitly, and retain a correlation ID when reporting a failure. An added optional field can be safe for a tolerant client; a removed field, changed type, or tighter validation may require code changes. Review [Deprecations](deprecations.html) before assuming a notice period or `Sunset` header protects an earlier alpha integration. --- source: https://docs.xkova.com/wallet-providers.html # Wallet Providers Thirdweb is XKOVA's current shared MPC wallet infrastructure for account holders and institution staff. XKOVA keeps provider mechanics below separate member and staff authorization layers, while externally controlled EOAs and Safe ownership retain their distinct signing models. ## The wallet provider interface A wallet provider supplies wallet creation, lookup, and signing infrastructure. Thirdweb is the shipping MPC provider for XKOVA human wallets today. Shared provider code constructs canonical owner subjects, resolves or pregenerates the exact provider wallet, normalizes errors, and creates short-lived browser ceremonies. Separate account-holder and institution-staff services decide who may provision or sign. This is the model from [Integrations and Orchestration](integrations-overview.html) applied to wallets: the vendor is an adapter below XKOVA's owner-specific authorization and lifecycle. > Wallet-provider selection is not a self-serve API. Thirdweb is the implemented MPC provider today. A future provider must implement and certify the same XKOVA lifecycle and authorization contracts before it can replace that infrastructure. ## Custody and signing models Private-key custody and the authority to open a signing session are separate facts. State both when evaluating a wallet integration. | Model | Security boundary | | --- | --- | | Externally controlled EOA | The external owner controls the private key and signs independently of XKOVA. | | Thirdweb MPC human wallet | Thirdweb keeps MPC private-key material non-exportable. A valid XKOVA custom JWT plus the public client can establish a general provider signing session. | | Safe vault | The Safe contract holds value and requires its configured owner quorum. Each owner can use a Thirdweb MPC wallet or another approved signer wallet. | | Custodial provider | A custodian controls signing under the institution's approved controls. | XKOVA never receives or exports human MPC private-key material. Contracts still require a valid owner-address signature or Safe quorum before the XKOVA Relayer can submit value movement. For Thirdweb MPC wallets, XKOVA additionally protects the signing-critical custom-JWT issuer, freezes the business payload, verifies the returned signature, and disposes the browser provider session. See [Non-custodial Model](non-custodial.html) for the mechanics and [Wallets and Balances](wallets.html) for the member wallet surface. ## Example vendors The provider seam is intended to bound a future replacement, but provider behavior is not assumed equivalent. Thirdweb is the available MPC provider today. Any later provider must be reviewed for identity, key custody, signing-session authority, recovery, export, and logout behavior. The product surface can remain stable only after that adapter and its security properties are implemented and certified. ## Related - [Wallets and Balances](wallets.html): the member wallet surface and balances. - [Non-custodial Model](non-custodial.html): member signing and relayer submission. - [Security Model](security.html): the non-custodial threat model and key handling. --- source: https://docs.xkova.com/wallets.html # Wallets and Balances A member's wallet is their on-chain account. XKOVA provisions the current Thirdweb MPC wallet from the exact tenant and account-holder identity, independently of Hosted Auth, FI OIDC, API, or another approved creation channel. XKOVA never receives or exports the MPC private-key material. ## Member in-app wallets Each account holder resolves to one canonical Thirdweb custom-JWT subject and MPC wallet. The subject is derived from the XKOVA tenant and account-holder records, not from an email address or the authentication provider. The wallet holds balances and produces the member's transfer signatures. Thirdweb keeps MPC private-key material non-exportable, so XKOVA and the integrator do not receive it. > A valid XKOVA custom JWT plus the public Thirdweb client can establish a general provider signing session for the canonical wallet. It is not limited by Thirdweb to one XKOVA payment. XKOVA therefore issues a fresh 60-second bootstrap only after purpose authorization, uses memory-only browser storage, verifies the exact frozen payload and signer, and disposes the provider session immediately. See [Non-custodial Model](non-custodial.html) and [Wallet Providers](wallet-providers.html). ## How a wallet is provisioned A wallet is provisioned through XKOVA's durable wallet lifecycle. A successful member binding creates or wakes one canonical provisioning job for that account holder. The steps are: 1. **Bind.** XKOVA derives the provider identity from the verified account-holder binding. 1. **Provision.** A durable coordinator creates or resolves the provider wallet and records the canonical public address. 1. **Reconcile.** Retries and sign-ins resume the same job until monitoring and wallet readiness are complete. The browser can send a bodyless wake request for its own signed-in member, but it cannot select an account holder, provider identity, address, chain, wallet, credential, or registration proof. The same verified member resolves to the same provider wallet. MPC private-key material never enters XKOVA, and ordinary provisioning does not grant browser signing authority. ## Balances A balance is the member's holding of an asset from the automatic workspace catalog, maintained as a running total that XKOVA updates as it observes the wallet's on-chain activity and keeps in sync with the chain through continuous reconciliation (rather than fetching a live chain read on every request). A member can have a wallet on each applicable active chain and can hold the active external and workspace-issued assets indexed there. Each balance entry carries a numeric chain_id that identifies the chain it lives on (one of the chains described in [Supported Networks](networks.html)); the full embedded chain descriptor is carried on the wallet resource, not on individual balance rows. ## The account holder behind the wallet Every member wallet hangs off an account holder: the member record your backend provisions, either explicitly or just in time on the member's first sign in. The record carries far more than the wallet: self-service balances and bank sub-accounts, a contacts address book, policy consents, a right-to-access data export, an avatar, and the compliance hold and release actions. See [Account Holders](account-holders.html) for the full surface. ## What a wallet is used for - **Signing transfers.** The member signs payment and escrow intents in their wallet, and the XKOVA Relayer submits them. See [Payments](payments.html) and [Escrow and Claims](escrow.html). - **Holding assets.** The wallet holds the member's stablecoin balance and any tokens issued to it through [XKOVA TAMS](tokenization-overview.html). ## Related See [Wallet Providers](wallet-providers.html) for the current Thirdweb MPC trust boundary and the provider abstraction, and the [interactive reference](reference.html) for wallet readiness, bodyless provisioning, listing, and balance operations. --- source: https://docs.xkova.com/webhooks.html # Webhooks Webhooks push events to your endpoint as they happen. This page covers the delivery envelope, signature verification, the retry and replay behavior, secret rotation, managing endpoints and deliveries from the API, and the full catalog of 47 events. ## The delivery envelope Every webhook is an HTTP POST to the endpoint you register. The body is a typed envelope with a stable id, the event type, a version, a created timestamp, the resource type and id, the actor, and an event specific data object. Each delivery carries these headers: | Header | Purpose | | --- | --- | | `XKOVA-Webhook-Delivery-Id` | Unique id for this delivery attempt. | | `XKOVA-Webhook-Event` | The event type, for example payment.confirmed. | | `XKOVA-Webhook-Event-Id` | Stable id of the event. Use it to dedupe retries. | | `XKOVA-Webhook-Signature` | HMAC-SHA256 signature over the raw body. | | `XKOVA-Webhook-Signature-Ed25519` | Asymmetric Ed25519 signature for key-pinned verification. | | `X-Correlation-Id` | Correlation id for tracing this event against related API requests and audit entries. | ## Verifying a webhook Verify before you trust. Compute an HMAC-SHA256 over the exact raw request body using your endpoint signing secret and compare it to `XKOVA-Webhook-Signature` in constant time. For stronger, key-pinned verification, check `XKOVA-Webhook-Signature-Ed25519` against the published verification key. The SDK ships a helper that does both. > Respond with any 2xx within ten seconds to acknowledge a delivery. A non 2xx, a timeout, or a connection error marks the delivery failed and schedules a retry. ## Delivery, retries, and replay Failed deliveries retry on a widening backoff across seven attempts (immediate, then 60 seconds, 5 minutes, 30 minutes, 3 hours, 12 hours, 24 hours), after which the delivery moves to a dead letter state. Because retries reuse the same `XKOVA-Webhook-Event-Id`, your handler should be idempotent and dedupe on that id. You can also replay a delivery on demand from the console. ## Rotating the signing secret When you rotate an endpoint signing secret, both the old and the new secret verify during a 24 hour grace window, so you can roll the secret without dropping in flight deliveries. Update your verifier to accept either secret during the window, then retire the old one. ## Managing Endpoints From The API Everything the console does with webhooks is a tenant-scoped REST surface you can drive from your own backend. There are three resource groups: endpoints, the event-type catalog, and deliveries. Every mutation is audited as a configuration change, and each route is gated by a `webhook.*` permission (see [Authorization](authz.html)). ### Endpoint Lifecycle An endpoint is the destination URL plus the set of events it subscribes to. Create, read, update, rotate, test, and delete it through these routes. | Route | What It Does | Permission | | --- | --- | --- | | `GET /v1/webhook-endpoints` | List your endpoints, cursor paginated. Never returns a secret. | `webhook.read` | | `POST /v1/webhook-endpoints` | Register an endpoint. Returns the plaintext signing secret once. | `webhook.create` | | `GET /v1/webhook-endpoints/{id}` | Read one endpoint, including its current and previous secret fingerprints. | `webhook.read` | | `PATCH /v1/webhook-endpoints/{id}` | Update the URL, subscribed events, description, or enabled flag. | `webhook.update` | | `POST /v1/webhook-endpoints/{id}/rotate` | Roll the signing secret with an overlap window. Returns the new secret once. | `webhook.rotate` | | `POST /v1/webhook-endpoints/{id}/test` | Fire a synthetic signed event at the endpoint and get the result synchronously. | `webhook.test` | | `DELETE /v1/webhook-endpoints/{id}` | Delete the endpoint. Returns 204. Past deliveries are kept. | `webhook.delete` | - **Create** takes a `url` (absolute, up to 2048 characters), an `events` array of 1 to 50 registered event keys, and an optional `description`. The response carries the plaintext `secret` exactly once; capture it or you must rotate to get a new one. Reads never return it. Create takes an `Idempotency-Key` (see [Idempotency](idempotency.html)). - **Update** is a partial patch; every field is optional and an empty body is a no-op. Set `enabled` to false to pause deliveries without deleting the endpoint. The secret is never changed here; use rotate for that. - **Rotate** is a step-up protected action and requires a recently MFA-verified session. `overlap_sec` (60 to 2592000 seconds, default 86400) keeps the previous secret valid for signing during the window so receivers can swap without missing deliveries; both the current and the `previous_hmac_secret_fingerprint` are visible on the endpoint read while an overlap is active. - **Test** fires a fully signed `webhook.test` event through the same envelope, HMAC, and Ed25519 path as a real delivery, synchronously. The result reports `delivered`, `status_code`, `duration_ms`, and `error`. It does not write a delivery record. It takes an `Idempotency-Key`; a retry with the same key replays the prior result instead of firing again. ### Subscribable Event Types Endpoints subscribe to events by name, for example `payment.confirmed`. `GET /v1/webhook-event-types` (permission `webhook.read`) is the canonical, machine-readable catalog of every subscribable event. Each entry carries a `name`, `domain`, `version`, a `sensitive` flag, a `description`, an `example_payload`, and `deprecated_at`. Subscribing an endpoint to a name that is not in this registry returns 422 `unknown_event`, so validate against this list before you create or update an endpoint. The full catalog is reproduced below. ### Inspecting And Replaying Deliveries Every delivery attempt is recorded and readable server side for the last 30 days, and you can re-fire a failed one on demand. | Route | What It Does | Permission | | --- | --- | --- | | `GET /v1/webhook-deliveries` | List recent deliveries, cursor paginated. Filter by `status` and `endpoint_id`. | `webhook.read` | | `GET /v1/webhook-deliveries/{id}` | Read one delivery, including its payload and last response for triage. | `webhook.read` | | `POST /v1/webhook-deliveries/{id}/replay` | Re-queue one terminal delivery, reusing its original event id. | `webhook.replay` | | `POST /v1/webhook-deliveries/bulk-replay` | Re-queue up to 100 terminal deliveries in one call. | `webhook.replay` | - A delivery row reports its `status` (`pending`, `delivered`, `failed`, or `dlq`), `attempt_count`, `last_status_code`, `last_response_body`, `next_retry_at`, `delivered_at`, and `correlation_id`. Match `correlation_id` against your API request logs and audit entries. List results are cursor paginated (see [Pagination](pagination.html)). - **Single replay** is allowed only for a delivery in a terminal state (`failed` or `dlq`). It resets the row to `pending`, clears the retry timer, and reuses the original `XKOVA-Webhook-Event-Id` so an idempotent handler deduplicates against the first attempt. Replaying a `pending` or `delivered` row returns 409 `invalid_state`. - **Bulk replay** takes 1 to 100 `delivery_ids` and is best effort over the set. The result returns the rows actually re-queued in `data` plus `replayed_count` and `skipped_count`; ids that are not found, belong to another tenant, are already pending or delivered, or are duplicates are counted in `skipped_count` rather than failing the call. A single malformed id rejects the whole request with 422 `validation_failed`. > The codes above are documented in full in [the error catalog](errors.html). ## Event catalog 47 events across 9 domains. Events marked sensitive carry identity or compliance adjacent data. ### billing 3 | Event | Summary | | | --- | --- | --- | | `invoice.payment_failed` | A billing provider payment attempt failed. The provider may still retry; this event alone does not restrict the tenant. | | | `subscription.reinstated` | The subscription recovered and XKOVA lifted the read-only restriction that billing had imposed. | | | `subscription.restricted` | A paid subscription ended and XKOVA restricted the tenant to read-only money movement. | | Example payload: invoice.payment_failed ``` { "id": "00000000-0000-4000-8000-000000000001", "type": "invoice.payment_failed", "version": "2026-06-23", "created_at": "2026-06-23T12:00:00Z", "resource_type": "billing_invoice", "resource_id": "in_01J9X2QH8K3M4N5P6R7S8T9V0W", "actor": { "type": "system" }, "data": { "invoice_id": "in_01J9X2QH8K3M4N5P6R7S8T9V0W", "provider_event_id": "evt_01J9X2QH8K3M4N5P6R7S8T9V0W", "subscription_id": "sub_01J9X2QH8K3M4N5P6R7S8T9V0W", "attempt_count": 1, "amount_due_minor": 12500, "currency": "usd" } } ``` Example payload: subscription.reinstated ``` { "id": "00000000-0000-4000-8000-000000000001", "type": "subscription.reinstated", "version": "2026-06-23", "created_at": "2026-06-23T12:00:00Z", "resource_type": "tenant", "resource_id": "tnt_000000001VgEh72lXvTXkH", "actor": { "type": "system" }, "data": { "tenant_id": "tnt_000000001VgEh72lXvTXkH", "subscription_status": "active", "tenant_status": "active", "reason": "subscription_active", "effective_at": "2026-08-22T12:15:00.000Z" } } ``` Example payload: subscription.restricted ``` { "id": "00000000-0000-4000-8000-000000000001", "type": "subscription.restricted", "version": "2026-06-23", "created_at": "2026-06-23T12:00:00Z", "resource_type": "tenant", "resource_id": "tnt_000000001VgEh72lXvTXkH", "actor": { "type": "system" }, "data": { "tenant_id": "tnt_000000001VgEh72lXvTXkH", "subscription_status": "unpaid", "tenant_status": "read_only", "reason": "payment_retries_exhausted", "effective_at": "2026-08-22T12:00:00.000Z" } } ``` ### claims 4 | Event | Summary | | | --- | --- | --- | | `claim.initiated` | A recipient started claiming a pending payment. | | | `claim.otp_sent` | A one-time passcode was dispatched to a claim recipient. Sensitive, the payload never carries the OTP code itself. | sensitive | | `claim.otp_verified` | A claim recipient verified their one-time passcode. Sensitive, identity-verification lifecycle. | sensitive | | `claim.resolved` | A claim completed and the pending payment was resolved. | | Example payload: claim.initiated ``` { "id": "00000000-0000-4000-8000-000000000001", "type": "claim.initiated", "version": "2026-06-23", "created_at": "2026-06-23T12:00:00Z", "resource_type": "escrow", "resource_id": "esc_000000001VgEh72lXvTXkH", "actor": { "type": "system" }, "data": { "pending_payment_id": "esc_000000001VgEh72lXvTXkH" } } ``` Example payload: claim.otp_sent ``` { "id": "00000000-0000-4000-8000-000000000001", "type": "claim.otp_sent", "version": "2026-06-23", "created_at": "2026-06-23T12:00:00Z", "resource_type": "escrow", "resource_id": "esc_000000001VgEh72lXvTXkH", "actor": { "type": "system" }, "data": { "pending_payment_id": "esc_000000001VgEh72lXvTXkH" } } ``` Example payload: claim.otp_verified ``` { "id": "00000000-0000-4000-8000-000000000001", "type": "claim.otp_verified", "version": "2026-06-23", "created_at": "2026-06-23T12:00:00Z", "resource_type": "escrow", "resource_id": "esc_000000001VgEh72lXvTXkH", "actor": { "type": "system" }, "data": { "pending_payment_id": "esc_000000001VgEh72lXvTXkH" } } ``` Example payload: claim.resolved ``` { "id": "00000000-0000-4000-8000-000000000001", "type": "claim.resolved", "version": "2026-06-23", "created_at": "2026-06-23T12:00:00Z", "resource_type": "escrow", "resource_id": "esc_000000001VgEh72lXvTXkH", "actor": { "type": "system" }, "data": { "pending_payment_id": "esc_000000001VgEh72lXvTXkH" } } ``` ### compliance 5 | Event | Summary | | | --- | --- | --- | | `compliance.case.closed` | A compliance case was resolved (closed). Sensitive, ids + the case-type/state enums only; the closed reason is NOT exposed on the webhook. | sensitive | | `compliance.case.opened` | A compliance case was opened (manual review, compliance flag, block, or dispute). Sensitive, the payload carries only ids + the case-type/state enums, never the summary, evidence, or any free-text (read those via the API as an authorized caller). | sensitive | | `verification.holder.expired` | An account holder's verification lapsed past its expiry (re-verification required). Sensitive, id + status only. | sensitive | | `verification.holder.rejected` | An account holder's verification was rejected. Sensitive, the payload carries only the holder id; the rejection reason is NOT exposed on the webhook (read it via the API as an authorized caller). | sensitive | | `verification.holder.verified` | An account holder's verification was approved (KYC/KYB). Sensitive, the payload carries only the holder id + status, never PII or vendor detail. | sensitive | Example payload: compliance.case.closed ``` { "id": "00000000-0000-4000-8000-000000000001", "type": "compliance.case.closed", "version": "2026-06-23", "created_at": "2026-06-23T12:00:00Z", "resource_type": "compliance_case", "resource_id": "cas_000000001VgEh72lXvTXkH", "actor": { "type": "system" }, "data": { "case_id": "cas_000000001VgEh72lXvTXkH", "case_type": "manual_review", "state": "closed", "subject_account_holder_id": "ah_000000001VgEh72lXvTXkH" } } ``` Example payload: compliance.case.opened ``` { "id": "00000000-0000-4000-8000-000000000001", "type": "compliance.case.opened", "version": "2026-06-23", "created_at": "2026-06-23T12:00:00Z", "resource_type": "compliance_case", "resource_id": "cas_000000001VgEh72lXvTXkH", "actor": { "type": "system" }, "data": { "case_id": "cas_000000001VgEh72lXvTXkH", "case_type": "manual_review", "state": "open", "subject_account_holder_id": "ah_000000001VgEh72lXvTXkH" } } ``` Example payload: verification.holder.expired ``` { "id": "00000000-0000-4000-8000-000000000001", "type": "verification.holder.expired", "version": "2026-06-23", "created_at": "2026-06-23T12:00:00Z", "resource_type": "account_holder", "resource_id": "ah_000000001VgEh72lXvTXkH", "actor": { "type": "system" }, "data": { "account_holder_id": "ah_000000001VgEh72lXvTXkH" } } ``` Example payload: verification.holder.rejected ``` { "id": "00000000-0000-4000-8000-000000000001", "type": "verification.holder.rejected", "version": "2026-06-23", "created_at": "2026-06-23T12:00:00Z", "resource_type": "account_holder", "resource_id": "ah_000000001VgEh72lXvTXkH", "actor": { "type": "system" }, "data": { "account_holder_id": "ah_000000001VgEh72lXvTXkH" } } ``` Example payload: verification.holder.verified ``` { "id": "00000000-0000-4000-8000-000000000001", "type": "verification.holder.verified", "version": "2026-06-23", "created_at": "2026-06-23T12:00:00Z", "resource_type": "account_holder", "resource_id": "ah_000000001VgEh72lXvTXkH", "actor": { "type": "system" }, "data": { "account_holder_id": "ah_000000001VgEh72lXvTXkH" } } ``` ### escrows 5 | Event | Summary | | | --- | --- | --- | | `escrow.cancelled` | An escrow was cancelled on-chain by the sender or an operator (funds returned to the sender). | | | `escrow.claimed` | An escrow was released to the recipient on-chain (the on-chain PaymentReleased event was observed). | | | `escrow.confirmed` | An escrow was funded + confirmed on-chain (the on-chain createPayment event was observed). | | | `escrow.created` | An escrow was created off-chain (pending sender signature / on-chain funding). | | | `escrow.expired` | An escrow passed its TTL and was cancelled on-chain (permissionless cancelAfterExpiry). | | Example payload: escrow.cancelled ``` { "id": "00000000-0000-4000-8000-000000000001", "type": "escrow.cancelled", "version": "2026-06-23", "created_at": "2026-06-23T12:00:00Z", "resource_type": "escrow", "resource_id": "esc_000000001VgEh72lXvTXkH", "actor": { "type": "system" }, "data": { "escrow_id": "esc_000000001VgEh72lXvTXkH", "tx_hash": "0xdef456…" } } ``` Example payload: escrow.claimed ``` { "id": "00000000-0000-4000-8000-000000000001", "type": "escrow.claimed", "version": "2026-06-23", "created_at": "2026-06-23T12:00:00Z", "resource_type": "escrow", "resource_id": "esc_000000001VgEh72lXvTXkH", "actor": { "type": "system" }, "data": { "escrow_id": "esc_000000001VgEh72lXvTXkH", "tx_hash": "0xdef456…" } } ``` Example payload: escrow.confirmed ``` { "id": "00000000-0000-4000-8000-000000000001", "type": "escrow.confirmed", "version": "2026-06-23", "created_at": "2026-06-23T12:00:00Z", "resource_type": "escrow", "resource_id": "esc_000000001VgEh72lXvTXkH", "actor": { "type": "system" }, "data": { "escrow_id": "esc_000000001VgEh72lXvTXkH", "on_chain_payment_id": "0xabc123…", "tx_hash": "0xdef456…" } } ``` Example payload: escrow.created ``` { "id": "00000000-0000-4000-8000-000000000001", "type": "escrow.created", "version": "2026-06-23", "created_at": "2026-06-23T12:00:00Z", "resource_type": "escrow", "resource_id": "esc_000000001VgEh72lXvTXkH", "actor": { "type": "system" }, "data": { "escrow_id": "esc_000000001VgEh72lXvTXkH" } } ``` Example payload: escrow.expired ``` { "id": "00000000-0000-4000-8000-000000000001", "type": "escrow.expired", "version": "2026-06-23", "created_at": "2026-06-23T12:00:00Z", "resource_type": "escrow", "resource_id": "esc_000000001VgEh72lXvTXkH", "actor": { "type": "system" }, "data": { "escrow_id": "esc_000000001VgEh72lXvTXkH" } } ``` ### payment_requests 6 | Event | Summary | | | --- | --- | --- | | `payment_request.approved` | A payment request was approved by the payer. | | | `payment_request.cancelled` | A payment request was cancelled by the requester. | | | `payment_request.created` | A payment request was created. | | | `payment_request.declined` | A payment request was declined by the payer. | | | `payment_request.expired` | A payment request expired before being actioned. | | | `payment_request.remind_sent` | A reminder for an open payment request was sent. | | Example payload: payment_request.approved ``` { "id": "00000000-0000-4000-8000-000000000001", "type": "payment_request.approved", "version": "2026-06-23", "created_at": "2026-06-23T12:00:00Z", "resource_type": "payment_request", "resource_id": "pyr_000000001VgEh72lXvTXkH", "actor": { "type": "system" }, "data": { "payment_request_id": "pyr_000000001VgEh72lXvTXkH" } } ``` Example payload: payment_request.cancelled ``` { "id": "00000000-0000-4000-8000-000000000001", "type": "payment_request.cancelled", "version": "2026-06-23", "created_at": "2026-06-23T12:00:00Z", "resource_type": "payment_request", "resource_id": "pyr_000000001VgEh72lXvTXkH", "actor": { "type": "system" }, "data": { "payment_request_id": "pyr_000000001VgEh72lXvTXkH" } } ``` Example payload: payment_request.created ``` { "id": "00000000-0000-4000-8000-000000000001", "type": "payment_request.created", "version": "2026-06-23", "created_at": "2026-06-23T12:00:00Z", "resource_type": "payment_request", "resource_id": "pyr_000000001VgEh72lXvTXkH", "actor": { "type": "system" }, "data": { "payment_request_id": "pyr_000000001VgEh72lXvTXkH" } } ``` Example payload: payment_request.declined ``` { "id": "00000000-0000-4000-8000-000000000001", "type": "payment_request.declined", "version": "2026-06-23", "created_at": "2026-06-23T12:00:00Z", "resource_type": "payment_request", "resource_id": "pyr_000000001VgEh72lXvTXkH", "actor": { "type": "system" }, "data": { "payment_request_id": "pyr_000000001VgEh72lXvTXkH" } } ``` Example payload: payment_request.expired ``` { "id": "00000000-0000-4000-8000-000000000001", "type": "payment_request.expired", "version": "2026-06-23", "created_at": "2026-06-23T12:00:00Z", "resource_type": "payment_request", "resource_id": "pyr_000000001VgEh72lXvTXkH", "actor": { "type": "system" }, "data": { "payment_request_id": "pyr_000000001VgEh72lXvTXkH" } } ``` Example payload: payment_request.remind_sent ``` { "id": "00000000-0000-4000-8000-000000000001", "type": "payment_request.remind_sent", "version": "2026-06-23", "created_at": "2026-06-23T12:00:00Z", "resource_type": "payment_request", "resource_id": "pyr_000000001VgEh72lXvTXkH", "actor": { "type": "system" }, "data": { "payment_request_id": "pyr_000000001VgEh72lXvTXkH" } } ``` ### payments 3 | Event | Summary | | | --- | --- | --- | | `payment.confirmed` | A payment was confirmed on-chain (settlement observed and verified). | | | `payment.created` | A payment was created and entered the pipeline (pending compliance/signing). Carries the payment id; fetch full details via GET /v1/payments/{id}. | | | `payment.failed` | A payment failed, a signing/relay error or an on-chain revert. `failure_reason_code` identifies the cause. | | Example payload: payment.confirmed ``` { "id": "00000000-0000-4000-8000-000000000001", "type": "payment.confirmed", "version": "2026-06-23", "created_at": "2026-06-23T12:00:00Z", "resource_type": "payment", "resource_id": "pay_000000001VgEh72lXvTXkH", "actor": { "type": "system" }, "data": { "payment_id": "pay_000000001VgEh72lXvTXkH", "workspace_id": "00000000-0000-4000-8000-000000000001", "tx_hash": "0xabc123…", "chain_id": 43113 } } ``` Example payload: payment.created ``` { "id": "00000000-0000-4000-8000-000000000001", "type": "payment.created", "version": "2026-06-23", "created_at": "2026-06-23T12:00:00Z", "resource_type": "payment", "resource_id": "pay_000000001VgEh72lXvTXkH", "actor": { "type": "system" }, "data": { "payment_id": "pay_000000001VgEh72lXvTXkH" } } ``` Example payload: payment.failed ``` { "id": "00000000-0000-4000-8000-000000000001", "type": "payment.failed", "version": "2026-06-23", "created_at": "2026-06-23T12:00:00Z", "resource_type": "payment", "resource_id": "pay_000000001VgEh72lXvTXkH", "actor": { "type": "system" }, "data": { "payment_id": "pay_000000001VgEh72lXvTXkH", "failure_reason_code": "on_chain_revert" } } ``` ### tokenization 2 | Event | Summary | | | --- | --- | --- | | `tokenization.operation.confirmed` | A privileged tokenization operation confirmed on-chain (verified success, not just mined). `kind` identifies the operation (deploy / mint / transfer / grant_role / …). | | | `tokenization.operation.failed` | A privileged tokenization operation failed, `reason` distinguishes an on-chain revert (mined but reverted) from an Engine error (never executed). | | Example payload: tokenization.operation.confirmed ``` { "id": "00000000-0000-4000-8000-000000000001", "type": "tokenization.operation.confirmed", "version": "2026-06-23", "created_at": "2026-06-23T12:00:00Z", "resource_type": "tokenization_operation", "resource_id": "tkop_000000001VgEh72lXvTXkH", "actor": { "type": "system" }, "data": { "operation_id": "tkop_000000001VgEh72lXvTXkH", "kind": "deploy_erc20", "tx_hash": "0xabc123…", "customer_token_id": "ctk_000000001VgEh72lXvTXkH" } } ``` Example payload: tokenization.operation.failed ``` { "id": "00000000-0000-4000-8000-000000000001", "type": "tokenization.operation.failed", "version": "2026-06-23", "created_at": "2026-06-23T12:00:00Z", "resource_type": "tokenization_operation", "resource_id": "tkop_000000001VgEh72lXvTXkH", "actor": { "type": "system" }, "data": { "operation_id": "tkop_000000001VgEh72lXvTXkH", "kind": "mint", "reason": "reverted", "tx_hash": "0xabc123…" } } ``` ### treasury 15 | Event | Summary | | | --- | --- | --- | | `treasury.revenue_change.created` | A revenue-treasury re-link change request was created. | | | `treasury.revenue_change.signed` | A signer attested a revenue-treasury re-link change request. | | | `treasury.revenue_designation_changed` | The workspace revenue Safe was re-pointed to a new treasury wallet. The subject is the applied change request (rtc_); the wallets ride as from/to references. | | | `treasury.transfer.cancelled` | A treasury transfer was cancelled. | | | `treasury.transfer.confirmed` | A treasury transfer was confirmed on-chain. | | | `treasury.transfer.drafted` | A treasury transfer was drafted. | | | `treasury.transfer.executed` | A treasury transfer was submitted on-chain. | | | `treasury.transfer.failed` | A treasury transfer failed or reverted. | | | `treasury.transfer.ready_to_execute` | A treasury transfer reached its signing threshold. | | | `treasury.transfer.signed` | A signer attested a treasury transfer. | | | `treasury.wallet.decommissioned` | A treasury wallet was decommissioned. | | | `treasury.wallet.deploy_confirmed` | A treasury Safe deployment was confirmed on-chain. | | | `treasury.wallet.deploy_failed` | A treasury Safe deployment failed or reverted. | | | `treasury.wallet.deploy_initiated` | A treasury Safe deployment was queued. | | | `treasury.wallet.designated_revenue` | A treasury wallet was designated as the revenue Safe. | | Example payload: treasury.revenue_change.created ``` { "id": "00000000-0000-4000-8000-000000000001", "type": "treasury.revenue_change.created", "version": "2026-06-23", "created_at": "2026-06-23T12:00:00Z", "resource_type": "revenue_treasury_change", "resource_id": "rtc_000000001VgEh72lXvTXkH", "actor": { "type": "system" }, "data": { "resource_id": "rtc_000000001VgEh72lXvTXkH", "from_treasury_wallet_id": "vlt_000000001VgEh72lXvTXkH", "to_treasury_wallet_id": "vlt_000000001VgEh72lXvTXkH", "required_approvals": 2 } } ``` Example payload: treasury.revenue_change.signed ``` { "id": "00000000-0000-4000-8000-000000000001", "type": "treasury.revenue_change.signed", "version": "2026-06-23", "created_at": "2026-06-23T12:00:00Z", "resource_type": "revenue_treasury_change", "resource_id": "rtc_000000001VgEh72lXvTXkH", "actor": { "type": "system" }, "data": { "resource_id": "rtc_000000001VgEh72lXvTXkH", "signer_staff_id": "stf_000000001VgEh72lXvTXkH", "signature_count": 1, "required_approvals": 2 } } ``` Example payload: treasury.revenue_designation_changed ``` { "id": "00000000-0000-4000-8000-000000000001", "type": "treasury.revenue_designation_changed", "version": "2026-06-23", "created_at": "2026-06-23T12:00:00Z", "resource_type": "revenue_treasury_change", "resource_id": "rtc_000000001VgEh72lXvTXkH", "actor": { "type": "system" }, "data": { "resource_id": "rtc_000000001VgEh72lXvTXkH", "from_treasury_wallet_id": "vlt_000000001VgEh72lXvTXkH", "to_treasury_wallet_id": "vlt_000000001VgEh72lXvTXkH" } } ``` Example payload: treasury.transfer.cancelled ``` { "id": "00000000-0000-4000-8000-000000000001", "type": "treasury.transfer.cancelled", "version": "2026-06-23", "created_at": "2026-06-23T12:00:00Z", "resource_type": "treasury_transfer", "resource_id": "trf_000000001VgEh72lXvTXkH", "actor": { "type": "system" }, "data": { "resource_id": "trf_000000001VgEh72lXvTXkH" } } ``` Example payload: treasury.transfer.confirmed ``` { "id": "00000000-0000-4000-8000-000000000001", "type": "treasury.transfer.confirmed", "version": "2026-06-23", "created_at": "2026-06-23T12:00:00Z", "resource_type": "treasury_transfer", "resource_id": "trf_000000001VgEh72lXvTXkH", "actor": { "type": "system" }, "data": { "resource_id": "trf_000000001VgEh72lXvTXkH" } } ``` Example payload: treasury.transfer.drafted ``` { "id": "00000000-0000-4000-8000-000000000001", "type": "treasury.transfer.drafted", "version": "2026-06-23", "created_at": "2026-06-23T12:00:00Z", "resource_type": "treasury_transfer", "resource_id": "trf_000000001VgEh72lXvTXkH", "actor": { "type": "system" }, "data": { "resource_id": "trf_000000001VgEh72lXvTXkH" } } ``` Example payload: treasury.transfer.executed ``` { "id": "00000000-0000-4000-8000-000000000001", "type": "treasury.transfer.executed", "version": "2026-06-23", "created_at": "2026-06-23T12:00:00Z", "resource_type": "treasury_transfer", "resource_id": "trf_000000001VgEh72lXvTXkH", "actor": { "type": "system" }, "data": { "resource_id": "trf_000000001VgEh72lXvTXkH" } } ``` Example payload: treasury.transfer.failed ``` { "id": "00000000-0000-4000-8000-000000000001", "type": "treasury.transfer.failed", "version": "2026-06-23", "created_at": "2026-06-23T12:00:00Z", "resource_type": "treasury_transfer", "resource_id": "trf_000000001VgEh72lXvTXkH", "actor": { "type": "system" }, "data": { "resource_id": "trf_000000001VgEh72lXvTXkH" } } ``` Example payload: treasury.transfer.ready_to_execute ``` { "id": "00000000-0000-4000-8000-000000000001", "type": "treasury.transfer.ready_to_execute", "version": "2026-06-23", "created_at": "2026-06-23T12:00:00Z", "resource_type": "treasury_transfer", "resource_id": "trf_000000001VgEh72lXvTXkH", "actor": { "type": "system" }, "data": { "resource_id": "trf_000000001VgEh72lXvTXkH" } } ``` Example payload: treasury.transfer.signed ``` { "id": "00000000-0000-4000-8000-000000000001", "type": "treasury.transfer.signed", "version": "2026-06-23", "created_at": "2026-06-23T12:00:00Z", "resource_type": "treasury_transfer", "resource_id": "trf_000000001VgEh72lXvTXkH", "actor": { "type": "system" }, "data": { "resource_id": "trf_000000001VgEh72lXvTXkH" } } ``` Example payload: treasury.wallet.decommissioned ``` { "id": "00000000-0000-4000-8000-000000000001", "type": "treasury.wallet.decommissioned", "version": "2026-06-23", "created_at": "2026-06-23T12:00:00Z", "resource_type": "treasury_wallet", "resource_id": "vlt_000000001VgEh72lXvTXkH", "actor": { "type": "system" }, "data": { "resource_id": "vlt_000000001VgEh72lXvTXkH" } } ``` Example payload: treasury.wallet.deploy_confirmed ``` { "id": "00000000-0000-4000-8000-000000000001", "type": "treasury.wallet.deploy_confirmed", "version": "2026-06-23", "created_at": "2026-06-23T12:00:00Z", "resource_type": "treasury_wallet", "resource_id": "vlt_000000001VgEh72lXvTXkH", "actor": { "type": "system" }, "data": { "resource_id": "vlt_000000001VgEh72lXvTXkH" } } ``` Example payload: treasury.wallet.deploy_failed ``` { "id": "00000000-0000-4000-8000-000000000001", "type": "treasury.wallet.deploy_failed", "version": "2026-06-23", "created_at": "2026-06-23T12:00:00Z", "resource_type": "treasury_wallet", "resource_id": "vlt_000000001VgEh72lXvTXkH", "actor": { "type": "system" }, "data": { "resource_id": "vlt_000000001VgEh72lXvTXkH" } } ``` Example payload: treasury.wallet.deploy_initiated ``` { "id": "00000000-0000-4000-8000-000000000001", "type": "treasury.wallet.deploy_initiated", "version": "2026-06-23", "created_at": "2026-06-23T12:00:00Z", "resource_type": "treasury_wallet", "resource_id": "vlt_000000001VgEh72lXvTXkH", "actor": { "type": "system" }, "data": { "resource_id": "vlt_000000001VgEh72lXvTXkH" } } ``` Example payload: treasury.wallet.designated_revenue ``` { "id": "00000000-0000-4000-8000-000000000001", "type": "treasury.wallet.designated_revenue", "version": "2026-06-23", "created_at": "2026-06-23T12:00:00Z", "resource_type": "treasury_wallet", "resource_id": "vlt_000000001VgEh72lXvTXkH", "actor": { "type": "system" }, "data": { "resource_id": "vlt_000000001VgEh72lXvTXkH" } } ``` ### webhooks 4 | Event | Summary | | | --- | --- | --- | | `webhook.delivery.dlq` | A webhook delivery was dead-lettered (retries exhausted or a permanent 4xx). Subscribe to learn when your endpoint is failing. | | | `webhook.endpoint.disabled` | A webhook endpoint was disabled; deliveries are paused until it is re-enabled. | | | `webhook.secret.rotated` | A webhook endpoint's signing secret was rotated; the previous secret stays valid during the overlap window. | | | `webhook.test` | A synthetic event fired by POST /v1/webhook-endpoints/{id}/test so you can verify reachability + signature verification. Never produced by real platform activity. | | Example payload: webhook.delivery.dlq ``` { "id": "00000000-0000-4000-8000-000000000001", "type": "webhook.delivery.dlq", "version": "2026-06-23", "created_at": "2026-06-23T12:00:00Z", "resource_type": "webhook_endpoint", "resource_id": "whe_000000001VgEh72lXvTXkH", "actor": { "type": "system" }, "data": { "webhook_endpoint_id": "whe_000000001VgEh72lXvTXkH", "webhook_delivery_id": "whd_000000001VgEh72lXvTXkH", "event": "payment.confirmed", "last_status_code": 410, "attempt_count": 7 } } ``` Example payload: webhook.endpoint.disabled ``` { "id": "00000000-0000-4000-8000-000000000001", "type": "webhook.endpoint.disabled", "version": "2026-06-23", "created_at": "2026-06-23T12:00:00Z", "resource_type": "webhook_endpoint", "resource_id": "whe_000000001VgEh72lXvTXkH", "actor": { "type": "system" }, "data": { "webhook_endpoint_id": "whe_000000001VgEh72lXvTXkH" } } ``` Example payload: webhook.secret.rotated ``` { "id": "00000000-0000-4000-8000-000000000001", "type": "webhook.secret.rotated", "version": "2026-06-23", "created_at": "2026-06-23T12:00:00Z", "resource_type": "webhook_endpoint", "resource_id": "whe_000000001VgEh72lXvTXkH", "actor": { "type": "system" }, "data": { "webhook_endpoint_id": "whe_000000001VgEh72lXvTXkH" } } ``` Example payload: webhook.test ``` { "id": "00000000-0000-4000-8000-000000000001", "type": "webhook.test", "version": "2026-06-23", "created_at": "2026-06-23T12:00:00Z", "resource_type": "webhook_endpoint", "resource_id": "whe_000000001VgEh72lXvTXkH", "actor": { "type": "system" }, "data": { "webhook_endpoint_id": "whe_000000001VgEh72lXvTXkH", "message": "This is a test event from XKOVA." } } ```