GolfAPI Driver Catalog — API Reference
Unlisted reference page for evaluation. API version 2026-09-15; the published catalog serves the 2026-09-08 shape. Last verified against production 2026-09-17.
This page contains no credentials. An API key is supplied separately by whoever gave you this link. Every endpoint except /health requires one.
A key is issued as two values, and only one of them authenticates. If you were handed a pair, send the second kind. These two names are canonical — store each value under the name in the first column and every example on this page will work as written:
| Store it as | Shape | Use |
|---|---|---|
GOLFDEV_API_KEY_ID | key_ + 26 uppercase alphanumerics, e.g. key_01M2R6HM2CWN2AJF9K3SPX0S6Q | Not a credential. It names the key for revocation and support. Sending it as a bearer token always returns 401. |
GOLFDEV_API_KEY | sk_live_ or sk_test_ + 32 lowercase hex, e.g. sk_live_ followed by 9f2c4a1e… out to 32 characters | The credential. This is the value that goes in the Authorization header. It is shown once at issue and stored only as a hash. |
sk_live_ reads production; sk_test_ reads a fixed sandbox fixture. The mode prefix is part of the secret, not a separate parameter.
What this API is
A catalog of golf drivers from major manufacturers. It exists to answer two questions: which club is this, and what exact build did the manufacturer offer.
It is deliberately positioned for programmatic consumers, including AI agents, rather than for a human browsing a website.
Product tiers
| Tier | Status | What it adds |
|---|---|---|
| Basic | Public, live now | Club identity and core specs: name, manufacturer, family, lofts, hands, head volume, adjustability flags, release date, intro price. |
| Club-builder | Gated, selected keys | Exact-build depth: variants, hosel systems and settings, stock shafts, grips, weights, head geometry, compatibility. |
| Full | Not built | Complete catalog plus insight fields, undefined pending customer demand. |
The omission rule
This is the central data contract and it matters when you evaluate the responses. An absent field means "not verified." It never means zero, unknown, or pending. A value only appears after a human has signed off on it. Nulls and placeholders are not used, so you will see objects with fields simply missing rather than set to null.
A real 0 or false is always returned, never dropped. "adjustable_weight": false means verified as not adjustable.
What is actually in the catalog right now
Measured against production on 2026-09-17:
| Metric | Value |
|---|---|
| Driver objects | 42 |
| Manufacturers | Drivers: 7 — Callaway (11), TaylorMade (8), Cobra (8), Titleist (4), PING (4), PXG (4), Srixon (3). Mini-drivers add Tour Edge, which no driver carries. |
| Model years present | 2025, 2026 |
| Model years absent | 2020–2024 |
| Mini-drivers | 3 objects: Titleist, Tour Edge, Callaway. Mini-driver records carry no profile, so ?profile= is not a filter on that route and is rejected as an unknown parameter. |
The stated launch target is complete major-manufacturer coverage from model year 2020 to present. The live catalog does not meet that yet. Records exist internally for roughly 101 drivers; 42 have passed sign-off and are published. The rest are omitted rather than shipped unverified.
Connecting
| Base URL | https://clubapi.golfapi.dev |
|---|---|
| Auth | Authorization: Bearer $GOLFDEV_API_KEY, header only, never a query parameter |
| Secret format | sk_live_ or sk_test_ followed by 32 lowercase hex characters. The key_… id (GOLFDEV_API_KEY_ID) is not the secret. |
| Format | JSON. No request body on any endpoint; everything is query parameters. |
curl -H "Authorization: Bearer $GOLFDEV_API_KEY" \
"https://clubapi.golfapi.dev/drivers?manufacturer=ping&limit=2"
Discovery is one document. GET / is unauthenticated and returns a JSON index: every routed resource with its path and the entitlement it needs, the auth scheme, the current version, and the health path. It is built from the same registries the router is, so a route cannot exist without appearing there.
There is still no OpenAPI document and no .well-known descriptor. Field-level shapes live on this page only.
Routes
| Route | Auth | Expected |
|---|---|---|
GET / | none | 200 resource index |
GET /health | none | 200 {"ok":true,"db":"ok"}, plus degraded when a later shape is unpublished |
GET /drivers | required | 200 list |
GET /drivers/{slug|id} | required | 200 single object |
GET /mini-drivers | required | 200 list |
GET /mini-drivers/{slug|id} | required | 200 single object |
GET /hosel-systems | required | 403 without Club-builder entitlement |
GET /weight-systems | required | 403 without the weight-systems entitlement; 503 with it — routed, catalog unpublished |
GET /shafts | required | 403 without the shafts entitlement; 503 with it — routed, catalog unpublished |
GET /grips | required | 403 without the grips entitlement; 503 with it — routed, catalog unpublished |
The equipment routes are entitlement-gated like every other resource. No key issued for evaluation carries them, so what you will see is 403, not 503. A key that does carry the entitlement gets 503 until the catalog is published: that is the designed pre-release state, not an outage. Branch on the status you actually receive — 403 means buy the entitlement, 503 means retry later.
/health and what it means
/health is the only unauthenticated endpoint and it is not a liveness check for the whole service. It answers 503 only when nothing can be served: the database is unreachable or unbound, a registered club type's table is missing, or the live build holds no document for the default shape.
A later API version that has been migrated but not yet materialized into a build is reported as degraded at 200 — a request that pins that version gets 503, every other request is served normally. Do not page on degraded.
GET /health
{ "ok": true, "db": "ok", "degraded": ["drivers@2026-09-15"], "request_id": "req_…" }
Query parameters
List routes
| Parameter | Accepts | Notes |
|---|---|---|
limit | 1–100 | Default 20. Out of range is 400. |
cursor | opaque string | Use page.next from the previous response unchanged. Edited cursors are 400. |
view | basic | expanded | Default basic. Asking for expanded without the Club-builder entitlement is 403, not a thinned document. |
fields | comma-separated | Restricts returned fields. An unknown name for the active view is 400. id is always returned, whether or not you name it, so a projection is always joinable back to a record. |
manufacturer | slug | Drivers: callaway, taylormade, cobra, titleist, ping, pxg, srixon. Mini-drivers add tour-edge. A slug no published club of any type carries is 400; a slug that exists on another club type is an honest 200 with "data": []. |
family | slug | e.g. g440. Same rule as manufacturer: unknown to the whole catalog is 400, not an empty page. |
profile | draw | neutral | low-spin | Drivers only. Not a parameter on /mini-drivers. |
hand | rh | lh | |
loft | number | Degrees, one decimal place allowed. |
adjustable_hosel | true | false | Literal strings only. |
adjustable_weight | true | false | Literal strings only. |
weight_system | fixed | movable | interchangeable | reversible | reversible exists only from version 2026-09-15. |
released_after | ISO date | YYYY-MM-DD |
released_before | ISO date | YYYY-MM-DD |
q | 1–200 chars | Free-text search. Control characters rejected. |
hosel_system | hos_ + 26 chars | Filter by hosel system id. The 26 characters are Crockford base32: digits and uppercase letters excluding I, L, O and U. Club-builder only. |
Detail routes
| Parameter | Accepts | Notes |
|---|---|---|
view | basic | expanded | Defaults to expanded here. A key without Club-builder silently receives the basic representation; explicitly asking for expanded is 403. |
fields | comma-separated | Validated against the active view. |
variants | all | rh | lh | loft | Default all. Shapes the expanded document only; on a basic response it changes neither the body nor the ETag. |
Response shapes
List — wrapped
GET /drivers?limit=1&manufacturer=ping
{
"data": [
{
"id": "clb_01M21MDKGYVNWAT3EFJ9XCE0PC",
"slug": "g440-k-driver",
"name": "G440 K Driver",
"manufacturer": { "slug": "ping", "name": "PING" },
"family": { "slug": "g440", "name": "G440" },
"profile": "neutral",
"released": "2026-01-29",
"lofts_deg": [9, 10.5, 12],
"hands": ["rh", "lh"],
"volume_cc": 460,
"adjustable_hosel": true,
"adjustable_weight": true,
"weight_system": "movable",
"intro_price": [
{ "market": "US", "msrp_minor": 70500, "currency": "USD" }
],
"url": "https://clubapi.golfapi.dev/drivers/g440-k-driver"
}
],
"page": { "limit": 1, "has_more": true, "next": "cur_eyJyIjoi..." }
}
Prices are minor units. 70500 with "currency": "USD" is $705.00.
Detail — not wrapped
GET /drivers/g440-k-driver
{
"id": "clb_01M21MDKGYVNWAT3EFJ9XCE0PC",
"slug": "g440-k-driver",
...
"url": "https://clubapi.golfapi.dev/drivers/g440-k-driver"
}
The detail route returns the object at the top level. It is not wrapped in data the way list responses are. Clients must handle both shapes.
Basic vs Club-builder depth
The same club, fetched with two different keys:
| Key | Fields returned |
|---|---|
| Basic | id, slug, name, manufacturer, family, profile, released, lofts_deg, hands, volume_cc, adjustable_hosel, adjustable_weight, weight_system, intro_price, url |
| Club-builder | the above plus head, hosel, notes, _links, variant detail |
Errors
Every error uses the same envelope, with a request_id and an optional param naming the offending input.
{
"error": {
"type": "invalid_request",
"message": "Invalid value for 'limit': must be between 1 and 100.",
"status": 400,
"request_id": "req_01M2R68V6A5K9J0A5HQWJB2N2P",
"param": "limit"
}
}
| Status | When |
|---|---|
400 | Unknown fields name, unknown parameter, limit out of range, malformed or edited cursor, invalid enum or date, a manufacturer or family slug no published club carries. |
401 | Missing, malformed, or unaccepted key. The three are distinct messages. "Malformed API key. Expected 'sk_live_' or 'sk_test_' followed by 32 hex characters." means the value is not a secret at all — most often the key_… id. A well-formed key that is not accepted says so instead, without confirming whether any particular key exists. |
403 | Known key without entitlement to the resource or to Club-builder depth. |
404 | Single fetch of an unknown record. An empty list is 200 with "data": [], never 404. |
503 | Entitled but unpublished catalog, or a version pin whose shape the published build does not hold. The message names the shape and sets "param": "Golfapi-Version" when you sent the header. |
Observed error messages: "Unknown field 'nope' for view 'basic'." · "Invalid cursor. Use the 'next' value from a previous page unchanged." · "No driver with slug 'no-such-club'."
Versioning, caching, redirects
- The current API version is
2026-09-15. The published build does not yet hold that shape, so the catalog serves2026-09-08. Golfapi-Versionon a response names the shape you were served, which is the newest shape at or before the version you asked for. On a response produced before the request resolved to a shape — a401, a405,/health,/— it names the API's current version instead. Those two are different numbers today, and that is why.- Send
Golfapi-Version: <date>to pin. A date before2026-09-08is400. A date whose shape the published build does not hold is503, withRetry-After; pinning a far-future date resolves to the current shape and gets the same answer, so it is not a poll worth running. - Responses carry an
ETag. A matchingIf-None-Matchreturns304, and so doesIf-None-Match: *. A list tag is a SHA-256 of the response body; a detail tag is"{id}-{modified epoch}"with a suffix naming the representation when the request shaped it (view,fields,variants,market). Treat both as opaque. - A
401carriesWWW-Authenticate: Bearer. It carries noRateLimit-*headers: the limit belongs to a key, and an unaccepted request has none. - A changed slug returns
301to the current path. Anidnever redirects — ids are stable, slugs are not. - Rate limit: 60 requests per window. Check
ratelimit-limitandratelimit-remainingresponse headers.
Known gaps
Stated plainly so evaluation focuses on what matters rather than rediscovering these:
- No model years 2020–2024. The catalog starts at 2025.
- 42 of roughly 101 known driver records are published.
- No OpenAPI document.
GET /lists resources and paths but not field-level shapes. /shafts,/grips,/weight-systemsare entitlement-gated and unpublished:403for the keys issued so far,503for an entitled key./hosel-systemsrequires the Club-builder entitlement.- List responses are wrapped in
data; detail responses are not. - The
2026-09-15shape is migrated but not materialized, so every request pinned to it is503and/healthreportsdegraded.
Testing etiquette
This is a live production service on a small budget. Probe correctness and edge cases freely; keep total requests modest and do not load-test, benchmark throughput, or run concurrent floods. Rate limiting will stop you at 60 per window regardless.