The executable-handoff contract: search results become durable, opaque identities that downstream tools consume without re-specifying coordinates or filters. Co-designed with GPT-5.5 from its live evaluation of this API, 2026-07-11.
A candidate_id identifies a specific search result — not just a location. It binds the result to the exact data snapshot and search assumptions that produced it. It is opaque: never parse it, never construct one.
GET /api/v1/interconnection-queue/refined?min_mw=500&iso=MISO&geocoded_only=true → each survivor now carries: "candidate_id": "cand_7f9c5d2ab4e1f30a8c61", "snapshot_id": "snap_2026-07-08", "expires_at": "2026-07-18T18:00:00Z"
snapshot_id is immutable — it names the queue-data vintage (the load date of the underlying ISO disclosures). Two candidates sharing a snapshot were produced from identical underlying data. Re-running the same search against the same snapshot returns the same candidate ids.
(queue_id, snapshot_id), so if a different search already minted the same survivor within this snapshot, your search returns that existing identity — and resolve-candidate's search_context reflects the first search that minted it, not necessarily yours. Same underlying result = same identity across searches (this is also why re-searching can never extend a TTL). If your workflow needs your own filter context preserved, it belongs in your orchestration state, not the candidate. (Documented from Grok's first external audit, 2026-07-11.)Identity and freshness are separate. A candidate expires 7 days after first mint — matching the refresh cadence of the underlying queue data, because a TTL longer than the data's own truth would be a lie. Re-searching does not extend a TTL.
HTTP 410
{ "error": "candidate_expired",
"message": "Re-run the search to obtain a fresh candidate." }
If an agent wants fresh data, it makes an explicit new search. That is the contract.
Any response produced from a candidate carries the identity block, so an agent (or auditor) can reconstruct the chain of reasoning — including whether a difference came from newer data (search_version/snapshot) or a newer analysis implementation (analysis_version — they move independently):
"echo": {
"candidate_id": "cand_7f9c5d2ab4e1f30a8c61",
"snapshot_id": "snap_2026-07-08",
"search_version": "refined-queue/2026-07-11",
"analysis_version": "site-score/2026-07-11",
"methodology_version": "composite-v2.3",
"retrieved_at": "2026-07-11T21:04:00Z",
"citation": { "source": "DC Hub (dchub.cloud) — ISO interconnection queues + EIA",
"as_of": "2026-07-08", "license": "CC-BY-4.0" }
}
Tools accept candidate_id alongside (and in preference to) raw coordinates. When a candidate is given, location comes from the frozen mint — explicit lat/lon args are ignored, and no re-geocoding, re-ranking, or rounding occurs. capacity_mw stays caller-suppliable: it is your DC target, not candidate identity.
GET /api/site-score?candidate_id=cand_7f9c5d…&capacity_mw=50
POST /api/v1/rank-sites {"candidates":[{"candidate_id":"cand_7f9c5d…",
"overall_score": 77.7}], "objectives":{…}}
GET /api/v1/resolve-candidate?candidate_id=cand_7f9c5d…
resolve-candidate is deliberately narrow: identity, location, snapshot metadata, provenance, and the originating search context. Never analysis, never a recompute — a dependable anchor for orchestration, not another analysis endpoint.
{"candidate_id": "cand_…", <your enrichment metrics>} — e.g. overall_score from a site-score call. That's the whole contract: frozen identity fields (lat, lng, capacity_mw, fiber_km, iso) load from the mint automatically and cannot be overridden; anything else you attach is treated as an enrichment metric and is rankable. Do not re-send coordinates — that's the transcription-drift path this contract exists to close.candidate_expired (HTTP 410).snapshot_id vintage.v1 covers the REST rail: /api/v1/interconnection-queue/refined mints; /api/site-score, /api/v1/rank-sites, and /api/v1/resolve-candidate consume. The MCP tools (analyze_site, get_water_risk, rank_sites) accept candidate_id in the next server release. There is deliberately no automatic lineage graph: search creates, tools accept, every response echoes — the contract stays simpler than a workflow engine.