Skip to content
booradPublic
forked from n8io/ghin

About

⛳ An unofficial wrapper for the GHIN api

Resources

Stars

2 stars

Watchers

2 watching

Forks

 
 

Repository files navigation

ghin

⛳ An unofficial wrapper for the GHIN api

npm version Downloads codecov Issues License

This TypeScript library provides a convenient and easy-to-use API wrapper for accessing the Golfer Handicap Index Network (GHIN) api unofficially. It allows you to interact with GHIN data, retrieve golfer handicaps, scores, and perform various operations related to golf handicaps.

API Access

This library is a client only — it ships no GHIN code or data, and it does not bypass authentication. You must supply your own credentials.

It also implements no part of the World Handicap System — no handicap formula, no Course Rating System. Handicap indexes and course/playing handicaps are returned by the USGA's centralized computation service; this library only transports them.

Bring credentials you are authorized to use. Vendor access is granted through the USGA's Golfer Product Access (GPA) program, under a GPA and API agreement between the vendor and the USGA — that agreement, not the consumer GHIN Terms of Service, governs API use, and each account carries its own GPA entitlements. If you aren't a GPA Technology Vendor, confirm your intended use is permitted before pointing this at the API. Your compliance with USGA's terms is your responsibility.

About This Fork

This is a fork of n8io/ghin, originally created by Nate Clark. This fork includes additional features and improvements.

Features

  • Retrieve golfer handicap information.
  • Retrieve course and playing handicaps for golfers.
  • Search for golfers by name, ID, or other criteria.
  • Access golfer scoring history.
  • And more!

Installation

To use this library in your TypeScript project, you can install it via npm or bun:

npm install @spicygolf/ghin
# or
bun add @spicygolf/ghin

Usage

Here's a quick example of how to use this library:

import { GhinClient } from '@spicygolf/ghin'

// Initialize the client. The constructor is the one part of the surface that
// still throws — a bad config is a boot-time programmer error, not a runtime
// API failure, so it raises `ConfigurationError` rather than returning a Result.
const ghin = new GhinClient({
  password: process.env.GHIN_PASSWORD,
  username: process.env.GHIN_USERNAME,
})

// Get a golfer's handicap. Any membership status: an inactive or lapsed member
// has a readable index and comes back here, so read `status` off the record if
// that matters to you. `undefined` means no usable row came back — an unknown
// GHIN number, or one dropped by the schema; wire `onDegraded` to tell them apart.
const ghinNumber = 1234567
const result = await ghin.handicaps.getOne(ghinNumber)

// Every method resolves to a `Result<T, GhinError>` and never rejects, so
// check the error case before touching the value.
if (result.isErr()) {
  // a `GhinError` — `code`, `message`, `cause`, and `statusCode` when the
  // failure came off the wire
  console.error(result.error.code, result.error.statusCode, result.error.message)
} else {
  // `undefined` when no golfer matches, and `handicap_index` is `null`
  // for a golfer with no established index (GHIN sends `"NH"`, or `"WD"` when withdrawn, on the wire)
  const golfer = result.value
  console.log(`Golfer ${ghinNumber} has a handicap of ${golfer?.handicap_index}`)
}

Looking up many golfers at once

golfers.getMany fetches a list of GHIN numbers in one batched call instead of one call per golfer — useful for a picker list, or a nightly Handicap Index refresh, against GHIN's per-account rate limit.

const result = await ghin.golfers.getMany([1234567, 7654321])

if (result.isOk()) {
  const { golfers, missing } = result.value
  console.log(`${golfers.length} found, ${missing.length} unknown to GHIN`)
}

// Only golfers whose record moved since a date — a delta refresh
const changed = await ghin.golfers.getMany(storedGhinNumbers, { updated_since: '2026-08-01' })

Three things it handles that a raw golfers.search leaves to you:

  • GHIN pages by row, not by golfer. A golfer appears once per club affiliation, so 100 GHIN numbers can be ~180 rows and there is no total to page against. getMany batches the numbers and pages each batch to exhaustion — 120 GHIN numbers is 3 HTTP calls, and 12 is one.
  • Multi-club golfers arrive more than once. Rows are deduplicated to one per GHIN number, preferring the golfer's home club. The handicap fields are identical across a golfer's rows; only the club differs.
  • Unknown GHIN numbers vanish silently. They come back in missing rather than simply not being there. status defaults to 'Active', so inactive golfers land in missing too — as does anyone whose only rows failed the schema, so wire onDegraded if you need to tell that apart. Pass { status: null } to cover every status in one call — that omits the parameter, which is GHIN's "both". ('All' is not: measured against api-uat on 2026-09-02, golfer 2890015 returns 0 rows for status=Active and status=All, and 3 rows for status=Inactive, no status at all, or an empty status=.) getOne never filters.

golfers is ordered to match the numbers you asked for, with duplicates collapsed. An empty list is a ValidationError, not an empty result.

Results, not exceptions

Every method on GhinClient returns Promise<Result<T, GhinError>> from neverthrow — already a dependency of this package, so there is nothing extra to install — and webhooks.iterateUndelivered yields one Result per envelope. None of them reject; a failure arrives as an Err carrying a GhinError subclass (AuthenticationError, NetworkError, RateLimitError, ValidationError, CacheError) with code, statusCode and cause intact, plus retryAfter on RateLimitError and field on ValidationError. new GhinClient(config) is the sole exception and still throws ConfigurationError.

"Not found" is an Ok, not an Err. handicaps.getOne and golfers.getOne return ok(undefined) when no golfer matches the GHIN number — "no such golfer" is a normal answer from GHIN, not a failure — so result.isErr() stays false and the undefined shows up in result.value. This is the one place a reader might reasonably expect an Err; treat ok(undefined) as the not-found signal.

TODOs

  • 🔑 Add client authentication
  • ♻️ Add client token auto-refresh
  • 💸 Add configurable cache client
  • ✨ Add golfer search
  • ✨ Add golfer scores fetching
  • ✨ Add course handicap fetching
  • 💄 Enforce code style for consistency
  • ✨ Add course search
  • ✨ Add course details fetching
  • 🧪 Test coverage all the things
  • 📘 Autogenerated documentation

Contributing

We welcome contributions from the community. If you'd like to contribute to this project, please follow these steps:

  1. Fork the repository.
  2. Create a new branch for your feature or bug fix.
  3. Make your changes and write tests if applicable.
  4. Commit your changes and push them to your fork.
  5. Open a pull request to the main repository.

License

MIT License

Copyright (c) 2023 Nate Clark
Copyright (c) 2025 Brad Anderson

Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

About

⛳ An unofficial wrapper for the GHIN api

Resources

Stars

2 stars

Watchers

2 watching

Forks

Releases

Packages

Contributors

Languages