Overruling an automated provider choice is a routine operation, not an incident report. A rule picks one provider out of a candidate set, and the person who owns the budget sometimes knows something the rule was never told. That is expected. What matters is the cost of disagreeing.
If the only way to change the outcome is to reopen discovery, rebuild the comparison, and hand-edit a query, then every override is a small migration. Overrides that expensive do not happen. The route stands because arguing with it is more work than living with it, which is the worst possible reason for a provider to keep your traffic.
A disagreement is usually a missing requirement
Start by assuming the rule did what it was told. Most objections to an automated route are not objections to the logic. They are objections to the inputs.
The requirement parameters that travel down the chain are few and explicit: `max_price`, `max_latency_ms`, `min_reliability`, `settlement=apiosk|direct`, `require_all_inputs`, and `optimize_for=price|latency|reliability|balanced`. If none of those encode the thing you actually care about, the rule could not have honoured it. A route that optimises for price will keep picking the cheapest eligible provider no matter how strongly you feel about the one you would have picked.
So the first question is not "was this wrong" but "was this ever asked for". The answer changes what you do next, and it is the difference between a one-line fix and a recurring argument with your own infrastructure.
Read the rejections before you reach for an alternative
A decision response separates two lists that look similar and are not interchangeable. `GET /v1/decide?candidates={id},{id}` returns the selected provider, the rule that selected it, every rejected candidate with the exact constraint that removed it, and the runners-up in order.
The rejected candidates failed something hard. The runners-up passed everything and simply ranked lower. That distinction is the whole basis of a safe override:
- A runner-up already satisfies every constraint in the query, including price ceiling, latency budget, reliability floor and settlement rail.
- A rejected candidate carries the named constraint it failed, so you can see whether your objection is really a request to relax that constraint.
- The 0–100 scores are relative to the candidates in that one comparison, and each response carries the weights and per-dimension contributions that produced them.
- Dimensions Apiosk has not measured for a candidate are dropped from the weighting and named, never scored as zero, so a low rank is never a silent penalty for missing data.
- External x402 results come back in a separate block with no candidate ID and cannot be promoted through the chain at all.
Reading the rejection list first usually resolves the argument without an override. Often the provider you wanted is sitting there with `max_price` written next to it, and the real decision in front of you is whether to raise the ceiling.
Three kinds of disagreement, three different fixes
Sort the objection before you act on it. There are only three shapes, and they take different repairs.
The first is a bad input. You asked for the cheapest eligible provider and you meant the fastest. The fix is the query, not the outcome: change `optimize_for`, or tighten `max_latency_ms`, and the rule produces the answer you wanted on its own. Nothing is overridden, because nothing was wrong.
The second is local knowledge. The rule is right in general and wrong for this call — a customer with a contractual provider, an account mid-migration, an incident you have been told about but the measurements have not caught up to yet. This is what the runners-up exist for. Promote an eligible alternative, record why, and leave the policy alone.
The third is a genuine policy error, where the rule would produce the same wrong answer for anyone. That is not an override at all. That is a change to how a provider gets chosen in the first place, and fixing it in one call by hand hides the bug from everyone else running the same route.
The escape hatch has to be one field wide
An override is only usable if it is cheap. The design target is that an operator changes the outcome by naming a runner-up that the response already contains, and nothing else moves — same requirements, same candidate set, same comparison.
That is why the ordered runners-up matter more than they look. They are the pre-computed answer to "what else could I have had", produced under the constraints that were actually in force. Without them, disagreeing means starting over at `GET /v1/discover?q=…`, and the operator has to trust that they reconstructed the same query. With them, the override is a selection from a list.
The corresponding record belongs in `POST /v1/decisions`, which is free and unauthenticated and stores which provider was chosen and which alternatives were rejected. Record the override as its own event rather than overwriting the original. A decision plus a documented disagreement is evidence. A quietly rewritten decision is just a different answer with no history, and it makes a readable decision record worth less than the storage it sits in.
What an override cannot repair
Some objections are legitimate and still not fixable by promoting a runner-up, because the thing you are reacting to was never in the comparison.
Apiosk measures price, settlement rail and input compatibility, plus latency and success rate for listings it has proxied enough calls of. It does not measure result quality, and it does not measure provider terms. If you are overriding because one provider's output reads better for your use case, or because its terms of service suit your customer, you are supplying information the score does not contain and cannot contain.
That is fine, and it is worth writing down as the reason. It is also a warning sign if it happens constantly: a dimension that decides most of your routes and lives entirely in operators' heads is a dimension your automation is guessing at.
Turn a repeated override into a stated constraint
Track how often routes are overruled and toward what. The rate is a diagnostic, not a scoreboard.
- The same provider promoted repeatedly usually means a requirement exists that nobody encoded — start with `min_reliability` and `settlement`.
- Overrides clustered on one job type mean the constraint belongs on that job, not on the global policy.
- Overrides that follow a price change mean `max_price` is now doing work it was not meant to do.
- A rate that falls to zero is not necessarily success; it can mean operators stopped bothering.
Every override you convert into a constraint is one the rule handles next time without a human. That is the point. The escape hatch is there so that disagreement is survivable, not so that it becomes the process.
Where Apiosk fits
Apiosk publishes the decision in a form that makes overriding it cheap. `/v1/decide` returns the winner, the rule that picked it, each rejected candidate with the constraint that removed it, and the runners-up in order, so an operator can promote an eligible alternative without reconstructing the query. `POST /v1/decisions` records what was chosen and what was passed over, including when the choice came from a person.
What Apiosk does not do is pretend the score covers everything. Result quality and provider terms are not measured, unmeasured dimensions are named rather than zeroed, and external x402 discoveries stay outside the comparable set. Those gaps are exactly where human overrides belong. If you want to see the requirement parameters and the payment terms the gateway currently advertises before wiring any of this in, the copy-paste integration examples are the shortest path from reading about the chain to calling it.
Frequently asked questions
When should an operator override an automated provider choice instead of changing the query?
Override when the objection applies to this one call and the rule itself is still correct: a support escalation, a customer-specific constraint, a provider you are mid-migration away from. Change the requirement when the same objection would apply to the next hundred calls. A one-off override that repeats is a requirement you never stated.
Are the runners-up in a decision response safe to promote?
Yes, in the sense that they already passed every hard constraint in that query. They were ranked below the winner, not removed. The rejected candidates are a separate list and are not safe to promote, because each one failed a specific constraint that is named alongside it.
What should an override record contain?
The original decision, the provider that was promoted, and the reason in the operator's words. Keeping the original result intact is what makes the override auditable later. If you overwrite the decision instead of recording a second event, you lose the evidence that the policy and the human disagreed.
Can an operator override toward an external x402 result?
Not through the decision chain. Apiosk returns external x402 discoveries in a separate block with no candidate ID, and they are unreviewed, unmeasured, and not settleable through the gateway. They cannot be compared or decided against, so promoting one is a manual integration rather than an override.