A standardized entry point for AI agents into the CM Ecosystem — protocol version 0.1
This page documents an interface, not a product. CM (Channel Manager) instances run their own business logic for availability, pricing and reservations. The Docking Port does not duplicate that logic — it is a thin discovery and routing layer that lets any AI agent, speaking plain HTTP and JSON, find out what a CM instance can answer and how to ask.
Each CM installation is an independent node. A node may optionally participate in a decentralized network of other CM nodes ("the Ecosystem"). There is no central server that holds the whole network — each node knows only a small set of geographically relevant neighbours. A request entering through any one node's Docking Port can, within limits, be forwarded through that local network and aggregated back into a single answer.
Reception is the only endpoint an agent ever calls. It:
This node's Reception:
POST /app/public/ai/reception.php
Content-Type: application/json
status: live — local availability, plus fallback-only forwarding through trusted neighbours (geography-gated, depth-limited, loop-safe). Occupied nodes that were actually contacted appear in checked[]; they are not silent gaps. See the full spec for the test record.
Start with the machine-readable manifest:
GET /.well-known/cm-ai-docking-port.json
GET /app/public/ai/manifest.json
It describes this node's identity, location, capabilities, and the Reception endpoint. Example (this node):
{
"protocol": "CM-AI-Docking-Port",
"version": "0.1",
"service": {
"name": "CM Ecosystem Reception",
"type": "ai-entry-point",
"node_id": "CM-AI-DEMO-058",
"location": {
"label": "Lancovo / Radovljica area (Demo 58)",
"lat": 46.34,
"lon": 14.17
}
},
"reception": {
"method": "POST",
"endpoint": "/app/public/ai/reception.php",
"content_type": "application/json"
}
}
Every request sent to Reception uses this shape:
{
"protocol": "CM-AI-Docking-Port",
"version": "0.1",
"request_id": "req-2026-08-10-abc123",
"request_expiry": "2026-08-10T12:00:00Z",
"origin": "ai-agent",
"target": {
"location": { "lat": 46.05, "lon": 14.50, "label": "Ljubljana" },
"radius_km": 30
},
"query": {
"type": "availability",
"from": "2026-08-10",
"to": "2026-08-16",
"guests": 2,
"units": null
},
"limits": {
"max_depth": 2,
"max_distance_km": 40,
"max_results": 10
},
"visited": []
}
request_id — unique per request; lets a node detect duplicates.request_expiry — required on every request. Absolute UTC timestamp (RFC3339). Checked at the door of every hop: a request that has already expired on arrival is rejected immediately (request_expired), with no local lookup and no forward. A hop that is already running is never interrupted mid-flight - the check only happens on entry. Each node also clamps the value it forwards down to its own ceiling (same pattern as limits.max_depth/entry_ceiling below) - it never extends what the agent asked for.target.location — the place the agent is actually asking about. This is not necessarily where the agent knocked. An agent may enter through this node and ask about a different city; Reception routes toward the target, not toward itself. An optional label (e.g. "Kranjska Gora") may be included alongside lat/lon for a human-readable trail - Reception never invents one.visited — filled in by Reception as the request travels, never by the agent. Prevents routing loops. Send it as [].limits — the agent's own bounds on how far and how wide the search may go. The node always clamps these to its own ceilings.Reception always answers with one aggregated response, never raw internal API output. A two-hop example (entry at this node, unit found two hops away; an occupied neighbour recorded separately):
{
"protocol": "CM-AI-Docking-Port",
"version": "0.1",
"request_id": "req-2026-12-01-example",
"ok": true,
"partial": false,
"search_meta": {
"entry_node": "CM-AI-DEMO-058",
"target_location": { "lat": 46.4858, "lon": 13.7905, "label": "Kranjska Gora" },
"nodes_queried": 2,
"nodes_reached": 2,
"max_depth_used": 2,
"entry_ceiling": 2
},
"results": [
{
"node_id": "CM-AI-STUB-KRGORA",
"node_location": { "lat": 46.4858, "lon": 13.7905 },
"depth": 2,
"source": "stub_test_node",
"freshness": { "generated_at": "2026-10-02T12:00:00+00:00" },
"data": { "...": "raw output of that node's own availability API, unmodified" }
}
],
"checked": [
{
"node_id": "CM-AI-STUB-BLED",
"depth": 1,
"location": { "lat": 46.3683, "lon": 14.1146 }
}
],
"errors": []
}
| Field | Meaning |
|---|---|
search_meta.entry_node | who delivered this HTTP response to the agent |
results[].node_id | who is the source of that available unit |
results[].depth | how many hops that entry travelled |
results[].node_location | where that source node is (travels with the entry; do not look it up yourself) |
checked[] | nodes successfully contacted that confirmed zero availability — not a failure, not a skip |
checked[].location | same idea as node_location, different key (see spec §27) |
partial | true only if a contacted node failed or timed out — never implied by an empty results[] |
search_meta.entry_ceiling | the entry node's own depth ceiling (clamped agent limit vs its own hard max) — not the depth actually reached by the chain as a whole. A response can legitimately show max_depth_used: 3 alongside entry_ceiling: 2: the entry node's own ceiling only bounds how far it is willing to look before giving up, deeper nodes have their own, separate ceilings. |
data inside each result is the untouched output of that node's own business
API — Reception wraps it with provenance but never reinterprets it.
results[] contains only genuine matches (available_count > 0).
checked[] never competes for max_results.
An empty checked[] does not mean nobody was asked — a node skipped for geography, or a local match that short-circuited forwarding, is not a confirmed zero.
If part of the network was unreachable, partial is true; a partial search is never presented as a complete one.
| id | scope | side effect | consent required | description |
|---|---|---|---|---|
availability | local | no | no | Check unit availability for a date range |
accommodation-discovery | ecosystem | no | no | Geographic discovery across participating CM nodes via Reception |
Every capability declares whether it changes anything (side_effect) and
whether it requires explicit consent. This first version is read/discovery only — nothing an
agent does through Reception creates a booking, a block, or any other write.
| limit | default | purpose |
|---|---|---|
max_depth | 2 | how many hops a request may be forwarded through the network (2 means two hops: A→B→D) |
max_distance_km | 50 | how far from the target location a node is still considered relevant |
max_results | 20 | caps results[] only — never checked[] |
search_meta.entry_node as the source of a result. Provenance is results[].node_id / depth.New capabilities are added to the manifest and the protocol — not by building a new AI system on top of CM. CM stays the source of truth; the Docking Port stays a thin, generic HTTP/JSON door that any AI provider can use without a CM-specific plugin.