Tenant markup v1 + on-chain three-way split (2026-04-25)
Tenant agencies can now set their own markup over supplier cost, see GMV roll-ups, and get historical-median recommendations. Bookings settle three legs atomically on Arc — vendor, agency, Sendero — via SenderoGuestEscrow v3. Includes new MCP tools, the standard error envelope, and OTP brute-force protection on the same contract.
Released: 2026-04-25
The full markup pipeline is live. Tenants set their own margin, customers pay one total, and Arc settles three recipients atomically in one transaction. No reconciliation, no end-of-month true-up.
What's new for tenants
- Set markup per booking kind.
/dashboard/settings/pricingnow exposes a wizard forflight,hotel,rail,car,other. Pickstaticmarkup in basis points; v2 will add tiered + volume-curve strategies behind the same schema. - Floor and ceiling guardrails. Self-set bounds. The floor stops free upgrades; the ceiling stops a hallucinating agent from booking a $50K hotel at 200% markup. Override the ceiling with a privileged scope (details).
- Two take behaviors. Choose
deduct_from_markup(Sendero is invisible to the buyer; agency keepsmarkup - take) oradd_to_customer(clean agency margin; customer payscost + markup + take). - GMV reporting. Per-tenant rollup of vendor / agency / Sendero legs from
Settlementrows, indexed offBookingSettledV2. - Historical-median recommendations. Once you have 100+ confirmed bookings of a given kind,
get_tenant_pricing_policysurfaces arecommendationfield with the median markup observed across your bookings. Refreshed weekly by/api/cron/refresh-markup-medians. - Plan-tier discounts. Take rate scales with plan: Free 50bps / Basic 47.5bps / Pro 45.0bps / Enterprise 42.5bps. The take floor scales with the same formula: $0.500 / $0.475 / $0.450 / $0.425.
What's new for agents
confirm_bookingextended. Now computes the full markup breakdown (cost + markup + take), enforces the policy ceiling, runs the override gate, persists the breakdown back onto theBooking, and encodes thecommitBookingV2userOp. The operator MSCA submits it; the tool only encodes.get_tenant_pricing_policy(new). Read-only twin ofGET /api/tenant/pricing-policy. Returns status (active/inactive/partial/sandbox_seed/not_initialized), missing kinds, floor / ceiling, and the optional historical-median recommendation. Tenant resolved from the API key — the LLM never passes a tenantId.activate_tenant_pricing_policy(new). Admin-only. Flips a tenant from "no policy" → "active production policy" entirely in-thread. Rejects sandbox keys unconditionally even when carrying'*'. Runs the treasury preflight before activation.- Standard error envelope. Every JSON route returns
{ code, message, details, docsUrl, agentInstruction, traceId }. TheagentInstructionfield carries canonical recovery copy that an LLM should surface to the human verbatim. See api-conventions/errors. - New error codes.
POLICY_INACTIVE,POLICY_PARTIAL_FOR_KIND,TREASURY_NOT_PROVISIONED,MARKUP_OVER_CEILING,MARKUP_UNDER_FLOOR,MARKUP_UNDER_TAKE_FLOOR,MARKUP_AMBIGUOUS_INPUT,OVERRIDE_REQUIRES_SCOPE,OVERRIDE_UNNECESSARY,MARKUP_CONFIG_INVALID. Full catalog at pricing/markup-error-codes. - Override scope.
tenant:pricing:overrideis the privileged escape hatch for confirms above the tenant ceiling. Never granted to sandbox keys. Signing is required (it lives inPRIVILEGED_TOOLS). See pricing/markup-scopes.
What's new on chain
SenderoGuestEscrowv3.0.0. NewcommitBookingV2(bookingId, vendorAmount, feeAmount, agencyAmount, vendor, agency, itineraryHash, itineraryCID)enforces the three-way split atomically. A revert anywhere reverts the whole transaction.BookingSettledV2(bookingId, vendor, agency, fee, vendorAmount, agencyAmount, feeAmount)event. The off-chain indexer (persistSettlementFromV2Event) writes threeSettlementLegrows from a single event — one per recipient. Existing v1BookingSettledevents still index to a singleSettlementrow.- OTP brute-force protection (same contract). Three failed claim attempts in the same trip trigger a 15-minute lockout. Wrong-code attempts emit
ClaimAttemptFailed(tripId, attemptCount)instead of reverting; lockout state is exposed viaClaimLockoutTriggered(tripId, lockedUntil). Code rotation viasetClaimCodeHashresets the failed-attempt counter (but does NOT clear the lockout). Full design: security/claim-code.
Migration notes
- Existing bookings. Any booking confirmed before this release is flagged
markupSource: 'pre_v1_no_markup_recorded'onBooking.metadata. They settle through the v1 single-leg path; no re-pricing happens. - GMV reporting starts from launch. The rollup denominator is
Settlementrows with non-nullagencyAmount, which only exist for v2-confirmed bookings. Pre-v1 bookings are excluded from agency-leg totals. - Sandbox keys. No behavior change — sandbox keys continue to mint
MeterEvent.status = 'sandbox'rows that never settle real USDC. The seededTenantPricingPolicyrow (withsandboxOnly = true) lets the smoke test fire on a brand-new org without any human config. - Tenants with no activated policy. Confirms throw
POLICY_INACTIVEwith the canonicalagentInstructionpointing to/dashboard/settings/pricing. Activation is a one-screen wizard.
OpenAPI + DX
- OpenAPI spec bumped to
1.1.0. Source:packages/tools/src/openapi.ts. The new spec addsconfirm_booking(extended),get_tenant_pricing_policy, andactivate_tenant_pricing_policyto the catalog. - v1.0.0 snapshot pinned. Old spec preserved at
/openapi/v1.0.0.jsonfor clients that need the previous shape during a phased rollout. Live spec stays at/api/openapi.json. - Per-page
.md. Every new docs page is automatically available as plain markdown — append.mdto any URL. Mirrors Sherpa's "Trips LLM Friendly" pattern. llms.txtupdated. The two new tools (get_tenant_pricing_policy,activate_tenant_pricing_policy) are listed inAGENT_TOOL_CATALOGwith deep-links to the activation flow.