{
  "$schema": "./_mapping.schema.json",
  "provider": "golfnow",
  "name": "GolfNow (Affiliate & Partner API 2.1)",
  "vertical": "golf",
  "verified_on": "2026-10-13",
  "docs": [
    {
      "title": "GolfNow API 2.1 home (Client Authentication, Getting Tee Times)",
      "url": "https://api.gnsvc.com/",
      "note": "Public. Sandbox https://sandbox.api.gnsvc.com/rest."
    },
    {
      "title": "Service operations list (WCF help, all endpoints)",
      "url": "https://api.gnsvc.com/rest/help",
      "note": "Public, auto-generated. Each operation page has JSON/XML examples and XSD schemas but almost no prose semantics."
    },
    { "title": "GetTeeTimesByFacility", "url": "https://api.gnsvc.com/rest/help/operations/GetTeeTimesByFacility" },
    { "title": "GetFacility", "url": "https://api.gnsvc.com/rest/help/operations/GetFacility" },
    { "title": "GetTeeTimeInvoice", "url": "https://api.gnsvc.com/rest/help/operations/GetTeeTimeInvoice" },
    { "title": "AddReservation", "url": "https://api.gnsvc.com/rest/help/operations/AddReservation" },
    { "title": "GetReservation", "url": "https://api.gnsvc.com/rest/help/operations/GetReservation" },
    { "title": "Reserving a Tee Time", "url": "https://api.gnsvc.com/pages/ReservingATeeTime.aspx" },
    { "title": "Affiliate Payments", "url": "https://api.gnsvc.com/pages/AffiliatePayments.aspx" },
    { "title": "FAQ", "url": "https://api.gnsvc.com/pages/Faq.aspx" },
    {
      "title": "Affiliate & Partner API portal (Getting Started)",
      "url": "https://affiliate.gnsvc.com/getting-started",
      "note": "Tutorials, workbench, request-access and status pages need a GolfNow login."
    }
  ],
  "access": {
    "public_docs": true,
    "how_to_get_access": "Sign in at affiliate.gnsvc.com with a GolfNow account and request access ('access is restricted'). Once approved, create applications to get client_id, client_secret and application_secret. Booking with our own payment (AffiliatePayment) also needs GolfNow business approval, an agreed revenue split, an inventory channel set up for affiliate payments, per-credential enablement and IP allow-listing.",
    "auth": "Per api.gnsvc.com: 'Simple Authentication' (UserName + Password request headers) or an Authorization signature: Base64(HMAC-SHA256(key=ClientApplicationSecret, UserName + Base64(SHA1(Password)) + UTC unix timestamp)), sent as `Authorization` with `UserName` and `Timestamp` headers (valid 1 minute). Customer-scoped calls also need a `CustomerToken` header. The affiliate portal says 'OAUTH 2.0'; the reference pages don't show an OAuth flow.",
    "base_url": "https://api.gnsvc.com/rest (sandbox: https://sandbox.api.gnsvc.com/rest; operations list shows https://api.gnsvc.com/GolfNowAPI.svc/rest)"
  },
  "splitclub": {
    "integration_provider": null,
    "recommended_path": "poll_and_post",
    "note": "GolfNow isn't on the inbound hook's list and the public reference has no webhooks. This is GolfNow's distribution (marketplace) API: SplitClub would act as a GolfNow affiliate on a channel, poll tee times per facility, post them as Offerings, and book through AddReservation with an AffiliatePayment once a group funds. The course-side GolfNow Business / G1 tee-sheet API (what a venue itself runs) has no public docs."
  },
  "objects": {
    "Offering": {
      "source": "GET /channel/{channelId}/facilities/{facilityId} (GetFacility). One Offering per facility (× HoleCount offered in its rates).",
      "kind": "tee_time",
      "source_url": "https://api.gnsvc.com/rest/help/operations/GetFacility",
      "note": "Field names below come from the operation's JSON example and XSD; there are no prose descriptions.",
      "fields": [
        { "to": "/external_id", "from": "Facility.ID (+ rate HoleCount)", "transform": "compose 'golfnow:{facilityId}:{holes}'", "source_url": "https://api.gnsvc.com/rest/help/operations/GetFacility" },
        { "to": "/kind", "from": "const:tee_time" },
        { "to": "/category", "from": "const:golf" },
        { "to": "/title", "from": "Facility.Name + HoleCount", "source_url": "https://api.gnsvc.com/rest/help/operations/GetFacility" },
        { "to": "/description", "from": "Facility.Description", "source_url": "https://api.gnsvc.com/rest/help/operations/GetFacility" },
        { "to": "/media/0/url", "from": "Facility.FullSizeImagePath", "transform": "use only if https", "source_url": "https://api.gnsvc.com/rest/help/operations/GetFacility", "unverified": true },
        { "to": "/policy/notes", "from": "Facility.TeeTimePolicy", "transform": "free text; can't be turned into cancellation steps", "source_url": "https://api.gnsvc.com/rest/help/operations/GetFacility", "unverified": true },
        { "to": "/metadata/golfnow_facility_id", "from": "Facility.ID", "source_url": "https://api.gnsvc.com/rest/help/operations/GetFacility" },
        { "to": "/availability/timezone", "from": "Facility.TimeZoneOffset (decimal) + Facility.Address", "transform": "TimeZoneOffset is a bare decimal (units undocumented, presumably hours) and not an IANA zone. Look up the IANA zone from Address.Latitude/Longitude or StateProvinceCode.", "source_url": "https://api.gnsvc.com/rest/help/operations/GetFacility", "unverified": true }
      ]
    },
    "PriceTier": {
      "source": "TeeTimeCollection.TeeTimes[].Rates[] (TeeTimeRate) from GetTeeTimesByFacility. Pointers are inline on the Offering: /tiers/N/…",
      "source_url": "https://api.gnsvc.com/rest/help/operations/GetTeeTimesByFacility",
      "note": "GolfNow prices are per tee-time rate (RateName, RateSetId, TeeTimeRateID), not rule-based. Make one tier per RateSetId (e.g. 'Hot Deal', '18 Holes w/ Cart') with a representative amount, and carry the per-slot price as a tier_override. days / time_window aren't exposed.",
      "fields": [
        { "to": "/tiers/0/external_id", "from": "Rates[].RateSetId + HoleCount", "transform": "compose 'golfnow-rs:{rateSetId}:{holes}'", "source_url": "https://api.gnsvc.com/rest/help/operations/GetTeeTimesByFacility", "unverified": true },
        { "to": "/tiers/0/label", "from": "Rates[].RateName", "source_url": "https://api.gnsvc.com/rest/help/operations/GetTeeTimesByFacility" },
        { "to": "/tiers/0/amount", "from": "Rates[].SinglePlayerPrice.GreensFees.Value", "transform": "Money.Value is xs:decimal in major units → integer minor units (× 100, round half up). SinglePlayerPrice.TotalDue.Value is the all-in alternative.", "source_url": "https://api.gnsvc.com/rest/help/operations/GetTeeTimesByFacility" },
        { "to": "/tiers/0/currency", "from": "Rates[].SinglePlayerPrice.GreensFees.CurrencyCode", "transform": "lowercase (e.g. 'USD' → 'usd'); falls back to Facility.CurrencyCode", "source_url": "https://api.gnsvc.com/rest/help/operations/GetTeeTimesByFacility" },
        { "to": "/tiers/0/unit", "from": "const:per_person", "transform": "the name SinglePlayerPrice implies per player", "source_url": "https://api.gnsvc.com/rest/help/operations/GetTeeTimesByFacility" },
        { "to": "/tiers/0/fees/0", "from": "Rates[].SinglePlayerPrice.TransactionFeesTotal.Value", "transform": "{type:'service', label:'GolfNow fee', amount: decimal → cents, included:false}", "source_url": "https://api.gnsvc.com/rest/help/operations/GetTeeTimesByFacility", "unverified": true },
        { "to": "/tiers/0/fees/1", "from": "Rates[].SinglePlayerPrice.SalesTaxTotal.Value", "transform": "{type:'tax', amount: decimal → cents, included:false}", "source_url": "https://api.gnsvc.com/rest/help/operations/GetTeeTimesByFacility", "unverified": true },
        { "to": "/tiers/0/max_party", "from": "Rates[].GroupSizeLimit", "transform": "xs:unsignedByte; meaning not documented in prose", "source_url": "https://api.gnsvc.com/rest/help/operations/GetTeeTimesByFacility", "unverified": true }
      ]
    },
    "Availability": {
      "source": "GET /channel/{channelId}/facilities/{facilityId}/tee-times?date-min&date-max&holes&players&time-min&time-max&take&skip (GetTeeTimesByFacility). Pointers are inline on the Offering: /availability/…",
      "source_url": "https://api.gnsvc.com/rest/help/operations/GetTeeTimesByFacility",
      "note": "Only tee times GolfNow distributes on our channel are visible, not the whole tee sheet. Searching with players=N returns only times that take N players ('Search for tee times with the number of players that you intend to book').",
      "fields": [
        { "to": "/availability/slots/0/external_id", "from": "TeeTimes[].DisplayRate.TeeTimeRateID", "transform": "Int64 → string. The id AddReservation books against.", "source_url": "https://api.gnsvc.com/rest/help/operations/GetTeeTimesByFacility" },
        { "to": "/availability/slots/0/starts_at", "from": "TeeTimes[].PlayDateUtc + TeeTimes[].Time", "transform": "PlayDateUtc is xs:dateTime serialised as WCF '/Date(ms+0000)/'; Time is an undocumented-format string. Combine into course-local time and apply the facility's IANA zone → ISO 8601 with offset. Confirm against a sandbox response first.", "source_url": "https://api.gnsvc.com/rest/help/operations/GetTeeTimesByFacility", "unverified": true },
        { "to": "/availability/slots/0/capacity", "from": "TeeTimes[].PlayerRule (or Rates[].PlayerRule)", "transform": "PlayerRule is a flags enum (None=0, One=1, Two=2, Three=4, …) of the bookable group sizes, not an open-spot count. capacity = largest group size flagged.", "source_url": "https://api.gnsvc.com/rest/help/operations/GetTeeTimesByFacility", "unverified": true },
        { "to": "/availability/slots/0/tier_overrides/0/amount", "from": "TeeTimes[].DisplayRate.SinglePlayerPrice.GreensFees.Value", "transform": "decimal → integer minor units", "source_url": "https://api.gnsvc.com/rest/help/operations/GetTeeTimesByFacility" },
        { "to": "/availability/slots/0/tier_overrides/0/tier", "from": "TeeTimes[].DisplayRate.RateSetId", "transform": "→ 'golfnow-rs:{rateSetId}:{holes}'", "source_url": "https://api.gnsvc.com/rest/help/operations/GetTeeTimesByFacility", "unverified": true }
      ]
    },
    "Checkout": {
      "source": "Price: GET /channel/{channelId}/facilities/{facilityId}/tee-times/{teeTimeRateId}/invoice?player-count=N (GetTeeTimeInvoice). Book: POST /customers/{email}/reservations (AddReservation, AffiliatePayment). Read: GET /customers/{email}/reservations/{reservationId} (GetReservation). Pointers are into checkout_create.",
      "source_url": "https://api.gnsvc.com/rest/help/operations/GetReservation",
      "note": "FAQ: 'All pricing is for the total number of players on the invoice.' AffiliatePayment carries PaymentAmount, PaymentReferenceNumber (put the SplitClub chk_ id there) and PaymentTimeStamp; confirmation emails are off by default for affiliate payments.",
      "fields": [
        { "to": "/external_booking_id", "from": "Reservation.ReservationID", "transform": "int → 'golfnow:{ReservationID}'. ConfirmationNumber goes in metadata.", "source_url": "https://api.gnsvc.com/rest/help/operations/GetReservation" },
        { "to": "/offering_external_id", "from": "Reservation.Invoice.FacilityID + Invoice.HoleCount", "transform": "rebuild 'golfnow:{facilityId}:{holes}'", "source_url": "https://api.gnsvc.com/rest/help/operations/GetReservation" },
        { "to": "/party_size", "from": "Reservation.Invoice.PlayerCount (or GroupSize)", "source_url": "https://api.gnsvc.com/rest/help/operations/GetReservation" },
        { "to": "/amount", "from": "Invoice.TotalDue.Value", "transform": "Money (decimal major units) for all players → integer minor units. DueOnline vs DueAtCourse split matters for what SplitClub actually collects.", "source_url": "https://api.gnsvc.com/rest/help/operations/GetTeeTimeInvoice" },
        { "to": "/currency", "from": "Invoice.TotalDue.CurrencyCode (or Invoice.CurrencyCode)", "transform": "lowercase", "source_url": "https://api.gnsvc.com/rest/help/operations/GetTeeTimeInvoice" },
        { "to": "/description", "from": "Invoice.RateName + Invoice.Time + Invoice.PlayerCount", "source_url": "https://api.gnsvc.com/rest/help/operations/GetTeeTimeInvoice" },
        { "to": "/pay_by", "from": "Invoice.PlayDateUtc + Invoice.Time", "transform": "→ ISO with offset, minus SplitClub's pay-by lead time (SplitClub policy)", "source_url": "https://api.gnsvc.com/rest/help/operations/GetTeeTimeInvoice", "unverified": true },
        { "to": "/metadata/golfnow_confirmation", "from": "Reservation.ConfirmationNumber", "source_url": "https://api.gnsvc.com/rest/help/operations/GetReservation" },
        { "to": "/metadata/golfnow_tee_time_rate_id", "from": "Invoice.TeeTimeRateID", "source_url": "https://api.gnsvc.com/rest/help/operations/GetTeeTimeInvoice" }
      ]
    }
  },
  "webhooks": {
    "supported": null,
    "events": [],
    "note": "The public operations list (https://api.gnsvc.com/rest/help) has no webhook or callback registration; the only push-related operations are mobile push notifications. Nothing in the public pages says whether partner webhooks exist. tee-time-rate-update-requests (RequestTeeTimeUpdates) is a POST you poll with."
  },
  "gaps": [
    "This is GolfNow's affiliate/distribution API: we see only inventory GolfNow distributes on our channel, not the course's whole tee sheet. The course-side GolfNow Business / G1 tee-sheet API has no public docs.",
    "Reference pages are auto-generated (field names and types only). Semantics of Time (string format), PlayDateUtc (UTC vs local), PlayerRule (allowed group sizes vs open spots), GroupSizeLimit and TimeZoneOffset units aren't stated. Check in the sandbox before relying on them.",
    "Auth is inconsistent between pages: the portal says OAuth 2.0, the API home documents UserName/Password headers or an HMAC Authorization signature.",
    "Paying with our own funds (AffiliatePayment) needs a commercial agreement, an agreed revenue split, IP allow-listing and channel setup with GolfNow.",
    "No rate rules (weekday/weekend/twilight); prices only per tee-time rate, so PriceTier days / time_window can't be filled.",
    "Cancellation policy is free text (Facility.TeeTimePolicy, Invoice.PolicyItems), not structured refund steps.",
    "No webhooks documented; polling only. Rate limits aren't stated in the public pages.",
    "Sandbox only books times ending in specific minutes/cents (see Reserving a Tee Time / FAQ), so tests need those."
  ]
}
