PTX Channel Manager — OTA API Technical Request (for Partner Engineering Teams)
Audience: OTA connectivity / integrations engineering teams (Booking.com Connectivity, Agoda YCS, Expedia Partner Solutions / EQC, Traveloka Connect).
Purpose: Engineer-to-engineer brief describing the endpoints, webhooks, auth, and test assets PTX-CM needs to move off session-scraping and onto certified APIs.
Companion doc:docs/ota-partner-application.md(business/compliance). This document is the technical annex.
Prepared by: PTX-CM Integrations Engineering
Version: 1.0
Date: 2026-04-24
1. TL;DR for partner engineers
We are a multi-property channel manager (NestJS 11 + PostgreSQL + Redis/BullMQ). We currently integrate with your platform via session-based scraping — it works but it is fragile and noisy for your infra. We would like to move our traffic onto your official APIs.
Please help us with the items in §9 What we need from you. Everything else in this document exists so your team can size the request and trust our client implementation.
2. Our side in one diagram
Property operator UI ─┐
Internal schedulers ─┼─► NestJS API ──► BullMQ (per-OtaAccount queue) ──► OTA Adapter ──► Partner API
│ ▲ │
│ │ ▼
└──────────┴──────────── Postgres ◄── Webhook receiver ◄────┘- One queue per
OtaAccount(property × OTA) — isolates noisy neighbours - Every outbound call carries
X-Request-Id+clientRequestId(for idempotency) - Every inbound webhook is persisted raw before ACK — replay is a
SELECT, not a re-fetch
3. Scope — capabilities we are asking for
3.1 Availability (write)
- Push room-level availability (
availableCountperratePlanId×date). - Date-range updates (we would prefer 365-day windows but will chunk to whatever max you support).
- Ideally delta updates only, with a "full resync" endpoint we call once per onboarding + weekly drift check.
3.2 Rates (write)
- Push base rate + up to N derived rate plans per room type.
- Support for per-date overrides, weekday patterns, and restrictions (MinLOS, MaxLOS, CTA, CTD, Stop-Sell).
- Currency: publisher currency of the property (OTA already knows this — we do not override).
3.3 Reservations (read + webhook)
- Preferred: push webhook on booking create / modify / cancel (see §5).
- Fallback: polling endpoint with
since=<ISO8601>cursor — used only if webhook delivery is unhealthy. - Payload must include: OTA booking id, property id, rate plan id, room type id, guest contact, arrival/departure, nightly rates, taxes/fees breakdown, payment model (merchant vs hotel collect), cancellation policy snapshot, special requests.
3.4 Room-type & rate-plan mapping (read)
- One-time read of the property's taxonomy so we can map to our internal
RoomType+RatePlanentities. - We want immutable IDs on your side; if yours change, please expose an ID-history endpoint.
3.5 Content (read, optional Phase 2)
- Static property content: descriptions, amenities, photos, policies — so we can reconcile cross-OTA differences and surface them to the operator.
3.6 Reviews (read + write, Phase 2)
- Pull review list + detail; post operator replies. OK if behind separate certification.
3.7 Out of scope (explicitly not requested)
- Search / shopping APIs
- Payment tokenisation
- Partner-side finance or invoicing endpoints
- Any end-traveller PII beyond the booking itself
4. Authentication — what we can support
Ranked from most-preferred to least:
- OAuth2 client-credentials (machine-to-machine, per OtaAccount). Rotating client secret. Scopes per resource.
- OAuth2 authorization-code (user consent flow) — fine if the property owner must explicitly consent; we store refresh tokens encrypted.
- Mutual TLS — we can provision a client cert per environment.
- Long-lived API key in
Authorizationheader — acceptable if rotatable + scoped. - HMAC request signing (
X-Signature: sha256=...) — acceptable standalone or layered over any of the above.
All secrets are stored AES-256-GCM at rest, one KMS key per env. Never logged, never sent to the browser.
5. Webhooks — what we can consume today
- Transport: HTTPS POST, TLS 1.3, JSON body (
application/json). - Endpoint shape:
https://webhooks.<our-domain>/ota/{partner}/{event}— one per event family, partner-agnostic. - Auth options we support receiving:
- HMAC over raw body (
X-Signatureheader, secret per OtaAccount) - mTLS client cert
- Bearer token (static, rotatable)
- HMAC over raw body (
- Response contract: we ACK with
2xxonly after we have durably persisted the raw payload. Processing is async downstream. - Retry expectation from partner side: exponential backoff, at-least-once delivery, include a stable
eventIdso we can dedupe. - IP allow-list: we can provide our static egress IPs; we ask for yours so we can allow-list inbound.
- Replay endpoint (nice-to-have): a partner-side endpoint we can call to ask "replay events between
T1andT2forpropertyId" after any outage on our side.
6. Client behaviour you can rely on
6.1 Idempotency
Every write request carries Idempotency-Key: <uuid-v4> (header name negotiable). Retries of the same logical mutation use the same key. We expect your side to return the original result on duplicate keys within a reasonable window (24h OK).
6.2 Retry & backoff
- Base: 1s, 2s, 4s, 8s, 16s, cap 30s, ±25% jitter
- Max 5 attempts for idempotent writes; 0 retries for 4xx except 408/425/429
- Honour
Retry-Afteron 429 / 503 exactly — we do not retry earlier
6.3 Error classification we apply
| HTTP | Our treatment |
|---|---|
| 2xx | Persist, emit business event |
| 400 / 422 | Reject locally, surface to user, no retry |
| 401 / 403 | Refresh token once → if still failing, mark OtaAccount.status = EXPIRED and alert |
| 404 | Treat as stale, log, no retry |
| 409 | Surface as conflict, manual resolution path in UI |
| 429 | Honour Retry-After, pause queue |
| 5xx | Retry with backoff, then circuit-break the account |
| Network timeout | Single retry with new correlation id, then backoff |
6.4 Rate-limit discipline
- We publish ourselves a per-OtaAccount concurrency budget and a per-endpoint token bucket.
- We read and respect all of:
X-RateLimit-Remaining,X-RateLimit-Reset,Retry-After. - If you document stricter limits than standard headers, please send us the doc — we will encode them.
6.5 Circuit breakers
Per-OtaAccount. Trips on sustained 5xx or auth failure, pauses traffic, alerts operator. Recovery is manual + health-check probe.
6.6 Observability / traceability
- We send
X-Request-Id: <uuid>on every outbound call. - We attach
X-Client-Version: ptx-cm/2.9.xandX-Partner-Account: <opaque-ptx-id>. - We store response headers + body (redacted for PII) for 30 days for joint-debug.
- Please echo our request id in your logs, and share your partner-side request id in the response (
X-Partner-Request-Idor similar) so we can join logs during incidents.
7. Data model & field expectations
For each resource we request, please supply:
- Schema: OpenAPI 3.x (preferred), JSON-Schema, or a versioned PDF.
- Semantics: meaning of nullable vs missing, timezone of date fields, rounding rules for money, currency codes (ISO 4217), language codes (BCP 47).
- Identifiers: are they global, or scoped per property?
- Enum stability: do enum values ever change? If yes, what is the deprecation window?
- Versioning policy: URL-path, header, or content-negotiation? Deprecation lead time?
8. Testing assets we need
- Sandbox environment with at least one test property pre-provisioned.
- Ability to create test bookings in sandbox (either self-service UI or a partner endpoint) so we can exercise the reservations pipeline end-to-end.
- Fault-injection hooks (nice-to-have): force 429, force 500, force auth-expired for contract-testing.
- Postman / Insomnia collection or equivalent — optional but speeds onboarding by weeks.
- Webhook test harness: ability to trigger synthetic webhook events to our sandbox receiver.
- Certification checklist, scored, so we know exactly what must pass before production.
9. What we need from you — action list
| # | Item | Priority | Owner (partner side) |
|---|---|---|---|
| 1 | Grant sandbox credentials (per §8.1) | P0 | Connectivity onboarding |
| 2 | Share OpenAPI / schema docs for scopes in §3.1–§3.4 | P0 | API team |
| 3 | Confirm auth method from §4 that your platform supports | P0 | Security / API team |
| 4 | Webhook registration flow + signing key issuance | P0 | Connectivity ops |
| 5 | Published rate limits per endpoint | P0 | API team |
| 6 | Certification checklist + test cases we must pass | P1 | Certification team |
| 7 | Incident escalation channel (email + Slack / Teams) | P1 | Support |
| 8 | Partner-side egress IPs (for our inbound allow-list) | P1 | Infra |
| 9 | Content + Reviews API access (Phase 2) | P2 | Product |
| 10 | Replay endpoint for webhook gaps (§5) | P2 | API team |
10. Proposed timeline
| Week | Milestone |
|---|---|
| 0 | Kick-off call, NDA if required |
| 1 | Sandbox credentials issued, OpenAPI shared |
| 2–3 | Adapter implementation against sandbox, contract tests green |
| 4 | Webhook receiver + replay tested end-to-end |
| 5 | Certification test cases executed, report sent for partner review |
| 6 | Go-live on pilot property (1 property, 1 OTA) |
| 7–8 | Ramp to 10 properties, monitor metrics in §11 |
| 9+ | Fleet-wide rollout |
We can compress this if your team can too; we will not push faster than your certification allows.
11. How we will measure success together
- Sync success rate per OtaAccount ≥ 99.5% (excluding 4xx caused by bad operator input)
- Push latency p99 ≤ 30s for availability, ≤ 5min for bulk rates
- Booking ingest latency p99 ≤ 60s via webhook, ≤ 150s via polling fallback
- Zero overbooking incidents attributable to PTX-CM
- Mean time to recover from partner-side incident ≤ partner's own SLA
We are happy to share a weekly dashboard with partner ops during ramp.
12. Sample request snippets (what our client will send)
12.1 Availability push (illustrative, partner shape will differ)
POST /v1/availability HTTP/1.1
Host: api.partner.example
Authorization: Bearer <token>
Content-Type: application/json
Idempotency-Key: 7f2c5d2e-2d1a-4a1c-9e6a-5c3b2a1f0d4e
X-Request-Id: 3f9b8e16-7a0c-4d2b-8b21-9a6f0e4b1c77
X-Client-Version: ptx-cm/2.9.3
{
"propertyId": "P-000123",
"updates": [
{ "ratePlanId": "RP-STD", "roomTypeId": "RT-DLX", "date": "2026-05-01", "available": 4 },
{ "ratePlanId": "RP-STD", "roomTypeId": "RT-DLX", "date": "2026-05-02", "available": 3 }
]
}12.2 Webhook we will accept from you
POST /ota/{partner}/reservation.created HTTP/1.1
Host: webhooks.ptx.example
Content-Type: application/json
X-Signature: sha256=9a7b8c...
X-Event-Id: evt_01HYZ3QF0G7B9PJMV4EE3W5T0X
{
"eventId": "evt_01HYZ3QF0G7B9PJMV4EE3W5T0X",
"eventType": "reservation.created",
"occurredAt": "2026-05-01T07:15:22Z",
"propertyId": "P-000123",
"reservation": { /* full reservation per §3.3 */ }
}12.3 Error we can round-trip cleanly
HTTP/1.1 429 Too Many Requests
Retry-After: 12
X-RateLimit-Remaining: 0
X-RateLimit-Reset: 1714551682
X-Partner-Request-Id: prt_req_5f4a
{ "error": { "code": "rate_limited", "message": "slow down", "docsUrl": "https://..." } }13. Questions we have for your team
- Which auth methods in §4 are supported in sandbox vs production?
- What is the maximum date range per availability push?
- Are
propertyId/roomTypeId/ratePlanIdglobally unique or scoped? - What is the webhook delivery SLA + retry policy on your side?
- Is there a bulk endpoint we should use for onboarding (full resync), distinct from steady-state deltas?
- Do you support partial updates or must we always send full objects?
- What is your published versioning + deprecation policy?
- Is there an idempotency-key header you already honour? If not, what would you accept?
- What is the certification lead time from "ready to test" → "live"?
- Who is our single point of contact during ramp-up?
14. Contacts on our side
| Role | Name | |
|---|---|---|
| Integrations engineering lead | to be filled | to be filled |
| On-call rotation | to be filled | to be filled |
| Security contact | to be filled | to be filled |
| Business / commercial | to be filled | to be filled |
Production egress IPs, sandbox webhook URL, and PGP key available on request.
Unresolved questions (internal, for PTX-CM team)
- Confirm exact max date-range our availability push supports after the BullMQ chunking change (§3.1).
- Decide whether Phase 2 content/review scopes (§3.5–§3.6) are requested in the first partner call or deferred.
- Finalise single point-of-contact names in §14 with the ops lead.
- Confirm static egress IP list with infra (§9 row 8 dependency).
- Validate §12 sample payloads against our current NestJS adapter contract to make sure shape matches what we actually send.