Routing you can read back: tool-aware lanes, rejection receipts, and privacy proofs

Every batch you submit to BatchRouter gets quoted across every eligible provider lane. The router applies hard gates — task support, model, capacity, rate limits, privacy, region, price ceiling, trust tier — then ranks what's left and routes to the cheapest lane that can finish in your window. That's the product. The thing we've spent the last week shipping is the part around the decision: making it legible. Three upgrades landed, and they all push in the same direction — you should be able to read back exactly why a lane won, and why every other lane didn't. All three changes are in production across the routing engine and the marketplace UI. No flag to flip — your next quote already reflects them. Tool-aware routing A growing share of batch work isn't just "run this prompt on this model." It needs hosted tools: web search, Python execution, calculator, time, file search, retrieval. Until now BatchRouter treated those as your problem to verify. Now they're a first-class routing input. Provider offerings carry explicit hosted_tools metadata, kept separate from general runtime capabilities, and the marketplace, model detail pages, calculator, and quote receipts all read from those offering snapshots instead of static, hand-maintained claims. On a quote you declare what the job needs with required_tools, and routing rejects any lane that can't satisfy every required tool before price even enters the picture. Quote-level tool requirements apply to every planned item or workflow input; item-level values merge with the quote-level list; and supported OpenAI-style tool declarations can be inferred straight from item payloads, so you don't have to repeat yourself. The decision comes back on the route receipt as tool_support, with the required and offered tool lists side by side — so you can see not just that a lane qualified, but on which tools. A receipt for every rejected lane The cheapest lane that didn't win is often the more interesting one. Previously, a non-selected lane just disappeared from the result. Now BatchRouter persists a rejection receipt for every lane it considered and skipped, stored alongside the lane allocations and reservations for the quote. Each receipt normalizes the underlying failure into a customer-safe reason code, while retaining the raw router code and the specific eligibility checks that failed underneath. The codes are deliberately readable: capacity_full — the lane was eligible but had no fresh queue or token capacity for your job size missing_web_search — the lane couldn't satisfy a required tool context_window_exceeded — your estimated context didn't fit the lane's model privacy_tier_mismatch — the lane didn't meet your data-handling requirement region_unavailable — the lane couldn't serve your required region or residency No more guessing why a job didn't route to the lane you expected. If a cheaper provider was on the table and got passed over, the receipt tells you the one thing you actually wanted to know: why. Per-lane privacy proofs Routing decisions and privacy posture used to live in different places — you'd pick a lane on price and then go read a provider's terms separately. We've collapsed that. Every quote response, billing receipt, usage receipt, and stored route receipt artifact now carries a data/privacy proof per lane — for the lane that was selected and for the ones that were rejected. Each proof spells out the provider's data collection, ZDR support, retention in days, serving regions, training-use posture, privacy terms, and how BatchRouter handles the resulting artifacts. To keep that honest at the supply side, public offering lanes have to declare retention, ZDR, artifact handling, and regions up front; lanes missing those fields are hidden or rejected unless they're explicitly marked unverified — in which case they stay routable but carry a verification penalty and receipt-visible reasons. If a lane was skipped for privacy_tier_mismatch, the proof is right there next to the r…