Developer Documentation

Connect travel websites to EasyTourAgent

คู่มือสำหรับทำเว็บทัวร์แบบ white-label: ดึงรายการทัวร์จริง, เปิดหน้ารายละเอียด, โหลดรอบเดินทาง, สร้าง booking และเตรียม webhook สำหรับเชื่อมระบบลูกค้า.

Endpoint Index

GET/api/widget/tours

Frontend-safe tour list for iframe or custom search UI

Widget token

GET/api/tenant/catalog

Server-side tour catalog with tenant visibility applied

catalog:read

GET/api/tenant/tours/{id}

Tour detail by UUID or slug

catalog:read

GET/api/tenant/tours/{id}/departures

Departure dates, seats, prices, and tenant prices

catalog:read

GET/api/gateway/tours/{id}/availability

Edge availability with Redis cache and API-key rate limit

catalog:read

POST/api/gateway/tours/{id}/hold

No-cache seat hold with Postgres row locking

booking:create

POST/api/tenant/bookings

Create booking lead and reserve seats when a departure is selected

booking:create

01 / Overview

ETA API คืออะไร

ETA API ใช้สำหรับให้เว็บไซต์ลูกค้าดึง catalog ทัวร์จากระบบ EasyTourAgent และสร้าง booking กลับเข้าระบบกลางได้ โดยแยก tenant, visibility, ราคา และสิทธิ์การใช้งานตาม key ของแต่ละเจ้า.

Widget

ฝัง iframe หรือเรียก API ที่ปลอดภัยสำหรับหน้าเว็บด้วย jt_widget token.

Server API

ใช้ jt_live key จาก server ของลูกค้าเท่านั้น ห้ามฝังใน browser.

Tenant-aware

ทุก endpoint ถูกกรองด้วย tenant visibility ก่อนส่งข้อมูลออก.

02 / Quick Start

เชื่อมต่อแบบเร็วที่สุด

  1. สร้าง tenant ในหน้า admin และตั้งสถานะเป็น trial หรือ active.
  2. ออก Widget token สำหรับเว็บที่ต้องการฝังหน้าค้นหาทัวร์.
  3. ออก Server API key พร้อม scope ที่ต้องใช้ เช่น catalog:read และ booking:create.
  4. ทดสอบดึง catalog, tour detail, departures และสร้าง booking ตัวอย่าง.
  5. ล็อก allowed origins, ตรวจราคา/รอบเดินทาง และตั้ง monitoring ก่อนเปิดจริง.
curl "https://easytouragent.com/api/tenant/catalog?limit=5&continent=asia" \
  -H "Authorization: Bearer jt_live_xxx"

03 / Authentication

Key, token และ scope

API key

ขึ้นต้นด้วย jt_live ใช้กับ server-to-server เท่านั้น และตรวจ scope ทุก request.

Widget token

ขึ้นต้นด้วย jt_widget ใช้กับ iframe หรือ widget API และผูก allowed origin ได้.

FieldTypeDescription
AuthorizationheaderBearer jt_live_xxx สำหรับ server API
api_keyqueryใช้สำรองได้ แต่แนะนำ header เพราะไม่หลุดใน log ง่าย
tokenqueryWidget token สำหรับ /api/widget/tours และ /embed/tours
x-jongtour-widget-tokenheaderlegacy widget header ที่ระบบยังรองรับอยู่

04 / Widget API

ฝังหน้าทัวร์หรือสร้างกล่องค้นหาเอง

ถ้าลูกค้ายังไม่มีทีม dev ให้เริ่มจาก iframe ก่อน เพราะใช้งานเร็วและปลอดภัยกว่า. ถ้าต้องการ UI ของตัวเอง ให้เรียก widget API ด้วย token ที่ล็อก origin แล้ว.

<iframe
  src="https://easytouragent.com/embed/tours?token=jt_widget_xxx"
  width="100%"
  height="720"
  style="border:0"
></iframe>
GET https://easytouragent.com/api/widget/tours?token=jt_widget_xxx&limit=12&continent=asia&q=japan
FieldTypeDescription
limit1-30จำนวนรายการที่ต้องการ ค่าเริ่มต้น 12
continentstringกรองทวีป เช่น asia, europe
qstringค้นหาจากชื่อทัวร์

05 / Catalog API

Catalog, detail และ departures

Server API เหมาะกับเว็บลูกค้าที่มี frontend/SEO ของตัวเอง และต้องการควบคุมหน้ารายละเอียดเองทั้งหมด.

curl "https://easytouragent.com/api/tenant/catalog?limit=24&continent=europe" \
  -H "Authorization: Bearer jt_live_xxx"
curl "https://easytouragent.com/api/tenant/tours/japan-autumn-5d" \
  -H "Authorization: Bearer jt_live_xxx"

curl "https://easytouragent.com/api/tenant/tours/japan-autumn-5d/departures" \
  -H "Authorization: Bearer jt_live_xxx"
FieldTypeDescription
limit1-100จำนวน catalog ต่อหน้า ค่าเริ่มต้น 24
continentstringกรองทวีป
idUUID | slugใช้ได้ทั้ง tour UUID และ slug
tenant_pricesobjectราคาที่คำนวณตาม tenant pricing layer

06 / Tour API Gateway

เช็กที่นั่งเร็วและ Hold แบบกัน Overbooking

ใช้ gateway endpoint สำหรับเว็บลูกข่ายที่ยิงเช็กที่นั่งถี่ ๆ หลายเว็บไซต์พร้อมกัน. Availability รันบน Edge พร้อม SWR 60 วินาทีและ Redis cache; Hold เป็น no-cache และเรียก RPC ที่ล็อกแถว departure ด้วย SELECT ... FOR UPDATE.

curl "https://easytouragent.com/api/gateway/tours/japan-autumn-5d/availability?departure_date=2026-10-12" \
  -H "Authorization: Bearer jt_live_xxx"
curl -X POST "https://easytouragent.com/api/gateway/tours/japan-autumn-5d/hold" \
  -H "Authorization: Bearer jt_live_xxx" \
  -H "Content-Type: application/json" \
  -d '{
    "departure_date": "2026-10-12",
    "quantity": 2,
    "hold_minutes": 10,
    "external_ref": "WEB-ORDER-1001",
    "customer": {
      "name": "Somchai Demo",
      "phone": "0812345678",
      "email": "somchai@example.com"
    }
  }'
FieldTypeDescription
UPSTASH_REDIS_REST_URLenvเปิด distributed cache และ rate limit ข้าม instance
UPSTASH_REDIS_REST_TOKENenvtoken ของ Upstash Redis REST API
GATEWAY_AVAILABILITY_RPMenvquota เช็กที่นั่งต่อนาทีต่อ API key ค่าเริ่มต้น 240
GATEWAY_HOLD_RPMenvquota hold ที่นั่งต่อนาทีต่อ API key ค่าเริ่มต้น 60

07 / Booking API

สร้าง booking จากเว็บลูกค้า

Booking API จะตรวจ tour visibility, ตรวจ departure ถ้าระบุวันที่, คำนวณราคา tenant และสร้าง lead สถานะ pending payment.

curl -X POST "https://easytouragent.com/api/tenant/bookings" \
  -H "Authorization: Bearer jt_live_xxx" \
  -H "Content-Type: application/json" \
  -d '{
    "tour_id": "japan-autumn-5d",
    "departure_date": "2026-10-12",
    "name": "Somchai Demo",
    "phone": "0812345678",
    "email": "somchai@example.com",
    "adults": 2,
    "payment_method": "reserve",
    "external_ref": "WEB-ORDER-1001"
  }'
FieldTypeDescription
tour_idrequiredUUID หรือ slug ของทัวร์
departure_dateoptionalYYYY-MM-DD ถ้าระบุ ระบบจะพยายาม reserve seat
namerequiredชื่อลูกค้า
phonerequiredเบอร์โทร ระบบ normalize เบื้องต้น
emailrequiredอีเมลลูกค้า
adultsoptionalค่าเริ่มต้น 1
payment_methodbank | reserve | stripeช่องทางชำระเงินของ tenant
external_refoptionalเลขอ้างอิงจากเว็บลูกค้า
{
  "ok": true,
  "booking_ref": "JT-12345",
  "lead_id": 12345,
  "tenant": { "id": "uuid", "slug": "happy-tour" },
  "total_price": 59800,
  "deposit_total": 20000,
  "status": "pending_payment"
}

07 / Webhooks

สถานะ webhook ตอนนี้

ระบบสร้าง pending delivery สำหรับ event booking.created แล้ว แต่ก่อนใช้กับลูกค้า production ขนาดใหญ่ ต้องทำ worker ส่ง webhook จริง, signing secret, retry policy และหน้า monitor delivery ให้ครบ.

{
  "event_type": "booking.created",
  "booking_ref": "JT-12345",
  "lead_id": 12345,
  "source": "tenant_api",
  "external_ref": "WEB-ORDER-1001",
  "customer": {
    "name": "Somchai Demo",
    "phone": "0812345678",
    "email": "somchai@example.com"
  },
  "departure_date": "2026-10-12",
  "total_price": 59800
}

08 / Errors

Error codes ที่ต้องรองรับ

401 Unauthorized

API key ผิด, หมดอายุ, inactive หรือไม่มี scope ที่ต้องใช้

404 Tour not found

tour ไม่อยู่ใน tenant visibility หรือ slug/id ไม่ถูกต้อง

409 Departure not available

รอบเดินทางไม่มีแล้ว ยกเลิกแล้ว หรือที่นั่งไม่พอ

500 Server error

ฐานข้อมูลหรือ upstream service มีปัญหา ควร retry แบบ exponential backoff

09 / Production Checklist

Checklist ก่อนส่งให้ลูกค้าเชื่อม API

Tenant active

tenant status เป็น trial หรือ active

Catalog ready

มี tour visible และ quality audit ไม่มี critical

API key scoped

ออก key แยกตามเว็บและให้ scope เท่าที่จำเป็น

Widget origin locked

allowed origins เป็น domain จริง ไม่ใช้ *

Pricing verified

สุ่มตรวจราคาและรอบเดินทางกับ supplier อย่างน้อย 5 รายการ

Booking tested

ทดสอบ create booking, reserve seat, และกรณี seat ไม่พอ

Webhook configured

ตั้ง endpoint, signature, retry และ monitor

Rate limit plan

กำหนด quota ตามแพ็กเกจและแจ้งเตือนเมื่อใช้สูงผิดปกติ

Ready for integration

ขั้นต่อไปคือออก key จริงและทดสอบ staging กับเว็บลูกค้า

ห้ามส่ง jt_live key ไปใน browser หรือแอปมือถือโดยตรง. ถ้าต้องแสดง catalog ฝั่ง browser ให้ใช้ widget token หรือทำ proxy ผ่าน server ของลูกค้าเท่านั้น.

Plug-in model

เริ่มจาก widget ได้ก่อน แล้วค่อยย้ายเป็น API-first เมื่อเว็บลูกค้าพร้อม.

Security first

แยก key ต่อ tenant, แยก scope และล็อก origin เพื่อกันข้อมูลรั่ว.

Full guide

ไฟล์ docs/eta-api-integration-guide.md ใช้ส่งให้ทีม dev หรือแนบใน proposal ได้.