GolfAPI Driver Catalog — API Reference
Unlisted reference page for evaluation. Version 2026-09-08. 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 | 7 — Callaway (11), TaylorMade (8), Cobra (8), Titleist (4), PING (4), PXG (4), Srixon (3) |
| Model years present | 2025, 2026 |
| Model years absent | 2020–2024 |
| Mini-drivers | Routed and entitled, catalog small |
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"
There is no discovery surface. / returns 404. There is no /openapi.json, no /docs, no .well-known descriptor, and no hypermedia index. This page is the only description of the API's shape. If you are evaluating how well this API serves automated consumers, that fact is evidence.
Routes
| Route | Auth | Expected |
|---|---|---|
GET /health | none | 200 {"ok":true,"db":"ok"} |
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 | 503 — routed, catalog unpublished |
GET /shafts | required | 503 — routed, catalog unpublished |
GET /grips | required | 503 — routed, catalog unpublished |
A 503 on the equipment routes is the designed pre-release state, not an outage. The route exists and the key is entitled; the catalog has not been published.
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. |
manufacturer | slug | e.g. ping, callaway, taylormade, titleist, cobra, pxg, srixon |
family | slug | e.g. g440 |
profile | draw | neutral | low-spin | |
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 | |
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. |
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. Club-builder depth only. |
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, limit out of range, malformed or edited cursor, invalid enum or date. |
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 | Routed resource whose catalog is unpublished, or a version pin requesting an unreleased catalog. |
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
- Every response carries a
Golfapi-Versionheader. Current:2026-09-08. - Send
Golfapi-Version: <date>to pin. Pinning to a date whose catalog is unreleased returns503. - Responses carry an
ETag. A matchingIf-None-Matchreturns304. - 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, no
/index, no machine-readable discovery of any kind. /shafts,/grips,/weight-systemsreturn503by design; catalogs unpublished./hosel-systemsrequires the Club-builder entitlement.- List responses are wrapped in
data; detail responses are not.
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.