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. 314 operations across 15 areas. 314 carry a full description. Tiers: 228 integrator, 86 partner. Admin only operations are not shown here.
This is the fast, scannable list. For schemas, examples, and a live try it console, open the interactive reference. Guide pages link into these operations.
Searches account holders within the tenant as a staff operator, returning a cursor-paginated list. Filter by free-text query, restrict to specific ids, and use fields to project a subset of each holder's attributes.
POST/account-holdersintegrator
Create an account holder (staff)
Staff-initiated account-holder creation. `tenant_id` is derived
from the authenticated context, never accepted from the client.
No raw PII crosses this boundary, `cip_payload` carries hashes
only (computed tenant-side).
GET/account-holders/{id}integrator
Get account holder (staff view; broader than /users/{id})
Returns the full staff view of one account holder, broader than the self-scoped profile, including compliance and lifecycle attributes. Use fields to project a subset of the response.
PATCH/account-holders/{id}integrator
Update mutable account-holder fields
Allowed mutations: `state` (subject to ALLOWED_TRANSITIONS),
`tier`, `state_reason`, `auto_save_contacts`. Immutable
via this endpoint: `tenant_id`, `cip_payload` (once set,
re-verification is a separate ceremony), `created_at`.
POST/account-holders/{id}/closeintegrator
Staff close/offboard an account holder
Closes and offboards an account holder, ending their relationship with the tenant and disabling further activity. Returns the holder in its closed state. Requires a staff role with offboarding permission.
GET/account-holders/{id}/historyintegrator
Account-holder tx history (staff)
Returns an account holder's transaction history as a staff operator, every payment touching a wallet they own, as sender or recipient. Cursor-paginated, newest first. Rows use the compact transaction-history shape (the same `WalletTransaction` element as GET /v1/wallets/{id}/history), not the full `Payment` row.
POST/account-holders/{id}/holdpartner
Staff-initiated hold on an account holder (customer_service/compliance_officer/admin; see override matrix)
Places a staff-initiated hold on an account holder, suspending their ability to transact pending review. Restricted to customer service, compliance officer, and admin roles per the override matrix. Returns the holder in its held state.
POST/account-holders/{id}/releasepartner
Staff release of a hold
Lifts a hold previously placed on an account holder, restoring their ability to transact. Returns the holder in its released state. Requires the appropriate staff role.
POST/account-holders/jitpartner
Explicit JIT-provision an account holder (backfill / non-SSO contexts)
Explicitly provisions an account holder on demand, for backfill or non-SSO contexts where no identity-provider sign-in created the record. Idempotent on the external subject, so repeat calls return the existing holder. When XKOVA hosts the member identity, provisioning an email whose member has not yet proven control returns a pending account holder, fully usable for wallet and payment flows, whose sign-in identity is claimed automatically the first time the member authenticates.
GET/account-holders/self/balanceintegrator
Get the current account holder's wallet balances
Account-holder self-service endpoint. Requires a tenant or
workspace API key plus account-holder OIDC headers so the API
can resolve the caller. Returns the caller's own wallet balances
from XKOVA's reconciled balance projection (not a
live RPC `balanceOf`). A member can only read their own balances.
GET/account-holders/self/bank-balanceintegrator
Get the current account holder's USD bank (savings) balance
Account-holder self-service endpoint. Requires a tenant or
workspace API key plus account-holder OIDC headers. Returns the
caller's own USD savings balance from the core banking provider,
the fiat side of their balance, complementing the on-chain dUSDC
from `/account-holders/self/balance`.
List the current account holder's internal bank transfers
Account-holder self-service endpoint. Returns the caller's own
internal bank transfers (share savings ↔ share draft), newest
first, the bank side of the unified activity feed. Deposit and
withdraw legs are excluded; they surface as on-chain payments via
`/account-holders/self/transactions`.
Move USD between the account holder's own share savings and share draft
Account-holder self-service endpoint. Requires a tenant or
workspace API key plus account-holder OIDC headers. Moves USD
between the caller's OWN two fiat accounts (share savings ↔ share
draft) as a same-institution internal transfer on the core ledger
, no chain. The `direction` enum constrains both legs to the
member's own accounts.
Get the current account holder's USD share-draft (checking) balance
Account-holder self-service endpoint. Requires a tenant or
workspace API key plus account-holder OIDC headers. Returns the
caller's own USD share-draft (checking) balance from the core
banking provider, the companion to the share-savings balance from
`/account-holders/self/bank-balance`.
GET/account-holders/self/consentsintegrator
Get the current account holder's consent state
Account-holder self-service endpoint. Requires a tenant or
workspace API key plus account-holder OIDC headers so the API
can resolve the caller and tenant binding.
POST/account-holders/self/consentsintegrator
Accept current tenant policies for the current account holder
Appends one consent record per accepted current policy version. The
first-use flag becomes complete when all required current policies are
accepted.
POST/account-holders/self/data-exportsintegrator
Request a right-to-access data export for the current account holder
Account-holder self-service endpoint. Requires a tenant or
workspace API key plus account-holder OIDC headers. The MVP
implementation materializes the export artifact during the
request and returns a 24-hour signed URL when ready.
Get data export status for the current account holder
Returns the status of a data-export job the current account holder requested for their own records, including its processing state and, once ready, the download reference. Authenticated as the account holder.
GET/account-holders/self/transactionsintegrator
List the current account holder's transactions
Account-holder self-service endpoint. Requires a tenant or
workspace API key plus account-holder OIDC headers. Returns the
caller's own payment history, every transaction where one of
their wallets is the sender or recipient, cursor-paginated,
newest first. A member can only read their own transactions.
GET/contactsintegrator
List account holder's address book
Lists contacts owned by a single account_holder. The
integrator's backend authenticates with a tenant-scope api
key and passes the holder's id explicitly via
`account_holder_id` (the integrator knows who's logged in
via their own auth flow).
Email + phone come back as HMAC-SHA-256 hashes (per-tenant
pepper), not plaintext, see §21.4 + the
`Building with member contacts` narrative for the security
model. To look up "do I already have a contact for
alice@example.com?" use `POST /v1/contacts/search` (body
keeps plaintext out of query strings + access logs).
POST/contactsintegrator
Add a contact
Creates a contact owned by `account_holder_id`. Plaintext
email / phone in the request body are HMAC-hashed at the
api boundary using the tenant's per-tenant pepper before
anything reaches the service or DB. Pino + audit redact
these fields so plaintext never lands in observability.
Returns 403 `contacts_disabled` when the tenant has
`contacts_feature_enabled = false` (the kill-switch).
DELETE/contacts/{id}integrator
Delete a contact
Deletes a saved contact from an account holder's address book so it no longer appears as a selectable payee. Scope the call to the owning holder with account_holder_id.
GET/contacts/{id}integrator
Get a contact
Fetches a single contact owned by `account_holder_id`. Email +
phone come back as HMAC-SHA-256 hashes (per-tenant pepper), not
plaintext, same security model as `GET /v1/contacts`.
PATCH/contacts/{id}integrator
Update a contact
Updates a saved contact (a stored payee an account holder sends to) with new details such as name, address, or label. Returns the updated contact. Scope the call to the owning holder with account_holder_id.
POST/contacts/searchintegrator
Look up a contact by email or phone hash
Body-based lookup so plaintext email / phone never leaks into
access logs (which capture query strings). The api hashes
the supplied identifier with the tenant's pepper and
searches the holder's address book for a matching row. Use
for the auto-create-on-send UX prompt ("do I already have
Alice in my contacts?") + dedup checks before creating
a new contact.
Cursor-paged (newest-first); OR semantics across email +
phone (members rarely have both stored).
Returns the tenant's compliance-case queue. Defaults to
`state=open` (the typical operator triage view); pass
`state=all` to drop the filter, or any specific state
(`investigating`, `sar_filed`, `closed`, `escalated`).
`case_type=compliance_block` is required to surface auto-
materialized block cases (those are born `closed` so they
don't appear in the default view). Each row carries its
latest `evidence` rows hydrated.
GET/compliance-cases/{id}partner
Get a single compliance case (with evidence timeline)
Returns a single compliance case with its full evidence timeline hydrated from the case record. Used by the case detail view to render state, assignment, and the chronological evidence history.
PATCH/compliance-cases/{id}partner
Edit case metadata (assignee / priority / summary)
Partial update of mutable case metadata. State transitions go
through POST /compliance-cases/{id}/transitions, not here. Every
field is optional; only provided fields are written.
POST/compliance-cases/{id}/assignpartner
Assign a case to a staff member (state-preserving)
Convenience for the common operator action of "I'll take
this one." Stamps `assignee_staff_id` without changing the
case state. Uses `POST /:id/transitions` for state changes.
POST/compliance-cases/{id}/closepartner
Close a case (convenience over /transitions with to=closed)
Closes a compliance case, recording the operator-supplied reason. A convenience over the transitions endpoint with to=closed, applying the same state-machine validation (closable from any state except already-closed).
GET/compliance-cases/{id}/commentspartner
List comments on a case
Returns the comments thread on a compliance case in chronological order. Used to render the investigator discussion alongside the case's evidence timeline.
POST/compliance-cases/{id}/commentspartner
Add a comment (supports @mentions)
Adds a comment to a compliance case, supporting @mentions of other operators to notify them. Returns the created comment for appending to the case discussion.
POST/compliance-cases/{id}/decisionpartner
Decide a review-held payment's case (clear or confirm the hold)
Decides the case of a payment held for compliance review. `cleared`
releases the held payment back into the pipeline (it proceeds to
signing); `confirmed` upholds the hold and blocks the payment. Either
way this case closes atomically with the payment outcome, and the
deciding staff identity is recorded on the case so the override is
auditable. Only a case whose subject is a review-held payment can be
decided (422 otherwise). Requires compliance-officer authority and an
operator staff identity.
POST/compliance-cases/{id}/escalatepartner
Escalate a case (convenience over /transitions with to=escalated)
Escalates a compliance case, optionally reassigning it and recording a free-text note as an evidence row on the timeline. A convenience over the transitions endpoint with to=escalated.
POST/compliance-cases/{id}/transitionspartner
Transition a case state (the underlying state-machine endpoint)
Generic state transition. The convenience endpoints `/close`
and `/escalate` wrap this with the relevant `to=` value.
Valid transitions are validated by the service; invalid
ones return 422. Optional fields are ignored when not
relevant to the chosen transition.
Permanently delete the account holder's avatar, removing both the stored image object and the reference on the holder. Returns the updated account holder record.
GET/account-holders/{id}/avatarintegrator
Get a short-TTL presigned GET URL for the holder's avatar
Return a short-lived presigned GET URL for the account holder's current avatar so a client can render it, or null when no avatar has been set.
Confirm avatar upload + persist object_key on the holder
Confirm that an avatar image was uploaded to the presigned URL and persist its object key on the account holder. Returns the updated account holder record.
Mint a presigned PUT URL for direct-to-S3 avatar upload
Mint a short-lived presigned PUT URL so a client can upload an account holder's avatar image directly to object storage. Returns the upload URL, the object key to send back on confirm, and an expiry.
Returns the state, amount, recipient_ref, branding theme, and
available claim paths for a pending escrow. Path-token-
authenticated, no tenant API key required.
POST/claims/{token}/refreshintegrator
Mint a fresh claim token for the same underlying escrow
Recipient-driven token rotation, useful when the original
token is about to expire mid-flow. Returns a new `claim_token`
+ `claim_url`; the old token continues to work until its
original expiry but should be discarded.
POST/claims/{token}/resendintegrator
Email a fresh claim link to the recipient on file
Recovery path for a recipient whose original claim link EXPIRED,
GET /claims/{token} and /refresh both reject an expired token, so a
stale emailed link is otherwise unrecoverable. This re-mints a fresh
token and emails it to the escrow's on-file recipient (a verified
contact); the new link is NEVER returned in the response.
The response is a uniform `202` with no escrow detail regardless of
whether the token matched, the escrow is still claimable, or an email
was actually sent, so a holder of a leaked or expired token cannot
fingerprint the escrow. Rate-limited to protect the recipient's inbox.
SMS recipients are not served (platform SMS delivery is not available).
POST/claims/{token}/resolveintegrator
Resolve the claim by choosing a path
Recipient picks `external_wallet` or
`wait`. Path-specific payload is validated server-side. The
`wait` path leaves the escrow open until expiry; every other
path drives the corresponding settlement flow.
Pre-condition: most paths require `otp_verified === true`
from a prior verify_otp call.
POST/claims/{token}/send-otpintegrator
Issue a one-time code to the recipient's chosen channel
Returns a `challenge_id` the recipient submits alongside the
code in the verify_otp call. The OTP itself is sent
out-of-band (email or SMS), never returned in the response.
POST/claims/{token}/verify-otpintegrator
Verify the one-time code the recipient received
Verify the one-time code a payment recipient received during a claim flow, matching it against the issued challenge. On success the claim advances toward resolution; an invalid or expired code is rejected.
Confirm a signup or email-change token from a verification email
Confirm a signup or email-change token delivered in a verification email. On success the member's email is marked verified and a fresh access and refresh token pair is issued, unless the member has MFA enabled, in which case a second-factor challenge is returned instead.
GET/member-auth/mfaintegrator
List the calling member's MFA factors
List the calling member's enrolled multi-factor authentication factors, including each factor's id, type, and verification status, for rendering MFA settings.
DELETE/member-auth/mfa/{factorId}integrator
Remove an MFA factor (XKOVA IDP requires aal2 for verified factors)
Remove a multi-factor authentication factor from the calling member. The XKOVA IDP requires an aal2 session to remove a factor that has already been verified.
POST/member-auth/mfa/{factorId}/verifyintegrator
Verify a TOTP code; flips the factor to verified + returns aal2 token pair
Verify a TOTP code against an enrolling factor. On success the factor is marked verified and an elevated aal2 access and refresh token pair is returned for the member.
POST/member-auth/mfa/enrollintegrator
Start TOTP enrollment for the calling member
Begin TOTP multi-factor enrollment for the calling member and return the secret and provisioning URI needed to display an authenticator QR code. The factor stays unverified until a code is confirmed.
POST/member-auth/password-reset/confirmintegrator
Confirm a password reset and issue a fresh session
Complete a password reset using the token from the reset email and a new
password, then issue a fresh access and refresh token pair so the member
is signed in immediately.
When the member has MFA enabled this returns an `mfa_required` challenge
instead, and THE PASSWORD IS NOT YET CHANGED, it is applied only once a
code is accepted at POST /v1/member-auth/sign-in/mfa/verify. A reset link
alone cannot take over an MFA-protected account.
POST/member-auth/password-reset/requestintegrator
Request a password-reset email
Send a password-reset email to the supplied address. The response confirms dispatch without disclosing whether the address is registered.
POST/member-auth/refreshintegrator
Rotate the refresh token + return a fresh access JWT
Refresh tokens are single-use. Replay (presenting a token
whose hash matches a `previous_refresh_token_hash`) revokes
the entire family + returns 401 `member_auth_refresh_replay_detected`.
GET/member-auth/sessionintegrator
Resolve the current member's identity + session info
Resolve the calling member's identity and session details from the bearer token, returning the member id, email, and session metadata. Used to hydrate an embedded UI after sign-in.
POST/member-auth/sessions/importintegrator
Wrap an existing XKOVA IDP token pair in a member_session
Bridge endpoint for integrators who already obtained XKOVA IDP
tokens via the implicit-grant browser flow (e.g. the XKOVA IDP client
magic-link) and want to migrate to the wrapped session model
without changing their XKOVA IDP config.
Verifies the access JWT against the federated XKOVA IDP JWKS,
ensures the local account_holder + binding + global profile
exist (JIT-provisioning if needed), then creates a
member_sessions row holding the XKOVA IDP refresh token
server-side. After this call the XKOVA IDP refresh never
leaves our server, all subsequent rotation / sign-out goes
through /v1/member-auth/refresh and /v1/member-auth/sign-out.
MFA is enforced on what the imported token PROVES, not on which flow
produced it: a token that has not cleared a second factor, for a member
who has one, returns an `mfa_required` challenge instead of a session
(and the token stays unusable on member-scoped routes until the
challenge is answered).
POST/member-auth/sign-inintegrator
Sign in with email + password
Authenticate a member with their email and password. Rate-limited to
protect against credential-stuffing.
Returns an access and refresh token pair, OR, when the member has a
verified MFA factor, an `mfa_required` challenge and no tokens at all.
Branch on the `status` field (a token set never carries it) and pass the
`mfa_token` plus a code to POST /v1/member-auth/sign-in/mfa/verify to
obtain the session.
Exchange the single-use token from a magic-link email for an authenticated session, returning a fresh access and refresh token pair for the member, or a second-factor challenge when the member has MFA enabled.
Send a passwordless magic-link email to the supplied address. The response confirms the email was dispatched and never reveals whether the address belongs to an existing member.
POST/member-auth/sign-in/mfa/verifyintegrator
Complete an MFA-challenged sign-in with a TOTP code
Second leg of a sign-in (or magic-link / email-confirm / password-reset
confirm / session import) that came back `mfa_required`. Exchanges the
`mfa_token` and a code from the member's authenticator for the token set
the first leg withheld.
The challenge is single-use, short-lived, and attempt-capped: a wrong
code can be retried, but a handful of wrong codes closes it and the
sign-in starts over.
POST/member-auth/sign-outintegrator
Sign the member out (per-session or global)
Revoke the member's current session, or every active session when global scope is requested, invalidating the associated refresh tokens. Returns no content on success.
POST/member-auth/sign-upintegrator
Sign up a new member (wrapped XKOVA IDP + JIT-provision local holder)
Register a new member with email and password through the XKOVA IDP and provision their local account holder. Returns a session token set, or a pending-verification response when email confirmation is required before sign-in.
Issue a wallet-derivation challenge (proof-of-possession)
Step 1 of the proof-of-possession protocol. Server issues a single-use
nonce + canonical EIP-191 payload; client signs the payload via its
Thirdweb in-app wallet session and submits via /register. 15min TTL.
Mint a Thirdweb-domain JWT for client-side wallet auth
Step 2 of the proof-of-possession protocol. Returns a Thirdweb-domain
JWT the client uses for `inAppWallet().connect({strategy: 'jwt'})`.
Discriminated-union response shape future-proofs for non-Thirdweb
providers (Turnkey, Privy, etc.).
Step 3 of the proof-of-possession protocol. Server ecrecover-verifies
the signature against the canonical message stored at challenge
issuance, persists wallet rows for every chain in the workspace's
chain-set (one signature → N rows for the same address), marks the
challenge consumed.
List funding requests for the tenant (paginated, recent first)
Returns a cursor-paginated list of the tenant's funding requests (fiat to stablecoin), newest first. Filter by state, account holder, and ramp profile to narrow the result set.
POST/funding-requestsintegrator
Create a funding request (fiat → stablecoin via [X] partner)
Initiates a funding request that converts fiat to stablecoin through a ramp partner: bank debit, compliance screening, and partner mint run as one orchestrated flow. Returns 202; track progress via GET /funding-requests/{id}. Pass an Idempotency-Key to make retries safe.
GET/funding-requests/{id}integrator
Get a funding request
Returns a single funding request by id, including its current state, fiat and stablecoin amounts, account holder, ramp profile, and any on-chain mint reference.
GET/off-ramp-requestsintegrator
List off-ramp requests for the tenant (paginated, recent first)
Returns a cursor-paginated list of the tenant's off-ramp requests (stablecoin to fiat), newest first. Filter by state, account holder, and ramp profile to narrow the result set.
POST/off-ramp-requestsintegrator
Create an off-ramp request (stablecoin → fiat via [X] partner)
Initiates an off-ramp request that converts stablecoin to fiat through a ramp partner. The member's wallet burns the stablecoin on-chain first; this call verifies the burn and triggers the fiat payout and bank credit. Returns 202; track progress via GET /off-ramp-requests/{id}.
GET/off-ramp-requests/{id}integrator
Get an off-ramp request
Returns a single off-ramp request by id, including its current state, stablecoin and fiat amounts, account holder, ramp profile, and the verified burn transaction reference.
GET/off-ramp-requests/burn-addressintegrator
The on-chain address to burn stablecoin TO when redeeming (off-ramp)
Off-ramp is member-burns-first: the member's wallet transfers the stablecoin to the conversion partner before the request is created. This resolves that destination from the active ramp profile's partner (the reserve in swap mode, else the partner's redeem address) so the client knows where to sign the burn transfer.
Named approver sets for the compound-quorum engine, newest-first.
Returns `{ data }` only (bounded list, not cursor-paged). Tenant-scoped.
GET/approval-groups/{id}partner
Get one approver group (with its members)
Returns a single approver group along with its member operators. Approver groups are named sets referenced by the compound-quorum engine when requiring approvals for governed actions.
GET/approval-requestspartner
List the workspace's approval requests (operator inbox)
The actionable set (open / partially_approved / ready) by default;
pass `include_terminal=true` to also see applied / cancelled. Newest
first; each request's compound quorum is evaluated. Returns `{ data }`
only (bounded list, not cursor-paged). Tenant-scoped.
GET/approval-requests/{id}partner
Get one approval request (with quorum status + member links)
The single-request read also carries `member_approval_urls`, one
signing link per active member subject in the quorum's groups.
GET/governance/actionspartner
Read the governance action matrix
The catalog of registered admin actions + each action's per-workspace
approval requirement (Fireblocks-style). Drives `requireApproval`,
which the registered mutations call before applying. Tenant-scoped.
GET/policiespartner
List the workspace's authorization policies (TAP firewall)
Cursor-paginated, newest-first. Optional `status` filter
(draft | committed | superseded). Each policy carries its nested
rules. Tenant-scoped, partner-scope callers get
`partner_scope_denied`.
GET/policies/{id}partner
Get one authorization policy (with its rules)
Returns a single value-movement authorization policy together with its rules. Used to inspect a draft or committed policy that governs which payments and transfers are permitted for the tenant.
POST/policies/{id}/simulatepartner
Dry-run an intent against a policy (read-only decision trace)
Returns the would-be decision a real intent WOULD get against this
(draft or committed) policy, with the full per-rule trace. Read-only,
mints + persists nothing. Available to any tenant-scoped caller.
GET/policies/decisionspartner
The firewall activity log, persisted authorization decisions
Cursor-paginated, newest-first. Every persisted authorization
decision for the workspace ("why was this allowed / denied"). Optional
`outcome` filter (allow | deny | allow_pending_approval). Tenant-scoped.
List the calling tenant's escrows (cursor-paginated, newest first)
Returns a cursor-paginated list of the calling tenant's escrows, newest first. Filter by state, recipient reference, claim path, and date range, and project a subset of fields via the fields parameter.
POST/escrowsintegrator
Create an escrow (sender-side)
Allocates an escrow row + mints a claim_token + signing_url
(decision 1B + Action 1). The on-chain createPaymentRelayed
happens in a follow-up phase when the sender's wallet signs
the CreatePaymentIntent. The row sits with
on_chain_payment_id=null until the on-chain PaymentCreated
event is observed and the reference is backfilled.
GET/escrows/{id}integrator
Get a single escrow
Returns a single escrow by id, including its state, amounts, recipient reference, claim details, and on-chain references. Use the fields parameter to project a subset of the response.
POST/escrows/{id}/cancelintegrator
Initiate a sender-cancel (mints the cancel signing URL)
Tenant-scoped. Mints a cancel HMAC token + URL the sender
opens to sign the CancelIntent. Pre-condition: row.state ===
'pending' AND on_chain_payment_id IS NOT NULL, you can only
cancel an escrow that actually landed on-chain.
GET/escrows/{id}/eventsintegrator
Audit-trail timeline for a single escrow
Returns the audit_events rows tied to this escrow's lifecycle
(create, sign, cancel-init, cancel-sign on the sender side;
claim.* on the recipient side), oldest first. Reads from
`audit_events` filtered by
`(target_type IN ('escrow', 'pending_payment'), target_id)`
, bracketed targetType list lets pre-rename rows surface
without a historical-row rewrite.
GET/escrows/{id}/member-jwtintegrator
Mint a Thirdweb custom-auth JWT for the sender's in-app wallet
PUBLIC endpoint, authenticated by the HMAC `token` query
param. The hosted signing UI calls this after the sender
loads the signing page; the returned JWT unlocks the in-app
wallet so it can sign the typed-data (create or cancel).
Same shape regardless of intent_type, the JWT is the wallet
unlock credential, not the signing payload.
POST/escrows/{id}/signaturesintegrator
Submit the sender-signed CreatePaymentIntent or CancelIntent
PUBLIC endpoint, authenticated by the same HMAC `token` query
param. Accepts the wallet's signature; the body's `intent_type`
discriminator routes to either createPaymentRelayed (create
flow) or cancelPaymentRelayed (cancel flow) via the XKOVA
Relayer.
Step 8c folded the create + cancel signature submission into
a single endpoint discriminated by `intent_type`.
- `intent_type='create'`, runs nonce-race + signer-mismatch
checks, submits createPaymentRelayed. Row stays
state='pending' until the on-chain PaymentCreated event is observed.
- `intent_type='cancel'`, runs cancel-nonce-race + signer
checks, submits cancelPaymentRelayed. Row stays
state='pending' until the on-chain PaymentCancelled
event is observed.
GET/escrows/{id}/signing-sessionintegrator
Build the sender's signing session (create or cancel intent)
PUBLIC endpoint, authenticated by the HMAC `token` query
param, NOT a tenant API key. Step 8c of integrator-docs-cleanup
folded what used to be five paths (signing-session, signatures,
member-jwt, cancel-signing-session, cancel-signatures) into
three discriminated paths. This endpoint covers both intent
types via the `intent_type` query param:
- `intent_type=create` (default), returns the
CreatePaymentIntent typed-data the sender wallet signs.
Pre-condition: row.state === 'pending'.
- `intent_type=cancel`, returns the CancelIntent typed-
data. Pre-condition: row.state === 'pending' AND
on_chain_payment_id IS NOT NULL (you can only cancel an
escrow that actually landed on-chain). Token must have
been minted via POST /escrows/{id}/cancel.
The response shape differs between intent types:
- create → message has primaryType='CreatePaymentIntent'
- cancel → message has primaryType='CancelIntent'
Clients dispatch on `typed_data.primaryType` (or on the
intent_type they sent).
GET/payment-requestsintegrator
List payment requests
Lists payment requests visible to the authenticated tenant,
newest first, cursor-paginated. Optionally filtered by
lifecycle `state`.
POST/payment-requestsintegrator
Create a payment request (recipient-initiated; inbound pay ask)
Creates a recipient-initiated payment request: an inbound ask for the payer to send funds. The payer can later approve, decline, or be reminded. Pass an Idempotency-Key to make retries safe.
GET/payment-requests/{id}integrator
Get a payment request
Returns a single payment request by id, including its state, requested amount, payer and recipient references, and any linked payment spawned on approval.
POST/payment-requests/{id}/approveintegrator
Approve a payment request (spawns a payment on the payer side)
Approves a pending payment request, which spawns a payment on the payer's side for the requested amount. Pass the sending wallet and account holder via the X-Sender-Wallet-Id and X-Account-Holder-Id headers. Returns the spawned payment.
POST/payment-requests/{id}/cancelintegrator
Cancel a payment request (verb-noun alias for DELETE /:id)
Soft-cancels a pending payment request (state →
`cancelled`, audit logged, row preserved). Only the
requester can cancel; recipients call
`POST /:id/decline` instead. Matches the lifecycle pattern
used by `/payments/{id}/cancel` and `/escrows/{id}/cancel`.
POST/payment-requests/{id}/declineintegrator
Decline a payment request
Declines a pending payment request so no payment is created. Returns the updated payment request in its declined state. Pass an Idempotency-Key to make retries safe.
POST/payment-requests/{id}/remindintegrator
Nudge the payer with a reminder
Sends a reminder notification to the payer for an open payment request. Returns no content on success. Pass an Idempotency-Key to avoid sending duplicate reminders on retry.
GET/paymentsintegrator
Tenant-wide payment list (cursor-paginated, rich filters, FTS on memo)
Returns a cursor-paginated list of the calling tenant's payments, newest first. Supports filtering by status, direction, counterparty, amount range, date range, category, and channel, plus full-text search on the memo and field projection via the fields parameter.
POST/paymentsintegrator
Create a payment
Creates a payment from a sender to a recipient. For member-signed sends the response carries the pending_sign state plus a signing_url; the member signs an EIP-712 intent in their wallet and the XKOVA Relayer submits the on-chain transfer. Pass an Idempotency-Key to make retries safe.
GET/payments/{id}integrator
Get a payment
Returns a single payment by id, including its current status, amounts, fee breakdown, counterparty, and on-chain transaction reference. Use the fields parameter to project a subset of the response.
POST/payments/{id}/cancelintegrator
Cancel a payment (pre-sign only)
**State-machine constraint.** A payment can only be cancelled
BEFORE the member signs the typed-data ceremony, i.e. while
`status = 'pending_sign'`. Once the signature lands the payment
advances to `submission_pending` (the relay payload is persisted
and about to be dispatched) and then `on_chain_pending`;
cancellation from `submission_pending` or beyond returns 422
`invalid_state` with a message naming the current status. This
isn't a server limitation, once the signature is collected the
payment authorization is on its way to chain settlement;
reversing it would mean asking the chain to un-confirm a
submitted tx.
For payments that have advanced past `pending_sign`, the
only "cancel" surface is on-chain: an escrow payment uses
`POST /escrows/{id}/cancel` (sender-managed, post-sign),
a direct-send payment is irreversible once the on-chain
executeTransfer lands (matches the chain's settlement
finality).
Use `POST /payment-requests/{id}/cancel` for cancelling a
REQUEST (pre-payment, requester-side), that's a different
surface and has no signature dependency.
GET/payments/{id}/eventsintegrator
Timeline of state transitions for a payment
Returns the ordered timeline of state transitions for a single payment, from creation through signing, relay, and on-chain settlement or failure. Use it to render an audit trail or to drive status-dependent UI.
POST/payments/{id}/flagintegrator
Flag a payment for compliance review
Raise a compliance case against a payment. The case is the same
`compliance_flag` shape the automated screening gate opens, so it
enters the existing review queue and lifecycle, only the author
differs.
**Whether this stops the payment depends on how far it has
travelled, and the response says which happened.**
- Pre-signature (`pending_quote`, `pending_compliance`,
`pending_approval`, `pending_confirm`, `pending_sign`) the
payment is HELD at `pending_compliance` and `held` is `true`.
Releasing it is the ordinary retry path, which re-runs the
compliance gate and closes the case.
- Once signed, the authorization is spent and the transfer is in
flight or already settled on-chain. The flag is RECORDED,
`held` is `false`, and the payment is not modified, on-chain
settlement cannot be reversed by an API call.
The structured `reason_code` lands on the case. The NARRATIVE
belongs in the required `X-Justification` header, which is written
to the tamper-evident audit record rather than to a mutable field.
A payment carries at most one open case: flagging one that already
has an open case returns `409 compliance_case_already_open` with
that case's id.
GET/payments/{id}/member-jwtintegrator
Mint a Thirdweb custom-auth JWT for the sender's in-app wallet
PUBLIC endpoint, authenticated by the HMAC `token` query
param. The hosted signing UI calls this after the sender
loads the signing page; the returned JWT unlocks the in-app
wallet so it can sign the TransferIntent typed-data.
POST/payments/{id}/retryintegrator
Retry a failed payment (only when is_retryable = true; reuses Idempotency-Key)
Re-attempts a failed payment, reusing the original Idempotency-Key so no duplicate transfer is created. Only succeeds when the payment's is_retryable flag is true; otherwise it returns an error. Returns the updated payment.
POST/payments/{id}/signaturesintegrator
Submit the sender-signed TransferIntent (+ optional Permit)
PUBLIC endpoint, authenticated by the same HMAC `token`
carried in the request body. Accepts the wallet's signature(s),
runs nonce-race + signer-mismatch checks, then submits the
meta-tx via the XKOVA Relayer.
GET/payments/{id}/signing-sessionintegrator
Build the sender's TransferIntent + Permit signing session
PUBLIC endpoint, authenticated by the HMAC `token` query
param minted at create time, NOT a tenant API key. The hosted
signing UI or an integrator-built equivalent drives this; it
returns the typed-data + permit-typed-data the
sender's wallet signs.
Pre-condition: payment.status === 'pending_sign'.
POST/payments/quoteintegrator
Get a fee + compliance quote for a prospective payment
Returns a stateless fee + compliance preview for a prospective payment, broken out by component, without creating a payment or reserving velocity. The same fee snapshot is recomputed authoritatively at POST /payments time; compare the returned fingerprints to detect drift (e.g. a fee-schedule change) between quote and create.
POST/payments/simulateintegrator
Simulate a payment's chain feasibility before signing (dry-run)
Pre-flight feasibility check, before the member signs. Reads the sender's on-chain balance + the token's permit capability and runs the fee/compliance pipeline, WITHOUT creating a row or submitting. Reports `would_succeed` + the first failing check rather than erroring. The meta-tx isn't signed yet, so `gas_estimate` is a representative figure, not an exact eth_estimateGas.
GET/transactionsintegrator
List the calling tenant's transactions across every payment-flow kind
UNIONs direct-send (`payments` table) + escrow
(`pending_payments` table) rows for the tenant, ordered by
`created_at DESC`. Each row carries a `kind: 'direct' |
'escrow'` discriminator + escrow-only nullable fields
(claim_state, claim_path, recipient_ref, on_chain_payment_id,
expires_at).
Filters apply across both kinds where they make sense
(status_canonical, date_from/to). The kind filter narrows to
a single side. Future payment-flow kinds (subscription
payouts, scheduled transfers, batch disbursements) will land
as new discriminator values without changing this contract.
Lists the tenant's API keys with their metadata, id, label, assigned roles, scope, and status, but never the secret key material, which is shown only once at creation. Use it to audit which programmatic keys exist for the tenant.
POST/api-keyspartner
Create an API key (plaintext returned once)
Issues a new tenant-scoped API key. The plaintext value is
returned in `key` exactly once, store it before navigating
away. Subsequent reads only return the prefix + metadata.
New keys are pinned to the caller's active workspace. Operator
JWT callers select that workspace via `X-Workspace-Id`; when the
header is absent, AuthGuard defaults the request to the tenant's
sandbox workspace. Production workspace keys are issued with an
`xkv_live_*` prefix and require complete production access:
`production_workspace` entitlement/operator approval plus
chargeable billing state. Sandbox workspace keys use
`xkv_dev_*` locally/test and `xkv_test_*` in hosted non-local
environments.
Roles + MFA elevation are pinned at creation. To mint an
api-key that can call treasury mutations:
1. The creating operator must be AAL2 (fresh MFA challenge).
2. Pass `mfa_elevated_at_creation: true` AND at least one
treasury role in `roles`.
A `mfa_elevated_at_creation: true` claim from a non-AAL2
operator session is rejected with `step_up_required`.
DELETE/api-keys/{id}partner
Revoke an API key
Permanently deletes an API key, removing it entirely. This requires an AAL2-elevated session; deleting a platform system key additionally requires an operator session, so a compromised key cannot remove the platform's own keys. Returns no content on success.
POST/api-keys/{id}/revokepartner
Revoke an api key (terminal, cannot be unrevoked)
Revokes an API key, immediately and permanently disabling it for authentication. Revocation is terminal, a revoked key cannot be restored. It requires an AAL2-elevated session, and revoking a platform system key additionally requires an operator session. Returns the revoked key.
GET/api-keys/{id}/role-grantspartner
List role grants on an api key
Includes revoked + expired grants for audit. Each grant
captures the trust chain, granted_by_staff_id is resolved
at read time so consumers always see the originating staff
regardless of grant depth.
POST/api-keys/{id}/role-grantspartner
Grant a role on a tenant to an api key
Caller must hold the role (or higher) being granted on the
target tenant. AAL2 required. The first admin grant on a
tenant must be authorized via staff JWT (returns 403
bootstrap_requires_staff_jwt otherwise; the DB trigger is
the source of truth).
For partner-scope keys, set X-Target-Tenant-Id to specify
which tenant under the partner the request acts on; the
guard resolves grants against that tenant. Tenant-scope
keys ignore the header.
Extends the expiry of a time-boxed role grant on an API key so the key retains that role for a longer window. The caller must hold the granted role or higher, and the request requires an AAL2-elevated session. Returns the updated grant.
Soft-revoke (sets revoked_at). Cascades transitively to all
child grants via the cascade-on-grant-revoke trigger.
AAL2 + admin role required. The api key itself stays
verifiable; only the role grant stops carrying authorization.
POST/api-keys/{id}/rotatepartner
Rotate an API key (step-up auth required; admin role)
Rotates the key in place, the row id is preserved, the
secret is regenerated, and the new plaintext is returned
once. The previous secret stops verifying immediately.
The key must belong to the caller's active workspace.
v1 ships WITHOUT a 24h grace window. Grace requires schema
columns (previous_key_hash + previous_key_hash_retired_at)
plus a verifier change to accept either hash during the
window; tracked as a follow-up. Until grace ships, operators
must coordinate their integration's secret-rotation rollout
manually.
Auth: AAL2 + admin role enforced (StaffRoleGuard +
Aal2Guard). API-key callers may manage keys only when they
have a valid admin role grant and resolvable staff actor.
GET/integration-gatewaysintegrator
List integration gateways
Lists the integration gateways the tenant has deployed, each with its status, pinned mTLS certificate fingerprint, and most recent conformance result. Gateways bridge XKOVA to an integrator's core banking systems over a mutually-authenticated connection.
POST/integration-gatewaysintegrator
Register a deployed integration gateway
Registers a deployed integration gateway with the platform, recording its endpoint and pinning its mTLS certificate fingerprint for mutually-authenticated calls. The registration is audited as a configuration change. Returns the registered gateway.
GET/integration-gateways/{id}integrator
Get an integration gateway
Returns a single integration gateway by id, including its endpoint, status, pinned mTLS certificate fingerprint, and latest conformance result. Use it to inspect one gateway's configuration and health.
PATCH/integration-gateways/{id}integrator
Edit gateway registration (name / base_url)
Partial update of registration metadata. Status changes go
through POST /integration-gateways/{id}/status. Every field is
optional; only provided fields are written.
Record a contract-conformance result for the gateway
Records the result of a contract-conformance test run against an integration gateway, attesting that the deployed gateway matches the expected adapter contract. The result is stored on the gateway and audited as a configuration change. Returns the updated gateway.
GET/integration-gateways/{id}/eventsintegrator
List a gateway's invocation events
The api->gateway invocation log, one entry per call XKOVA
made to this gateway (resolveMember, placeHold, captureHold, ...),
newest first. Cursor-paged: pass the prior page's `next_cursor` as
`before`.
POST/integration-gateways/{id}/pin-certintegrator
Pin or rotate the gateway's mTLS cert fingerprint
Pins a sha256 client-cert fingerprint for the optional mTLS
channel. Pinning a new fingerprint rotates the
current one into `previous_cert_fingerprint` and stamps
`cert_rotated_at`. Fingerprints are public digests, never key
material.
POST/integration-gateways/{id}/statusintegrator
Enable or disable an integration gateway
Enables or disables an integration gateway. Disabling a gateway stops the platform from routing calls to it without removing its registration, so it can be re-enabled later. The change is audited and the updated gateway is returned.
GET/permissionspartner
List the permission catalog
The read-only ACP permission catalog, grouped by resource family.
Operators compose custom roles (`/roles`) from these; the catalog is
code-defined (each permission maps to a route gate), so it is not
editable.
GET/rolespartner
List custom roles
Lists the tenant's custom roles. A role is a named bundle of permissions referenced by its key in staff and api-key role assignments and resolved by the authorization guard at request time. Use it to review which custom roles exist.
POST/rolespartner
Create a custom role
Creates a tenant-authored role, a named bundle of permissions. The
requested permissions must exist, be non-platform, and be a subset of
what the caller holds (anti-escalation → `role_denied`). `key` is the
immutable slug carried in staff/api-key role lists; it is derived from
`label` when omitted and must not collide with a built-in role.
DELETE/roles/{id}partner
Delete a custom role
Deletes a custom role from the tenant. The role key is removed from the authorization registry so it no longer resolves to any permissions at request time. The deletion is audited and returns no content on success.
GET/roles/{id}partner
Get a custom role
Returns a single custom role by id, including its key and the set of permissions it bundles. Use it to inspect exactly which actions a role confers before assigning it to staff or an api key.
PATCH/roles/{id}partner
Update a custom role
Updates a custom role's name or permission set. Changes are bounded by anti-escalation: you cannot add a permission you do not already hold. The change is audited and the updated role is returned.
GET/webhook-deliveriesintegrator
List recent webhook deliveries (last 30 days)
Returns the most recent deliveries across the tenant's
webhook endpoints. Pass `endpoint_id` to scope to a single
endpoint. Each delivery row includes status, attempt count,
last response code, and a body excerpt for triage.
GET/webhook-deliveries/{id}integrator
Fetch a single webhook delivery
Returns a single webhook delivery attempt by id, including the target endpoint, event type, HTTP response status, attempt count, and current delivery state. Use it to diagnose why a specific notification succeeded or failed.
POST/webhook-deliveries/{id}/replayintegrator
Re-fire a past delivery (preserves original Idempotency-Key)
Re-fires a past webhook delivery to its endpoint, reusing the original Idempotency-Key so the receiver can deduplicate against the first attempt. Replay is allowed only for deliveries in a terminal failed or dead-letter state. Use it to recover from a missed or failed notification.
POST/webhook-deliveries/bulk-replayintegrator
Bulk re-fire terminal deliveries
Re-queues up to 100 terminal (failed|dlq) deliveries in one call.
Best-effort over the set: ids that are not found, cross-tenant, already
pending/delivered, or duplicates are counted in `skipped_count` rather
than failing the request. A malformed id rejects the whole request.
GET/webhook-endpointsintegrator
List webhook endpoints
Lists the webhook endpoints configured for the calling workspace, cursor-paginated. Each entry returns the destination URL, subscribed event types, and status, but never the signing secret. Use it to audit which endpoints are registered to receive event notifications.
POST/webhook-endpointsintegrator
Create a webhook endpoint
Registers a webhook endpoint that XKOVA will POST signed event notifications to. The response includes the plaintext signing secret exactly once, capture it immediately to verify delivery signatures, since it is never returned again and can only be replaced by rotation.
DELETE/webhook-endpoints/{id}integrator
Delete webhook endpoint
Permanently deletes a webhook endpoint so it no longer receives event notifications. In-flight and historical deliveries for the endpoint are unaffected. Returns no content on success.
GET/webhook-endpoints/{id}integrator
Get webhook endpoint
Returns a single webhook endpoint by id, including its destination URL, subscribed event types, and current status. The signing secret is never included in the response. Use it to inspect one endpoint's configuration.
PATCH/webhook-endpoints/{id}integrator
Update webhook endpoint
Updates a webhook endpoint's destination URL, subscribed event types, or status. The change is audited as a configuration change and takes effect for subsequent event deliveries. Returns the updated endpoint.
POST/webhook-endpoints/{id}/rotateintegrator
Rotate the HMAC signing secret for this endpoint
Returns the new plaintext secret ONCE. The previous secret
stays valid for `overlap_sec` (default 24h) so receivers can
update their stored copy without missing deliveries. After
the overlap window the previous secret is invalidated.
POST/webhook-endpoints/{id}/testintegrator
Send a test event to this endpoint
Fires a synthetic, fully-signed `webhook.test` event at the endpoint
SYNCHRONOUSLY and returns the result. Uses the same envelope + HMAC +
Ed25519 signing path as a real delivery, so a successful test confirms
the endpoint is reachable and that your signature verification works.
Does NOT create a delivery record. Like every mutation it takes an
`Idempotency-Key`; a retry with the same key replays the prior result
without re-firing (use a fresh key to fire again).
GET/webhook-event-typesintegrator
List subscribable webhook event types
The canonical catalog of events a webhook endpoint may subscribe
to, name, domain, version, sensitivity, and an example payload.
Read-only platform reference data (identical for every caller).
Subscribing an endpoint to a name not in this list returns 422
`unknown_event`.
Queries the tenant's audit trail of recorded actions, returning a cursor-paginated list. Filter by category, action, actor, target, and date range, and sort to assemble compliance and activity reports. Requires staff authentication.
Get this tenant's resolved branding + the raw tenant-wide theme
Returns the fully-resolved branding (what actually renders across
member-facing surfaces) plus the raw tenant-wide row the owner
edits (null fields = inherited). Tenant-scoped; a partner-scope
key gets 403.
PUT/brandingintegrator
Upsert this tenant's branding (tenant-wide)
Patches the caller-tenant's tenant-wide branding row. Admin/owner
only. Logo is set via the separate upload flow.
GET/branding/email-domainsintegrator
List this tenant's custom sending domains + their DNS records
Returns the tenant's registered sending domains, each with the
SPF/DKIM DNS records to publish + the live verification status.
Tenant-scoped; a partner-scope key gets 403. `provider_configured`
is false when the platform's domains-capable Resend key is unset.
POST/branding/email-domainsintegrator
Register a custom sending domain
Admin/owner only. Registers the domain with the email provider +
returns the SPF/DKIM DNS records the tenant must publish. Status
starts `pending` / `not_started` until verification succeeds.
DELETE/branding/email-domains/{id}integrator
Remove a custom sending domain
Admin/owner only. Removes the domain at the provider + locally.
POST/branding/email-domains/{id}/verifyintegrator
Trigger + refresh verification for a sending domain
Admin/owner only. Asks the provider to re-check the domain's DNS
records, then returns the refreshed status + records. Status flips
to `verified` once the published records propagate.
DELETE/branding/logointegrator
Remove the brand logo
Remove the brand logo from the calling tenant's branding so member surfaces fall back to the default. Returns the updated tenant branding with the logo cleared. Requires the branding.manage permission.
POST/branding/logointegrator
Confirm a brand logo upload (magic-byte validated)
Admin/owner only. The api re-fetches the uploaded object + sniffs
its magic bytes (the client Content-Type is not trusted); a
non-image is rejected (422). Persists the logo on the tenant-wide
branding row.
POST/branding/logo/upload-urlintegrator
Presigned PUT URL for a brand logo upload
Admin/owner only. Returns a short-lived presigned PUT URL + the
object key to confirm with. The client PUTs the image bytes
directly to the URL, then calls POST /v1/branding/logo.
GET/fee-schedulesintegrator
List fee schedules for this tenant
List the fee schedules for the calling tenant, cursor-paginated and filterable by status. Returns each schedule's rules and lifecycle state, used to review draft and committed pricing.
POST/fee-schedulesintegrator
Propose a new fee schedule (validates floor enforcement)
Create a draft fee schedule for the calling tenant. The proposal is validated against floor enforcement before it is stored, and remains a draft until committed. Returns the drafted schedule.
GET/fee-schedules/{id}integrator
Get a single fee schedule (with effective rules)
Returns one fee_schedule by id, including its `rules` array
+ commit metadata. Use `GET /fee-schedules` to list all
schedules; this endpoint hydrates a specific schedule's
full rule shape for the operator console's edit view.
POST/fee-schedules/{id}/commitpartner
Commit a drafted fee schedule (admin-only)
Commit a drafted fee schedule so it becomes the active pricing, atomically superseding any prior committed schedule for the same surface and transaction type. Admin-only; requires a justification. Returns the committed schedule.
GET/fee-schedules/platform-feeintegrator
Read-only XKOVA platform-fee rule for this workspace
Returns `{ rule: <FeeRule> }` for the seeded
`beneficiary='xkova'` row on the workspace's active
committed schedule, or `{ rule: null }` if not yet
configured. Tenants cannot mutate this rule via the api,
only XKOVA can adjust the platform fee (direct DB SQL).
POST/onboarding/setupintegrator
Run the onboarding intent-fork, pin the chain + activate the workspace
Records the chosen intent, server-resolves the sandbox chain (the canonical active testnet), and flips the workspace `pending_setup` → `active` (one-time). Idempotent, calling it again on an already-active workspace returns the current state.
GET/onboarding/statusintegrator
Onboarding state for the calling tenant's sandbox workspace
Read the onboarding state of the calling tenant's sandbox workspace, indicating whether setup is still pending or complete. Used to drive the self-serve onboarding flow before the workspace is activated.
GET/public/brandingintegrator
Public branding for a hosted claim page (pre-auth)
Resolves the tenant's public branding from a claim token, for
rendering a hosted member-facing page BEFORE any auth. No api key
, the claim_token is the capability (same as /v1/claims/*).
An invalid / expired / missing token returns the platform-default
bundle with a 200 (never a 404), so token validity cannot be
fingerprinted by status.
GET/tenantintegrator
Get tenant config
Read the calling tenant's own configuration: identity, status, branding, and operational settings. Resolves the tenant from the request's authentication scope, so no id is required.
GET/tenant/custody-secretspartner
List custody-secret metadata (fingerprint + last-rotated)
List metadata for the calling tenant's custody secrets, newest-rotated first. Returns only each secret's fingerprint and last-rotated timestamp; the secret material itself is never exposed through the API.
GET/tenant/member-auth/allowed-originspartner
List allowed browser Origin values for member-auth flows
List the browser Origin values the calling tenant has allowlisted for its member-auth flows. Member-auth requests from origins not on this list are rejected by the cross-origin guard.
POST/tenant/member-auth/allowed-originspartner
Allowlist a new browser Origin for member-auth flows
Allowlist a browser Origin for the calling tenant's member-auth flows so requests from that origin are accepted. Returns the stored allowed-origin entry.
Soft-delete a member-auth allowed Origin from the calling tenant's allowlist. Member-auth requests from that origin are rejected afterward; historical references are preserved.
GET/tenant/member-auth/redirect-urlspartner
List allowed redirect URLs the wrapped member-auth flows accept
List the redirect URLs the calling tenant has allowlisted for its member-auth email flows, such as email confirmation, password reset, and magic-link. Only allowlisted URLs are accepted in those emails.
POST/tenant/member-auth/redirect-urlspartner
Allowlist a new redirect URL for member-auth emails
Allowlist a redirect URL for a member-auth email purpose (email confirm, password reset, or magic link). The URL must be on the allowlist before member-auth emails will redirect to it. Returns the stored entry.
Soft-delete a member-auth redirect URL from the calling tenant's allowlist. Once removed, member-auth emails no longer redirect to that URL; historical references are preserved.
GET/tenant/policiespartner
Get tenant policy registry (ToS, privacy, marketing)
Read the calling tenant's policy registry covering terms of service, privacy, and marketing. Returns the current version and acceptance metadata for each policy, used to present and track member consent.
PUT/tenant/send-initiation-lockintegrator
Arm or disarm the send-initiation lock
When armed (`locked: true`), creating a payment additionally
requires the `payment.create` permission, so the tenant can restrict
which operators initiate sends (open by default). Owner/admin only.
GET/workspace-tokensintegrator
List enabled token deployments for the calling workspace
List the token deployments currently enabled for the calling workspace, which are the tokens its payments and transfers may use. The workspace is resolved from the calling api key.
POST/workspace-tokensintegrator
Enable a token deployment on the calling workspace
Add a token deployment to the calling workspace's allowlist so it can be used for payments and transfers. The token must match the workspace's chain. Returns the enabled workspace-token record.
DELETE/workspace-tokens/{id}integrator
Disable a token deployment on the calling workspace (soft delete)
Disable a token deployment on the calling workspace via soft delete, removing it from the allowlist without deleting historical references. The token can no longer be selected for new payments or transfers.
GET/workspace-tokens/availablepartner
List token deployments eligible for the calling workspace (chain-matched) with enabled flag
Returns every `token_deployment` whose `chain_id` matches the
calling workspace's pinned chain, joined with the workspace's
active `workspace_tokens` rows so the UI can render an
enable/disable toggle per row. Powers the
`/settings/workspaces/[id]/tokens` console page.
GET/workspacesintegrator
List the workspace pair under this tenant
List the workspaces under the calling tenant, scoped by the tenant's authentication. Returns the sandbox and production workspace records so an integrator can discover their workspace ids.
GET/workspaces/{id}integrator
Get a single workspace
Retrieve a single workspace under the calling tenant by id, including its name, type, status, and chain. Reads are scoped to the tenant; a workspace outside the tenant is not visible.
PATCH/workspaces/{id}integrator
Update mutable workspace fields (name + status; type and chain are immutable)
Update the mutable fields of a workspace: its display name and status. The workspace type and chain are immutable after creation and cannot be changed. Returns the updated workspace record.
The workspace's own RWA asset schemas, drafts + published, newest
first. The schema-builder console's list page reads this. Requires
`rwa_tokenization`; authed (tokenize_admin). asset_schema/version ids are raw
uuids (the asset_schema registry is reference data).
GET/rwa/asset-schemas/{id}integrator
Get one workspace asset schema and its version history
A single workspace asset schema + its full version history (newest
first). Requires `rwa_tokenization`; authed (tokenize_admin). `id` is the raw
asset_schema uuid.
POST/rwa/asset-schemas/{id}/publishintegrator
Publish the workspace schema's open draft
Publish the schema's open draft through the publish gate (mints the standalone
validator + freezes the version). Requires `rwa_tokenization`; authed
(tokenize_admin); `@Audited` config_change.
POST/rwa/asset-schemas/compile-previewintegrator
Compile a field-palette and report the publish-gate verdict
Live compile of a field-type palette into a Draft-2020-12 schema +
x-xkova vocab, plus whether it would clear the publish gate, WITHOUT
persisting. The schema-builder's CompiledSchemaPreview calls this on edit. A
malformed palette (unknown field-type, unbounded fragment) is a 422. Requires
`rwa_tokenization`; authed (tokenize_admin).
POST/rwa/asset-schemas/draftintegrator
Save a palette as the workspace schema's editable draft
Compile a palette and store it as the workspace schema's editable
DRAFT, a new schema (omit asset_schema_id) or an edit of an
existing one's open draft. The draft carries NO validator; publishing is the
only door that mints one. Requires `rwa_tokenization`; authed (tokenize_admin);
`@Audited` config_change.
POST/rwa/asset-schemas/forkintegrator
Fork a platform template into a workspace draft
Copy a curated platform template's latest published version into a
fresh editable WORKSPACE draft. Requires
`rwa_tokenization`; authed (tokenize_admin); `@Audited` config_change.
GET/rwa/asset-schemas/templatesintegrator
List the forkable platform templates
The curated platform asset-schema templates a tenant can fork into
a workspace draft. Requires `rwa_tokenization`; authed (tokenize_admin).
POST/rwa/asset-schemas/upload-publishintegrator
Publish a raw JSON Schema through the publish gate
Run an already-compiled JSON Schema (with x-xkova-* annotations) through the
hardened publish gate and, if it passes, land a new workspace schema +
a PUBLISHED version (raw-upload). An unsafe schema (external $ref,
unbounded pattern, …) is rejected 422 with a field-anchored diagnostic.
Requires `rwa_tokenization`; authed (tokenize_admin); `@Audited` config_change.
Get a collection's contract metadata (contractURI target)
The PUBLIC collection-level descriptor a marketplace fetches once per
collection, name/description/image/external_link from the
collection-grain fields. Its image doubles as the per-token Tier-2 default.
PUT/rwa/holders/{holderRef}/eligibilityintegrator
Upsert a holder's global eligibility attributes
Upserts a holder's GLOBAL accreditation status + jurisdiction,
keyed by (workspace, holder), one row per holder, a repeat call
overwrites. The `source` records provenance (kyc_provider |
operator_attested | integrator_supplied). These attributes feed the
expiry-aware EFFECTIVE accreditation the (token, holder) eligibility read
and the transfer door evaluate. Requires `tokenize_admin`;
`@Audited` compliance.
POST/rwa/schema-rebindsintegrator
Propose an attested schema-evolution rebind ceremony
Opens a forward-only schema-rebind ceremony: re-points a SET of
the caller's own RWA/customer tokens from one published asset-schema version
to a strictly-LATER published version of the SAME asset schema. Forward-only
is validated here (same asset_schema_id, both published, to_version >
from_version, every token currently bound to from_version). NO data moves,
the ceremony lands `status: proposed`; the binding is moved + each token's
metadata re-committed only at the later commit stage. Versions are addressed
by their human integer within the schema, not their uuid. Requires
`rwa_tokenization`; authed (tokenize_admin); `@Audited` compliance.
GET/rwa/schema-rebinds/{id}integrator
Read a rebind ceremony's lifecycle state
Returns a schema-rebind ceremony's current state: its status
(proposed -> dry_run_passed | dry_run_failed -> attested -> committed |
failed), the source/target version integers, the token set (ctk_ ids), the
transform + dry-run result hashes, the recovered signer address, and the
commit timestamp. Requires `rwa_tokenization`; authed read; workspace-scoped
(a ceremony outside the authenticated workspace 404s).
POST/rwa/schema-rebinds/{id}/attestintegrator
EIP-712 KMS-attest the rebind proposal digest
Signs an EIP-712 KMS attestation over a digest of the full proposal tuple
(from/to versions, token set, transform hash, dry-run result hash) and
records the signature + the recovered signer address. The signed
round-trip is verified immediately: the signature must recover to the address
the signer reports. Runnable only from `dry_run_passed`; advances the
ceremony to `attested`. Requires `rwa_tokenization`; authed (tokenize_admin);
`@Audited` compliance.
POST/rwa/schema-rebinds/{id}/commitintegrator
Commit a rebind, move bindings + re-commit metadata
Atomically finalizes a rebind ceremony: re-verifies the stored
EIP-712 attestation against a freshly-recomputed proposal digest (a tampered
proposal recovers to a different address → the commit is refused and the
ceremony is poisoned to `failed`), then in ONE transaction moves every
token's binding to the target version and re-commits each token's metadata
through the ingestion engine (appending a `schema_rebind` provenance version
per token). Runnable only from `attested`; advances the ceremony to
`committed`. Requires `rwa_tokenization`; authed (tokenize_admin); `@Audited`
compliance.
POST/rwa/schema-rebinds/{id}/dry-runintegrator
Dry-run a rebind against the target schema (no write)
Validates every token in the ceremony against the TARGET version:
for each token, reads its CURRENT field values, applies the transform, and
runs the target validator + CEL + frozen-change diff through the ingestion
engine in dry-run mode, NO write. A token whose data cannot satisfy the new
schema fails the ceremony; the per-token verdict is pinned by
`dry_run_result_hash` and the ceremony advances to dry_run_passed or
dry_run_failed. Runnable from `proposed` or to re-run from `dry_run_failed`.
Requires `rwa_tokenization`; authed (tokenize_admin); `@Audited` compliance.
GET/rwa/tokens/{id}/attestersintegrator
List a token's registered attesters
The trusted external attesters registered for this token,
including any soft-revoked entries (carrying a non-null `revoked_at`).
Authed read (`tokenize_admin`); scoped to the authenticated workspace
(a token outside it 404s).
POST/rwa/tokens/{id}/attestersintegrator
Register a token-pinned external attester
Registers a trusted external party (auditor / appraiser / custodian /
etc.) whose own key may thereafter sign append-log facts for THIS
token. The registry is pinned to the token via the `{id}` path
(`customer_token` ctk_ id). `attester_address` is EIP-55-validated then
stored lowercased. A second ACTIVE registration of the same address for
the same token 422s `attester_already_registered`. Requires
`tokenize_admin`; workspace-scoped (a token outside it 404s); `@Audited`.
Soft-revokes a registered attester: the row is stamped
`revoked_at` and the party can no longer sign accepted append-log facts.
A second revoke of an already-revoked attester 422s `attester_revoked`.
Requires `tokenize_admin`; workspace-scoped; `@Audited`.
GET/rwa/tokens/{id}/cap-tableintegrator
Get a token's cap-table summary
Latest supply snapshot for an RWA/customer token's cap table:
total supply, holder count, and the block it reflects. The cap table is
rebuilt from chain by a background replay (reorg-safe), so this reads a
materialized projection of the on-chain NFT balance log. Authed read;
scoped to the authenticated workspace (a token outside it 404s).
POST/rwa/tokens/{id}/corporate-actionsintegrator
Open a corporate action on an RWA token
Opens a corporate-action lifecycle on the caller's
own token: a `redemption`, `split`, `consolidation`, or
`additional_issuance`. The action lands `draft`, no chain leg fires
here; the kind-specific params are validated + frozen into the stored
`params`. Drive it forward with the `advance` endpoint. The
balance-rewriting kinds (redemption/split/consolidation) are ERC20X-only
and require an access registry (else 409 conflict_state); the redemption
price-out settles through the `/v1/payments` chokepoint at advance time.
`additional_issuance` mints to named recipients on any standard
(recipient shape is standard-specific: erc20 amount only; erc721
token_id only; erc1155 both). `capital_call` and `mandatory_conversion`
are not yet available, opening one is rejected with 409 conflict_state
and a named `deferred_to`. Requires AAL2 + `tokenize_admin`; `@Audited`
financial; idempotent by `Idempotency-Key`. The `rwa_tokenization`
entitlement gate applies (403 feature_not_enabled without it).
Reads a corporate action and its per-leg progress: the current
`state`, the frozen `params`, the snapshot block + freeze timestamp, and
the ordered legs (each with the linked tokenization operation id). Authed
read (`tokenize_minter`); workspace-scoped (a token outside it 404s). The
`rwa_tokenization` entitlement gate applies (403 feature_not_enabled without
it).
Drives the corporate-action lifecycle one step forward (ADR
0037). RE-ENTRANT: draft → frozen (the transfer freeze commits BEFORE any
on-chain leg) → executing_on_chain (recover-orchestration legs, one Safe op
at a time) → reconciling (cap-table replay confirms positions) → completed
(unfreeze). Each on-chain leg rides the shared tokenization
build→sign→relay→confirm lifecycle; a reverted token leg lands `reverted`,
an the XKOVA Relayer-level failure lands `failed_resumable` (re-call advance to
re-enter). Returns the action's current state + leg progress. Requires AAL2
+ `tokenize_admin`; `@Audited` financial. The `rwa_tokenization` entitlement
gate applies (403 feature_not_enabled without it).
POST/rwa/tokens/{id}/distributionsintegrator
Create a pro-rata cap-table distribution
Freezes a pro-rata payout set across the token's latest cap-table
snapshot: the `total_amount_raw` of `payout_token_address` is
split across current holders, floor-division dust to the largest holder
so the payouts sum EXACTLY to the total. NO money moves, the
distribution lands `state: draft`. The payouts are paid OUT of the
workspace treasury Safe via one batched N-of-M multiSend transfer at a
separate execute step (non-custodial, the issuer's own funds). Requires
AAL2 + `tokenize_admin`; `@Audited` financial; idempotent by Idempotency-Key.
Execute a draft distribution (treasury-Safe multiSend payout)
Pays the distribution out of the chosen workspace treasury Safe
, the issuer's own funds, non-custodial. Re-screens every
holder and EXCLUDES any sanctioned holder's leg (that payout → failed),
then drafts ONE batched multiSend N-of-M treasury transfer over the clean
legs; the existing treasury sign + execute flow approves + relays it. The
distribution links the transfer and moves to `paying`; a settlement
poller flips the payouts to `settled` once the transfer confirms.
Requires AAL2 + `tokenize_admin` AND the caller must be an active signer
of the chosen Safe; `@Audited` financial. Resumable + idempotent: a
re-run after the transfer is drafted returns the current state without
drafting a second.
Grant a recipient access to a token's gated documents
Records an active document entitlement: the named grantee may
thereafter fetch the token's gated documents. Idempotent. Authed
(tokenize_admin); `@Audited`.
Revokes an active document entitlement. Authed (tokenize_admin); `@Audited`.
GET/rwa/tokens/{id}/documents/{docId}integrator
Resolve an RWA document to a fetch URL
Public documents resolve to a stable content-addressed URL; gated
documents resolve to a short-TTL presigned GET only when the named
`grantee` has an ACTIVE entitlement (else 403 rwa_document_not_entitled).
Authed (tokenize_minter).
POST/rwa/tokens/{id}/documents/confirmintegrator
Confirm an uploaded RWA document (integrity + provenance)
Finalizes an upload: the server recomputes the SHA-256 from the
stored bytes and rejects a `expectedContentHash` mismatch; a public
document is promoted to the content-addressed prefix; the integrity row is
written and the content hash is stamped into the bound field THROUGH the
ingestion gate, so confirming a document IS a metadata commit and earns a
provenance version, atomically. Authed (tokenize_minter); `@Audited`.
Mints a short-TTL presigned PUT to a gated staging key for an RWA
document/media object. The client PUTs the bytes directly to
the returned `upload_url`, then calls confirm. Authed (tokenize_minter).
Read the (token, holder) transfer-eligibility context
The (token, holder) CEL context the transfer door-check consumes:
the holder's EFFECTIVE accreditation (expiry-aware, a
verified/self_attested holder past `accreditation_expires_at` reads back
`expired`), jurisdiction, and the per-holding lockup release instant.
`accreditation` is `none` and `jurisdiction`/`lockup_until` are null when
no attribute/lockup row exists. Authed read (tokenize_minter floor);
scoped to the authenticated workspace (a token outside it 404s).
GET/rwa/tokens/{id}/fieldsintegrator
Describe the token's bound-schema fields + current values
The bound asset-schema's field descriptors the management-console metadata
editor renders: each field's `mutability` (frozen / versioned /
append-log), `visibility`, `storage_backend`, whether it is `editable`
through the field-commit route, and the field's current value (the inline
/ frozen current value, the versioned current row + `version`, or the
latest append-log payload + `entry_count`). Pass `on_chain_token_id` to
describe an instance-level (ERC721X/ERC1155X) field set; omit for the
contract-level / ERC20X set. A relationship / media / time-series /
holder-positions field is returned with `editable: false` (it has a
dedicated write path). Requires `tokenize_minter`; workspace-scoped (a
token outside the workspace 404s); 404 if the token has no bound schema.
POST/rwa/tokens/{id}/fields/{fieldPath}integrator
Commit an operator-authored field value
Commits one operator-authored field value through the single
ingestion gate, the live console twin of the attested-entry
route (that records an external party's signed append-log fact; this is
the workspace operator editing a versioned / append-log / inline / frozen
field). The value runs the same five stages every other writer uses:
schema validation against the field's compiled fragment, the field's CEL
constraints, and the mutability diff. A `frozen` field rejects a change
after it is set (`validation_failed`); a `versioned` field appends a new
version; an `append-log` field appends an entry. A relationship / media /
time-series / holder-positions field 422s with a pointer to its dedicated
write path. Requires `tokenize_admin`; workspace-scoped; `@Audited`. The
`idempotency_key` dedupes a retried save (a fresh edit supplies a new key
so a versioned field appends).
Records an append-log fact carrying a registered attester's OWN
signature. The server recomputes the EIP-712 AttestedFact digest over
`{ tokenRef, fieldPath, payloadDigest }`, recovers the signer, and rejects
a `attester_signature_mismatch` if it is not the named attester's
registered address, no row is written. On success the fact commits
through the single ingestion gate (provenance source `attested`), earning
a provenance version + chain leaf. Only `append-log` fields can be
attested. Requires `tokenize_admin`; workspace-scoped; `@Audited`.
GET/rwa/tokens/{id}/holdersintegrator
List a token's cap-table holders
Cap-table holder positions, largest balance first. For erc1155
a position is per (holder, sub-id); for erc721/erc20 it is one whole
position per holder (erc721 balance = count of owned tokenIds). Cursor-
paginated. Authed read; scoped to the authenticated workspace.
GET/rwa/tokens/{id}/instancesintegrator
List a token's per-instance ownership (NFT gallery)
Per-instance ownership for an ERC-721/1155 RWA token: one row
per (on-chain tokenId, holder) with a live balance, ordered by tokenId.
Not applicable to an erc20 (fungible) share class, those return 422
`validation_failed`. Cursor-paginated. Authed read; workspace-scoped.
PUT/rwa/tokens/{id}/lockups/{holderRef}integrator
Set a per-holding lockup on an RWA token
Upserts the per-(token, holder) lockup: the holder cannot move
this token until `lockup_until`. One row per (token, holder), a repeat
call overwrites. The (token, holder) eligibility read surfaces it, and
the transfer door denies a move before the release instant.
Requires `tokenize_admin`; `@Audited` compliance. Scoped to the
authenticated workspace (a token outside it 404s).
Get an RFC-6962 consistency proof (public transparency log)
Returns the proof that the size-`from` tree is a prefix of the size-`to`
tree (the log only ever appended, never rewritten between two STHs).
PUBLIC, no api key. Feed the response to `verifyConsistency` from
`@xkova/sdk/rwa-verify`.
GET/rwa/tokens/{id}/log/proof/inclusionintegrator
Get an RFC-6962 inclusion proof (public transparency log)
Returns the audit path proving a metadata version is included in the tree
committed to by the latest signed tree head. PUBLIC, no api key. Feed the
response to `verifyInclusion` from `@xkova/sdk/rwa-verify` to check
it offline against the STH `root`.
GET/rwa/tokens/{id}/log/sthintegrator
Get a token's signed tree head (public transparency log)
Returns the latest (or a specific-size) RFC-6962 signed tree head for a
token's RWA metadata transparency log. PUBLIC, no api key, because the
log carries only HASHES (never gated content); a holder/auditor uses the
STH + the proof routes + the `@xkova/sdk` verifier to confirm a
version was logged and the log was never rewritten, WITHOUT trusting
XKOVA's database. A once-published STH is append-only and always served.
POST/rwa/tokens/{id}/log/witnessintegrator
Submit a third-party witness co-signature for an STH
Submits a witness's EIP-712 TreeHead co-signature over a published signed
tree head. The server recomputes the TreeHead digest, recovers
the co-signer, and accepts only if it is an active REGISTERED witness for
this log, recording the co-signature on the STH's third_party receipt.
Authed (tokenize_minter); `@Audited`. (NOT public, only the GET /log
reads are keyless.)
GET/rwa/tokens/{id}/metadataintegrator
Get a token's metadata (ERC-721 tokenURI target)
The PUBLIC ERC-721 metadata document a marketplace/explorer fetches off
the tokenURI. No api key, the token's per-field public/gated
policy IS the access control; gated fields project only as a
`{content_hash, url:null, doc_ref}` reference (never the bytes). Flat
STANDARD (name/description/image/attributes) at the top level + the rich
asset data under `x-xkova-typed` + identity under `x-xkova-system`. Never
404s a token that ever existed; a burned token returns its last body +
`x-xkova-lifecycle: burned`.
GET/rwa/tokens/{id}/metadata/{tokenId}integrator
Get per-instance metadata (ERC-1155 uri {id} target)
The PUBLIC per-instance metadata for an ERC-1155 token id. Same
shape + posture as the ERC-721 tokenURI; an uncommitted-but-reserved id
returns the schema-default projection (never 404).
GET/rwa/tokens/{id}/placeholderintegrator
Get a token's branded placeholder image
The PUBLIC Tier-3 branded-placeholder image the STANDARD
`image` points at when a `x-xkova-placeholder: generated` schema has no
committed/collection image. A DETERMINISTIC SVG composed from the tenant
brand resolved off the public token ref, platform CHROME (no
content_hash, out of the provenance chain).
GET/rwa/tokens/{id}/provenance/verifyintegrator
Verify a token's metadata provenance chain
Verifies one token instance's RWA metadata provenance chain against its
latest signed checkpoint, the on-demand form of the tamper-PROOF check
that backs the "immutable by system design" posture. The server recomputes
the metadata hash-chain, confirms the latest checkpoint head still matches
(catching any 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).
`ok: true` with no `reason` means intact + covered; `reason: unanchored`
(also `ok: true`) means no checkpoint exists yet. `ok: false` carries a
`reason`: `chain_broken` (a version's leaf doesn't recompute),
`head_mismatch` (a covered version was rewritten), `signature_invalid`
(the checkpoint signature doesn't recover to the live signer), or
`signer_unreachable` (transient, retry). Authed read (no signature, no
AAL2); scoped to the authenticated workspace.
GET/rwa/tokens/{id}/relationshipsintegrator
List a token's outgoing relationship edges
The integrity-checked asset graph behind an RWA/customer token's
relationship/reference metadata fields: the token's outgoing
edges, grouped by `field_path` and ordered by `cardinality_seq`. Each
target is one of three kinds, a sibling token (`to_customer_token_id`,
a `ctk_…` prefixed id; token→token edges are commit-time cycle-checked),
an in-tenant identity (`to_identity_ref`, an `ah_…` account-holder or
`tka_…` token-authority ref), or an external system reference
(`to_external_ref`, a `{ system, id }` object with an optional `url`).
Optionally narrowed to a single `field_path`. Authed read
(tokenize_minter floor); scoped to the authenticated workspace (a token
outside it 404s).
GET/rwa/tokens/{id}/schema-bindingintegrator
Read the token's bound asset schema
The token's current asset-schema binding, the published
`(asset_schema_id, version)` the metadata plane reads. 404 if the token
is unbound. Requires `tokenize_minter`; workspace-scoped (a token outside
the workspace 404s).
POST/rwa/tokens/{id}/schema-bindingintegrator
Bind a published asset schema to the token
Binds a published asset-schema version to the token, the
missing first leg of the RWA metadata plane (publish → BIND → edit →
resolve). The version is addressed by `(asset_schema_id, version)` and
must be PUBLISHED + visible to the token's workspace (a platform template
or a same-workspace schema). A token binds one version, SET ONCE:
re-binding the SAME version is idempotent; binding a DIFFERENT version
409s `token_already_bound` (change it through the schema-rebind ceremony).
Requires `tokenize_admin`; workspace-scoped; `@Audited`.
POST/rwa/tokens/{id}/witnessesintegrator
Register a trusted third-party transparency witness
Registers an EOA whose signed-tree-head CO-SIGNATURE will be accepted for
this token's transparency log. A third-party witness (auditor,
custodian, oracle) independently co-signs the published STHs; only a
co-signature recovering to a REGISTERED active witness is accepted. Authed
(tokenize_admin); `@Audited`.
GET/tokenization/authoritiesintegrator
List this workspace's token authorities
Returns the role-holding authorities registered in the authenticated
workspace, newest first. A `server_key` carries `verified_at: null`
until its proof-of-possession lands; a `safe` is verified at
registration.
POST/tokenization/authoritiesintegrator
Register a token authority (server-key EOA or treasury Safe)
Registers an address that can hold ERC20X roles for this workspace. Two
kinds:
- `server_key`, a customer-controlled EOA (1-of-1). Registered
UNVERIFIED (`verified_at: null`); the customer must prove control via
the proof-of-possession flow (`/proof-challenge` → sign →`/proof`)
before the platform trusts it.
- `safe`, a Gnosis Safe whose N-of-M owner set IS its control.
References an existing treasury Safe (`treasuryWalletId`); verified
immediately (`verified_at` set at registration; no PoP step).
Authorities are WORKSPACE-scoped, registered once, usable across every
token in the workspace. Registration is keyed on `(workspace, address)`:
registering an address that the role poller already auto-created enriches
that same row rather than creating a duplicate. Requires AAL2 step-up and
the `tokenize_admin` role (tenant admins inherit it).
GET/tokenization/authorities/{id}integrator
Get one token authority with its role matrix
Returns one authority by its `tka_` prefixed id, scoped to the
workspace, including its ACTIVE role matrix, the
`token_role_assignments` (role + customer token) the authority currently
holds across the workspace's tokens.
Submit a proof-of-possession for a server-key authority
Submits the `challenge` from `/proof-challenge` plus a `signature` over
its `message`, produced by the authority's EOA. The api verifies the
challenge HMAC + expiry, recovers the signer via `recoverMessageAddress`,
and accepts only if the recovered address equals the authority's
registered address. On success the authority's `verified_at` is set.
A `server_key` is provable exactly ONCE, a proof submitted for an
already-verified authority returns
`tokenization_authority_already_verified`. A forged/expired challenge or
a wrong-signer signature returns `tokenization_proof_invalid`. Only valid
for a `server_key` (a `safe` returns
`tokenization_authority_kind_mismatch`). Authed integrator/operator route
(NOT a member `?token=` capability), the recovered signature is the
cryptographic gate. Requires AAL2 step-up and the `tokenize_admin` role.
Issue a proof-of-possession challenge for a server-key authority
Issues a stateless challenge for a `server_key` authority. Returns an
opaque `challenge` token (an HMAC-bound capability) plus the exact
`message` the authority's EOA must sign
(`account.signMessage({ message })`). The message binds a fixed domain
string + the authority id + workspace id + a random nonce, so the
signature cannot be replayed for another authority or purpose. Submit
both to `/proof`.
Only valid for a `server_key` authority, a `safe` is verified by its
on-chain owner set, so calling this on a `safe` returns
`tokenization_authority_kind_mismatch`. Requires AAL2 step-up and the
`tokenize_admin` role.
Create a token-control Safe and register it as an authority
Deploys a NEW Gnosis Safe dedicated to TOKEN CONTROL (purpose=
token_control) with the given name, staff-MPC signers, and N-of-M
threshold, the same quorum flow as a treasury vault, and registers it
as a verified `safe` authority in one step. Use this (rather than
`POST /tokenization/authorities` with an existing Safe) to stand up a
fresh controller. The Safe deploys asynchronously (state deploying →
active); the returned authority's address is the counterfactual (==
deployed) address, and a role grant against it waits for the Safe to be
live on-chain. Requires AAL2 step-up and the `tokenize_admin` role.
POST/tokenization/erc1155integrator
Deploy a customer gasless ERC-1155 (ERC1155X) contract on-chain
Deploys a customer-owned gasless ERC1155X multi-token contract proxy plus
a per-customer Zodiac Roles policy proxy on-chain, relayed (gas-paid) by
the platform relayer. Same lifecycle + customer-provided
admin-Safe model as the ERC20X / ERC-721 deploy; the contract carries no
`decimals` and is NEVER registered on the payment rails (LD-4). A
DEDICATED route (not the shared `/erc20` endpoint) so the per-tenant
`rwa_tokenization` entitlement gate applies here without touching the
ungated ERC20X path.
Returns immediately with `status: pending_deploy` once both deploy
transactions are queued; poll `GET /tokenization/tokens/{id}` for the
resolved `token_proxy_address` and the `active` status. Requires AAL2
step-up and the `tokenize_admin` role. Idempotent by `Idempotency-Key`.
POST/tokenization/erc20integrator
Deploy a customer gasless ERC-20 (ERC20X) on-chain
Deploys a customer-owned gasless ERC20X token proxy plus a per-customer
Zodiac Roles policy proxy on-chain, relayed (gas-paid) by the platform
relayer. The admin authority is a CUSTOMER-PROVIDED Safe, this endpoint
does NOT provision a Safe; the supplied Safe becomes the token's
DEFAULT_ADMIN / MINTER / PAUSER and the Roles owner/avatar/target.
Returns immediately with `status: pending_deploy` once both deploy
transactions are queued. A background confirmation poller flips the
token to `active` once BOTH deploys confirm on-chain success (a reverted
deploy is never treated as success), or records a `token_deploy_failed`.
Poll `GET /tokenization/tokens/{id}` for the resolved
`token_proxy_address` and the `active` status.
Requires AAL2 step-up and the `tokenize_admin` role (tenant admins inherit
it). Idempotent by `Idempotency-Key`.
POST/tokenization/erc721integrator
Deploy a customer gasless ERC-721 (ERC721X) collection on-chain
Deploys a customer-owned gasless ERC721X collection proxy plus a
per-customer Zodiac Roles policy proxy on-chain, relayed (gas-paid) by
the platform relayer. Same lifecycle + customer-provided
admin-Safe model as the ERC20X deploy; the collection carries no
`decimals` and is NEVER registered on the payment rails (LD-4). A
DEDICATED route (not the shared `/erc20` endpoint) so the per-tenant
`rwa_tokenization` entitlement gate applies here without touching the
ungated ERC20X path.
Returns immediately with `status: pending_deploy` once both deploy
transactions are queued; poll `GET /tokenization/tokens/{id}` for the
resolved `token_proxy_address` and the `active` status. Requires AAL2
step-up and the `tokenize_admin` role. Idempotent by `Idempotency-Key`.
GET/tokenization/tokensintegrator
List this workspace's customer-deployed tokens
Returns the customer tokens (ERC20X) in the authenticated
workspace, newest first. The off-chain mirror of on-chain
token state, a token in `pending_deploy` /
`awaiting_admin_setup` has no on-chain proxy address yet
(`token_proxy_address` / `access_registry_proxy_address` are
null until the deploy saga lands them). Read-only surface;
deploy / mint / role / policy operations land in later
tokenization slices.
GET/tokenization/tokens/{id}integrator
Get a customer-deployed token
Returns one customer token by its `ctk_` prefixed id, scoped
to the authenticated workspace. A token in another workspace
(or a nonexistent id) returns 404, the same shape, so the
response never distinguishes the two.
Add addresses to a customer token's access list (SAFE-PATH)
Builds a `Safe.execTransaction(to = accessRegistry, data =
accessListAdd(addresses))` against the token's admin Safe (which holds
the access-list admin role on the registry, granted by the deploy-time
wire op) and records a tokenization operation in `pending_sign`. The
Safe's N-of-M owners sign the returned SafeTx and POST signatures until
the threshold is met, then the api relays it via the platform relayer. A
background confirmation poller flips the operation to `confirmed` once
the relayed tx confirms on-chain success, a reverted-but-mined tx is
never treated as success.
For an `allowlist` token, adding an address PERMITS it to send/receive;
for a `denylist` token, adding an address BLOCKS it. On-chain
`registry.hasAccess(addr, ...)` reflects the change once confirmed.
Requires AAL2 step-up and the `tokenize_admin` role (tenant admins inherit
it). 404 `tokenization_access_registry_not_set` if the token was deployed
without `accessControl`. Idempotent by `Idempotency-Key`.
Remove addresses from a customer token's access list (SAFE-PATH)
Builds a `Safe.execTransaction(to = accessRegistry, data =
accessListRemove(addresses))` against the token's admin Safe and records
a tokenization operation in `pending_sign`. The Safe's N-of-M owners sign
the returned SafeTx and POST signatures until the threshold is met, then
the api relays it via the platform relayer. A background confirmation
poller flips the operation to `confirmed` once the relayed tx confirms
on-chain success, a reverted-but-mined tx is never treated as success.
For an `allowlist` token, removing an address REVOKES its ability to
send/receive (and makes its balance recoverable via the token's
recovery role); for a `denylist` token, removing an address UN-BLOCKS it.
Requires AAL2 step-up and the `tokenize_admin` role (tenant admins inherit
it). 404 `tokenization_access_registry_not_set` if the token was deployed
without `accessControl`. Idempotent by `Idempotency-Key`.
Attach an access registry to an open token (post-deploy)
Attaches a FRESH allow/deny access registry to an already-active OPEN
token, one deployed WITHOUT `accessControl`. At deploy time a registry
is optional; this endpoint adds one LATER, reusing the exact deploy-time
machinery.
Deploys a per-customer AllowList/DenyList registry proxy (relayer-paid
CREATE) initialized with the token's admin Safe as its admin, records the
chosen `mode` on the token's `access_control` immediately, and returns
with `status: relayed`. A background poller then resolves the registry
address (once the CREATE confirms on-chain success, a reverted-but-mined
CREATE is never treated as success), writes
`access_registry_proxy_address` on the token, and AUTO-BUILDS a
Safe-signed WIRE operation (a `set_policy` op batching
grantRole(CONTRACT_ADMIN) + accessRegistryUpdate(registry) +
grantRole(ACCESS_LIST_ADMIN) via MultiSend) the Safe's owners sign via
`POST /tokenization/tokens/{id}/operations/{opId}/signatures`. Once that
wire op confirms, the token gates transfers through the registry and the
Safe can manage membership via
`POST /tokenization/tokens/{id}/access-list/{add,remove}`.
Requires AAL2 step-up, the `tokenize_admin` role (tenant admins inherit
it), and a fully-set-up workspace (it deploys on-chain). Idempotent by
`Idempotency-Key`.
Detach the access registry from a token (SAFE-PATH → open transfers)
Detaches the token's allow/deny access registry by building a
`Safe.execTransaction(to = token, data = accessRegistryUpdate(0))` against
the token's admin Safe (which holds `CONTRACT_ADMIN_ROLE` from the
enable/deploy wire) and recording a `disable_access_registry` operation in
`pending_sign`. With `accessRegistry == 0` the token's transfer gate is
bypassed, transfers become OPEN again.
The Safe's N-of-M owners sign the returned SafeTx EIP-712 typed-data and
POST signatures until the threshold is met, then the api relays the detach
via the platform relayer. A background confirmation poller flips the
operation to `confirmed` once the relayed tx confirms on-chain success (a
reverted-but-mined tx is never treated as success), then clears the token's
`access_registry_proxy_address` + `access_control` to null. After that a
fresh registry, possibly the OTHER mode, can be attached via
`POST /tokenization/tokens/{id}/access-registry`, so an allowlist↔denylist
switch is a two-step (disable then re-enable).
The request takes no functional parameters, only the optional idempotency
key. Requires AAL2 step-up and the `tokenize_admin` role (tenant admins
inherit it). 404 `tokenization_access_registry_not_set` if the token has no
registry (transfers are already open). Idempotent by `Idempotency-Key`.
POST/tokenization/tokens/{id}/burnintegrator
Burn a customer token via its admin Safe (SAFE-PATH)
Builds a `Safe.execTransaction(to = token, data = burn(amount))` against
the token's admin Safe and records a tokenization operation in
`pending_sign`. `burn(uint256)` is `onlyRole(BURNER_ROLE)` and burns from
the executing admin Safe, so this reduces the Safe's own balance + the
total supply, the Safe must hold BURNER_ROLE (grant it via
`/roles/grant` first if the deploy didn't). The Safe's N-of-M owners sign
the returned SafeTx EIP-712 typed-data and POST signatures until the
threshold is met, then the api relays the burn (gas-paid) via the
platform relayer. A background confirmation poller flips the operation to
`confirmed` once the relayed tx confirms on-chain success, a
reverted-but-mined tx is never treated as success.
`amount` is a smallest-unit (raw uint256) integer string. Requires AAL2
step-up and the `tokenize_admin` role (tenant admins inherit it). Idempotent
by `Idempotency-Key`.
GET/tokenization/tokens/{id}/contractsintegrator
List a token's explorable control contracts
Returns the token's control-surface contracts so a client can drive a
generic contract explorer WITHOUT hardcoding any address: the token
proxy, its access registry, and (once resolvable) its Zodiac Roles
Modifier. Each entry carries the resolved on-chain `address` and an
`abi_name` key into the committed
`@xkova/sdk/abi` barrel (`ERC20XGasless` |
`AccessRegistry` | `Roles`), from which the client reads the full
function list. The `kind` of each entry is exactly the `contract`
enum value `GET .../read` accepts.
`roles_modifier` is omitted when the token has no resolvable Roles
Modifier proxy yet (a deploy still in flight, or an older deploy whose
Roles address wasn't recorded). Authed read (no signature, no AAL2);
scoped to the authenticated workspace.
Re-drives a customer-token deploy that is in `failed_resumable`, a state
reached when a deploy leg failed to SUBMIT to the relayer (a partial
bundle) or the relayer reported a leg errored/cancelled (the transaction
never executed on-chain). The api re-checks each leg's current status and
re-submits ONLY the leg(s) that never landed; a leg that already mined is
kept and never re-deployed (no double-deploy). The operation returns to
`relayed` and the token to `pending_deploy`; poll
`GET /tokenization/tokens/{id}` until it flips to `active`.
Only a `failed_resumable` token can be resumed. `failed_terminal` (an
on-chain revert) is NOT resumable, the same calldata reverts again; that
case needs a fresh deploy. Requires AAL2 step-up and the `tokenize_admin`
role (tenant admins inherit it).
POST/tokenization/tokens/{id}/deprecateintegrator
Deprecate a customer token (off-chain retire)
Retires a customer token from the rails. This is an OFF-CHAIN
operation, the token contract stays deployed and immutable on-chain;
only the platform's off-chain view changes. In lockstep it sets the
token's status to `deprecated` and unregisters it from the workspace's
approved-token allowlist, so it is no longer offered for new payments or
treasury transfers (it stops passing the workspace token gate and drops
out of the available-tokens picker). The mirror of the registration the
platform performs when a token first goes `active`.
Only valid from `active`; deprecating a token in any other state (still
deploying, failed, or already deprecated) returns
`tokenization_invalid_state_transition`. Requires AAL2 step-up and the
`tokenize_admin` role (tenant admins inherit it).
Relay a signed gasless holder-transfer ForwardRequest
Submits a holder-signed ERC-2771 `ForwardRequest` (from
`.../gasless/transfer-request`) + the holder's `signature`. The api
validates the signature recovers to `forwardRequest.from`, the request
targets THIS token (its inner-call `to` is the token proxy), and the
deadline is unexpired, each a fast-fail BEFORE the relayer spends gas
(the on-chain forwarder enforces the same). It then submits
`forwarder.execute(request)` via the relayer (which pays gas) and flips
the matching `gasless_relay` operation to `relayed`; a background poller
confirms on-chain success (`mined` is never trusted as success). The token
enforces its own access-list/pause + the holder's balance on the inner
transfer, so an over-balance / denied transfer resolves `reverted`, never
a silent success.
Requires AAL2 step-up and the `tokenize_minter` role.
Builds the ERC-2771 `ForwardRequest` a token HOLDER signs to transfer
their `ERC20XGasless` tokens WITHOUT paying gas, the holder-facing
gasless UX. The api resolves the token's chain + the shared
`TrustedForwarder` the token was initialized with, reads the forwarder's
on-chain EIP-712 domain + the holder's current `nonces(from)`, builds the
inner `transfer(to, amount)` calldata, records a `gasless_relay`
operation, and returns the ForwardRequest EIP-712 (domain = the
forwarder) for the holder to sign. The signed request is then POSTed to
`.../gasless/relay`; the XKOVA relayer submits `forwarder.execute(...)`
and pays gas.
The HOLDER is any EOA, NOT a registered account-holder or token
authority. The integrator api key authorizes this call (the
rate-limit/billing surface); the holder's signature authorizes the balance
movement. Requires AAL2 step-up and the `tokenize_minter` role (tenant admins
+ `tokenize_admin` inherit it). Idempotent by `Idempotency-Key`.
POST/tokenization/tokens/{id}/mintintegrator
Mint a customer token via its admin Safe (SAFE-PATH)
Builds a `Safe.execTransaction(to = token, data = mint(to, amount))`
against the token's admin Safe (which holds MINTER_ROLE, granted at
deploy) and records a tokenization operation in `pending_sign`. The
Safe's N-of-M owners then sign the returned SafeTx EIP-712 typed-data
and POST signatures until the threshold is met, at which point the api
relays the mint (gas-paid) via the platform relayer. A background
confirmation poller flips the operation to `confirmed` once the relayed
tx confirms on-chain success, a reverted-but-mined tx is never treated
as success.
`amount` is a smallest-unit (raw uint256) integer string. Requires AAL2
step-up and the `tokenize_minter` role (tenant admins + `tokenize_admin`
inherit it). Idempotent by `Idempotency-Key`.
Submits a holder-signed ERC-2771 `ForwardRequest` (from
`.../nft-transfers/build`) + the holder's `signature`. The api
recovers the signer (must equal `forwardRequest.from`), confirms the
request targets THIS token's proxy, and the deadline is unexpired; then
runs the GRADED transfer-eligibility door (holder-cap / lockup / freeze
predicates over the eligibility store + cap-table + lifecycle freezes) and
the shared TAP egress gate (OFAC-screens the recipient fail-closed) BEFORE
the relayer spends gas. It then submits `forwarder.execute(request,
signature)` via the relayer (which pays gas) and flips the matching
`nft_transfer_relay` operation to `relayed`; a background poller confirms
on-chain success (`mined` is never trusted as success).
The `{id}` is a `ctk_`-prefixed customer-token id. Requires AAL2 step-up;
blocked when the tenant is read_only/dunning (`@MoneyMovement`);
`@Audited`.
Build a gasless holder NFT-transfer ForwardRequest
Builds the ERC-2771 `ForwardRequest` a token HOLDER signs to transfer
their `ERC721X` / `ERC1155X` NFT WITHOUT paying gas, the
holder-facing gasless NFT UX. The api resolves the token's chain + the
shared `TrustedForwarder` the token was initialized with, reads the
forwarder's on-chain EIP-712 domain + the holder's current `nonces(from)`,
builds the inner `safeTransferFrom(from, to, tokenId[, amount])` calldata
(ERC-721 omits `amount`; ERC-1155 defaults it to `1`), records a
`nft_transfer_relay` operation, and returns the ForwardRequest EIP-712
(domain = the forwarder) for the holder to sign. The signed request is
then POSTed to `.../nft-transfers`; the XKOVA relayer submits
`forwarder.execute(...)` and pays gas.
The `{id}` is a `ctk_`-prefixed customer-token id. The HOLDER is any EOA,
NOT a registered account-holder. The integrator api key authorizes this
call; the holder's signature authorizes the custody movement. NFTs are
never registered on the payment rails, this is custody movement, not a
currency send. Requires AAL2 step-up; blocked when the tenant is
read_only/dunning (`@MoneyMovement`); `@Audited`.
GET/tokenization/tokens/{id}/operationsintegrator
List a customer token's privileged operations
Returns the tokenization operations (mint, …) for the token, newest
first, scoped to the authenticated workspace. Each carries its lifecycle
status (`pending_sign` → `relayed` → `confirmed` | `reverted` |
`failed`), collected owner signatures, and (while `pending_sign`) the
SafeTx typed-data to sign.
Returns one tokenization operation by its `tkop_` prefixed id, scoped to
the token + workspace. Poll this for the operation's lifecycle status
after relaying.
Predicts whether a signature from `signer` would be ACCEPTED by
`POST .../operations/{opId}/signatures` right now, without submitting one.
The console gates its sign UI on this so it never offers a sign the
signature endpoint would reject.
Mirrors the `/signatures` acceptance conditions exactly (pinned by a
contract test so the two can't drift): `can_sign` is true only when the
operation is `pending_sign`, its kind is a Safe-owner-signable kind, and
`signer` is an active owner of the token's admin Safe (checked against
on-chain `Safe.getOwners()`). For the gasless kinds the verdict is always
false in-browser: `policy_mint` is signed headlessly by your server-key
backend (`signer_kind: server_key`) and `gasless_relay` by the token
holder (`signer_kind: holder`).
Submit the authority's ModuleTx signature for a policy mint
Submits the scoped server-key authority's EIP-712 signature over a
`policy_mint` operation's `ModuleTx` (domain `verifyingContract` = the
token's Roles Modifier proxy). The api recovers the signer and rejects
unless it equals the authority the operation was opened against (the Roles
Modifier attributes the relayed call to the recovered signer, a wrong key
would revert `NotAuthorized`). On a valid signature the api assembles the
relayed payload (`data ++ salt ++ sig`) and submits it to the Roles proxy
via the relayer; the operation goes `relayed`, then a background poller
confirms on-chain success (`reverted` on an over-cap mint).
The signer is NOT supplied in the body, only the signature. Requires AAL2
step-up and the `tokenize_minter` role.
Submit a Safe-owner signature for a tokenization operation
Submits an EIP-712 signature over the operation's SafeTx, produced by an
OWNER of the token's admin Safe. The api recovers the signer via
`recoverTypedDataAddress` and accepts only if (a) the recovered address
equals the supplied `signer` and (b) `signer` is an active owner of the
Safe (checked against on-chain `Safe.getOwners()`). When the collected
signatures reach the Safe's threshold, the api relays
`Safe.execTransaction(...)` via the platform relayer and the operation
advances to `relayed`.
Authed integrator/operator route (NOT a member `?token=` capability),
the Safe owner set is the cryptographic gate. Requires AAL2 step-up.
POST/tokenization/tokens/{id}/pauseintegrator
Pause a customer token via its admin Safe (SAFE-PATH circuit breaker)
Builds a `Safe.execTransaction(to = token, data = pause())` against the
token's admin Safe and records a tokenization operation in `pending_sign`.
`pause()` is `onlyRole(PAUSER_ROLE)` on ERC20XGasless and flips the global
circuit breaker, while paused, every transfer/mint/burn reverts on-chain.
The executing admin Safe must hold PAUSER_ROLE (granted at deploy). The
Safe's N-of-M owners sign the returned SafeTx EIP-712 typed-data and POST
signatures until the threshold is met, then the api relays the pause
(gas-paid) via the platform relayer. A background confirmation poller flips
the operation to `confirmed` once the relayed tx confirms on-chain success
, a reverted-but-mined tx is never treated as success.
The request takes no functional parameters, only the optional idempotency
key. Requires AAL2 step-up and the `tokenize_admin` role (tenant admins
inherit it). Idempotent by `Idempotency-Key`.
POST/tokenization/tokens/{id}/policiesintegrator
Set a mint-cap policy for a server-key authority (SAFE-PATH)
Scopes a verified `server_key` authority on the token's per-token Zodiac
Roles Modifier so it can mint GASLESSLY within an on-chain per-call amount
cap, the product's "unattended capped minting". Builds a Safe-signed
`MultiSendCallOnly.multiSend(...)` (DELEGATECALL) batching, atomically:
enable the Roles module on the Safe, `assignRoles(authority)`,
`scopeTarget(token)`, and `scopeFunction(mint, amount <= cap)`. Records a
`set_mint_policy` operation in `pending_sign`; the Safe's N-of-M owners
sign the returned SafeTx and POST signatures until the threshold is met,
then the api relays it. On confirmation the off-chain `token_policies`
mirror is written. A subsequent gasless mint goes through
`POST /tokenization/tokens/{id}/policy-mint`.
The authority must be a VERIFIED `server_key` (a server key must prove
control of its EOA via `/authorities/{id}/proof` first). Requires AAL2
step-up and the `tokenize_admin` role (tenant admins inherit it). Idempotent
by `Idempotency-Key`.
Revoke a server-key authority's mint-cap policy (SAFE-PATH)
Revokes the active mint-cap policy for a `server_key` authority on the
token: builds a Safe-signed `roles.assignRoles(authority,
[roleKey], [false])`, a single owner-only CALL on the Roles Modifier,
removing the authority from its scoped role. Records a `revoke_policy`
operation in `pending_sign`; the Safe's N-of-M owners sign + the api
relays. On confirmation the `token_policies.active` mirror flips to false
and the authority's `policy-mint` reverts on-chain. Addressed by authority
(the system has no policy-id addressing). Requires AAL2 step-up and the
`tokenize_admin` role. Idempotent by `Idempotency-Key`.
Starts a policy-scoped GASLESS mint: a server-key authority that holds a
CONFIRMED mint-cap policy mints within its cap, with the XKOVA relayer
paying gas. Distinct from the Safe-path `/mint`, the thing to sign is a
zodiac-core `ModuleTx` (a member-signed meta-tx), NOT a SafeTx. The api
builds an `execTransactionWithRole(to = token, mint(to, amount), …)` for
the token's Roles Modifier, records a `policy_mint` operation, and returns
the `ModuleTx` EIP-712 (domain `verifyingContract` = the Roles proxy) for
the server key to sign. The signature is then POSTed to
`.../operations/{opId}/module-signature`; the relayer submits the meta-tx.
A mint within the cap succeeds; an over-cap mint reverts on-chain (Roles
`ConditionViolation`) and the operation resolves `reverted`, never a
silent success. Requires AAL2 step-up and the `tokenize_minter` role (tenant
admins + `tokenize_admin` inherit it). Idempotent by `Idempotency-Key`.
Build a raw governance SafeTx on one of the token's own contracts (SAFE-PATH escape hatch)
The ADVANCED governance escape hatch. Builds a `Safe.execTransaction`
wrapping an ARBITRARY function call against ONE of the token's three OWN
control contracts, `token`, `access_registry`, or `roles_modifier`, and
records a tokenization operation in `pending_sign`. The target address is
SERVER-RESOLVED from the token (a fixed enum is the only knob); an
arbitrary address can never be reached. The inner calldata is encoded from
the COMMITTED ABI, so a `function` not in the resolved contract's ABI, or
`args` whose count/shape do not match its inputs, is rejected with
`tokenization_raw_call_invalid` BEFORE any SafeTx is built.
It exists to reach admin functions the typed verticals (mint / burn /
roles / access-list / pause / policy) don't cover, e.g.
`contractUriUpdate`, `updateTrustedForwarder`, a Roles `scopeFunction`
tweak. It is a NORMAL `pending_sign` SafeTx: it CANNOT broadcast without
the admin Safe's N-of-M owner signatures (submitted to the shared
`operations/{opId}/signatures` endpoint) + the relayer, so on-chain
authorization is the unchanged, fully non-custodial Safe gate. It is
OUTSIDE the value-movement (TAP authorize/screening) gate (it moves no
value generically) and writes NO domain-mirror rows (it is un-mirrored by
design; on-chain is the source of truth). Every raw call is `@Audited`.
Requires AAL2 step-up and the `tokenize_admin` role (tenant admins inherit
it), the most privileged tokenization act. Idempotent by `Idempotency-Key`.
GET/tokenization/tokens/{id}/readintegrator
Read a view function on a token's control contract
Performs a single `eth_call` of ONE view/pure function on ONE of the
token's three control contracts (the contract explorer's read path).
The contract enum + function are validated against the committed ABI
BEFORE any RPC: an unknown contract, an unknown function, a non-view
function, or a wrong argument shape is rejected with `validation_failed`
(422). The target address is server-resolved from the token, so the call
is strictly bounded to the token's own contracts.
The `result` is the ABI-decoded return value with every `bigint`
stringified (a single-output function yields the scalar; a multi-output
function yields an array; tuple/struct outputs are objects), JSON-safe
by construction. Authed read (no signature, no AAL2); scoped to the
authenticated workspace.
POST/tokenization/tokens/{id}/recoverintegrator
Recover (claw back) tokens from a blocked account (SAFE-PATH)
The compliance claw-back. Builds a `Safe.execTransaction(to = token, data
= recoverTokens(account, amount))` against the token's admin Safe and
records a tokenization operation in `pending_sign`. `recoverTokens(address,
uint256)` is `onlyRole(RECOVERY_ROLE)` and moves the blocked account's
balance to the executing admin Safe, so the recovered tokens land in the
Safe, and the Safe must hold RECOVERY_ROLE (grant it via `/roles/grant`
first; the deploy grants DEFAULT_ADMIN / MINTER / PAUSER but NOT
RECOVERY_ROLE). The Safe's N-of-M owners sign the returned SafeTx EIP-712
typed-data and POST signatures until the threshold is met, then the api
relays the recover (gas-paid) via the platform relayer. A background
confirmation poller flips the operation to `confirmed` once the relayed tx
confirms on-chain success, a reverted-but-mined tx is never treated as
success.
`recoverTokens` only applies to a token deployed WITH `accessControl`
whose target is currently DENIED (`registry.hasAccess(account) == false`)
, recovering from a still-active account is rejected with
`tokenization_recover_target_active`. Block the account first (denylist:
`/access-list/add`; allowlist: `/access-list/remove`), then recover.
`account` is the blocked holder; `amount` is a smallest-unit (raw uint256)
integer string. Requires AAL2 step-up and the `tokenize_admin` role (tenant
admins inherit it). Idempotent by `Idempotency-Key`.
Grant an ERC20X role via the admin Safe (SAFE-PATH)
Builds a `Safe.execTransaction(to = token, data = grantRole(roleHash,
account))` against the token's admin Safe (which holds
DEFAULT_ADMIN_ROLE, the admin of every named ERC20X role) and records a
tokenization operation in `pending_sign`. The Safe's N-of-M owners sign
the returned SafeTx and POST signatures until the threshold is met, then
the api relays the grant via the platform relayer. On confirmation the
off-chain role mirror (`token_role_assignments`) is updated, on-chain is
the source of truth, so the mirror is written post-confirmation.
`role` is an ERC20X role NAME from the closed set (MINTER_ROLE,
BURNER_ROLE, PAUSER_ROLE, UPGRADER_ROLE, CONTRACT_ADMIN_ROLE,
RECOVERY_ROLE, SALVAGE_ROLE); an unknown name is rejected with
`tokenization_unknown_role`. Requires AAL2 step-up and the `tokenize_admin`
role (tenant admins inherit it). Idempotent by `Idempotency-Key`.
Revoke an ERC20X role via the admin Safe (SAFE-PATH)
Builds a `Safe.execTransaction(to = token, data = revokeRole(roleHash,
account))` against the token's admin Safe and records a tokenization
operation in `pending_sign`. The Safe's N-of-M owners sign the returned
SafeTx and POST signatures until the threshold is met, then the api
relays the revoke via the platform relayer. On confirmation the
`token_role_assignments` mirror row for this (role, account) is closed
(`revoked_at` set), on-chain is the source of truth.
`role` is an ERC20X role NAME from the closed set; an unknown name is
rejected with `tokenization_unknown_role`. Requires AAL2 step-up and the
`tokenize_admin` role (tenant admins inherit it). Idempotent by
`Idempotency-Key`.
Remove a customer token from this workspace's treasury readers
Disables the customer token's treasury eligibility, it no
longer appears in the treasury asset picker / balances and new treasury
transfers of it are rejected. Reversible via treasury-enable. NOT an
on-chain operation. Requires AAL2 step-up and the `tokenize_admin` role.
Enable a customer token for treasury custody + transfers (workspace-scoped)
Marks the customer token eligible to be held + moved through this
workspace's treasury Safe. The deploy poller auto-enables an
active token, so this is the operator override to re-enable a previously
disabled token and/or flag it a stablecoin (so it contributes to the
treasury 1099 USD aggregation). NOT an on-chain operation, it flips a
workspace-scoped eligibility flag the treasury readers honor; the token
is invisible to every other tenant's treasury. Requires AAL2 step-up and
the `tokenize_admin` role.
Read a customer token's current treasury-eligibility state
Read-only current treasury-eligibility for the token, backs
the console's quiet treasury-status display so the operator sees the real
on/off state on load. An active ERC20X is auto-enabled at deploy; a token
with no deployment or no eligibility row reads `treasury_enabled: false`.
Workspace-scoped (`tokenize_admin`); no AAL2 (it is a read).
POST/tokenization/tokens/{id}/unpauseintegrator
Unpause a customer token via its admin Safe (SAFE-PATH circuit breaker)
Builds a `Safe.execTransaction(to = token, data = unpause())` against the
token's admin Safe and records a tokenization operation in `pending_sign`.
`unpause()` is `onlyRole(PAUSER_ROLE)` on ERC20XGasless and restores the
token after a `pause()`, transfers/mint/burn resume. The executing admin
Safe must hold PAUSER_ROLE. The Safe's N-of-M owners sign the returned
SafeTx and POST signatures until the threshold is met, then the api relays
the unpause (gas-paid) via the platform relayer. A background confirmation
poller flips the operation to `confirmed` once the relayed tx confirms
on-chain success, a reverted-but-mined tx is never treated as success.
The request takes no functional parameters, only the optional idempotency
key. Requires AAL2 step-up and the `tokenize_admin` role (tenant admins
inherit it). Idempotent by `Idempotency-Key`.
Returns the caller's own row(s). Today there's a 1:1 mapping
(one MPC per staff per scope), so this typically returns 0
or 1 entries. Operators visit this on every treasury page
to gate UI access, the topbar badge + onboarding flow
read from here.
POST/staff/me/mpc-wallets/provisionpartner
Mint a Thirdweb JWT + issue PoP challenge (idempotent)
Step 1 of MPC onboarding (proof-of-possession):
1. Call this endpoint → receive `thirdweb_jwt` + `challenge`
2. UI initializes the Thirdweb in-app wallet SDK with the JWT
(custom-auth strategy); browser generates the MPC wallet
3. UI signs `challenge.payload_to_sign` via account.signMessage
4. UI POSTs `{challenge_id, address, signature}` to /register;
server ecrecover-verifies and flips state to active
Idempotency: if the caller already has a row, returns it
unchanged + a fresh JWT + a fresh challenge.
Requires AAL2.
POST/staff/me/mpc-wallets/registerpartner
Verify proof-of-possession + activate the staff MPC wallet
Step 2 of MPC onboarding. Server `ecrecover`s the
signature, verifies it recovers to the claimed address, then
transitions the row pending_provisioning → active and consumes
the challenge. Replaces the prior `/confirm` endpoint which
trusted the client-supplied address without verification.
Requires AAL2.
GET/treasury/assetspartner
List registered assets for a chain
Lists the assets registered for a given chain that treasury vaults can hold and transfer, such as supported stablecoins. Filter by chain_id to scope the result to one network.
POST/treasury/assetspartner
Register a token in the supported-asset registry
Validates the ERC-20 interface via static call (symbol,
name, decimals all respond) before insertion. The
`symbol`/`name`/`decimals` come from the contract, clients
don't supply them. `is_stable` is operator-asserted.
Idempotent on `(chain_id, token_address)`, re-registering
returns the existing row's id rather than failing.
GET/treasury/counterpartiespartner
List counterparties (keyset-paged, ordered by display_name)
Keyset-paginated over a composite `(display_name, id)` cursor.
Counterparties are unbounded per tenant, so the response is the
standard `CursorPage` envelope, follow `next_cursor` until
`has_more` is false. The id tiebreak in the cursor makes the page
walk total and gap-free even when two rows share a display_name.
POST/treasury/counterpartiespartner
Create a counterparty
Creates a named treasury counterparty, a saved payee that groups one or more verified destination addresses for reuse in vault transfers. Returns the new counterparty. Requires AAL2 and may be held for approval.
PATCH/treasury/counterparties/{id}partner
Update a counterparty (any subset of fields)
Updates the mutable fields of an existing treasury counterparty, such as its display name or notes. Accepts any subset of fields and returns the updated counterparty. Requires AAL2.
Map an address to a counterparty (per-chain unique)
Adding an address claims `(chain_id, address)` as belonging
to this counterparty. The mapping drives:
- tax classification on confirmed transfers
- 1099 aggregation on the year-summary export
- the operator-facing "send to <counterparty>" UX
UNIQUE(chain_id, address) means the same address can't be
mapped to two different counterparties on the same chain
(would cause ambiguity in 1099 aggregation).
Removes the (counterparty_id, address) mapping. Confirmed
transfers that previously aggregated under this counterparty
are NOT retroactively unclassified, the tax export reads
the classification at the time of the transfer's
confirmation, not the live mapping.
GET/treasury/revenue-changespartner
List revenue-treasury change (re-link) requests
Lists the workspace's revenue-treasury re-link requests, the quorum-gated proposals to redirect platform fee revenue to a different treasury vault. Returns each request with its current state and approval progress, newest first.
POST/treasury/revenue-changespartner
Initiate a quorum-gated revenue-treasury re-link
Opens a request to re-point the workspace's revenue treasury to a
different Safe. Approval requires N-of-M attestations from
the CURRENT treasury's signers (`required_approvals` is snapshotted
from its threshold). Returns the request with the `signing_context`
each signer signs. Requires treasury_admin + AAL2. At most one
non-terminal request per workspace.
GET/treasury/revenue-changes/{id}partner
Get a revenue-treasury change request (with signing_context)
Returns a single revenue-treasury re-link request, including the EIP-712 signing_context that current-vault signers attest against. Use this to read a request's state and collect the signatures needed to apply it.
POST/treasury/revenue-changes/{id}/applypartner
Apply an approved revenue-treasury change
Atomically swaps the revenue-treasury designation A→B and re-stamps
the committed earned schedule's earned rules to Safe B. Only valid
from ready_to_apply. Requires treasury_admin + AAL2.
POST/treasury/revenue-changes/{id}/cancelpartner
Cancel a revenue-treasury change request
Cancels an in-flight revenue-treasury re-link request before it is applied, abandoning any signatures already collected. Returns the request in its cancelled state. Requires AAL2 and a treasury admin role.
Submit a signer's attestation for a revenue-treasury change
The caller must be an active signer on the CURRENT (from) treasury.
The signature is recovered (ecrecover) against their MPC wallet over
the request's `signing_context`. At threshold the request transitions
to ready_to_apply. Requires treasury_admin or treasury_signer + AAL2.
GET/treasury/tax/1099-misc.csvpartner
1099-MISC CSV export (one row per reportable counterparty)
Same aggregation as 1099-summary. Columns:
recipient_legal_name, recipient_display_name,
recipient_tax_id, recipient_tax_id_type, classification,
total_usd, transactions_count.
GET/treasury/tax/1099-summarypartner
1099-MISC year summary (counterparties with USD totals)
Aggregates `confirmed` + `kind=transfer` rows in the year
(UTC boundary) per counterparty. Only stable assets
(`is_stable=true`) contribute to the USD total, non-stable
asset transfers are recorded but excluded from 1099 totals
(operators can run a separate FX reconciliation).
Filtered to counterparties with annual total `>= 600` USD-
equiv (federal 1099-MISC reporting floor for non-employee
compensation).
GET/treasury/tax/transactions.csvpartner
Confirmed-transfers CSV for a date range (optionally counterparty-filtered)
Per-transfer row (no aggregation). Columns: transfer_id,
confirmed_at, wallet_id, counterparty_id, to_address,
asset_token_address, amount, memo, classification.
`from` is inclusive, `to` is exclusive, e.g. the full year
2026 is `from=2026-01-01&to=2027-01-01`.
Permission: viewer-or-higher (more permissive than the 1099
paths since this is operator-facing reconciliation rather
than tax-authority-facing reporting).
GET/treasury/transfers/{id}partner
Get a transfer (with signing_context when collectable)
`signing_context` is populated only when state ∈ {drafted,
partially_signed, ready_to_execute}, i.e. while signatures
are being collected. After execute (state=submitted) the
typed-data would be misleading (the signed hash is stored
in `safe_tx_hash` already), so the field is stripped.
POST/treasury/transfers/{id}/cancelpartner
Cancel a transfer (off-chain only)
Marks the transfer `cancelled` and releases the Safe nonce
slot for re-use by a future draft. The on-chain Safe is
unaffected, there's no chain-side cancel; cancellation only
prevents further signature collection or execution via this
api.
Allowed states: drafted | partially_signed | ready_to_execute.
Authorization: treasury_admin OR the staff who initiated
the transfer.
POST/treasury/transfers/{id}/executepartner
Submit the assembled execTransaction via the XKOVA Relayer (requires state=ready_to_execute)
Builds `Safe.execTransaction(...)` calldata with the
collected signatures concatenated in ascending signer-address
order (Safe convention) and submits via the XKOVA Relayer for gas
relay. Idempotent on `execute_tx_queue_id`, a second call
with the slot already populated returns the same record
without re-submitting (race resolution for two operators
clicking Execute simultaneously).
State transition: `ready_to_execute → submitted`. The
transfer flips submitted → confirmed (or → failed) on the
on-chain receipt.
POST/treasury/transfers/{id}/signaturespartner
Submit a signature for a transfer
Verifies via `recoverTypedDataAddress` that the signature
recovers to the caller's `staff_mpc_wallets.address`. On
success the row is appended to `treasury_transfer_signatures`
(UNIQUE on (transfer_id, signer_staff_id) blocks duplicates)
and the transfer state advances:
- signatures < threshold → `partially_signed`
- signatures == threshold → `ready_to_execute`
Both transitions emit webhook events
(`treasury.transfer.signed`,
`treasury.transfer.ready_to_execute`).
GET/treasury/walletspartner
List the tenant's vaults (newest first)
Lists the tenant's treasury vaults, the N-of-M multisignature Safes that hold pooled funds. Returns each vault's address, chain, and configuration, newest first.
POST/treasury/walletspartner
Create a treasury vault (deploys a Safe v1.4.1 multisig)
Inserts treasury_wallets + treasury_signers in a DB transaction,
computes the counterfactual Safe address, and submits the deploy
tx via the XKOVA Relayer. Returns immediately with state='deploying'; the
vault flips to 'active' (and the address is corrected from
the ProxyCreation receipt log) when the deploy mines.
Lost-signer policy: `signer_count >= threshold + 2` is the
default-safe baseline. Below that, set
`acknowledged_unsafe: true` to confirm, the wallet may become
permanently unrecoverable on key loss.
GET/treasury/wallets/{id}partner
Get a vault by id (with signer membership)
Returns a single treasury vault by id, including its N-of-M signing threshold and the current signer membership. Use this to inspect a vault's quorum configuration before proposing transfers or signer changes.
GET/treasury/wallets/{id}/balancepartner
Read the vault's on-chain balances (native + every registered ERC-20 on its chain)
Reads happen in parallel via a viem PublicClient pointed at
the chain's RPC. A single failed read returns `raw='0'` for
that asset rather than tanking the whole response. Native gas
asset is always included regardless of registry membership.
POST/treasury/wallets/{id}/decommissionpartner
Decommission an active vault (state='decommissioned')
Soft state transition only, the on-chain Safe is unaffected
(operators can still execute via direct chain interaction if
needed). New transfers and signer changes are blocked once
decommissioned.
Designate this vault as the workspace's revenue treasury
Marks this active Safe as the workspace's revenue treasury, the
server-derived destination earned fees are PUSHed to (the earned-fee
beneficiary is stamped to this address at fee-schedule commit). At
most one per workspace.
FIRST-link operation: succeeds only when the workspace has no
existing revenue treasury. Re-pointing revenue to a different Safe
is gated by the current treasury's N-of-M quorum (the
revenue-treasury-change flow), so a second-different designation is
rejected with `revenue_treasury_already_designated`. Re-designating
the same active wallet is idempotent. Requires treasury_admin + AAL2.
GET/treasury/wallets/{id}/signerspartner
List signers on a vault (active + in-flight pending_add/pending_remove)
Lists the signers on a treasury vault, including those active and any in-flight pending_add or pending_remove memberships from a quorum-gated signer change still being approved on-chain. Returns each signer's address and status.
POST/treasury/wallets/{id}/signerspartner
Draft an add-signer Safe owner-mgmt transaction
Inserts a `treasury_signers` row in state `pending_add` AND
drafts a Safe `addOwnerWithThreshold(newOwner, threshold)`
transaction. The current signers' threshold-many signatures
must be collected + executed before the new signer becomes
canonical on-chain (and the row flips to `active`).
Marks the signer's row `pending_remove` AND drafts a Safe
`removeOwner(prevOwner, owner, threshold)` transaction.
`prevOwner` is computed from the current active-signer
ordering (Safe's linked-list semantics), so the api needs the
staff ids in their original add-order to derive correctly.
On-chain confirmation flips the row to `removed` (terminal).
POST/treasury/wallets/{id}/thresholdpartner
Draft a Safe threshold-change transaction
Drafts a Safe `changeThreshold(newThreshold)` transaction.
New threshold must be in `[1, active_signer_count]` and
different from the current threshold. Raising to a value
where `signer_count - threshold < 2` requires
`acknowledged_unsafe: true`. Lowering threshold never trips
the lost-signer guard.
GET/treasury/wallets/{id}/transactionspartner
Confirmed-transfer history for a vault (reconciliation snapshot)
Cursor-paginated, newest-first snapshot of CONFIRMED treasury
transfers for this vault, the reconciliation / reporting / audit
surface (E4). Live in-flight transfer state is on
GET /treasury/wallets/{id}/transfers; this is the confirmed subset.
The NFTs (ERC721X / ERC1155X positions) the treasury Safe owns,
sourced from the cap-table where the holder == the Safe address. Custody is
first-class, a Safe can receive + hold any NFT with NO eligibility gate on
RECEIVING (the door fires only on a SEND, the draft path). Cursor-
paginated; keyset-ordered by (collection, instance id). Gated on the
`treasury` feature; scoped to the authenticated workspace.
Drafts a `kind='nft_transfer'` treasury transfer: a
`Safe.execTransaction(safeTransferFrom(...))` the existing N-of-M
sign/execute lifecycle carries (sign + execute are the shared
`/treasury/transfers/{id}` routes). The Safe must OWN the NFT (checked
on-chain at draft), and the GRADED transfer-eligibility door runs at draft
against the recipient. Custody egress like every treasury transfer:
requires AAL2 step-up + completed workspace setup; blocked when the tenant
is read_only/dunning (`@MoneyMovement`); gated on the `treasury` feature;
`@Audited`. NFTs are never a treasury currency, this is custody movement,
not a registered asset send.
GET/treasury/wallets/{walletId}/transferspartner
List transfers for a vault (newest first)
Lists the transfers initiated from a treasury vault, newest first, with cursor pagination. Returns each transfer's amount, asset, recipient, and approval and settlement state across its N-of-M signing lifecycle.
POST/treasury/wallets/{walletId}/transferspartner
Draft a transfer (reserves Safe nonce + computes EIP-712 hash)
Inside a DB transaction:
1. Reserves the next Safe nonce (max of DB-side max +
`on-chain Safe.nonce()` then `+1`)
2. Builds the inner SafeTx, for ERC-20 sets `to=token`
and encodes `transfer(recipient, amount)` calldata; for
native sets `to=recipient, value=amount, data='0x'`
3. Computes the canonical Safe EIP-712 hash signers will
sign
4. Persists the row in `state='drafted'`
Cancellation/failure release the nonce slot for reuse.
Lists every wallet belonging to one account holder, returning each wallet's address, chain, and state. Use this to enumerate a holder's wallets before initiating a transfer or reading their balances.
GET/wallets/{id}integrator
Get a wallet
Returns a single wallet by id, including its on-chain address, owning account holder, chain, and current state. Use this to resolve a wallet's details before referencing it in a payment or history query.
GET/wallets/{id}/balanceintegrator
Materialized running balance for a (wallet, chain, token)
Reads the materialized running balance. The balance is maintained
transactionally alongside ledger entries as on-chain activity is
observed. A nightly drift-check asserts this matches
`sum(credits) - sum(debits)` across the ledger; an alarm fires
on mismatch.
If the wallet has never been seen on this (chain, token), the
response is a zero-balance placeholder, NOT a 404. Callers
don't have to special-case the first-touch case.
POST/wallets/{id}/decommissionintegrator
Decommission a wallet (state=decommissioned; not deleted)
Decommissions a wallet, moving it to the decommissioned state so it can no longer be used to send or receive. The wallet is retained for history rather than deleted. Returns the updated wallet.
GET/wallets/{id}/historyintegrator
Wallet-scoped payment history (same shape as GET /payments with wallet filter)
Returns the transaction history scoped to one wallet, the same row shape as the tenant-wide payment list, pre-filtered to payments touching this wallet. Cursor-paginated, newest first.
GET/wallets/{id}/ledgerintegrator
Bank-statement timeline for a wallet (atomic debits + credits across every token movement)
Ledger-level view of every on-chain token movement touching this
wallet, newest first. Complementary to `/wallets/{id}/history`
(payment-level view): same tx can produce multiple ledger rows
(e.g., a router payment emits a debit for the payer and a credit
for the recipient in the same tx). Ledger rows also capture
movement that bypasses the router, faucet mints, bridge
deposits, direct external transfers, tagged via `category`.
Data source: XKOVA's read-only ledger projection, materialized
from on-chain activity.
POST/wallets/{id}/qrintegrator
Generate a receive QR (EIP-681 + XKOVA extensions) for an explicit wallet
Step 8b of integrator-docs-cleanup relocated this from
the bare-verb `/v1/receive-qr` (which derived the wallet
from the `X-Account-Holder-Id` header) to the resource-
scoped path under the wallet that owns the receiving
address. Multi-wallet members now disambiguate up front
via the path id.