Fees and Pricing
A fee schedule is the pricing your workspace applies to payments. It is a versioned set of rules, each one addressed to a beneficiary and tagged with the transaction types it prices. A schedule is authored as a draft, reviewed, then committed to become the active pricing. Committing supersedes the prior schedule atomically, so the workspace always has exactly one active schedule and a full version history behind it. This page covers the lifecycle, the rule shape, the transaction type registry, the revenue treasury a schedule needs before it can commit, and the read-only accessor for the XKOVA platform fee.
The Draft to Commit Lifecycle
A fee schedule moves through three states. You create it as a draft, commit the draft to make it active, and a schedule is superseded automatically the moment a newer draft for the same workspace commits.
| State | Meaning |
|---|---|
draft | Authored but not yet active. Editable only by replacement: to change a draft, create a new one. A draft can sit un-committed indefinitely with no effect on pricing. |
committed | The active pricing for the workspace. Exactly one committed schedule exists per workspace at a time. A committed schedule is immutable. |
superseded | A schedule that was committed and then replaced when a newer draft committed. Retained for history, never active again. |
Because a committed schedule is immutable, there is no rule-editing endpoint. To change pricing you author a new draft carrying the full rule set you want, then commit it. Committing looks up the workspace's current committed schedule, marks it superseded, and flips the new draft to committed in one transaction. Version numbers are assigned by the server, starting at 1 and incrementing per workspace, so the version on a schedule tells you where it sits in that workspace's history.
flowchart LR A[Create draft] --> B[draft] B --> C[Commit] C --> D[committed, active pricing] D -- newer draft commits --> E[superseded]
The Endpoints
| Call | What it does |
|---|---|
GET /v1/fee-schedules | List the workspace's schedules, cursor-paginated and
filterable by status (draft, committed, or superseded).
Each schedule includes its full rules array. |
GET /v1/fee-schedules/{id} | Fetch one schedule with its rules and commit metadata. |
POST /v1/fee-schedules | Create a new draft from a rules array.
Returns the drafted schedule. |
POST /v1/fee-schedules/{id}/commit | Commit a draft. Returns the committed
schedule. A justification is captured into the audit log. |
GET /v1/fee-schedules/platform-fee | Read the XKOVA platform fee rule that applies to the workspace. Read-only. |
Fee schedules are gated behind the revenue controls entitlement; a tenant without it gets
feature_not_enabled on every call here. See Entitlements for how
entitlements are enabled. The list is a standard cursor page: it returns data,
next_cursor, and has_more. See Pagination.
Rules and Beneficiaries
A schedule is a list of rules. Each rule directs one fee share to one beneficiary, priced over one or more transaction types. A rule carries these fields.
| Field | Meaning |
|---|---|
beneficiary | Who the fee share is for. One of the typed roles
(institution, custody_provider, screening_provider,
issuer, integrator) or external, a tenant-named slot. |
applies_to_tx_types | Which transaction types this rule prices. At least one required. See the registry below. |
basis | flat, bps, or percent. |
value | Decimal string interpreted per the basis, for example
"30" for 30 basis points or "0.25" for 0.25 percent. |
beneficiary_address | The on-chain destination for this share. Required for paid rules, and must be omitted for earned rules (see below). |
label | A display name. Required and non-empty when
beneficiary is external; optional for the typed roles. |
min_amount, max_amount | Optional decimal bounds on the computed fee. |
Earned Rules Versus Paid Rules
Beneficiaries split into two classes, and the split decides where the beneficiary_address
comes from. Earned fees are revenue the tenant keeps; they route to the workspace's revenue treasury, whose
address the server stamps at commit time. Paid fees pass through to a provider or integrator destination the
tenant controls, so the tenant supplies the address on the rule.
| Class | Beneficiaries | Address |
|---|---|---|
| Earned | institution, external, issuer |
Omit it. The server stamps the workspace revenue treasury Safe address at commit. |
| Paid | custody_provider, screening_provider,
integrator | Required. The tenant-controlled destination for the share. |
Sending an address on an earned rule, or omitting one on a paid rule, is rejected at create with
conflict_state. An external rule needs a label and, being earned, omits the address.
You cannot add a rule for the xkova beneficiary: that is the platform fee, covered below, and it
is not tenant-settable.
The Transaction Type Registry
Every rule tags the transaction types it prices in applies_to_tx_types. The allowed values are
a fixed registry, published as the FeeScheduleTxType enum in the API spec and the SDK. Each type
is either live or preparing. The API accepts rules tagged with a preparing type so pricing can be authored
ahead of the flow going live, but no fee is charged for a preparing type until its payment flow ships.
| Value | Prices | Status |
|---|---|---|
p2p_send | A member-to-member transfer inside the same tenant. | Live |
external_send | A member sending to an external wallet. | Live |
escrow_claim | An escrow as a whole. One fee per escrow lifecycle, realized at release, not separate fees on create and release. | Live |
ecommerce_payment | A member paying a merchant through a checkout session. | Preparing |
A rule with an empty applies_to_tx_types, or one naming a value outside the registry, is
rejected at create with conflict_state. See Payments and
Escrow and Claims for the flows these types price.
The Revenue Treasury Prerequisite
A schedule that carries any earned rule cannot be committed unless the workspace has an active revenue treasury. The reason is direct: an earned fee's destination is the revenue treasury Safe, and there is nothing to route the fee to without one. When you commit a schedule that carries earned rules, the server resolves the workspace's active revenue treasury Safe and stamps its address onto every earned rule in the same transaction that flips the schedule to committed. The draft holds no address for those rules until then.
If the workspace has no active revenue treasury, commit fails with revenue_treasury_required
and the schedule stays a draft. The fix is to establish a treasury Safe and designate it as the workspace's
revenue treasury first, then commit. Paid rules do not depend on this: their addresses are tenant-supplied and
present from create. See Treasury for establishing a Safe and designating the
revenue treasury.
The Platform Fee
The XKOVA platform fee is applied to a workspace's payments alongside the tenant's own rules. It is not
tenant-adjustable: only XKOVA sets it, and it is not writable through this API. Attempting to include a rule
for the xkova beneficiary in a create is rejected.
Read it with GET /v1/fee-schedules/platform-fee. The response is an envelope,
{ "rule": <FeeRule> }, or { "rule": null } when the workspace has no platform
fee configured yet. Surfacing it read-only lets an operator see the full fee stack that applies to every
transaction, tenant rules and platform fee together.
The platform fee is preserved across commits automatically. When you commit a new schedule, the platform fee rule from the prior committed schedule is carried onto the new one and widened to cover every live transaction type, so committing your own pricing never drops the platform fee.
Authorization
Fee schedules are tenant-scoped; the platform scope above tenants does not manage them. Reading schedules
and the platform fee is available to any tenant-scope caller with the revenue controls entitlement. Authoring
is more restricted: creating a draft requires the fee_schedule.create permission and committing
requires fee_schedule.commit, both held by the admin and owner roles. A commit is recorded in the
audit log as a configuration change, including the justification supplied on the call. See
Authorization and RBAC for roles and permissions, and the
audit log for what a commit records.
Errors
The error catalog documents every code; these are the ones each step most commonly returns.
| Step | Codes |
|---|---|
| Create | validation_failed, conflict_state (a reserved
xkova rule, an external rule with no label, an earned rule carrying an address, a
paid rule missing one, an empty or unknown applies_to_tx_types),
role_denied, feature_not_enabled,
idempotency_conflict |
| Commit | not_found, invalid_state (the schedule is not a draft),
conflict_state (no rules defined), revenue_treasury_required,
role_denied, feature_not_enabled |
| Read | feature_not_enabled, not_found |
Create accepts an Idempotency-Key header so a retried create does not draft a duplicate. See
Idempotency.
Related
- Treasury: establishing the revenue treasury an earned schedule needs.
- Payments: the flows a fee schedule prices.
- Reporting: reading back the earned and platform-fee sides of settled fees.
- Entitlements: enabling the revenue controls entitlement.