API reference · v1.1

Golf Equipment API docs

Normalized product data, fit scoring, recommendations and comparisons. Every response carries confidence data and list endpoints use cursors.

Authentication

Every request needs an API key. Send it in an x-api-key header, or as a Bearer token in the Authorization header.

During the free beta every key starts fms_test_. Create and manage up to 10 keys per account in the developer console.

x-api-key header
curl https://www.fitmyswing.com/api/v1/health \
  -H "x-api-key: fms_test_..."
Bearer token
curl https://www.fitmyswing.com/api/v1/health \
  -H "Authorization: Bearer fms_test_..."

Rate limits

Limits apply per account: every key on the account shares one allowance. Requests are counted in a sliding one-minute window shared across our servers; if that shared counter is briefly unavailable, each server counts on its own. A separate per-IP limit (120 requests a minute by default) is checked before your key. The sandbox limits below apply during the beta; email us if you need higher ones.

TierRequests a minuteData calls a monthFit credits a month
Sandbox502,500100

Authenticated responses carry X-RateLimit-Limit, X-RateLimit-Remaining and X-RateLimit-Reset (Unix seconds). When you are over a limit you get a 429 with those headers, a Retry-After header (whole seconds) and error.details.resetAt (an ISO 8601 time). Wait that long before retrying. The monthly allowance returns the same 429 and resets on the first of the month (UTC); at 90% of it responses carry an X-Usage-Warning header.

Errors

Every error uses the same JSON shape:

error response
{ "error": { "code": "not_found", "message": "Product not found." }, "meta": { "version": "v1.1", "requestId": "eq_..." } }
CodeHTTPMeaning
unauthorized401Missing or invalid API key
invalid_request400Malformed body or invalid parameters
not_found404Resource not found
method_not_allowed405The endpoint does not support that method (see the Allow header)
rate_limited429Rate limit, per-IP limit or monthly allowance exceeded (see Retry-After)
internal_error500Unexpected server error
unavailable503Reserved. The v1 endpoints do not return it today

A bad query parameter name returns invalid_request with details.unknown and details.supported. Every response has an X-Request-Id header and meta.requestId: send your own x-request-id (1 to 64 characters from letters, digits, ., _ and -) to choose it, otherwise we generate one. Browsers can call the API directly: every endpoint answers OPTIONS preflight requests.

Pagination

List endpoints use cursors. Pass the nextCursor from one response as cursor to get the next page. When nextCursor is null, you’ve reached the end.

Freshness and caching

Keyed responses are sent with Cache-Control: private, no-store, so do not cache them in a shared cache. Each of our servers keeps the catalog in memory for up to 5 minutes, so a response can be up to 5 minutes behind a catalog update. Use GET /v1/changelog to see what changed and when.

Confidence and data quality

Every product has a confidence object: an overall score from 0 to 1, a verification tier and a score per field. Use minConfidence to filter out weaker data.

verified≥ 0.85Confirmed by several sources
partial≥ 0.65Some fields verified
low≥ 0.40Limited verification
unverified< 0.40Seed data only

Webhooks

Webhooks are switched off during the beta.

OpenAPI spec

The full contract is public, with no key needed, at /api/v1/openapi. It is the raw OpenAPI 3.1 document, not the { data, meta } envelope. Its version is the API version, v1.1; catalog data releases are separate and listed in the changelog.

TypeScript SDK

A typed TypeScript client is on the way. Until it’s published, call the API with plain fetch, as the examples below do. Every example needs Node 18 or newer, or any runtime with fetch.

Endpoint reference

Endpoints

Parameters, examples and response shapes for every endpoint. Sample responses are real output, trimmed for length: some fields and list items are left out.

GET

/v1/health

Health & coverage summary

Returns API status, version, product counts by category, and coverage metadata. Useful for monitoring integrations. Counts can be up to 5 minutes behind a catalog update.

Response
json
{
  "data": {
    "status": "ok",
    "version": "v1.1",
    "dbAvailable": true,
    "supportedCategories": [
      "driver",
      "iron",
      "driver_shaft",
      "iron_shaft",
      "ball",
      "grip",
      "wedge",
      "putter",
      "fairway_wood",
      "hybrid"
    ],
    "plannedCategories": [
      "bag"
    ],
    "coverageTarget": {
      "fromYear": 2016,
      "throughYear": 2026
    },
    "coverage": {
      "dbAvailable": true,
      "categories": {
        "driver": 255,
        "iron": 321,
        "driver_shaft": 137,
        "iron_shaft": 84,
        "ball": 142,
        "grip": 40,
        "wedge": 120,
        "putter": 253,
        "fairway_wood": 130,
        "hybrid": 94
      },
      "totalProducts": 1576
    },
    "authMode": "tenant"
  },
  "meta": {
    "version": "v1.1",
    "coverageTarget": {
      "fromYear": 2016,
      "throughYear": 2026
    },
    "requestId": "eq_sample"
  }
}
Examples
typescript
const res = await fetch("https://www.fitmyswing.com/api/v1/health", {
  headers: { "x-api-key": process.env.FITMYSWING_API_KEY! },
})
const { data } = await res.json()
console.log(data.status) // "ok"
curl
curl "https://www.fitmyswing.com/api/v1/health" \
  -H "x-api-key: fms_test_..."
python
import requests

r = requests.get("https://www.fitmyswing.com/api/v1/health",
    headers={"x-api-key": "fms_test_..."})
print(r.json()["data"]["status"])
GET

/v1/products/{id}

Get product by ID

Retrieve a single product by its canonical ID. Optionally include source provenance (field-level observations and verification summary). Fit-profile values (launch and spin bias, forgiveness, handicap and swing-speed windows) appear as observations with a sourceType of manufacturer_claim, derived (by a published rule) or editorial; they do not count towards confidence. An id from before a rename still works: the response is the current product, meta.resolvedFrom is the id you sent and the Content-Location header is the canonical URL. Wedges carry bounceOptions, grindOptions, construction, material and soleWidth; putters carry lengthOptions, toeHang, faceMilling and swingSpeedRange.

Parameters
NameInTypeDescription
id*pathstringCanonical product ID (e.g. wedge:callaway:opus-sp:2025)
includequerystringSet to 'provenance' to include source observations
Response
json
{
  "data": {
    "product": {
      "id": "wedge:callaway:opus-sp:2025",
      "category": "wedge",
      "brand": "Callaway",
      "model": "Opus SP",
      "displayName": "Callaway Opus SP",
      "year": 2025,
      "lifecycleStatus": "current",
      "price": {
        "amountUsd": 200,
        "tier": "premium",
        "source": "seed"
      },
      "sourceSummary": {
        "sourceCount": 3,
        "primarySourceType": "manufacturer",
        "provenanceStatus": "verified"
      },
      "confidenceScore": 1,
      "confidence": {
        "overall": 1,
        "verificationTier": "verified",
        "fields": {
          "bounceOptions": 1,
          "brand": 1,
          "finish": 1,
          "grindOptions": 1,
          "lofts": 1,
          "material": 1,
          "model": 1,
          "priceUSD": 1,
          "year": 1
        }
      },
      "lastVerifiedAt": "2026-10-01T00:00:00.000Z",
      "legacyRef": {
        "table": "wedges",
        "id": 887
      },
      "fitAttributes": {
        "swingSpeedRange": [
          70,
          120
        ],
        "handicapRange": [
          0,
          28
        ],
        "bounceOptions": [
          "6",
          "8",
          "10",
          "12",
          "14"
        ],
        "grindOptions": [
          "T",
          "C",
          "S",
          "W",
          "X"
        ],
        "launchBias": "mid",
        "spinBias": "high",
        "bestFor": "Players who want a lower, more controlled wedge flight with high spin from a tour-shaped head."
      },
      "specs": {
        "lofts": [
          "48",
          "50",
          "52",
          "54",
          "56",
          "58",
          "60"
        ],
        "bounceOptions": [
          "6",
          "8",
          "10",
          "12",
          "14"
        ],
        "grindOptions": [
          "T",
          "C",
          "S",
          "W",
          "X"
        ],
        "bounce": [
          "6",
          "8",
          "10",
          "12",
          "14"
        ],
        "grind": [
          "T",
          "C",
          "S",
          "W",
          "X"
        ],
        "finish": "chrome",
        "material": "carbon-steel",
        "soleWidth": "medium",
        "keyTech": "Spin Pocket two-piece head (forged 1025 face, cast 8620 body), Spin Gen 2.0 face",
        "url": "https://www.callawaygolf.com/product/wedges-2025-opus-sp-chrome"
      }
    }
  },
  "meta": {
    "version": "v1.1",
    "coverageTarget": {
      "fromYear": 2016,
      "throughYear": 2026
    },
    "requestId": "eq_sample"
  }
}
Examples
typescript
const res = await fetch("https://www.fitmyswing.com/api/v1/products/wedge:callaway:opus-sp:2025?include=provenance", {
  headers: { "x-api-key": process.env.FITMYSWING_API_KEY! },
})
const { data } = await res.json()
console.log(data.product.displayName)
curl
curl "https://www.fitmyswing.com/api/v1/products/wedge:callaway:opus-sp:2025?include=provenance" \
  -H "x-api-key: fms_test_..."
python
import requests

r = requests.get("https://www.fitmyswing.com/api/v1/products/wedge:callaway:opus-sp:2025?include=provenance",
    headers={"x-api-key": "fms_test_..."})
product = r.json()["data"]["product"]
POST

/v1/fit-score

uses a fit credit

Score product fit

Score how well one or more products fit a golfer profile, with the same scorer and guardrails as the FitMySwing fitting. Each score has totalScore (0–100, equal to matchScore), rawScore and maxScore, the breakdown by factor, reasons, warnings and flags (over_budget, off_category, off_band, womens_model, discontinued), plus rankingReasons (the fitting scorer's own awards and penalties, adding up to rawScore; none for putters) and rankedBecause (one sentence built from them). swingSpeed is optional: when it is missing it is estimated from handicap and gender, and the resolved golferProfile in the response says so with swingSpeedEstimated. Without gender, women's models are flagged. Consumes 1 fit credit per call.

Request body
json
{
  "golferProfile": {
    "handicap": 12,
    "miss": "Slice",
    "trajectory": "Mid",
    "budget": "$300–600",
    "gender": "Men"
  },
  "productIds": [
    "driver:ping:g440-max:2025",
    "driver:titleist:gts2:2026"
  ]
}
Response
json
{
  "data": {
    "scores": [
      {
        "productId": "driver:ping:g440-max:2025",
        "product": {
          "id": "driver:ping:g440-max:2025",
          "category": "driver",
          "brand": "Ping",
          "model": "G440 MAX",
          "displayName": "Ping G440 MAX",
          "year": 2025,
          "lifecycleStatus": "current",
          "price": {
            "amountUsd": 500,
            "tier": "premium",
            "source": "seed"
          },
          "confidenceScore": 0.94,
          "confidence": {
            "overall": 0.94,
            "verificationTier": "verified"
          }
        },
        "totalScore": 57,
        "matchScore": 57,
        "rawScore": 57,
        "maxScore": 100,
        "flags": [
          "over_budget"
        ],
        "scoreBreakdown": {
          "categoryFit": 12,
          "speedFit": 14,
          "handicapFit": 12,
          "budgetFit": 3,
          "launchSpinFit": 8,
          "forgivenessWorkabilityFit": 10,
          "shaftWeightKickFit": 0,
          "recencyFit": 9
        },
        "reasons": [
          "Category matches the golfer profile.",
          "Swing speed falls close to the product fit window.",
          "Handicap range is aligned with the product target player.",
          "Launch and spin bias support the stated ball-flight needs.",
          "Forgiveness/workability profile is appropriate for the player."
        ],
        "warnings": [
          "over_budget"
        ],
        "rankingReasons": [
          {
            "signal": "category",
            "points": 15,
            "max": 25,
            "note": "It is a max forgiveness head, one of the types that suit you"
          },
          {
            "signal": "handicap_window",
            "points": 16,
            "max": 20,
            "note": "Your 12 handicap sits inside its 5-28 handicap window"
          },
          {
            "signal": "speed_window",
            "points": 14,
            "max": 15,
            "note": "Your estimated 93 mph sits inside its 80-110 mph window"
          },
          {
            "signal": "price_tier",
            "points": 3,
            "max": 10,
            "note": "Its premium price tier is not the mid tier your $300–600 budget points to"
          },
          {
            "signal": "recency",
            "points": 9,
            "max": 10,
            "note": "It is a 2025 model, and newer models score higher"
          }
        ],
        "rankedBecause": "Scored for you because your 12 handicap sits inside its 5-28 handicap window; it is a max forgiveness head, one of the types that suit you; and your estimated 93 mph sits inside its 80-110 mph window."
      },
      {
        "productId": "driver:titleist:gts2:2026",
        "product": {
          "id": "driver:titleist:gts2:2026",
          "category": "driver",
          "brand": "Titleist",
          "model": "GTS2",
          "displayName": "Titleist GTS2",
          "year": 2026,
          "lifecycleStatus": "current",
          "price": {
            "amountUsd": 699,
            "tier": "premium",
            "source": "seed"
          },
          "confidenceScore": 0.96,
          "confidence": {
            "overall": 0.96,
            "verificationTier": "verified"
          }
        },
        "totalScore": 58,
        "matchScore": 58,
        "rawScore": 58,
        "maxScore": 100,
        "flags": [
          "over_budget"
        ],
        "scoreBreakdown": {
          "categoryFit": 12,
          "speedFit": 11,
          "handicapFit": 14,
          "budgetFit": 3,
          "launchSpinFit": 8,
          "forgivenessWorkabilityFit": 11,
          "shaftWeightKickFit": 0,
          "recencyFit": 10
        },
        "reasons": [
          "Category matches the golfer profile.",
          "Swing speed falls close to the product fit window.",
          "Handicap range is aligned with the product target player.",
          "Launch and spin bias support the stated ball-flight needs.",
          "Forgiveness/workability profile is appropriate for the player."
        ],
        "warnings": [
          "over_budget"
        ],
        "rankingReasons": [
          {
            "signal": "category",
            "points": 15,
            "max": 25,
            "note": "It is a max forgiveness head, one of the types that suit you"
          },
          {
            "signal": "handicap_window",
            "points": 19,
            "max": 20,
            "note": "Your 12 handicap sits inside its 0-22 handicap window"
          },
          {
            "signal": "speed_window",
            "points": 11,
            "max": 15,
            "note": "Your estimated 93 mph sits inside its 85-115 mph window"
          },
          {
            "signal": "price_tier",
            "points": 3,
            "max": 10,
            "note": "Its premium price tier is not the mid tier your $300–600 budget points to"
          },
          {
            "signal": "recency",
            "points": 10,
            "max": 10,
            "note": "It is a 2026 model, and newer models score higher"
          }
        ],
        "rankedBecause": "Scored for you because your 12 handicap sits inside its 0-22 handicap window; it is a max forgiveness head, one of the types that suit you; and your estimated 93 mph sits inside its 85-115 mph window."
      }
    ],
    "golferProfile": {
      "handicap": 12,
      "miss": "Slice",
      "trajectory": "Mid",
      "budget": "$300–600",
      "gender": "Men",
      "swingSpeed": 93,
      "swingSpeedEstimated": true
    }
  },
  "meta": {
    "version": "v1.1",
    "coverageTarget": {
      "fromYear": 2016,
      "throughYear": 2026
    },
    "requestId": "eq_sample"
  }
}
Examples
typescript
const res = await fetch("https://www.fitmyswing.com/api/v1/fit-score", {
  method: "POST",
  headers: { "x-api-key": process.env.FITMYSWING_API_KEY!, "Content-Type": "application/json" },
  body: JSON.stringify({"golferProfile":{"handicap":12,"miss":"Slice","trajectory":"Mid","budget":"$300–600","gender":"Men"},"productIds":["driver:ping:g440-max:2025","driver:titleist:gts2:2026"]}),
})
const { data } = await res.json()
console.log(data.scores[0].totalScore, data.scores[0].flags)
curl
curl -X POST https://www.fitmyswing.com/api/v1/fit-score \
  -H "x-api-key: fms_test_..." \
  -H "Content-Type: application/json" \
  -d '{"golferProfile":{"handicap":12,"miss":"Slice","trajectory":"Mid","budget":"$300–600","gender":"Men"},"productIds":["driver:ping:g440-max:2025","driver:titleist:gts2:2026"]}'
python
import requests

r = requests.post("https://www.fitmyswing.com/api/v1/fit-score",
    json={"golferProfile":{"handicap":12,"miss":"Slice","trajectory":"Mid","budget":"$300–600","gender":"Men"},"productIds":["driver:ping:g440-max:2025","driver:titleist:gts2:2026"]},
    headers={"x-api-key": "fms_test_..."})
scores = r.json()["data"]["scores"]
POST

/v1/recommendations

uses a fit credit

Get recommendations

Generate deterministic, DB-backed product recommendations for a golfer profile, ranked with the FitMySwing fitting's own scorers, so the API and the site pick the same clubs. Each product has rank, matchScore (0–100 against the most that scorer can award, comparable across categories), rawScore, maxScore, flags, and rankingReasons (the fitting scorer's own awards and penalties, adding up to rawScore) with rankedBecause (one sentence built from them), none for putters. Optional: swingSpeed (estimated from handicap and gender when missing), currentClubIds (clubs the golfer plays today: never recommended, and returned as keepCurrent when they would still rank at or near the top) and excludeDiscontinued. Without gender, women's models are left out. budget accepts $300-600 with a plain hyphen. modelUsage.rankingVersion names the ranking version. No LLM involved. Consumes 1 fit credit per call.

Request body
json
{
  "golferProfile": {
    "swingSpeed": 95,
    "handicap": 12,
    "miss": "Slice",
    "fittingFor": "driver",
    "budget": "$300–600",
    "gender": "Men"
  },
  "categories": [
    "driver"
  ],
  "limit": 2,
  "excludeDiscontinued": true
}
Response
json
{
  "data": {
    "golferProfile": {
      "swingSpeed": 95,
      "handicap": 12,
      "fittingFor": "driver",
      "miss": "Slice",
      "budget": "$300–600",
      "gender": "Men"
    },
    "recommendations": [
      {
        "category": "driver",
        "products": [
          {
            "productId": "driver:ben-hogan-golf:ptx-max:2025",
            "product": {
              "id": "driver:ben-hogan-golf:ptx-max:2025",
              "category": "driver",
              "brand": "Ben Hogan Golf",
              "model": "PTx MAX",
              "displayName": "Ben Hogan Golf PTx MAX",
              "year": 2025,
              "lifecycleStatus": "current",
              "price": {
                "amountUsd": 380,
                "tier": "mid",
                "source": "seed"
              },
              "confidenceScore": 0.96,
              "confidence": {
                "overall": 0.96,
                "verificationTier": "verified"
              }
            },
            "totalScore": 63,
            "matchScore": 63,
            "rank": 1,
            "rawScore": 63,
            "maxScore": 100,
            "flags": [],
            "scoreBreakdown": {
              "categoryFit": 12,
              "speedFit": 13,
              "handicapFit": 12,
              "budgetFit": 10,
              "launchSpinFit": 8,
              "forgivenessWorkabilityFit": 10,
              "shaftWeightKickFit": 0,
              "recencyFit": 9
            },
            "reasons": [
              "Category matches the golfer profile.",
              "Swing speed falls close to the product fit window.",
              "Handicap range is aligned with the product target player.",
              "Launch and spin bias support the stated ball-flight needs.",
              "Forgiveness/workability profile is appropriate for the player.",
              "Price tier fits the stated budget."
            ],
            "warnings": [],
            "rankingReasons": [
              {
                "signal": "category",
                "points": 15,
                "max": 25,
                "note": "It is a max forgiveness head, one of the types that suit you"
              },
              {
                "signal": "handicap_window",
                "points": 16,
                "max": 20,
                "note": "Your 12 handicap sits inside its 5-30 handicap window"
              },
              {
                "signal": "speed_window",
                "points": 13,
                "max": 15,
                "note": "Your 95 mph sits inside its 75-108 mph window"
              },
              {
                "signal": "price_tier",
                "points": 10,
                "max": 10,
                "note": "Its mid price tier matches your $300–600 budget"
              },
              {
                "signal": "recency",
                "points": 9,
                "max": 10,
                "note": "It is a 2025 model, and newer models score higher"
              }
            ],
            "rankedBecause": "Ranked first because your 12 handicap sits inside its 5-30 handicap window; it is a max forgiveness head, one of the types that suit you; and your 95 mph sits inside its 75-108 mph window."
          },
          {
            "productId": "driver:sub-70:859:2024",
            "product": {
              "id": "driver:sub-70:859:2024",
              "category": "driver",
              "brand": "Sub 70",
              "model": "859",
              "displayName": "Sub 70 859",
              "year": 2024,
              "lifecycleStatus": "recent",
              "price": {
                "amountUsd": 399,
                "tier": "mid",
                "source": "seed"
              },
              "confidenceScore": 0.98,
              "confidence": {
                "overall": 0.98,
                "verificationTier": "verified"
              }
            },
            "totalScore": 60,
            "matchScore": 60,
            "rank": 2,
            "rawScore": 60,
            "maxScore": 100,
            "flags": [],
            "scoreBreakdown": {
              "categoryFit": 12,
              "speedFit": 13,
              "handicapFit": 10,
              "budgetFit": 10,
              "launchSpinFit": 8,
              "forgivenessWorkabilityFit": 9,
              "shaftWeightKickFit": 0,
              "recencyFit": 8
            },
            "reasons": [
              "Category matches the golfer profile.",
              "Swing speed falls close to the product fit window.",
              "Handicap range is aligned with the product target player.",
              "Launch and spin bias support the stated ball-flight needs.",
              "Price tier fits the stated budget."
            ],
            "warnings": [],
            "rankingReasons": [
              {
                "signal": "category",
                "points": 15,
                "max": 25,
                "note": "It is a max forgiveness head, one of the types that suit you"
              },
              {
                "signal": "handicap_window",
                "points": 14,
                "max": 20,
                "note": "Your 12 handicap sits inside its 8-30 handicap window"
              },
              {
                "signal": "speed_window",
                "points": 13,
                "max": 15,
                "note": "Your 95 mph sits inside its 75-108 mph window"
              },
              {
                "signal": "price_tier",
                "points": 10,
                "max": 10,
                "note": "Its mid price tier matches your $300–600 budget"
              },
              {
                "signal": "recency",
                "points": 8,
                "max": 10,
                "note": "It is a 2024 model, and newer models score higher"
              }
            ],
            "rankedBecause": "Scored for you because it is a max forgiveness head, one of the types that suit you; your 12 handicap sits inside its 8-30 handicap window; and your 95 mph sits inside its 75-108 mph window."
          }
        ]
      }
    ],
    "modelUsage": {
      "llmUsed": false,
      "method": "deterministic_db_ranking",
      "rankingVersion": "r6-2026-10-10"
    }
  },
  "meta": {
    "version": "v1.1",
    "coverageTarget": {
      "fromYear": 2016,
      "throughYear": 2026
    },
    "requestId": "eq_sample"
  }
}
Examples
typescript
const res = await fetch("https://www.fitmyswing.com/api/v1/recommendations", {
  method: "POST",
  headers: { "x-api-key": process.env.FITMYSWING_API_KEY!, "Content-Type": "application/json" },
  body: JSON.stringify({"golferProfile":{"swingSpeed":95,"handicap":12,"miss":"Slice","fittingFor":"driver","budget":"$300–600","gender":"Men"},"categories":["driver"],"limit":2,"excludeDiscontinued":true}),
})
const { data } = await res.json()
data.recommendations[0].products.forEach((p) => console.log(p.rank, p.product.displayName, p.matchScore))
curl
curl -X POST https://www.fitmyswing.com/api/v1/recommendations \
  -H "x-api-key: fms_test_..." \
  -H "Content-Type: application/json" \
  -d '{"golferProfile":{"swingSpeed":95,"handicap":12,"miss":"Slice","fittingFor":"driver","budget":"$300–600","gender":"Men"},"categories":["driver"],"limit":2,"excludeDiscontinued":true}'
python
import requests

r = requests.post("https://www.fitmyswing.com/api/v1/recommendations",
    json={"golferProfile":{"swingSpeed":95,"handicap":12,"miss":"Slice","fittingFor":"driver","budget":"$300–600","gender":"Men"},"categories":["driver"],"limit":2,"excludeDiscontinued":True},
    headers={"x-api-key": "fms_test_..."})
recs = r.json()["data"]["recommendations"]
POST

/v1/compare

Compare products

Compare 2–8 products side-by-side. Optionally score each against a golfer profile (swingSpeed is required here) to find the best fit. Returns normalized specs, price and lifecycle. Scores use the same scorer, flags and warnings as /fit-score and /recommendations, so a product has the same matchScore, rawScore, maxScore and flags in all three; they carry no rank. The response echoes the golferProfile used.

Request body
json
{
  "productIds": [
    "driver:ping:g440-max:2025",
    "driver:titleist:gts2:2026"
  ],
  "golferProfile": {
    "swingSpeed": 95,
    "handicap": 12
  }
}
Response
json
{
  "data": {
    "products": [
      {
        "id": "driver:ping:g440-max:2025",
        "displayName": "Ping G440 MAX",
        "category": "driver",
        "year": 2025,
        "lifecycleStatus": "current",
        "price": {
          "amountUsd": 500,
          "tier": "premium",
          "source": "seed"
        },
        "confidenceScore": 0.94
      },
      {
        "id": "driver:titleist:gts2:2026",
        "displayName": "Titleist GTS2",
        "category": "driver",
        "year": 2026,
        "lifecycleStatus": "current",
        "price": {
          "amountUsd": 699,
          "tier": "premium",
          "source": "seed"
        },
        "confidenceScore": 0.96
      }
    ],
    "fitScores": [
      {
        "productId": "driver:titleist:gts2:2026",
        "product": {
          "id": "driver:titleist:gts2:2026",
          "category": "driver",
          "brand": "Titleist",
          "model": "GTS2",
          "displayName": "Titleist GTS2",
          "year": 2026,
          "lifecycleStatus": "current",
          "price": {
            "amountUsd": 699,
            "tier": "premium",
            "source": "seed"
          },
          "confidenceScore": 0.96,
          "confidence": {
            "overall": 0.96,
            "verificationTier": "verified"
          }
        },
        "totalScore": 56,
        "matchScore": 56,
        "rawScore": 56,
        "maxScore": 100,
        "flags": [],
        "scoreBreakdown": {
          "categoryFit": 12,
          "speedFit": 12,
          "handicapFit": 14,
          "budgetFit": 7,
          "launchSpinFit": 8,
          "forgivenessWorkabilityFit": 11,
          "shaftWeightKickFit": 0,
          "recencyFit": 10
        },
        "reasons": [
          "Category matches the golfer profile.",
          "Swing speed falls close to the product fit window.",
          "Handicap range is aligned with the product target player.",
          "Launch and spin bias support the stated ball-flight needs.",
          "Forgiveness/workability profile is appropriate for the player."
        ],
        "warnings": [],
        "rankingReasons": [
          {
            "signal": "category",
            "points": 15,
            "max": 25,
            "note": "It is a max forgiveness head, one of the types that suit you"
          },
          {
            "signal": "handicap_window",
            "points": 19,
            "max": 20,
            "note": "Your 12 handicap sits inside its 0-22 handicap window"
          },
          {
            "signal": "speed_window",
            "points": 12,
            "max": 15,
            "note": "Your 95 mph sits inside its 85-115 mph window"
          },
          {
            "signal": "recency",
            "points": 10,
            "max": 10,
            "note": "It is a 2026 model, and newer models score higher"
          }
        ],
        "rankedBecause": "Scored for you because your 12 handicap sits inside its 0-22 handicap window; it is a max forgiveness head, one of the types that suit you; and your 95 mph sits inside its 85-115 mph window."
      },
      {
        "productId": "driver:ping:g440-max:2025",
        "product": {
          "id": "driver:ping:g440-max:2025",
          "category": "driver",
          "brand": "Ping",
          "model": "G440 MAX",
          "displayName": "Ping G440 MAX",
          "year": 2025,
          "lifecycleStatus": "current",
          "price": {
            "amountUsd": 500,
            "tier": "premium",
            "source": "seed"
          },
          "confidenceScore": 0.94,
          "confidence": {
            "overall": 0.94,
            "verificationTier": "verified"
          }
        },
        "totalScore": 55,
        "matchScore": 55,
        "rawScore": 55,
        "maxScore": 100,
        "flags": [],
        "scoreBreakdown": {
          "categoryFit": 12,
          "speedFit": 15,
          "handicapFit": 12,
          "budgetFit": 7,
          "launchSpinFit": 8,
          "forgivenessWorkabilityFit": 10,
          "shaftWeightKickFit": 0,
          "recencyFit": 9
        },
        "reasons": [
          "Category matches the golfer profile.",
          "Swing speed falls close to the product fit window.",
          "Handicap range is aligned with the product target player.",
          "Launch and spin bias support the stated ball-flight needs.",
          "Forgiveness/workability profile is appropriate for the player."
        ],
        "warnings": [],
        "rankingReasons": [
          {
            "signal": "category",
            "points": 15,
            "max": 25,
            "note": "It is a max forgiveness head, one of the types that suit you"
          },
          {
            "signal": "handicap_window",
            "points": 16,
            "max": 20,
            "note": "Your 12 handicap sits inside its 5-28 handicap window"
          },
          {
            "signal": "speed_window",
            "points": 15,
            "max": 15,
            "note": "Your 95 mph sits inside its 80-110 mph window"
          },
          {
            "signal": "recency",
            "points": 9,
            "max": 10,
            "note": "It is a 2025 model, and newer models score higher"
          }
        ],
        "rankedBecause": "Scored for you because your 12 handicap sits inside its 5-28 handicap window; it is a max forgiveness head, one of the types that suit you; and your 95 mph sits inside its 80-110 mph window."
      }
    ],
    "golferProfile": {
      "swingSpeed": 95,
      "handicap": 12
    },
    "bestFitProductId": "driver:titleist:gts2:2026"
  },
  "meta": {
    "version": "v1.1",
    "coverageTarget": {
      "fromYear": 2016,
      "throughYear": 2026
    },
    "requestId": "eq_sample"
  }
}
Examples
typescript
const res = await fetch("https://www.fitmyswing.com/api/v1/compare", {
  method: "POST",
  headers: { "x-api-key": process.env.FITMYSWING_API_KEY!, "Content-Type": "application/json" },
  body: JSON.stringify({"productIds":["driver:ping:g440-max:2025","driver:titleist:gts2:2026"],"golferProfile":{"swingSpeed":95,"handicap":12}}),
})
const { data } = await res.json()
console.log("Best fit:", data.bestFitProductId)
curl
curl -X POST https://www.fitmyswing.com/api/v1/compare \
  -H "x-api-key: fms_test_..." \
  -H "Content-Type: application/json" \
  -d '{"productIds":["driver:ping:g440-max:2025","driver:titleist:gts2:2026"],"golferProfile":{"swingSpeed":95,"handicap":12}}'
python
import requests

r = requests.post("https://www.fitmyswing.com/api/v1/compare",
    json={"productIds":["driver:ping:g440-max:2025","driver:titleist:gts2:2026"],"golferProfile":{"swingSpeed":95,"handicap":12}},
    headers={"x-api-key": "fms_test_..."})
print("Best:", r.json()["data"]["bestFitProductId"])
GET

/v1/changelog

Catalog change log

What changed in the catalog, newest first: one entry per changed field, or per product added or removed. Keep a high-water mark and pass it as since (exclusive) to sync. type=verification returns the older source-observation feed instead (camelCase field names, one entry per product field) and accepts only type, since, category, limit and cursor. Unknown query parameters are rejected with a 400.

Parameters
NameInTypeDescription
typequerystringcatalog (default) or verification
sincequerystringOnly entries after this ISO 8601 time (exclusive)
untilquerystringOnly entries at or before this time (catalog only)
productIdquerystringOnly changes to this product (catalog only)
categoryquerystringFilter by category (driver, iron, driver_shaft, iron_shaft, ball, grip, wedge, putter, fairway_wood, hybrid)
changeTypequerystringadded, updated or removed (catalog only)
limitqueryintegerEntries per page (1–100, default 25)
cursorquerystringPagination cursor from previous response
Response
json
{
  "data": {
    "entries": [
      {
        "id": 4292,
        "productId": "putter:odyssey:ai-dual-1:2026",
        "category": "putter",
        "changeType": "updated",
        "fieldName": "hosel",
        "oldValue": "plumber-neck",
        "newValue": "crank-neck",
        "changedAt": "2026-10-09T16:07:10.000Z",
        "source": "backfill",
        "prNumber": 72
      },
      {
        "id": 4291,
        "productId": "putter:odyssey:dfx-2-ball-blade-ch:2025",
        "category": "putter",
        "changeType": "updated",
        "fieldName": "hosel",
        "oldValue": "plumber-neck",
        "newValue": "crank-neck",
        "changedAt": "2026-10-09T16:07:10.000Z",
        "source": "backfill",
        "prNumber": 72
      },
      {
        "id": 4290,
        "productId": "putter:odyssey:dfx-1w-ch:2025",
        "category": "putter",
        "changeType": "updated",
        "fieldName": "hosel",
        "oldValue": "plumber-neck",
        "newValue": "crank-neck",
        "changedAt": "2026-10-09T16:07:10.000Z",
        "source": "backfill",
        "prNumber": 72
      }
    ],
    "pagination": {
      "limit": 3,
      "cursor": null,
      "nextCursor": "eyJvZmZzZXQiOjN9",
      "total": 4292
    }
  },
  "meta": {
    "version": "v1.1",
    "coverageTarget": {
      "fromYear": 2016,
      "throughYear": 2026
    },
    "requestId": "eq_sample"
  }
}
Examples
typescript
const res = await fetch("https://www.fitmyswing.com/api/v1/changelog?limit=3", {
  headers: { "x-api-key": process.env.FITMYSWING_API_KEY! },
})
const { data } = await res.json()
for (const c of data.entries) console.log(c.changeType, c.productId, c.fieldName, c.oldValue, c.newValue)
curl
curl "https://www.fitmyswing.com/api/v1/changelog?limit=3" \
  -H "x-api-key: fms_test_..."
python
import requests

r = requests.get("https://www.fitmyswing.com/api/v1/changelog?limit=3",
    headers={"x-api-key": "fms_test_..."})
entries = r.json()["data"]["entries"]