# Findings — TransMékong offer API 0.1.0 at `https://api.transmekong.com/v1`

Read 2026-09-07T18:53:51+00:00, key presented: no. **1 blocker · 14 gap · 13 nit.** Mechanical findings are the contract walked over the payloads; editorial ones state what they infer.

### ✗ blocker · catalogue · `catalogue.detail-flat`

**The service detail is the Service object, not the contract's ServiceDetail**  
Consequence: Code written on the contract reads `service`, `occupancy`, `extras`, `rules` off the detail and finds no `service` and no `occupancy`: the party picker has no sale types, no compositions, no age groups — nothing to sell with.  
Seen: GET /services/{serviceFamily}/{serviceCode} (/services/accommodation/nambo) → top-level keys ['ageGroups', 'compositions', 'content', 'counterpart', 'disembarkPlaceId', 'durationMinutes', 'embarkPlaceId', 'embarkTimeLocal']…; contract ServiceDetail requires service, occupancy, extras, rules  
Suggested: Either nest the wire as ServiceDetail, or declare the flat shape in the contract — one of the two moves.  
Provenance: editorial

### △ gap · availability · `availability.no-range`

**A 30-day calendar costs 30 requests**  
Consequence: The contract declares no date-range call; a month view is one round trip per day, and a booking widget's first paint waits on all of them.  
Seen: GET /availability?date=… × 30 for 2026-09-07 + 30 days  
Suggested: A `from`/`days` (or `to`) parameter on /availability, declared in the contract.  
Provenance: editorial

### △ gap · catalogue · `catalogue.age-band-inverted.accommodation-lighthouse.child`

**Age group `child` on accommodation/lighthouse runs from 5 to 4**  
Consequence: A party picker built on these bounds offers no age at all for this group; a reader cannot tell whether the group exists.  
Seen: GET /services/accommodation/lighthouse: ageGroups {'id': 'child', 'ageFrom': 5, 'ageTo': 4, 'level': 'family'}  
Suggested: State an empty group explicitly (or omit it) rather than an inverted band.  
Provenance: editorial

### △ gap · catalogue · `catalogue.age-band-inverted.accommodation-nambo.child`

**Age group `child` on accommodation/nambo runs from 5 to 4**  
Consequence: A party picker built on these bounds offers no age at all for this group; a reader cannot tell whether the group exists.  
Seen: GET /services/accommodation/nambo: ageGroups {'id': 'child', 'ageFrom': 5, 'ageTo': 4, 'level': 'family'}  
Suggested: State an empty group explicitly (or omit it) rather than an inverted band.  
Provenance: editorial

### △ gap · catalogue · `probe.family-unknown`

**a family token that is not in the contract's enum: expected 404 family-unknown, got 200 ; 2 services returned — the filter was ignored**  
Consequence: A typo in a family answers the full catalogue instead of a refusal; the integrator's filter silently does nothing.  
Seen: GET /services → 200 ; 200 ; 2 services returned — the filter was ignored  
Provenance: editorial · exchanges 572

### △ gap · catalogue · `probe.family-unmodelled`

**the family `overnight-cruise` (in the enum, not listed): expected 404 family-unmodelled, or a filtered list without it, got 200 ; 2 of 2 services returned, none of them `overnight-cruise`**  
Consequence: A picker filtered by family shows every family regardless; the integrator cannot tell filtering is unsupported.  
Seen: GET /services → 200 ; 200 ; 2 of 2 services returned, none of them `overnight-cruise`  
Provenance: editorial · exchanges 573

### △ gap · catalogue · `schema.missing-required.get-services-servicefamily-servicecode.extras[].priceRef`

**`extras[].priceRef` is required by the contract and absent**  
Consequence: Code written on the contract reads nothing here and carries on with a hole.  
Seen: GET /services/{serviceFamily}/{serviceCode}: 8 instance(s) over 2 response(s); e.g. `extras[0].priceRef` expected present, seen absent  
Provenance: schema (mechanical) · exchanges 538, 539

### △ gap · catalogue · `schema.missing-required.get-services-servicefamily-servicecode.occupancy`

**`occupancy` is required by the contract and absent**  
Consequence: Code written on the contract reads nothing here and carries on with a hole.  
Seen: GET /services/{serviceFamily}/{serviceCode}: 2 instance(s) over 2 response(s); e.g. `occupancy` expected present, seen absent  
Provenance: schema (mechanical) · exchanges 538, 539

### △ gap · catalogue · `schema.missing-required.get-services-servicefamily-servicecode.service`

**`service` is required by the contract and absent**  
Consequence: Code written on the contract reads nothing here and carries on with a hole.  
Seen: GET /services/{serviceFamily}/{serviceCode}: 2 instance(s) over 2 response(s); e.g. `service` expected present, seen absent  
Provenance: schema (mechanical) · exchanges 538, 539

### △ gap · catalogue · `schema.wrong-type.get-services.services[].variants[].axes`

**`services[].variants[].axes` is a array where the contract says object**  
Consequence: The integrator's code takes the wrong branch — iterating an object, indexing a list — and fails silently.  
Seen: GET /services: 6 instance(s) over 3 response(s); e.g. `services[0].variants[0].axes` expected object, seen array  
Provenance: schema (mechanical) · exchanges 537, 572, 573

### △ gap · discovery · `discovery.contract-not-served`

**Nothing machine-readable is served**  
Consequence: An integrator cannot fetch the contract from the API; whatever copy they hold may be stale and there is no way to check.  
Seen: GET https://api.transmekong.com/v1/openapi.yaml → 404 text/html  
Suggested: Serve the OpenAPI document at the base URL, or link it from the front door.  
Provenance: editorial

### △ gap · discovery · `discovery.front-door-foreign.https-api-transmekong-com`

**The front door at https://api.transmekong.com/ does not describe this API**  
Consequence: A reader who arrives at the host is sent to another base URL and never finds this surface; the links they follow do not answer here.  
Seen: names https://octo.transmekong.com/v1; mentions this contract's paths: no; excerpt: “ TransMékong Developer API :root { --tm-bg: #fdfcf8; --tm-header: #eae0af; --tm-text: #222; --tm-accent: #63574c; } * { ”  
Suggested: Describe this contract's base and paths on the front door.  
Provenance: editorial

### △ gap · discovery · `discovery.front-door-foreign.https-api-transmekong-com-llms-txt`

**The front door at https://api.transmekong.com/llms.txt does not describe this API**  
Consequence: A reader who arrives at the host is sent to another base URL and never finds this surface; the links they follow do not answer here.  
Seen: names https://octo.transmekong.com/v1; mentions this contract's paths: no; excerpt: “# TransMékong / Bassac cruises — API for agents and integrators > Bassac (TransMékong) runs one-night Mekong Delta river”  
Suggested: Describe this contract's base and paths on the front door.  
Provenance: editorial

### △ gap · media · `media.none`

**No service carries a picture reference**  
Consequence: A page cannot show a room, a boat or a place; a listing without an image converts poorly and the integrator will ask where the pictures are.  
Seen: 2 service(s), 2 detail(s) read; no contentRefs, no URL-shaped value, nothing under /media/. No service payload carries a picture reference — no contentRefs, no URL-shaped value, nothing under /media/. The page shows none rather than a placeholder.  
Suggested: Publish contentRefs into the static /media/ path the contract already names.  
Provenance: editorial

### △ gap · transport · `probe.cors-headers`

**OPTIONS /services — the headers CORS names: expected only headers this contract defines (Authorization, Content-Type), got names Octo-Capabilities**  
Consequence: A browser is told to expect a header this contract never defines; harmless today, confusing on every read of the preflight.  
Seen: OPTIONS /services → 204 ; names Octo-Capabilities  
Provenance: editorial · exchanges 581

### · nit · availability · `schema.beyond-contract.get-availability.entries[].asOf`

**`entries[].asOf` arrives and is not in the contract**  
Consequence: Additive is allowed; but an undeclared field cannot be relied on, and a reader cannot tell whether it is stable.  
Seen: GET /availability: 60 instance(s) over 30 response(s); e.g. `entries[0].asOf` expected not declared, seen string  
Provenance: schema (mechanical) · exchanges 540, 541, 542, 543, 544

### · nit · availability · `schema.beyond-contract.get-availability.entries[].nights`

**`entries[].nights` arrives and is not in the contract**  
Consequence: Additive is allowed; but an undeclared field cannot be relied on, and a reader cannot tell whether it is stable.  
Seen: GET /availability: 60 instance(s) over 30 response(s); e.g. `entries[0].nights` expected not declared, seen integer  
Provenance: schema (mechanical) · exchanges 540, 541, 542, 543, 544

### · nit · catalogue · `catalogue.list-is-detail`

**The list already carries the whole detail**  
Consequence: GET /services returns every field of every service; a catalogue page downloads the deep answer for services it will not open.  
Seen: list object keys == detail object keys (18 keys); list payload for 2 services  
Provenance: editorial

### · nit · catalogue · `schema.beyond-contract.get-services-servicefamily-servicecode.ageGroups`

**`ageGroups` arrives and is not in the contract**  
Consequence: Additive is allowed; but an undeclared field cannot be relied on, and a reader cannot tell whether it is stable.  
Seen: GET /services/{serviceFamily}/{serviceCode}: 2 instance(s) over 2 response(s); e.g. `ageGroups` expected not declared, seen array  
Provenance: schema (mechanical) · exchanges 538, 539

### · nit · catalogue · `schema.beyond-contract.get-services-servicefamily-servicecode.compositions`

**`compositions` arrives and is not in the contract**  
Consequence: Additive is allowed; but an undeclared field cannot be relied on, and a reader cannot tell whether it is stable.  
Seen: GET /services/{serviceFamily}/{serviceCode}: 2 instance(s) over 2 response(s); e.g. `compositions` expected not declared, seen array  
Provenance: schema (mechanical) · exchanges 538, 539

### · nit · catalogue · `schema.beyond-contract.get-services-servicefamily-servicecode.content`

**`content` arrives and is not in the contract**  
Consequence: Additive is allowed; but an undeclared field cannot be relied on, and a reader cannot tell whether it is stable.  
Seen: GET /services/{serviceFamily}/{serviceCode}: 2 instance(s) over 2 response(s); e.g. `content` expected not declared, seen array  
Provenance: schema (mechanical) · exchanges 538, 539

### · nit · catalogue · `schema.beyond-contract.get-services-servicefamily-servicecode.saleTypes`

**`saleTypes` arrives and is not in the contract**  
Consequence: Additive is allowed; but an undeclared field cannot be relied on, and a reader cannot tell whether it is stable.  
Seen: GET /services/{serviceFamily}/{serviceCode}: 2 instance(s) over 2 response(s); e.g. `saleTypes` expected not declared, seen array  
Provenance: schema (mechanical) · exchanges 538, 539

### · nit · catalogue · `schema.beyond-contract.get-services.services[].ageGroups`

**`services[].ageGroups` arrives and is not in the contract**  
Consequence: Additive is allowed; but an undeclared field cannot be relied on, and a reader cannot tell whether it is stable.  
Seen: GET /services: 6 instance(s) over 3 response(s); e.g. `services[0].ageGroups` expected not declared, seen array  
Provenance: schema (mechanical) · exchanges 537, 572, 573

### · nit · catalogue · `schema.beyond-contract.get-services.services[].compositions`

**`services[].compositions` arrives and is not in the contract**  
Consequence: Additive is allowed; but an undeclared field cannot be relied on, and a reader cannot tell whether it is stable.  
Seen: GET /services: 6 instance(s) over 3 response(s); e.g. `services[0].compositions` expected not declared, seen array  
Provenance: schema (mechanical) · exchanges 537, 572, 573

### · nit · catalogue · `schema.beyond-contract.get-services.services[].content`

**`services[].content` arrives and is not in the contract**  
Consequence: Additive is allowed; but an undeclared field cannot be relied on, and a reader cannot tell whether it is stable.  
Seen: GET /services: 6 instance(s) over 3 response(s); e.g. `services[0].content` expected not declared, seen array  
Provenance: schema (mechanical) · exchanges 537, 572, 573

### · nit · catalogue · `schema.beyond-contract.get-services.services[].extras`

**`services[].extras` arrives and is not in the contract**  
Consequence: Additive is allowed; but an undeclared field cannot be relied on, and a reader cannot tell whether it is stable.  
Seen: GET /services: 6 instance(s) over 3 response(s); e.g. `services[0].extras` expected not declared, seen array  
Provenance: schema (mechanical) · exchanges 537, 572, 573

### · nit · catalogue · `schema.beyond-contract.get-services.services[].rules`

**`services[].rules` arrives and is not in the contract**  
Consequence: Additive is allowed; but an undeclared field cannot be relied on, and a reader cannot tell whether it is stable.  
Seen: GET /services: 6 instance(s) over 3 response(s); e.g. `services[0].rules` expected not declared, seen object  
Provenance: schema (mechanical) · exchanges 537, 572, 573

### · nit · catalogue · `schema.beyond-contract.get-services.services[].saleTypes`

**`services[].saleTypes` arrives and is not in the contract**  
Consequence: Additive is allowed; but an undeclared field cannot be relied on, and a reader cannot tell whether it is stable.  
Seen: GET /services: 6 instance(s) over 3 response(s); e.g. `services[0].saleTypes` expected not declared, seen array  
Provenance: schema (mechanical) · exchanges 537, 572, 573
