Served provider
The difference between endpointId and servedEndpointId/servedProviderId, route.servedBy, and what a runs_list row shows after a fallback run.
endpointId and providerId on a run always name the endpoint you asked for (or, on a job run,
the one looot picked) and its provider. They never change to reflect who actually answered.
Who answered
Two more fields name whoever’s answer is in result: servedEndpointId and servedProviderId.
On a run with no route, they equal endpointId and providerId once the run completes. On a
fallback run, they name the route’s hit, or, if nothing hit, the last attempt that missed. A route
never falls back to the endpoint you asked for; the served endpoint is always one that was
actually attempted.
Both are absent when no provider answered: a failed, blocked, stopped, or still-running run, or a
route that called nobody at all (route_capped).
route.servedBy inside the fallback block carries the same endpoint id.
In practice
A job:people.email.find run with fallback might pick tomba-email-finder first
(endpointId), miss, and have hunter-email-finder answer:
{
"endpointId": "tomba-email-finder",
"providerId": "tomba",
"servedEndpointId": "hunter-email-finder",
"servedProviderId": "hunter",
"route": { "servedBy": "hunter-email-finder" }
}
runs_list rows
Each runs_list row (REST GET /v1/runs, MCP runs_list) is a bounded summary that carries
servedEndpointId and servedProviderId too, so you can tell who answered without pulling the
full run. looot runs list shows this in its human table as <endpoint> -> <served provider> in
the ENDPOINT column; a run its own endpoint answered shows just the one name.
Rows carry no route block; the served fields are all a row adds over the requested endpoint. For
the full attempt-by-attempt trail, read runs_evidence. See Receipts.