Constraint-based API filtering turns an agent's buying requirements into one contract that every selection step can apply. The contract must survive discovery, comparison, and the final decision without a price ceiling becoming a preference or an input requirement disappearing on the way.
The common alternative is to rebuild the filter at each step. Discovery uses a task description. Comparison adds a budget. Selection introduces a latency preference. Each operation looks reasonable on its own, but the winner no longer answers the question the agent originally asked. A provider can be admitted, ranked, or rejected under three different versions of the requirement set.
Separate eligibility from preference
Current routing language often describes the sound sequence as hard constraints first, scoring second. That distinction matters because the two operations answer different questions.
A constraint decides whether a provider may serve the request. A price above `max_price`, latency above `max_latency_ms`, reliability below `min_reliability`, or incompatible input is a reason to remove the candidate. No strength on another dimension repairs that failure. A cheap endpoint does not become eligible for an interactive workflow when it breaches the required latency ceiling.
An optimization preference orders the candidates that remain. `optimize_for=price` favors the cheapest eligible offer. The alternatives are latency, reliability, or a balanced rule. This is not another eligibility test. It decides which valid option wins.
Keeping those layers separate makes the result legible. The rejection says which condition failed. The ranking says why one survivor placed ahead of another. For a closer view of the second layer, see how to optimize for price, latency, or reliability without turning the preference into a hidden threshold.
Write one requirement contract
The agent should assemble the contract before it asks for providers. In Apiosk, the shared parameters are deliberately small:
- `max_price` sets the maximum payment amount the request may accept.
- `max_latency_ms` sets the measured latency ceiling for the workload.
- `min_reliability` sets the measured success-rate floor.
- `settlement=apiosk|direct` states the required settlement path.
- `require_all_inputs` decides whether every supplied input must be supported.
- `optimize_for=price|latency|reliability|balanced` orders eligible candidates.
These values describe the request, not the provider. They belong to the buying job and should be passed once, then reused. That is the central mechanism of constraint-based API filtering: downstream steps consume the contract instead of interpreting the original instruction again.
The separation also makes revisions controlled. If the candidate set is empty, the agent can relax one named requirement and create a new contract. It might raise the price ceiling or accept a slower response. It should not quietly weaken both and present the outcome as if it satisfied the first request.
Preserve the contract across the chain
Discovery, comparison, and decision operate on different data, so requirement drift can be easy to miss.
Discovery resolves a job and returns candidates. Input compatibility and settlement requirements can already remove ineligible offers. Capability-based API discovery explains why the job and input boundary must be settled before price or performance can mean much.
Comparison takes stable candidate identifiers and places eligible offers side by side. It should receive the same ceilings, floors, and input rule. Adding a new candidate does not justify dropping the original contract; it only changes the set against which the measurements are evaluated.
Decision applies the same filters and then ranks what remains. The response should name the rule, the selected provider, every rejected candidate with the constraint that removed it, and the ordered runners-up. This narrows the focus of how AI agents choose an API provider: the decision is reproducible only when all earlier steps used the same inputs.
A useful implementation check is to log a requirement-set identifier beside each operation. The exact storage design is yours, but the values should be inspectable. If discovery and decision show different contracts, the system should treat that as a changed request rather than one continuous selection.
Handle unknown measurements explicitly
Missing data is where many filters become dishonest. Treating an unknown latency as zero lets an unmeasured candidate pass the strictest ceiling. Treating it as infinite removes the candidate while pretending a failure was measured. Neither convention states what is actually known.
Apiosk's relative 0–100 comparison score drops dimensions that have not been measured for a candidate and names the omission. The response also returns the weights and each candidate's contribution. An unknown dimension is not scored zero. The score is relative to the candidates in that one comparison, not a permanent provider grade.
That scoring behavior does not turn missing evidence into compliance with a hard floor. If a workload requires a measured success rate, the filter needs evidence for that condition. The rejection or omission record should make the boundary visible so an operator can decide whether to relax the requirement.
Price, settlement rail, and input compatibility can be checked from the offer and request. Latency and success rate are available only after Apiosk has proxied enough calls for a listing. Result quality and provider terms are not measured anywhere, so they should not appear as numeric constraints or contributions. A buyer that needs either must evaluate it outside this score.
Keep external results outside the filtered set
Broader discovery can find endpoints that are not ready for the same decision chain. `GET /v1/discover?q=…` returns external x402 hits in a separate `external_candidates` block. They have no `candidate_id`, are unreviewed and unmeasured, and cannot enter `/v1/compare` or `/v1/decide`.
This is not a low score or a failed constraint. It is a different evidence class. Mixing those hits into the reviewed set would imply that their compatibility, measurements, and settlement path had been established when they had not. The reviewed and unreviewed listing boundary preserves reach without presenting unknown offers as equivalent candidates.
Make rejection records operational
A useful rejection record names the candidate, the failed field, the required value, and the observed value when one exists. “Not suitable” is not operational. “Removed by `max_latency_ms`” can be investigated, reproduced, and challenged.
The ordered runners-up matter for the same reason. If the chosen provider becomes unavailable, an operator can inspect the next eligible option without repeating discovery under a possibly changed contract. If the system runs selection again, it should retain the old decision record rather than overwrite history.
Providers benefit from this precision. A rejection tied to input compatibility points to schema work. A latency rejection points to observed delivery performance. A price rejection points to the payment terms the gateway returned. The provider may not agree with the buyer's limit, but it can see the rule it failed.
Where Apiosk fits
Apiosk carries the same shared requirement parameters through `GET /v1/discover`, `GET /v1/compare`, and `GET /v1/decide`. Over MCP, the equivalent tools are `apiosk_discover`, `apiosk_compare`, and `apiosk_decide`. The decision returns the chosen provider, its selection rule, exact rejection constraints, and ordered runners-up. `POST /v1/decisions` records the choice and rejected alternatives without authentication or a fee.
For an API provider, this makes eligibility concrete. Publish an accurate input contract, keep the payment terms current, and allow measured calls to establish latency and success rate. You can list your API when those fields are ready. Apiosk does not decide whether your result quality or terms suit the buyer; it filters and ranks only the requirements and measurements it can expose.
Frequently asked questions
What is constraint-based API filtering?
Constraint-based API filtering removes providers that fail explicit requirements before ranking the eligible providers. The same requirements should apply throughout discovery, comparison, and selection so the result answers one consistent buying question.
What is the difference between a constraint and an optimization preference?
A constraint is a pass-or-fail condition such as a maximum price or required input. An optimization preference orders the candidates that passed, for example by price, latency, reliability, or a balanced rule.
How should an agent handle missing provider measurements?
The handling rule must be explicit and must match the requirement being enforced. In Apiosk scoring, an unmeasured dimension is dropped and named rather than scored zero, but a candidate cannot be claimed to satisfy a measurement floor that has not been established.
Why reuse the same constraints across API discovery and selection?
Reuse prevents requirement drift between steps. Without it, discovery can admit providers under one contract while comparison or selection silently applies another, leaving a winner that cannot be reproduced from the original request.