Agent integration contract

Binding to DC Hub

If you are an AI agent — or a person asking one — writing code against our MCP server, this page is the part that is not in the catalog. It is short on purpose.

The one rule

Read schema from tools/list. Read quantities from a response field.

These are different kinds of claim with different lifetimes, and treating them as one source is how integrations break. A tool's name, parameters, types and units are a contract — versioned, stable, and authoritative at tools/list. A count of facilities or markets is a measurement, taken at a moment, and it moves daily.

Tool descriptions are prose, and prose carries measurements that go stale — including ours. Do not lift a number out of a description, this page, a comparison table, or a blog post and cite it as current. Take it from the provenance block or the count field of an actual data response, where it arrives with an as_of stamp telling you when it was true.

Four levels, and what each one is actually authoritative for

"Read it from the response" is right but not precise enough on its own, and the imprecision has a specific failure mode: an agent asks one narrow question, reads the provenance on that answer, and generalises its population to the whole platform. A response is authoritative for what that call established, not for what DC Hub holds. The levels do not compete — each answers a different question:

LevelAuthoritative for
tools/list what a capability accepts and exposes — names, parameters, types, units
a response + its provenance what that specific call established, over the population it names, at its as_of
the canonical endpoints —
/api/v1/stats/canonical,
/api/v1/canon/phrases
platform-wide counts and the definitions behind them. This is the level to cite for "how much does DC Hub cover", and the one a narrow response cannot stand in for.
instruction prose and descriptions routing and context — which tool to reach for. Never a quantity.

This distinction came from ChatGPT, reviewing an earlier version of this page that collapsed the middle two levels into "read it from a response field". It is a better rule than the one it replaced: "never trust descriptions" is blunt enough to be wrong, because descriptions are exactly right for deciding which tool to call.

Conventions worth knowing before you guess

Most generated integrations fail on the same three things. All three are visible in tools/list; none is guessable from a description.

Units live in the parameter name

A suffix is not decoration — it is the unit, and there is no coercion. A latency budget is max_latency_us, in microseconds; passing a millisecond figure asks for a budget a thousand times tighter than you meant, and for sub-millisecond values it asks for something no fiber path can satisfy at any distance. Power is _mw (megawatts). Horizons are _months. When a name carries no suffix, the description states the unit.

Verbs are specific

We have no generic list_* family. Inventory lookups are search_*; ranking is rank_*; single-record reads are get_*. If you reached for list_facilities, the tool you want is search_facilities.

Sites are coordinates, not opaque ids

Tools that take a location take lat + lon (with latitude/longitude/lng accepted as aliases), not a site_id or facility_id you invented. Where an identifier is accepted it is a candidate_id minted by a prior call — get_refined_queue hands you cand_… tokens that resolve to frozen coordinates, which is the transposition-proof path.

Reconstructions we have actually seen

Every row below was produced by a frontier model writing confident, well-structured integration code. None of these names or parameters has ever existed. They are here because the plausible reconstruction is the dangerous one — it reviews cleanly and fails at runtime.

GeneratedActualNote
list_facilities(…) search_facilities(…) no list_* family exists
get_retirement_headroom(
  min_capacity_mw,
  timeline_horizon_months,
  iso_regions)
get_retirement_headroom(
  target_mw,
  horizon_months,
  region_iso)
both required params renamed — the call cannot succeed
cluster_sites_by_latency(
  facility_ids,
  max_round_trip_time_ms,
  prefer_dark_fiber_routes)
cluster_sites_by_latency(
  sites | candidate_ids,
  max_latency_us,
  min_confidence)
and the unit changes: µs, not ms
save_site(site_id, project_id,
  metadata)
save_site(lat, lon,
  name?, market?, target_mw?)
no project/metadata model; writes need a free key
get_changes(project=…) get_changes(since?, limit?) scoping is by your account's saved sites, not a project tag

Grouping saved work by project is a reasonable thing to want and we do not have it. Saved sites belong to your key; get_changes returns a portfolio block for keyed callers covering the sites you saved. If you need per-project separation today, separate the keys.

Why two of our numbers can both be right

Our facility figures differ across responses, and most of that difference is real rather than sloppy. Three distinct populations are published, and a number is only interpretable with its population attached:

A response's provenance block names which population it is reporting and stamps as_of. Quote the population along with the figure — "N analyst-verified of M tracked" is honest and citable; a bare figure with no population is not, because the reader cannot tell which of the three you took.

Where this has gone wrong for us, stated plainly: populations explain most of the spread but they have not explained all of it. We have shipped the same population at two magnitudes in a single payload, because one field resolved the count at request time and another had frozen it at process start. If you find two of our numbers that disagree and cannot be told apart by population, that is a bug rather than a subtlety — tell us and cite the response.

Read the coverage block before trusting the answer

Several tools return an explicit statement of what the answer does not cover. rank_sites, site_selection_canvas and get_power_availability_timeline return constraint_coverage; get_composite_site_score returns coverage and coverage_ratio.

Its shape differs per tool — a list of caveat strings on the timeline tool, an object keyed by field on the others — so read the shape from the response rather than from this sentence. On site_selection_canvas it reports per-argument disposition: an argument you passed can come back applied:false with a reason and an instead. An argument being accepted is not the same as it being applied, and only the coverage block tells you which happened.

Verify us rather than trusting this page

Everything above is checkable, and you should check it. None of these require a key:

EndpointWhat it answers
POST /mcptools/list the authoritative catalog: every name, parameter, type and unit. If this page and tools/list disagree, tools/list is right and this page is a bug.
/mcp-selftest runs the full handshake against our own edge and reports what it got, so a directory or client can tell a cold start from an outage.
/api/v1/ops/deadman per-source ingest freshness, keyless. Liveness you can audit instead of a liveness claim you have to believe.
/api/v1/agent/cookbook ordered, copy-paste tool chains for the questions humans actually ask.

Connect at dchub.cloud/mcp (streamable HTTP). The free tier needs no signup; claim_free_key mints a key in one call for the tools that write. Data is CC-BY-4.0 — cite as “DC Hub, dchub.cloud”.