Routing

Every run answers three questions before it starts.

How much of your data leaves, which model catalogue is eligible, and which jurisdiction the work may stand in. They are three separate axes, they are resolved in one place each, and the answer is on the response — not in a support ticket.

tenants.phi_mode defaults to OFF since 2026-09-01 · standard is what an unconfigured workspace gets

Three axes

Orthogonal, not a single dial.

Conflating these is the classic mistake: “full-data” does not mean “PHI”, and “PHI” does not mean “EU”. Each axis has its own resolver, its own default, and its own failure direction.

Axis 1

Mode — how much data leaves

Schema-only sends column headers and at most three sample values per column, each truncated to 80 characters. Full-data lets the AI layer see rows, and is gated on an active subscription.

Chokepoint · clampForSchemaOnly() in lib/parser.ts, applied at the HTTP edge in every /api/v1 route that accepts sample rows.

Axis 2

Routing — which catalogue is eligible

Standard runs on our own pinned general models. PHI · enterprise sends X-PHI to phi-cloud, which then forces a PHI-eligible model. This axis is orthogonal to the mode above — one does not imply the other.

Resolver · resolveRunPhi() in lib/agreements.ts, threaded as phiOverride into match(), runTransform() and runReshapeRequest().

Axis 3

Region — where compute may stand

The workspace region pin travels on standard runs too. PHI decides which models are eligible; the region decides which ground they are allowed to run on, and the program sandbox refuses a run with no placeable jurisdiction.

Rule · Zeroing the region on a standard run made every standard code-strategy reshape fail. It is pinned on both modes now — see /network.

  • Standard is the default
  • PHI locked behind the BAA
  • Pinned ids, never a silent auto-route
  • +20% follows the actual routing
  • Host allowlist fails closed

The decision

One resolver, and it defaults to the cheaper answer.

Until 2026-09-01 an unconfigured workspace defaulted to PHI. That was reversed: standard is now what you get, and PHI is an explicit opt-in alongside accepting the BAA. Every fail-soft path resolves to standard too — an unreadable settings row must never silently upgrade unknown traffic into a surcharge and an agreement gate.

One resolver

resolveRunPhi(tenantId, requested) → lib/agreements.ts

  1. 1phi_mode in the request body — the Workbench’s Standard / PHI · enterprise switch, or your own API call.
  2. 2Otherwise tenants.phi_mode, the workspace default.
  3. 3Otherwise standard — and that is also where an unreadable settings row lands.
  4. 4An explicit PHI ask with no BAA acceptance on file is 403 agreement_required, with a pointer to the settings page. Never a silent downgrade.

Standard

the default
Headers
X-Region only. No X-PHI is sent.
Model
A pinned id from our own three-rung ladder, resolved by modelFor().
Region
US for the ladder call; the workspace pin still governs sandbox placement.
Price
No uplift.

PHI · enterprise

explicit opt-in
Headers
X-PHI: true plus X-Region set to your workspace pin.
Model
Named by phi-cloud from the residency-locked PHI catalogue. We name nothing.
Region
Your pin. The host must also be on the BAA allowlist or the call is refused.
Price
+20% on the whole charge.

What goes on the wire

Two header sets, one endpoint.

Both branches call the same phi-cloud endpoint. The difference is two headers and whether we name a model at all. A standard call carries a concrete pinned id and the region that serves it; a PHI call carries the data class and the jurisdiction and lets the gateway that holds the BAA make the pick.

The allowlist sits underneath both: MAPR_PHI_ALLOWED_HOSTS names the hostnames permitted to receive full-data traffic, and in production an unset allowlist refuses rather than permits. See the fail-closed gates.

on the wire
# Standard routing — the default for an unconfigured workspace.
POST /v1/chat/completions            → phi-cloud
X-Region: US                          # the region that SERVES the pinned id
model:    inception-mercury-2         # low rung, resolved by modelFor()
                                      # no X-PHI header at all

# PHI · enterprise routing — only after the BAA is accepted in-app.
POST /v1/chat/completions            → phi-cloud
X-PHI:    true                        # forces the PHI-eligible catalogue
X-Region: CH                          # the workspace pin
model:    auto                        # phi-cloud picks, in-region
→ a general call must ship a region, or phi-cloud reads it as WORLD and auto-routes silently

The general ladder

Three rungs for traffic that is not regulated.

Non-PHI work runs on models we pin by id. The rungs are a capability and latency ladder, not a price ladder — and the ids are pinned precisely so nobody, including us, can quietly re-route them.

RungModelWhen it runsOverride
lowinception-mercury-2The default. Most turns, and every call that names no tier.MAPR_GENERAL_MODEL_LOW
midzai-glm-5-3-flashOne real piece of work.MAPR_GENERAL_MODEL_MID
highzai-glm-5-3The rung a tier:"quality" codegen or critic turn is promoted to.MAPR_GENERAL_MODEL_HIGH

Every general call also ships X-Region: US, because those model rows serve US and a region-less call matches nothing. Set MAPR_GENERAL_MODEL_REGION to change it, or MAPR_GENERAL_LADDER=off to fall back to a single configured model everywhere.

The ids are pinned on purpose

phi-cloud’s own router is price-ordered, and the low-rung model is not anyone’s default. Leaving the choice to the gateway would mean a general call quietly landing on the mid rung and the low rung never running at all.

Consequence · A pinned id must ship the region that serves it. Without X-Region the request reads as WORLD, matches no model, and auto-routes — a 200 from a model nobody chose.

The ladder is not price-ordered

The low rung is a latency pick and costs more per token than the middle rung. Read the rungs as capability and latency, never as “low is the cheap one” — that misreading is how a cost model ends up backwards.

Where · lib/models.ts. Override per rung with MAPR_GENERAL_MODEL_LOW / _MID / _HIGH, or turn the ladder off entirely with MAPR_GENERAL_LADDER=off.

PHI is deliberately not on it

A PHI call names no model. It sends X-PHI and X-Region and takes phi-cloud’s residency-locked pick, because model ids for regulated traffic belong on the gateway that enforces the BAA — not in an application repository.

Where · modelFor() is the one resolver both policies go through, so a call site cannot let them drift apart.

Heavy turns are promoted

A codegen or critic turn asks for the quality tier, and that request is what promotes it to the top rung. The tier the call asked for and the model it lands on stay one decision rather than two that can disagree.

Where · rungForTier() maps the tier hint to a rung; MAPR_CODEGEN_TIER controls it, and “none” disables the promotion.

What routing costs

The uplift follows the routing a run actually got.

Not the routing it asked for, and not the policy the workspace has set. If a run was downgraded to standard for any reason, it is billed as standard and flagged as downgraded — that is a compliance rule wearing a pricing rule's clothes.

What is chargedRateNotes
Every map, deterministic or notFlat per-map fee (~$0.001)Charged even when the cascade resolves every column without touching a model, and on a layout-fingerprint cache hit that uses no AI at all.
AI cleanup, convert, reshapeProvider cost × 2The AI tokens actually consumed. × 0.5 when you bring your own LLM key.
Excel macro executionPer second, five-second floorOnly when you ask for a procedure to be run in real Excel. Authoring, linting and the read-back check are not metered per second.
PHI · enterprise routing+20% on the whole chargeFlat fee included. Applied only when the run actually routed as PHI — see the rule below.

Figures are the defaults in lib/pricing.ts, the single source of truth, and every one of them is environment-tunable. There is no free tier: the wallet is prepaid, from a $10 minimum, and shared across the phi-cloud suite.

the downgrade rule
# The kill switch that makes this rule necessary.
MAPR_RESHAPE_DISABLE_PHI=1

# It strips X-PHI from the sandbox execution call. The run therefore
# did NOT go through the PHI path, so effectiveRunPhi() returns false:

  charge         standard rate, no 20% uplift
  audit payload  phi_downgraded: true
  response       a user-visible warning saying so

# Charging a BAA premium for a run that bypassed the BAA gate is a
# claim we cannot make — /pricing sells the uplift as "routed
# in-region under a BAA", and this run was not.

Why this matters

A premium you cannot justify is a claim you should not make.

The 20% is sold as “routed in-region under a BAA”. If an operator kill-switch strips the PHI header from a sandbox execution, that particular sentence stops being true for that particular run — so the run is charged at the standard rate, phi_downgraded lands in the audit payload, and you see a warning in the result.

It would have been easier to bill the requested policy. It would also have been an invoice line describing something that did not happen. See the full pricing model and the BAA gate.

Routing, per run

Standard by default. PHI when you say so.

Flip the switch per run in the Workbench, or send phi_mode in the request body. Either way the gate reads your acceptance before anything moves.

No free tier · prepaid wallet from $10 · PHI routing +20%, locked until the BAA is accepted in-app