API Reference

API Guide (Partner Reservation Integration)

Authentication

The GOVIGO API uses API key authentication. Include the X-API-Key header in every request.

Request header example
GET /api/golf-clubs/plans.php?golf_club_id=1 HTTP/1.1
Host: en.govigolf.comX-API-Key: YOUR_API_KEY
API keys are issued from the admin panel. Each key has scopes (permissions); use a key granted the scopes required for your operations. Reservation partners are issued a partner-type key.

Available scopes:

ScopeDescription
golf_clubs:readRead golf club / plan information
tee_times:readRead availability / price calendar
reservations:readRead quote / reservation information
reservations:writeCreate / update / cancel reservations
webhooks:read / webhooks:writeView/manage Webhooks (notifications for your own reservations) (described later)

Common response format

All API responses are returned in the following common format.

{
  "success": true,
  "data": { ... },
  "message": "",
  "http_code": 200
}
FieldTypeDescription
successbooleantrue on success, false on failure
dataobject|nullResponse data (null on error)
messagestringMessage (error details on error)
http_codeintegerHTTP status code

Error codes

HTTP codeMeaningDescription
400Bad RequestInvalid request parameters
401UnauthorizedAPI key invalid or missing
403ForbiddenInsufficient scope
404Not FoundResource does not exist (or no permission)
405Method Not AllowedInvalid HTTP method
409ConflictResource conflict (duplicate booking, non-editable state, etc.)
429Too Many RequestsRate limit exceeded
500Internal Server ErrorInternal server error

Reservation lookup & authorization

A reservation can be referenced by two keys.

KeyDescription
reservation_id / idGOVIGO internal reservation ID (integer). Returned in responses.
external_refThe partner system's reservation reference ID (string). If passed at creation, you can reference / update / cancel using your own ID afterwards. Recommended.
Authorization (important): An owner scope applies to API keys. A partner key can reference / operate only on reservations it created. Specifying another party's reservation ID returns 404 Not Found. Keep external_ref unique within the same partner (a duplicate returns the existing reservation).

Connectivity check

GET /api/ping.php any

Checks the API key validity, owner, granted scopes and rate limit. Use it for initial connectivity testing (no scope required).

Response example
{
  "success": true,
  "data": {
    "authenticated": true,
    "owner": { "type": "partner", "id": 3, "name": "Partner A" },
    "key_prefix": "a1b2c3d4",
    "scopes": ["golf_clubs:read", "tee_times:read", "reservations:read", "reservations:write"],
    "rate_limit": 120,
    "expires_at": null,
    "server_time": "2026-06-01T10:00:00+07:00"
  },
  "message": "pong",
  "http_code": 200
}

Golf club & plan information

GET /api/golf-clubs/detail.php golf_clubs:read

Gets golf club details. Omit id to return a list.

ParameterTypeRequiredDescription
idintegerOptionalGolf club ID
countryintegerOptionalCountry code (0=Vietnam, 1=Thailand, 2=Japan). Currently only Vietnam (0) is available (other countries return no results)
Response example
{
  "success": true,
  "data": {
    "count": 1,
    "golf_clubs": [
      {
        "id": 1,
        "name": "Sample Golf Club",
        "area": "Hanoi",
        "country": 0,
        "address": "123 Golf Street",
        "google_map": "https://maps.google.com/...",
        "course_info": "18 holes, par 72",
        "cancel_policy": "Free up to 3 days before",
        "rental": {
          "club_available": true,
          "club_fee": 300000,
          "shoes_available": false,
          "shoes_fee": null
        },
        "options": {
          "rental_cart":      { "available": true,  "fee": 500000 },
          "rental_umbrella":  { "available": false, "fee": null },
          "caddy":            { "available": true,  "fee": 400000 },
          "caddy_designate":  { "available": false, "fee": null },
          "caddybag_storage": { "available": false, "fee": null },
          "sent_caddybag":    { "available": false, "fee": null },
          "accompany":        { "available": false, "fee": null },
          "pickup":           { "available": false, "fee": null }
        }
      }
    ]
  },
  "message": "",
  "http_code": 200
}
rental (clubs/shoes) are rentals that can be specified as booking parameters in this API (quote/create). options (cart, caddy, pickup, etc.) are reference only (not bookable via API), returning availability (available) and fee (fee; null = not offered) for vendor-side display/control. Both null and 0 fees are treated as "not offered".
GET /api/golf-clubs/plans.php golf_clubs:read

Gets the golf club plan list. The id obtained here is the golf_plan_id (required) used when booking.

ParameterTypeRequiredDescription
golf_club_idintegerRequiredGolf club ID
datestringOptionalOnly plans valid on the given date (YYYY-MM-DD)
Response example
{
  "success": true,
  "data": {
    "golf_club_id": 1,
    "count": 1,
    "plans": [
      {
        "id": 10,
        "source": "golf_plan",
        "name": "Weekday Plan",
        "day_type": "weekday",
        "price_local": 1500000,
        "price_inbound": 2000000,
        "partner_price_local": 1450000,
        "partner_price_inbound": 1850000,
        "apply_period": { "from": "2026-01-01", "to": "2026-12-31" },
        "time_range": "06:00-10:00",
        "minimum_players": 1,
        "summary": "Great value weekday-only plan",
        "cancel_policy": "Free cancellation up to 3 days before",
        "rental_club_fee": 300000,
        "rental_shoes_fee": 100000
      }
    ]
  },
  "message": "",
  "http_code": 200
}
price_local is the local price, price_inbound is the inbound (visitor) price. Prices are applied per player-type counts (local / inbound), which can be mixed within a single reservation. Bookings / quotes are finalized at partner_price_local / partner_price_inbound (partner prices). Only plans with bookable: true (source: golf_plan) can be booked. cancel_policy is the customer-facing cancellation policy: it returns the plan-specific policy if set, otherwise the golf club's cancellation policy.

Availability

GET /api/tee-times/available.php tee_times:read

Gets available tee-time slots for the given date. To use a tee-time slot, pass tee_time_slot_id when booking (optional).

ParameterTypeRequiredDescription
golf_club_idintegerRequiredGolf club ID
datestringRequiredDate (YYYY-MM-DD)
Response example
{
  "success": true,
  "data": {
    "golf_club_id": 1,
    "date": "2026-03-15",
    "is_closed": false,
    "count": 1,
    "slots": [
      {
        "slot_id": 101,
        "tee_date": "2026-03-15",
        "tee_time": "07:00",
        "total_slots": 4,
        "available_slots": 2,
        "allow_join": true,
        "plan": { "id": 10, "name": "Weekday Plan", "price": 1500000 }
      }
    ]
  },
  "message": "",
  "http_code": 200
}
GET /api/tee-times/min-prices.php tee_times:read

Gets the daily minimum price for the given month (for calendar display).

ParameterTypeRequiredDescription
golf_club_idintegerRequiredGolf club ID
yearintegerOptionalYear (default: this year)
monthintegerOptionalMonth (default: this month)
Response example
{
  "success": true,
  "data": {
    "golf_club_id": 1,
    "year": 2026,
    "month": 3,
    "prices": {
      "2026-03-01": 1500000,
      "2026-03-02": 1800000
    }
  },
  "message": "",
  "http_code": 200
}

Price quote

GET /api/reservations/quote.php reservations:read

Gets the final amount before creating a reservation. The create API recalculates with the same logic, so it matches this quote. Amounts returned are partner prices (regular price minus the partner discount).

ParameterTypeRequiredDescription
golf_club_idintegerRequiredGolf club ID
golf_plan_idintegerRequiredPlan ID (id from plans)
local_playersintegerRequired*Number of players at local price
inbound_playersintegerRequired*Number of players at inbound price (* at least one of the two. Total 1-20. Can be mixed)
datestringOptionalPlay date (YYYY-MM-DD). If given, plan validity period / weekday are also validated
rental_clubintegerOptionalNumber of rental clubs (only at clubs that offer it)
rental_shoesintegerOptionalNumber of rental shoes (only at clubs that offer it)
Rental clubs/shoes availability differs per golf club. Check availability and fees before booking via /api/golf-clubs/detail.php (rental.club_available / shoes_available) or /api/golf-clubs/plans.php (rental_club_fee / rental_shoes_fee; null = not offered). Requesting a quantity at a club that does not offer it (fee not set) returns a 409 error (no zero-fee quote or booking is made).
Response example
{
  "success": true,
  "data": {
    "plan_id": 10,
    "plan_name": "Weekday Plan",
    "players": {
      "local":   { "count": 2, "regular_unit_price": 1500000, "discount_per_player": 50000, "unit_price": 1450000, "amount": 2900000 },
      "inbound": { "count": 2, "regular_unit_price": 2000000, "discount_per_player": 150000, "unit_price": 1850000, "amount": 3700000 }
    },
    "player_count": 4,
    "minimum_players": 1,
    "base_amount": 6600000,
    "rental": {
      "club_count": 2, "club_fee": 600000,
      "shoes_count": 0, "shoes_fee": 0,
      "subtotal": 600000
    },
    "currency": "VND",
    "total_amount": 7200000,
    "golf_club_id": 1
  },
  "message": "Quote calculated.",
  "http_code": 200
}

Reservations

POST /api/reservations/create.php reservations:write

Creates a new reservation. The request body is JSON. The amount (total_amount) is recalculated on the server from golf_plan_id, so it need not be specified in the request.

ParameterTypeRequiredDescription
golf_club_idintegerRequiredGolf club ID
golf_plan_idintegerRequiredPlan ID (id from plans)
tee_datestringRequiredPlay date(YYYY-MM-DD)
customer_namestringRequiredBooker name
customer_emailstringRequiredBooker email
customer_phonestringOptionalBooker phone (if provided, saved as the booker's contact)
playersobjectRequiredPlayer counts by price type {local?, inbound?} (can be mixed; total 1-20)
tee_timestringOptionalDesired tee time (HH:MM). Normal booking only. Default 08:00
tee_time_slot_idintegerOptionalIf specified, instantly confirmed as a real-time slot and reflected on the golf course tee sheet (obtained via available). The slot's date must match tee_date. If omitted, a normal (request) booking
optionsobjectOptionalRentals etc. {rental_club, rental_shoes}
remarkstringOptionalRemark
attendeesarrayOptionalCompanion list ({name, email?, phone?})
external_refstringOptionalPartner reservation reference ID (recommended, unique)
Request example
POST /api/reservations/create.php HTTP/1.1
Host: en.govigolf.comX-API-Key: YOUR_API_KEY
Content-Type: application/json

{
  "golf_club_id": 1,
  "golf_plan_id": 10,
  "tee_date": "2026-03-15",
  "customer_name": "Taro Tanaka",
  "customer_email": "[email protected]",
  "customer_phone": "090-1234-5678",
  "players": { "local": 2, "inbound": 2 },
  "options": { "rental_club": 2 },
  "external_ref": "EXT-2026-00500"
}
Response example (201 Created)
{
  "success": true,
  "data": {
    "reservation_id": 500,
    "status": 1,
    "golf_club_id": 1,
    "tee_date": "2026-03-15",
    "tee_time": "08:00",
    "player_count": 4,
    "players": { "local": 2, "inbound": 2 },
    "total_amount": 7200000,
    "source": "api_partner"
  },
  "message": "Reservation created successfully.",
  "http_code": 201
}
GET /api/reservations/list.php reservations:read

Gets the reservation list (only reservations you created). Supports pagination.

ParameterTypeRequiredDescription
golf_club_idintegerOptionalFilter by golf club ID
statusintegerOptionalStatus code (1=requested, 2=club confirming, 3=payment requested, 4=paid/confirmed, 5=rescheduling, 6=cancel requested, 7=cancelling(no refund), 8=cancelling(refund), 9=completed, 10=completed(cancelled))
date_fromstringOptionalPlay date start (YYYY-MM-DD)
date_tostringOptionalPlay date end (YYYY-MM-DD)
pageintegerOptionalPage number (default: 1)
per_pageintegerOptionalItems per page (default: 20, max: 100)
sortstringOptionalSort field (tee_date / created)
orderstringOptionalSort order (asc / desc)
Response example
{
  "success": true,
  "data": {
    "reservations": [
      {
        "id": 500,
        "golf_club_id": 1,
        "golf_club_name": "Sample Golf Club",
        "customer_name": "Taro Tanaka",
        "tee_date": "2026-03-15",
        "tee_time": "08:00",
        "player_count": 4,
        "players": { "local": 2, "inbound": 2 },
        "total_amount": 7200000,
        "status": "1.requested",
        "status_code": 1,
        "source": "api_partner",
        "external_ref": "EXT-2026-00500",
        "created_at": "2026-03-01 10:00:00"
      }
    ],
    "pagination": {
      "page": 1, "per_page": 20, "total_count": 1,
      "total_pages": 1, "has_next": false, "has_prev": false
    }
  },
  "message": "",
  "http_code": 200
}
GET /api/reservations/detail.php reservations:read

Gets reservation details. Specify either id or external_ref.

ParameterTypeRequiredDescription
idintegerone ofGOVIGO reservation ID
external_refstringone ofPartner reference ID
Response example
{
  "success": true,
  "data": {
    "reservation": {
      "id": 500,
      "golf_club_id": 1,
      "golf_club_name": "Sample Golf Club",
      "plan_id": 10,
      "plan_name": "Weekday Plan",
      "customer_name": "Taro Tanaka",
      "customer_email": "[email protected]",
      "customer_phone": "090-1234-5678",
      "tee_date": "2026-03-15",
      "tee_time": "08:00",
      "player_count": 4,
      "players": { "local": 2, "inbound": 2 },
      "total_amount": 7200000,
      "remark": null,
      "options": { "rental_club": 2, "rental_shoes": 0 },
      "status": "1.requested",
      "status_code": 1,
      "source": "api_partner",
      "external_ref": "EXT-2026-00500",
      "created_at": "2026-03-01 10:00:00",
      "updated_at": "2026-03-01 10:00:00",
      "attendees": [
        { "name": "Hanako Sato", "email": null, "phone": null }
      ]
    }
  },
  "message": "",
  "http_code": 200
}
POST /api/reservations/update.php reservations:write

Updates the reservation. Only the specified fields are updated. If players / options change, total_amount is recalculated on the server. players follows the same interpretation as reservation creation: omitted types are treated as 0 players (always specify counts for all types). For real-time bookings, changing the total number of players also adjusts the golf course inventory. Cancelled (status=6-8) / completed (status=9,10) reservations cannot be updated.

ParameterTypeRequiredDescription
reservation_id / external_refinteger/stringRequired (one of)Reference key of the target reservation
playersobjectOptionalPlayer counts by price type {local?, inbound?} (recalculates total; omitted types are treated as 0)
optionsobjectOptionalRentals etc. (recalculates amount on change)
customer_name / customer_email / customer_phonestringOptionalBooker info
tee_datestringOptionalPlay date(YYYY-MM-DD). Real-time bookings (those with tee_time_slot_id) cannot be changed (cancel and re-create instead)
remarkstringOptionalRemark
attendeesarrayOptionalCompanions (full replacement if specified)
Request example
POST /api/reservations/update.php HTTP/1.1
Host: en.govigolf.comX-API-Key: YOUR_API_KEY
Content-Type: application/json

{
  "external_ref": "EXT-2026-00500",
  "players": { "local": 2, "inbound": 1 },
  "remark": "1 person cancelled"
}
Response example
{
  "success": true,
  "data": {
    "reservation_id": 500,
    "golf_club_id": 1,
    "plan_id": 10,
    "player_count": 3,
    "players": { "local": 2, "inbound": 1 },
    "total_amount": 5350000,
    "status": "1.requested",
    "status_code": 1,
    "external_ref": "EXT-2026-00500",
    "updated_at": "2026-03-02 09:00:00"
  },
  "message": "Reservation updated.",
  "http_code": 200
}
POST /api/reservations/cancel.php reservations:write

Cancels the reservation. Specify reservation_id or external_ref. Like member bookings, it is accepted as a cancellation request (status=6) and finalized by staff. Real-time slot inventory is restored when the cancellation is finalized (not at request time). Reservations already cancelling (status=6-8) / completed (status=9,10) cannot be cancelled.

ParameterTypeRequiredDescription
reservation_id / external_refinteger/stringRequired (one of)Reference key of the target reservation
reasonstringOptionalCancellation reason
Request example
POST /api/reservations/cancel.php HTTP/1.1
Host: en.govigolf.comX-API-Key: YOUR_API_KEY
Content-Type: application/json

{
  "external_ref": "EXT-2026-00500",
  "reason": "Due to schedule change"
}
Response example
{
  "success": true,
  "data": {
    "reservation_id": 500,
    "status": "cancel_requested",
    "status_code": 6,
    "cancelled_at": "2026-03-02T10:00:00+07:00"
  },
  "message": "Reservation cancelled successfully.",
  "http_code": 200
}

Webhook

A mechanism to notify your system of status changes for reservations you (the partner) created. All reservations made with your API key are covered, regardless of golf club (no golf_club_id needed). Notifications are sent via HTTPS POST when events occur.

Available events:
reservation.created (created) / reservation.updated (content change: players, options, etc.) / reservation.status_changed (status transition) / reservation.cancelled (cancellation) / *(all events)
Difference between reservation.cancelled and reservation.status_changed:
reservation.status_changed fires on every status transition (payload includes old_status / new_status). reservation.cancelled is a convenience event additionally fired when the reservation reaches a confirmed cancellation (status 9 / 10) — it does not fire on a cancellation request (status 6), since a request can still be rejected. So a confirmed cancellation fires both. To track cancellations from the request stage, watch reservation.status_changed for new_status=6.
Status list (status / *_status_label)
CodeLabelDescription
0applyingApplying (tentative)
1confirmingConfirming (just after booking)
2confirmedConfirmed
3payment_waitingAwaiting payment
4paidPaid
5completedCompleted (played)
6cancel_requestedCancellation requested (pending)
7cancel_rejectedCancellation rejected
8refundedRefunded
9cancelledCancelled
10cancelledCancelled (deactivated)
POST /api/webhooks/subscribe.php webhooks:write

Registers a Webhook receiving URL. Delivery scope is automatically limited to reservations you created.

ParameterTypeRequiredDescription
urlstringRequiredWebhook receiving URL (HTTPS required)
eventsarrayRequiredEvent types to subscribe
Request example
POST /api/webhooks/subscribe.php HTTP/1.1
Host: en.govigolf.comX-API-Key: YOUR_API_KEY
Content-Type: application/json

{
  "url": "https://your-system.com/webhook/govigo",
  "events": ["reservation.created", "reservation.status_changed", "reservation.cancelled"]
}
Response example (201 Created)
{
  "success": true,
  "data": {
    "subscription_id": 10,
    "url": "https://your-system.com/webhook/govigo",
    "events": ["reservation.created", "reservation.status_changed", "reservation.cancelled"],
    "secret": "a1b2c3d4e5f6..."
  },
  "message": "",
  "http_code": 201
}
secret is used for HMAC verification of the Webhook payload. Verify X-Webhook-Signature: sha256=... on receipt to confirm authenticity. The reservation.status_changed payload includes old_status / new_status (numeric) and old_status_label / new_status_label.
GET /api/webhooks/list.php webhooks:read

Gets the list of registered Webhooks (no parameters).

Response example
{
  "success": true,
  "data": {
    "count": 1,
    "subscriptions": [
      {
        "id": 10,
        "url": "https://your-system.com/webhook/govigo",
        "events": ["reservation.created", "reservation.status_changed"],
        "is_active": true,
        "created_at": "2026-03-01 10:00:00",
        "updated_at": "2026-03-01 10:00:00"
      }
    ]
  },
  "message": "",
  "http_code": 200
}
POST /api/webhooks/unsubscribe.php webhooks:write

Unsubscribes a registered Webhook.

ParameterTypeRequiredDescription
subscription_idintegerRequiredSubscription ID to unsubscribe
Request example
POST /api/webhooks/unsubscribe.php HTTP/1.1
Host: en.govigolf.comX-API-Key: YOUR_API_KEY
Content-Type: application/json

{ "subscription_id": 10 }
POST /api/webhooks/test.php webhooks:write

Sends a test notification to a registered Webhook to verify URL connectivity and signature verification.

ParameterTypeRequiredDescription
subscription_idintegerRequiredSubscription ID to test
Request example
POST /api/webhooks/test.php HTTP/1.1
Host: en.govigolf.comX-API-Key: YOUR_API_KEY
Content-Type: application/json

{ "subscription_id": 10 }