{
  "openapi": "3.1.0",
  "info": {
    "title": "ELDRICK Golf Fitting API",
    "description": "Stable ELDRICK Public API v1. Fits drivers, fairway woods, hybrids, irons and wedges with the same fitting engine eldrick.com uses, and serves the club catalog (search, detail, compare, fit scores) and store.eldrick.com links. Built on 100,000+ fittings: trained on 80,000, plus 25,000+ ELDRICK has done itself. Backward-compatible fitting improvements ship behind this endpoint; responses identify the client-facing fitting release and its ruleset provenance in meta. The same tools are available over MCP at https://api.eldrick.com/mcp. Methodology: https://eldrick.com/methodology. AI golf club fitting: https://eldrick.com/ai-golf-club-fitting.",
    "version": "1.2.0",
    "contact": {
      "name": "ELDRICK",
      "url": "https://eldrick.com"
    }
  },
  "servers": [
    {
      "url": "https://api.eldrick.com",
      "description": "Production API"
    }
  ],
  "security": [
    {
      "bearerAuth": []
    },
    {
      "oauth": []
    }
  ],
  "paths": {
    "/health": {
      "get": {
        "operationId": "getHealth",
        "summary": "Public service liveness check",
        "security": [],
        "responses": {
          "200": {
            "description": "Service is live"
          }
        }
      }
    },
    "/readyz": {
      "get": {
        "operationId": "getReadiness",
        "summary": "Public application and database readiness check",
        "security": [],
        "responses": {
          "200": {
            "description": "Service and database are ready"
          },
          "503": {
            "description": "Service is not ready"
          }
        }
      }
    },
    "/v1/fitting/analyze": {
      "post": {
        "security": [
          {
            "bearerAuth": []
          },
          {
            "oauth": [
              "fittings:create"
            ]
          }
        ],
        "operationId": "analyzeFitting",
        "summary": "Fit a driver, fairway wood, hybrid, irons or wedges and return specs plus club recommendations",
        "parameters": [
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "schema": {
              "type": "string",
              "maxLength": 255
            },
            "description": "Unique key for safe retries. Replaying the same request returns the original fitting; changing the request returns 409."
          },
          {
            "name": "detail",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "enum": [
                "full"
              ]
            },
            "description": "Set to full to include detailed analysis text and reasoning."
          },
          {
            "name": "X-ELDRICK-Platform",
            "in": "header",
            "required": false,
            "schema": {
              "type": "string",
              "maxLength": 40
            },
            "description": "Optional: the AI platform or app making the call (e.g. chatgpt, claude, gemini, copilot, perplexity, lechat), for usage reporting. OAuth connections are attributed automatically."
          },
          {
            "name": "X-ELDRICK-Client",
            "in": "header",
            "required": false,
            "schema": {
              "type": "string",
              "maxLength": 100
            },
            "description": "Optional: your client or agent name, for usage reporting."
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/FittingRequest"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Fitting specs and product recommendations",
            "headers": {
              "X-Request-ID": {
                "schema": {
                  "type": "string"
                },
                "description": "Stable fitting request ID for tracing and retrieval."
              },
              "Idempotent-Replayed": {
                "schema": {
                  "type": "string",
                  "enum": [
                    "true"
                  ]
                },
                "description": "Present when this is a safe retry response."
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/FittingSuccessResponse"
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "409": {
            "description": "Idempotency-Key was reused with a different request",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited, monthly quota exhausted (QUOTA_EXCEEDED), or, for end users connected through an AI platform with OAuth, the daily agent fitting cap (DAILY_CAP_REACHED; resets at midnight UTC).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        }
      }
    },
    "/v1/fittings/{fittingId}": {
      "get": {
        "operationId": "getFitting",
        "security": [
          {
            "bearerAuth": []
          },
          {
            "oauth": [
              "fittings:read"
            ]
          }
        ],
        "summary": "Retrieve a prior fitting and its evidence",
        "description": "Returns a fitting previously created by the authenticated API account, including its saved input, output, and engine provenance.",
        "parameters": [
          {
            "name": "fittingId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Stored fitting record",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/StoredFittingResponse"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "404": {
            "description": "Fitting not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        }
      }
    },
    "/v1/fittings/{fittingId}/lab-handoff": {
      "post": {
        "operationId": "createLabHandoff",
        "security": [
          {
            "bearerAuth": []
          },
          {
            "oauth": [
              "fittings:read"
            ]
          }
        ],
        "summary": "Open a completed fitting in ELDRICK Lab",
        "description": "Creates a 15-minute, signed Lab URL for a completed fitting owned by the authenticated API account. The URL contains no API credentials or account identity.",
        "parameters": [
          {
            "name": "fittingId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Short-lived Lab handoff",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "success",
                    "fittingId",
                    "labUrl",
                    "expiresAt"
                  ],
                  "properties": {
                    "success": {
                      "type": "boolean",
                      "const": true
                    },
                    "fittingId": {
                      "type": "string"
                    },
                    "labUrl": {
                      "type": "string",
                      "format": "uri"
                    },
                    "expiresAt": {
                      "type": "string",
                      "format": "date-time"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "404": {
            "description": "Fitting not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "422": {
            "description": "Fitting is not complete",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "503": {
            "description": "Lab handoff is not configured",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        }
      }
    },
    "/v1/sessions/ingest": {
      "post": {
        "operationId": "ingestSession",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "summary": "Ingest a Marshal launch-monitor capture session",
        "description": "Stores a batch-captured launch-monitor session without consuming fitting-analysis quota. Re-posting the same sessionId replaces the stored payload.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/SessionIngestRequest"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Existing session replaced idempotently",
            "headers": {
              "X-Min-Payload-Version": {
                "schema": {
                  "type": "integer"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SessionIngestResponse"
                }
              }
            }
          },
          "201": {
            "description": "Session created",
            "headers": {
              "X-Min-Payload-Version": {
                "schema": {
                  "type": "integer"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SessionIngestResponse"
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "426": {
            "description": "Payload version no longer supported",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "minPayloadVersion": {
                      "type": "integer"
                    }
                  },
                  "required": [
                    "minPayloadVersion"
                  ]
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        }
      }
    },
    "/v1/key/status": {
      "get": {
        "operationId": "getKeyStatus",
        "summary": "Inspect API key validity and fitting availability",
        "responses": {
          "200": {
            "description": "Key status with included allowance and purchased credits reported separately",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/KeyStatusResponse"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized"
          }
        }
      }
    },
    "/v1/status": {
      "get": {
        "tags": [
          "Status"
        ],
        "security": [
          {
            "bearerAuth": []
          },
          {
            "oauth": []
          }
        ],
        "operationId": "getApiStatus",
        "summary": "Active API, fitting-engine and ruleset versions",
        "responses": {
          "200": {
            "description": "Versions",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "apiVersion": {
                      "type": "string"
                    },
                    "engineVersion": {
                      "type": "string"
                    },
                    "rulesetRevision": {
                      "type": "string"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        }
      }
    },
    "/v1/catalog": {
      "get": {
        "tags": [
          "Catalog"
        ],
        "security": [
          {
            "bearerAuth": []
          },
          {
            "oauth": [
              "fittings:read"
            ]
          }
        ],
        "operationId": "getCatalog",
        "summary": "The full catalog: heads (with stock shaft options and 30-day popularity), shafts and grips",
        "description": "Free (no fitting quota). Rate limited per key. Supports ETag / If-None-Match. For agents, prefer /v1/catalog/clubs.",
        "responses": {
          "200": {
            "description": "Catalog",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "additionalProperties": true
                }
              }
            }
          },
          "304": {
            "description": "Not modified"
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "503": {
            "description": "Catalog temporarily unavailable",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        }
      }
    },
    "/v1/catalog/club-models": {
      "get": {
        "tags": [
          "Catalog"
        ],
        "security": [
          {
            "bearerAuth": []
          },
          {
            "oauth": [
              "fittings:read"
            ]
          }
        ],
        "operationId": "getClubModels",
        "summary": "Current-club dropdown: brand → model → generation/year",
        "description": "Free (no fitting quota). Every club model ELDRICK can date, by brand, with each generation of a reused name (TaylorMade P790 2017/2019/2021/2023/2025, Titleist T100, Callaway Apex, Big Bertha) as its own option. An option has a head photo (image) only where the catalog has that exact generation, usually the current one. Send the picked option's value as currentEquipment.model (for example \"P790 (2019)\") so the fitting states the club's age exactly.",
        "parameters": [
          {
            "name": "clubType",
            "in": "query",
            "required": true,
            "schema": {
              "type": "string",
              "enum": [
                "driver",
                "fairway",
                "hybrid",
                "iron",
                "wedge"
              ]
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Brands, models and options (label, value, year, yearLabel, estimate, note, image, headId)",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "additionalProperties": true
                }
              }
            }
          },
          "400": {
            "description": "Invalid clubType",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "503": {
            "description": "Temporarily unavailable",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        }
      }
    },
    "/v1/catalog/clubs": {
      "get": {
        "tags": [
          "Catalog"
        ],
        "security": [
          {
            "bearerAuth": []
          },
          {
            "oauth": [
              "fittings:read"
            ]
          }
        ],
        "operationId": "searchClubs",
        "summary": "Search and filter club heads",
        "description": "Free (no fitting quota), read-only. Same as the search_clubs MCP tool.",
        "parameters": [
          {
            "name": "clubType",
            "in": "query",
            "schema": {
              "type": "string",
              "enum": [
                "driver",
                "fairway",
                "hybrid",
                "iron",
                "wedge"
              ]
            }
          },
          {
            "name": "brand",
            "in": "query",
            "schema": {
              "type": "string"
            },
            "description": "Case-insensitive brand, e.g. Ping"
          },
          {
            "name": "category",
            "in": "query",
            "schema": {
              "type": "string"
            },
            "description": "Head category, e.g. low_spin, draw, light, core, tour, game_improvement, players_iron, blade"
          },
          {
            "name": "minPrice",
            "in": "query",
            "schema": {
              "type": "number"
            }
          },
          {
            "name": "maxPrice",
            "in": "query",
            "schema": {
              "type": "number"
            }
          },
          {
            "name": "q",
            "in": "query",
            "schema": {
              "type": "string"
            },
            "description": "Words that must all appear in the brand, model or category"
          },
          {
            "name": "hand",
            "in": "query",
            "schema": {
              "type": "string",
              "enum": [
                "right",
                "left"
              ]
            }
          },
          {
            "name": "sort",
            "in": "query",
            "schema": {
              "type": "string",
              "enum": [
                "popular",
                "price_low",
                "price_high",
                "name"
              ]
            }
          },
          {
            "name": "limit",
            "in": "query",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 50,
              "default": 20
            }
          },
          {
            "name": "offset",
            "in": "query",
            "schema": {
              "type": "integer",
              "minimum": 0
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Matching clubs and facet counts",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "success": {
                      "const": true
                    },
                    "total": {
                      "type": "integer"
                    },
                    "offset": {
                      "type": "integer"
                    },
                    "limit": {
                      "type": "integer"
                    },
                    "clubs": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/ClubSummary"
                      }
                    },
                    "facets": {
                      "type": "object",
                      "additionalProperties": true
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "503": {
            "description": "Catalog temporarily unavailable",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        }
      }
    },
    "/v1/catalog/clubs/{headId}": {
      "get": {
        "tags": [
          "Catalog"
        ],
        "security": [
          {
            "bearerAuth": []
          },
          {
            "oauth": [
              "fittings:read"
            ]
          }
        ],
        "operationId": "getClub",
        "summary": "One club head with its lofts, stock and upgrade shafts, grips, lengths, lies and sets",
        "parameters": [
          {
            "name": "headId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Club",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "success": {
                      "const": true
                    },
                    "club": {
                      "$ref": "#/components/schemas/ClubDetail"
                    }
                  }
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        }
      }
    },
    "/v1/catalog/compare": {
      "post": {
        "tags": [
          "Catalog"
        ],
        "security": [
          {
            "bearerAuth": []
          },
          {
            "oauth": [
              "fittings:read"
            ]
          }
        ],
        "operationId": "compareClubs",
        "summary": "Compare 2-4 club heads side by side",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "headIds"
                ],
                "properties": {
                  "headIds": {
                    "type": "array",
                    "minItems": 2,
                    "maxItems": 4,
                    "items": {
                      "type": "string"
                    }
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Comparison",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "success": {
                      "const": true
                    },
                    "clubs": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "additionalProperties": true
                      }
                    },
                    "differences": {
                      "type": "object",
                      "additionalProperties": true
                    },
                    "notFound": {
                      "type": "array",
                      "items": {
                        "type": "string"
                      }
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "404": {
            "description": "Fewer than two clubs found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        }
      }
    },
    "/v1/catalog/scores": {
      "post": {
        "tags": [
          "Catalog"
        ],
        "security": [
          {
            "bearerAuth": []
          },
          {
            "oauth": [
              "fittings:read"
            ]
          }
        ],
        "operationId": "scoreCatalog",
        "summary": "Score every catalog club for a golfer (fit score, verdict, evidence, recommended build)",
        "description": "Free and deterministic. Body: { goal?, fittings?: { driver?, iron? } (saved fitting input + result), golfer?: preset id or { speed, miss, needsLaunch, highSpin }, hand?, headIds?, clubTypes?, shafts? }, or { contexts: [...] } for up to 32 at once. The full answer is large (~0.4 MB); the score_clubs MCP tool returns a ranked, trimmed view.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "additionalProperties": true
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Scores",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "additionalProperties": true
                }
              }
            }
          },
          "304": {
            "description": "Not modified"
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        }
      }
    },
    "/v1/catalog/evidence": {
      "get": {
        "tags": [
          "Catalog"
        ],
        "security": [
          {
            "bearerAuth": []
          },
          {
            "oauth": [
              "fittings:read"
            ]
          }
        ],
        "operationId": "getCatalogEvidence",
        "summary": "Aggregated fitting evidence behind the scores (per-head summaries and golfer presets)",
        "responses": {
          "200": {
            "description": "Evidence",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "additionalProperties": true
                }
              }
            }
          },
          "304": {
            "description": "Not modified"
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        }
      }
    },
    "/v1/store/link": {
      "post": {
        "tags": [
          "Store"
        ],
        "security": [
          {
            "bearerAuth": []
          },
          {
            "oauth": [
              "fittings:read"
            ]
          }
        ],
        "operationId": "buildStoreLink",
        "summary": "A store.eldrick.com link for a club and fitted build",
        "description": "Read-only: nothing is bought or reserved. The link opens the club's product page (never checkout). Each link is recorded with its platform and fitting to measure fitting → store conversion.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/StoreLinkRequest"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Link",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/StoreLinkResponse"
                }
              }
            }
          },
          "400": {
            "description": "Validation error (shaft, grip or hand not offered on this club)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "404": {
            "description": "Club or fitting not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        }
      }
    },
    "/v1/agent/token": {
      "post": {
        "tags": [
          "Agents"
        ],
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "operationId": "issueAgentToken",
        "summary": "Exchange an Agent Connection credential (eac_) for a short-lived eat_ access token",
        "responses": {
          "200": {
            "description": "Token",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "accessToken": {
                      "type": "string"
                    },
                    "tokenType": {
                      "const": "Bearer"
                    },
                    "expiresIn": {
                      "type": "integer"
                    },
                    "scopes": {
                      "type": "array",
                      "items": {
                        "type": "string"
                      }
                    },
                    "connection": {
                      "type": "object",
                      "properties": {
                        "id": {
                          "type": "string"
                        },
                        "name": {
                          "type": "string"
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "Invalid, inactive or expired credential",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        }
      }
    },
    "/mcp/health": {
      "get": {
        "tags": [
          "Status"
        ],
        "security": [],
        "operationId": "getMcpHealth",
        "summary": "MCP server health: builds the MCP server and lists its tools (no auth, no model calls)",
        "responses": {
          "200": {
            "description": "status ok and the tool list",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "status": {
                      "const": "ok"
                    },
                    "toolCount": {
                      "type": "integer"
                    },
                    "tools": {
                      "type": "array",
                      "items": {
                        "type": "string"
                      }
                    },
                    "serverVersion": {
                      "type": "string"
                    }
                  }
                }
              }
            }
          },
          "503": {
            "description": "Degraded or unavailable",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "additionalProperties": true
                }
              }
            }
          }
        }
      }
    },
    "/v1/grip-size": {
      "post": {
        "tags": [
          "Fitting tools"
        ],
        "security": [
          {
            "bearerAuth": []
          },
          {
            "oauth": [
              "fittings:read"
            ]
          }
        ],
        "operationId": "getGripSize",
        "summary": "Grip size and extra wraps from hand measurements",
        "description": "Free and deterministic (MCP: get_grip_size). The master fitter hand chart: hand length (wrist crease to the tip of the longest finger) sets the size; a longest finger over 3 inches adds one wrap; over 9 1/4 inches is Jumbo + 2 wraps; a size jump and extra wraps are never combined. Optionally lists catalog grips in that size.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/GripSizeRequest"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Recommendation",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/GripSizeResponse"
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        }
      }
    },
    "/v1/bag/gapping": {
      "post": {
        "tags": [
          "Fitting tools"
        ],
        "security": [
          {
            "bearerAuth": []
          },
          {
            "oauth": [
              "fittings:read"
            ]
          }
        ],
        "operationId": "analyzeBagGapping",
        "summary": "Gapping analysis of a golfer's set",
        "description": "Free and deterministic (MCP: analyze_bag_gapping). Carry and loft gaps between consecutive clubs, flags (too big, too small, overlap, duplicate loft, wedge gap too wide), the recommended wedge set from the PW loft, and suggested changes, each with the club type to fit (fitWith). Missing carries are estimated from loft and driver or 7-iron speed and marked estimated; missing lofts use stock lofts and are marked.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/BagGappingRequest"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Analysis",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/BagGappingResponse"
                }
              }
            }
          },
          "400": {
            "description": "Validation error (fewer than 2 clubs, an unrecognized club, or a club with no known loft)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        }
      }
    },
    "/v1/fitters": {
      "get": {
        "tags": [
          "Fitting tools"
        ],
        "security": [
          {
            "bearerAuth": []
          },
          {
            "oauth": [
              "fittings:read"
            ]
          }
        ],
        "operationId": "findFitters",
        "summary": "Club fitters near a ZIP code, city or point",
        "description": "Free (MCP: find_fitter). One location: zip, or city and state, or lat and lng. ZIPs are placed with the U.S. Census ZCTA internal points; distance is great-circle miles. ELDRICK partners within the radius are listed first (label \"ELDRICK partner\"), then everyone by distance. Public business information; a rating appears only with its source. 503 FITTERS_UNAVAILABLE until the directory is loaded.",
        "parameters": [
          {
            "name": "zip",
            "in": "query",
            "schema": {
              "type": "string",
              "pattern": "^\\d{5}(-?\\d{4})?$"
            },
            "description": "US ZIP code"
          },
          {
            "name": "city",
            "in": "query",
            "schema": {
              "type": "string"
            },
            "description": "City (with state)"
          },
          {
            "name": "state",
            "in": "query",
            "schema": {
              "type": "string"
            },
            "description": "2-letter code or name (with city)"
          },
          {
            "name": "lat",
            "in": "query",
            "schema": {
              "type": "number"
            },
            "description": "Latitude (with lng)"
          },
          {
            "name": "lng",
            "in": "query",
            "schema": {
              "type": "number"
            },
            "description": "Longitude (with lat)"
          },
          {
            "name": "radiusMiles",
            "in": "query",
            "schema": {
              "type": "number",
              "minimum": 1,
              "maximum": 500,
              "default": 50
            },
            "description": "Search radius in miles"
          },
          {
            "name": "services",
            "in": "query",
            "schema": {
              "type": "string"
            },
            "description": "Comma-separated; fitters listing all of them: driver, fairway_hybrid, irons, wedges, putter, full_bag"
          },
          {
            "name": "brand",
            "in": "query",
            "schema": {
              "type": "string"
            },
            "description": "Brand fitted, case-insensitive"
          },
          {
            "name": "limit",
            "in": "query",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 25,
              "default": 10
            },
            "description": "Fitters to return"
          }
        ],
        "responses": {
          "200": {
            "description": "Fitters",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/FittersResponse"
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "404": {
            "description": "LOCATION_NOT_FOUND: the ZIP or city could not be placed",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "503": {
            "description": "FITTERS_UNAVAILABLE",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        }
      }
    }
  },
  "components": {
    "securitySchemes": {
      "bearerAuth": {
        "type": "http",
        "scheme": "bearer"
      },
      "oauth": {
        "type": "oauth2",
        "description": "Authorization Code with mandatory S256 PKCE, public clients (token_endpoint_auth_method=none). Clients register by Client ID Metadata Document (ChatGPT, Claude), by dynamic client registration at /oauth/register (RFC 7591), or by pre-registering an exact HTTPS callback with hi@eldrick.golf. Include resource=https://api.eldrick.com/v1 (REST) or https://api.eldrick.com/mcp (MCP). Tokens are bound to that resource. See /docs#oauth.",
        "flows": {
          "authorizationCode": {
            "authorizationUrl": "https://api.eldrick.com/oauth/authorize",
            "tokenUrl": "https://api.eldrick.com/oauth/token",
            "refreshUrl": "https://api.eldrick.com/oauth/token",
            "scopes": {
              "fittings:create": "Create fittings using your account allowance or credits",
              "fittings:read": "Read fittings saved in your account"
            }
          }
        }
      }
    },
    "schemas": {
      "KeyStatusResponse": {
        "type": "object",
        "required": [
          "valid",
          "developer",
          "usage"
        ],
        "properties": {
          "valid": {
            "const": true
          },
          "developer": {
            "type": "object",
            "required": [
              "companyName",
              "email",
              "tier"
            ],
            "properties": {
              "companyName": {
                "type": "string"
              },
              "email": {
                "type": "string",
                "format": "email"
              },
              "tier": {
                "type": "string",
                "description": "Internal entitlement label; clients should use structured usage fields for billing availability."
              }
            }
          },
          "usage": {
            "type": "object",
            "required": [
              "current",
              "limit",
              "remaining",
              "percentUsed",
              "unlimited",
              "creditBased",
              "resetsAt",
              "includedLimit",
              "includedUsed",
              "includedRemaining",
              "purchasedCreditBalance",
              "availability",
              "nextFittingSource"
            ],
            "properties": {
              "current": {
                "type": "integer",
                "minimum": 0,
                "description": "All counted fitting requests in the current month."
              },
              "limit": {
                "type": [
                  "integer",
                  "null"
                ]
              },
              "remaining": {
                "type": [
                  "integer",
                  "null"
                ]
              },
              "percentUsed": {
                "type": "integer",
                "minimum": 0
              },
              "unlimited": {
                "type": "boolean"
              },
              "creditBased": {
                "type": "boolean"
              },
              "resetsAt": {
                "type": "string",
                "format": "date-time"
              },
              "includedLimit": {
                "type": [
                  "integer",
                  "null"
                ]
              },
              "includedUsed": {
                "type": "integer",
                "minimum": 0
              },
              "includedRemaining": {
                "type": [
                  "integer",
                  "null"
                ]
              },
              "purchasedCreditBalance": {
                "type": "integer",
                "minimum": 0
              },
              "availability": {
                "type": "string",
                "enum": [
                  "included",
                  "credit",
                  "exhausted",
                  "enterprise",
                  "unlimited"
                ]
              },
              "nextFittingSource": {
                "type": "string",
                "enum": [
                  "included",
                  "purchased_credit",
                  "enterprise",
                  "unlimited",
                  "unavailable"
                ]
              }
            }
          }
        }
      },
      "StoredFittingResponse": {
        "type": "object",
        "required": [
          "success",
          "fittingId",
          "clubType",
          "status",
          "input",
          "createdAt"
        ],
        "properties": {
          "success": {
            "const": true
          },
          "fittingId": {
            "type": "string"
          },
          "clubType": {
            "type": "string"
          },
          "status": {
            "type": "string"
          },
          "input": {
            "$ref": "#/components/schemas/FittingRequest"
          },
          "output": {
            "type": [
              "object",
              "null"
            ],
            "additionalProperties": true
          },
          "processingTimeMs": {
            "type": [
              "integer",
              "null"
            ]
          },
          "createdAt": {
            "type": "string",
            "format": "date-time"
          }
        }
      },
      "SessionIngestRequest": {
        "type": "object",
        "required": [
          "payloadVersion",
          "appVersion",
          "sessionId",
          "fitterId",
          "startedAtUtc",
          "processedAtUtc",
          "segments"
        ],
        "properties": {
          "payloadVersion": {
            "type": "integer",
            "minimum": 1
          },
          "appVersion": {
            "type": "string"
          },
          "customerRef": {
            "type": "string",
            "description": "Optional customer email or web-app customer id."
          },
          "sessionId": {
            "type": "string",
            "format": "uuid"
          },
          "fitterId": {
            "type": "string"
          },
          "startedAtUtc": {
            "type": "string",
            "format": "date-time"
          },
          "processedAtUtc": {
            "type": "string",
            "format": "date-time"
          },
          "segments": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/SessionSegment"
            }
          }
        }
      },
      "SessionSegment": {
        "type": "object",
        "required": [
          "id",
          "shots"
        ],
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "label": {
            "type": [
              "string",
              "null"
            ]
          },
          "shots": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/SessionShot"
            }
          }
        }
      },
      "SessionShot": {
        "type": "object",
        "required": [
          "shotId",
          "shotNumber",
          "capturedAtUtc",
          "source",
          "measurementRef",
          "units",
          "needsReview"
        ],
        "properties": {
          "shotId": {
            "type": "string",
            "format": "uuid"
          },
          "shotNumber": {
            "type": "integer",
            "minimum": 1
          },
          "capturedAtUtc": {
            "type": "string",
            "format": "date-time"
          },
          "club": {
            "type": [
              "string",
              "null"
            ]
          },
          "source": {
            "type": "string",
            "enum": [
              "ocr-foresight",
              "ocr-trackman",
              "synthetic",
              "csv"
            ]
          },
          "measurementRef": {
            "type": "string",
            "enum": [
              "club-face",
              "club-cog"
            ]
          },
          "units": {
            "type": "string"
          },
          "ocrConfidence": {
            "type": [
              "number",
              "null"
            ],
            "minimum": 0,
            "maximum": 1
          },
          "needsReview": {
            "type": "boolean"
          },
          "ball": {
            "$ref": "#/components/schemas/NullableMetricBag"
          },
          "club_": {
            "$ref": "#/components/schemas/NullableMetricBag"
          },
          "extended": {
            "$ref": "#/components/schemas/NullableMetricBag"
          }
        }
      },
      "NullableMetricBag": {
        "type": [
          "object",
          "null"
        ],
        "additionalProperties": {
          "type": [
            "number",
            "null"
          ]
        }
      },
      "SessionIngestResponse": {
        "type": "object",
        "required": [
          "sessionUrl",
          "customerMatched",
          "shotsAccepted"
        ],
        "properties": {
          "sessionUrl": {
            "type": "string",
            "format": "uri"
          },
          "customerMatched": {
            "type": "boolean"
          },
          "shotsAccepted": {
            "type": "integer"
          }
        }
      },
      "FittingRequest": {
        "type": "object",
        "required": [
          "clubType",
          "golfer",
          "swingMetrics"
        ],
        "properties": {
          "clubType": {
            "type": "string",
            "enum": [
              "driver",
              "fairway",
              "hybrid",
              "iron",
              "wedge"
            ],
            "description": "driver; fairway (fairway wood); hybrid; iron (an iron set, fitted from the 7-iron); wedge. swingMetrics are measured with that club: driver numbers for driver and fairway (or 3-wood numbers with clubDetails.fairwaySpeedSource=three_wood), the hybrid's own numbers, 7-iron numbers for irons, the wedge's own numbers for wedges."
          },
          "golfer": {
            "type": "object",
            "required": [
              "handedness"
            ],
            "properties": {
              "handedness": {
                "type": "string",
                "enum": [
                  "left",
                  "right"
                ]
              },
              "handicap": {
                "oneOf": [
                  {
                    "type": "number",
                    "minimum": -5,
                    "maximum": 25
                  },
                  {
                    "const": "25+"
                  }
                ]
              },
              "heightFeet": {
                "type": "integer",
                "minimum": 3,
                "maximum": 8
              },
              "heightInches": {
                "type": "integer",
                "minimum": 0,
                "maximum": 11
              },
              "weight": {
                "type": "integer",
                "minimum": 50,
                "maximum": 500
              },
              "wristToFloor": {
                "type": "number",
                "minimum": 20,
                "maximum": 50,
                "description": "Optional. Without it lengths stay standard."
              },
              "gender": {
                "type": "string",
                "enum": ["male", "female"],
                "description": "Optional, only when the golfer said. female applies the women's rules (Ladies flex, women's lengths and heads); omitted = fitted on the men's tables."
              },
              "age": {
                "type": "integer",
                "minimum": 5,
                "maximum": 100,
                "description": "Optional, only when the golfer said. Juniors (12 and under) get the junior rules."
              },
              "averageScore": {
                "type": "number",
                "minimum": 60,
                "maximum": 150,
                "description": "Optional: the golfer's average 18-hole score, for a golfer with no handicap. Send handicap instead when they have one."
              }
            }
          },
          "swingMetrics": {
            "type": "object",
            "description": "Provide swingSpeed or ballSpeed.",
            "properties": {
              "swingSpeed": {
                "type": "number",
                "minimum": 30,
                "maximum": 150
              },
              "ballSpeed": {
                "type": "number",
                "minimum": 50,
                "maximum": 250
              },
              "launchAngle": {
                "type": "number",
                "minimum": -10,
                "maximum": 30
              },
              "attackAngle": {
                "type": "number",
                "minimum": -15,
                "maximum": 15
              },
              "clubPath": {
                "type": "number",
                "minimum": -15,
                "maximum": 15
              },
              "faceAngle": {
                "type": "number",
                "minimum": -10,
                "maximum": 10
              },
              "spinRate": {
                "type": "number",
                "minimum": 1000,
                "maximum": 12000
              },
              "carryDistance": {
                "type": "number",
                "minimum": 30,
                "maximum": 400
              },
              "totalDistance": {
                "type": "number",
                "minimum": 150,
                "maximum": 500,
                "description": "Driver total distance (carry plus roll) in yards"
              },
              "peakHeight": {
                "type": "number",
                "minimum": 50,
                "maximum": 200
              },
              "landingAngle": {
                "type": "number",
                "minimum": 10,
                "maximum": 50
              },
              "strikePattern": {
                "type": "string",
                "enum": [
                  "heel",
                  "toe",
                  "center",
                  "mix"
                ]
              },
              "missPattern": {
                "type": "string",
                "enum": [
                  "fade",
                  "hook",
                  "straight",
                  "both"
                ]
              },
              "missSize": {
                "type": "string",
                "enum": [
                  "slight",
                  "moderate",
                  "large"
                ],
                "description": "Optional, after missPattern: how big the usual miss is (slight, moderate or large). A driver's stated miss moves the adjustable weight to Draw or Fade Bias, and earns a Draw Bias head, only when it is moderate or large; slight or omitted keeps Standard and no Draw Bias head. The same goes for the Draw head on a fairway wood or hybrid. A \"less-slices\" or \"less-hooks\" entry in preferences.fixes counts as a moderate miss. Measured club path and face angle still decide (Moderate or Large rating moves the weight)."
              },
              "ballFlight": {
                "type": "string",
                "enum": [
                  "low",
                  "normal",
                  "high"
                ],
                "description": "Optional, for golfers without a launch monitor: how their ball flight usually looks (low = too low, normal = good, high = too high). Driver, and fairway with driver numbers: used as a launch angle of 9° / 13° / 17°, the mapping ELDRICK's fitting form uses. Fairway with 3-wood numbers, hybrid, iron and wedge: passed to the fitting as the golfer's description; no launch number is assumed. A measured launchAngle takes precedence."
              }
            }
          },
          "currentEquipment": {
            "type": "object",
            "properties": {
              "brand": {
                "type": "string"
              },
              "model": {
                "type": "string",
                "description": "The golfer's current club model: free text, or the value of an option from GET /v1/catalog/club-models (for example \"P790 (2019)\", \"GTS3 (current model)\"). A model name sold in several generations without its year (\"P790\") or a model ELDRICK cannot date gets no age in the write-up."
              },
              "shaftModel": {
                "type": "string"
              },
              "shaftLaunchProfile": {
                "type": "string",
                "enum": [
                  "Low",
                  "Mid-Low",
                  "Mid",
                  "Mid-High",
                  "High"
                ],
                "description": "Launch profile of the golfer's current shaft (for example the catalog shaft they picked from GET /v1/catalog). Launch-profile rules start from it. Omitted: unknown, treated as Mid, and the result lists the current_shaft_launch_unknown data gap."
              },
              "loft": {
                "type": "number",
                "description": "Driver/iron: the current club's loft. Fairway (12-23), hybrid (17-28) and wedge (44-62): the loft of the club being fitted; a wedge defaults to 52."
              },
              "length": {
                "type": "number"
              },
              "shaftWeight": {
                "type": "integer",
                "minimum": 40,
                "maximum": 130
              }
            }
          },
          "preferences": {
            "type": "object",
            "properties": {
              "tempo": {
                "type": "string",
                "enum": [
                  "slow",
                  "medium",
                  "fast"
                ]
              },
              "transition": {
                "type": "string",
                "enum": [
                  "smooth",
                  "medium",
                  "quick"
                ]
              },
              "shaftType": {
                "type": "string",
                "enum": [
                  "steel",
                  "graphite"
                ]
              },
              "release": {
                "type": "string"
              },
              "feelBalance": {
                "type": "string"
              },
              "goals": {
                "type": "string"
              },
              "fixes": {
                "type": "array",
                "maxItems": 8,
                "items": { "type": "string", "enum": ["less-spraying", "less-slices", "less-hooks", "more-distance", "reduce-spin", "more-spin", "lower-trajectory", "higher-trajectory"] },
                "description": "Optional: the ball-flight issues the golfer wants fixed (eldrick.com's chips). less-slices earns the Draw Bias weight and less-hooks the Fade Bias weight even without a missPattern."
              }
            }
          },
          "clubDetails": {
            "type": "object",
            "description": "Fairway, hybrid and wedge only (ignored for driver and iron). The same extra inputs eldrick.com uses for these clubs.",
            "properties": {
              "fairwayDesignation": {
                "type": "string",
                "enum": [
                  "3-wood",
                  "5-wood",
                  "7-wood"
                ],
                "description": "Fairway: which wood is being fitted (sets the stock length)."
              },
              "fairwaySpeedSource": {
                "type": "string",
                "enum": [
                  "driver",
                  "three_wood"
                ],
                "description": "Fairway: whether swingMetrics are driver numbers (default) or 3-wood numbers (converted to driver-equivalent before fitting)."
              },
              "driverSpeed": {
                "type": "number",
                "minimum": 30,
                "maximum": 150,
                "description": "Fairway/hybrid: a measured driver speed (mph). Sets the driver shaft baseline the wood weight steps up from."
              },
              "sevenIronSpeed": {
                "type": "number",
                "minimum": 30,
                "maximum": 150,
                "description": "Wedge: a measured 7-iron speed (mph). Wedge shaft rows are 7-iron calibrated; without it the wedge speed ÷ 0.885 is used."
              },
              "pwLoft": {
                "type": "number",
                "minimum": 35,
                "maximum": 52,
                "description": "Wedge: pitching wedge loft. The recommended wedge set gaps from it."
              },
              "lobWedgeFullSwing": {
                "type": "string",
                "enum": [
                  "yes",
                  "no",
                  "unsure"
                ],
                "description": "Wedge: does the golfer often hit the lob wedge full? Sets the SW→LW gap."
              },
              "wedgeHit": {
                "type": "string",
                "enum": [
                  "PW",
                  "GW",
                  "AW",
                  "UW",
                  "SW",
                  "LW"
                ],
                "description": "Wedge: which wedge the swingMetrics were hit with (AW/UW = gap wedge). The numbers are read for that club: GW or SW ball-flight table (none for PW/LW), stock length (steel GW 35.5\", SW 35.25\", LW 35\"), SW/LW swing weight rule. Without currentEquipment.loft it sets the loft (PW 46°, GW 50°, SW 56°, LW 60°). Omitted: the loft decides. Wedge spin is graded up to 11,000 rpm."
              },
              "ironFittingId": {
                "type": "string",
                "description": "Wedge: a completed iron fitting from this account. Its shaft weight, flex, swing weight, launch and 7-iron speed carry into the wedges."
              },
              "ironShaft": {
                "type": "object",
                "description": "Wedge, without ironFittingId: the golfer's current iron shaft.",
                "properties": {
                  "flex": {
                    "type": "string",
                    "enum": [
                      "Ladies",
                      "Senior",
                      "Regular",
                      "Stiff",
                      "Stiff+",
                      "Extra Stiff"
                    ]
                  },
                  "weightGrams": {
                    "type": "number",
                    "minimum": 40,
                    "maximum": 140
                  },
                  "material": {
                    "type": "string",
                    "enum": [
                      "Steel",
                      "Graphite"
                    ]
                  },
                  "launchProfile": {
                    "type": "string",
                    "enum": [
                      "Low",
                      "Mid-Low",
                      "Mid",
                      "Mid-High",
                      "High"
                    ]
                  }
                }
              }
            }
          },
          "golferWords": {
            "type": "object",
            "description": "Optional, display only. The golfer's own short words for inputs the fitting takes as enums and numbers, shown on the fitting instead of them (\"Slice miss\", \"From 106-108 mph\"). Each value: at most 24 characters of letters, digits, spaces and - / . °, no links, handles or emails; swingSpeed and carry need a digit, miss and ballFlight a letter. A value that breaks a rule is dropped without an error. Never read by the fitting engine or the matcher: still send swingMetrics.missPattern, swingSpeed, carryDistance and ballFlight.",
            "properties": {
              "miss": {
                "type": "string",
                "maxLength": 24,
                "pattern": "^[A-Za-z0-9 ./°-]+$",
                "examples": [
                  "slice"
                ]
              },
              "swingSpeed": {
                "type": "string",
                "maxLength": 24,
                "pattern": "^[A-Za-z0-9 ./°-]+$",
                "examples": [
                  "106-108 mph"
                ]
              },
              "carry": {
                "type": "string",
                "maxLength": 24,
                "pattern": "^[A-Za-z0-9 ./°-]+$",
                "examples": [
                  "240-250 yd"
                ]
              },
              "ballFlight": {
                "type": "string",
                "maxLength": 24,
                "pattern": "^[A-Za-z0-9 ./°-]+$",
                "examples": [
                  "low"
                ]
              }
            },
            "additionalProperties": true,
            "examples": [
              {
                "miss": "slice",
                "swingSpeed": "106-108 mph"
              }
            ]
          }
        }
      },
      "FittingSpecs": {
        "type": "object",
        "required": [
          "loft",
          "shaftFlex",
          "shaftWeight",
          "length",
          "lieAngle",
          "headProfile",
          "swingWeight"
        ],
        "properties": {
          "loft": {
            "type": "string"
          },
          "shaftFlex": {
            "type": "string"
          },
          "shaftWeight": {
            "type": "string"
          },
          "length": {
            "type": "string"
          },
          "lieAngle": {
            "type": "string"
          },
          "headProfile": {
            "type": "string",
            "description": "Driver/iron head category; fairway/hybrid wood head category (Core, Draw, Light, Low Spin, Tour); \"Wedge\" for wedges."
          },
          "swingWeight": {
            "type": "string"
          }
        }
      },
      "ProductRecommendation": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string"
          },
          "sku": {
            "type": "string"
          },
          "name": {
            "type": "string"
          },
          "brand": {
            "type": [
              "string",
              "null"
            ]
          },
          "model": {
            "type": [
              "string",
              "null"
            ]
          },
          "category": {
            "type": [
              "string",
              "null"
            ]
          },
          "price": {
            "type": [
              "number",
              "null"
            ]
          },
          "image": {
            "type": [
              "string",
              "null"
            ]
          },
          "matchScore": {
            "type": "number"
          },
          "matchReasons": {
            "type": "array",
            "items": {
              "type": "string"
            }
          }
        }
      },
      "ShaftRecommendation": {
        "allOf": [
          {
            "$ref": "#/components/schemas/ProductRecommendation"
          },
          {
            "type": "object",
            "properties": {
              "flex": {
                "type": [
                  "string",
                  "null"
                ]
              },
              "weightBand": {
                "type": [
                  "string",
                  "null"
                ]
              },
              "launchProfile": {
                "type": [
                  "string",
                  "null"
                ]
              },
              "pairedHeadId": {
                "type": "string"
              },
              "stock": {
                "type": "boolean"
              }
            }
          }
        ]
      },
      "GripRecommendation": {
        "allOf": [
          {
            "$ref": "#/components/schemas/ProductRecommendation"
          },
          {
            "type": "object",
            "properties": {
              "size": {
                "type": [
                  "string",
                  "null"
                ]
              }
            }
          }
        ]
      },
      "ClubRecommendations": {
        "type": "object",
        "properties": {
          "heads": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/ProductRecommendation"
            }
          },
          "stockShafts": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/ShaftRecommendation"
            }
          },
          "aftermarketShafts": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/ShaftRecommendation"
            }
          },
          "grips": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/GripRecommendation"
            }
          },
          "matchingCriteria": {
            "type": "object",
            "properties": {
              "headCategory": {
                "type": "string",
                "description": "Served head category. Wedges: \"Blade\" or \"Cavity back\"."
              },
              "shaftFlex": {
                "type": "string"
              },
              "shaftWeight": {
                "type": "string"
              },
              "shaftWeightBucket": {
                "type": "string"
              },
              "launchProfile": {
                "type": "string"
              },
              "handedness": {
                "type": "string",
                "enum": [
                  "left",
                  "right"
                ]
              }
            }
          },
          "diagnostics": {
            "type": "object",
            "properties": {
              "catalogVersion": {
                "type": [
                  "string",
                  "null"
                ]
              },
              "candidateHeadCount": {
                "type": "number"
              },
              "pairingCount": {
                "type": "number"
              },
              "aftermarketShaftCount": {
                "type": "number"
              },
              "gripCount": {
                "type": "number"
              },
              "fallbackUsed": {
                "type": "boolean"
              }
            }
          }
        }
      },
      "Analysis": {
        "type": "object",
        "properties": {
          "configurations": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "category": {
                  "type": "string"
                },
                "recommendations": {
                  "type": "array",
                  "items": {
                    "type": "string"
                  }
                },
                "reasoning": {
                  "type": "string"
                }
              }
            }
          },
          "summary": {
            "type": "string"
          },
          "keyInsights": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "swingWeight": {
            "type": "string"
          }
        }
      },
      "FittingSuccessResponse": {
        "type": "object",
        "properties": {
          "success": {
            "const": true
          },
          "fittingId": {
            "type": "string"
          },
          "requestId": {
            "type": "string"
          },
          "clubType": {
            "type": "string",
            "enum": [
              "driver",
              "fairway",
              "hybrid",
              "iron",
              "wedge"
            ]
          },
          "labUrl": {
            "type": [
              "string",
              "null"
            ],
            "format": "uri",
            "description": "15-minute Lab share URL for driver and iron fittings. Null for other clubs or when Lab handoff is not configured."
          },
          "labExpiresAt": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          },
          "specs": {
            "$ref": "#/components/schemas/FittingSpecs"
          },
          "clubRecommendations": {
            "$ref": "#/components/schemas/ClubRecommendations"
          },
          "analysis": {
            "$ref": "#/components/schemas/Analysis"
          },
          "meta": {
            "type": "object",
            "properties": {
              "apiVersion": {
                "type": "string",
                "example": "v1",
                "description": "The stable request/response contract version."
              },
              "processingTimeMs": {
                "type": "number"
              },
              "engineVersion": {
                "type": "string",
                "example": "eldrick-fit-legacy-deterministic-v1",
                "description": "The client-facing fitting release that generated this result."
              },
              "rulesetRevision": {
                "type": "string",
                "example": "core@3000454",
                "description": "The internal source/ruleset revision for traceability."
              },
              "modelVersion": {
                "type": "string"
              },
              "catalogVersion": {
                "type": [
                  "string",
                  "null"
                ]
              }
            }
          },
          "woodFit": {
            "type": "object",
            "description": "Fairway and hybrid only: club, designation, recommended loft and label, gapping note, driver and wood shaft weight bands, weight step (g) and length.",
            "additionalProperties": true
          },
          "wedgeFit": {
            "type": "object",
            "description": "Wedge only: recommended and target loft, bounce (label, bracket, target, min, max, range, note), the recommended wedge set makeups with per-wedge loft and bounce, gap/sand wedge ball-flight flags, and wedgeHit (the wedge the numbers were read for: PW, GW, SW, LW, or null).",
            "additionalProperties": true
          }
        }
      },
      "ErrorResponse": {
        "type": "object",
        "properties": {
          "success": {
            "const": false
          },
          "error": {
            "type": "object",
            "properties": {
              "code": {
                "type": "string",
                "description": "VALIDATION_ERROR, INPUT_REJECTED, UNAUTHORIZED, NOT_FOUND, CONFLICT, RATE_LIMITED, QUOTA_EXCEEDED, DAILY_CAP_REACHED, CATALOG_UNAVAILABLE, INTERNAL_ERROR."
              },
              "message": {
                "type": "string"
              },
              "details": {
                "type": "array",
                "items": {
                  "type": "object",
                  "properties": {
                    "field": {
                      "type": "string"
                    },
                    "message": {
                      "type": "string"
                    }
                  }
                }
              },
              "requestId": {
                "type": "string"
              },
              "retryAfter": {
                "type": "number",
                "description": "Seconds until the request can be retried."
              },
              "scope": {
                "type": "string",
                "enum": [
                  "user",
                  "platform"
                ],
                "description": "DAILY_CAP_REACHED only: which daily cap was reached."
              },
              "resetsAt": {
                "type": "string",
                "format": "date-time"
              }
            }
          }
        }
      },
      "ClubSummary": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string"
          },
          "name": {
            "type": "string"
          },
          "brand": {
            "type": "string"
          },
          "clubType": {
            "type": "string",
            "enum": [
              "driver",
              "fairway",
              "hybrid",
              "iron",
              "wedge"
            ]
          },
          "category": {
            "type": [
              "string",
              "null"
            ]
          },
          "categoryLabel": {
            "type": [
              "string",
              "null"
            ]
          },
          "bias": {
            "type": [
              "string",
              "null"
            ]
          },
          "price": {
            "type": [
              "number",
              "null"
            ],
            "description": "USD; irons are priced per club"
          },
          "pricedPer": {
            "type": "string",
            "enum": [
              "club",
              "iron"
            ]
          },
          "image": {
            "type": [
              "string",
              "null"
            ]
          },
          "lofts": {
            "type": "array",
            "items": {
              "type": "number"
            }
          },
          "adjustable": {
            "type": "boolean"
          },
          "handedness": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "recommendedLast30Days": {
            "type": "integer"
          },
          "stockShaftOptions": {
            "type": "integer"
          },
          "storeUrl": {
            "type": "string",
            "format": "uri"
          }
        }
      },
      "ShaftChoice": {
        "type": "object",
        "properties": {
          "shaftId": {
            "type": "string",
            "description": "Pass to POST /v1/store/link"
          },
          "name": {
            "type": "string"
          },
          "brand": {
            "type": [
              "string",
              "null"
            ]
          },
          "kind": {
            "type": "string",
            "enum": [
              "stock",
              "upgrade"
            ]
          },
          "weightBand": {
            "type": "string"
          },
          "launch": {
            "type": "string"
          },
          "flexes": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "upcharge": {
            "type": "number"
          },
          "tier": {
            "type": [
              "string",
              "null"
            ]
          }
        }
      },
      "ClubDetail": {
        "allOf": [
          {
            "$ref": "#/components/schemas/ClubSummary"
          },
          {
            "type": "object",
            "properties": {
              "family": {
                "type": [
                  "string",
                  "null"
                ]
              },
              "variant": {
                "type": [
                  "string",
                  "null"
                ]
              },
              "options": {
                "type": "object",
                "properties": {
                  "hands": {
                    "type": "array",
                    "items": {
                      "type": "string"
                    }
                  },
                  "lofts": {
                    "type": "array",
                    "items": {
                      "type": "string"
                    }
                  },
                  "stockShafts": {
                    "type": "array",
                    "items": {
                      "$ref": "#/components/schemas/ShaftChoice"
                    }
                  },
                  "upgradeShafts": {
                    "type": "array",
                    "items": {
                      "$ref": "#/components/schemas/ShaftChoice"
                    }
                  },
                  "upgradeShaftCount": {
                    "type": "integer"
                  },
                  "grips": {
                    "type": "array",
                    "items": {
                      "type": "object",
                      "properties": {
                        "gripId": {
                          "type": "string"
                        },
                        "name": {
                          "type": "string"
                        },
                        "brand": {
                          "type": [
                            "string",
                            "null"
                          ]
                        },
                        "size": {
                          "type": [
                            "string",
                            "null"
                          ]
                        },
                        "price": {
                          "type": [
                            "number",
                            "null"
                          ]
                        }
                      }
                    }
                  },
                  "gripSizes": {
                    "type": "array",
                    "items": {
                      "type": "string"
                    }
                  },
                  "lengths": {
                    "type": "array",
                    "items": {
                      "type": "string"
                    }
                  },
                  "lies": {
                    "type": "array",
                    "items": {
                      "type": "string"
                    }
                  },
                  "sets": {
                    "type": "array",
                    "items": {
                      "type": "string"
                    }
                  }
                }
              }
            }
          }
        ]
      },
      "StoreLinkRequest": {
        "type": "object",
        "required": [
          "headId"
        ],
        "properties": {
          "headId": {
            "type": "string"
          },
          "fittingId": {
            "type": "string",
            "description": "A completed fitting from this account; its hand, loft, shaft, flex, length and lie fill the build."
          },
          "hand": {
            "type": "string",
            "enum": [
              "right",
              "left"
            ]
          },
          "loft": {
            "type": "string"
          },
          "shaftId": {
            "type": "string"
          },
          "flex": {
            "type": "string"
          },
          "length": {
            "type": "string"
          },
          "lie": {
            "type": "string"
          },
          "gripId": {
            "type": "string"
          },
          "gripSize": {
            "type": "string",
            "enum": [
              "Undersize",
              "Standard",
              "Midsize",
              "Jumbo"
            ]
          },
          "set": {
            "type": "string",
            "enum": [
              "4-PW",
              "5-PW",
              "6-PW"
            ]
          }
        }
      },
      "StoreLinkResponse": {
        "type": "object",
        "properties": {
          "success": {
            "const": true
          },
          "linkId": {
            "type": "string"
          },
          "url": {
            "type": "string",
            "format": "uri",
            "description": "store.eldrick.com/club/{headId} with the build as query parameters (hand, loft, shaft, flex, length, lie, grip, gripSize, set, fit) plus utm_source/utm_medium/utm_campaign and eld_ref. Opens the product page, never checkout."
          },
          "club": {
            "$ref": "#/components/schemas/ClubSummary"
          },
          "build": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "label": {
                  "type": "string"
                },
                "value": {
                  "type": "string"
                }
              }
            }
          },
          "buildParams": {
            "type": "object",
            "additionalProperties": true
          },
          "prefillSupported": {
            "type": "boolean",
            "description": "True once store.eldrick.com's builder reads the build parameters; until then the golfer picks the build on the page."
          },
          "estimatedPrice": {
            "type": [
              "number",
              "null"
            ]
          },
          "note": {
            "type": "string"
          },
          "fittingId": {
            "type": "string"
          }
        }
      },
      "GripSizeRequest": {
        "type": "object",
        "properties": {
          "handLengthIn": {
            "type": "number",
            "minimum": 3,
            "maximum": 13,
            "description": "Hand length in inches: palm down, from the crease of the wrist to the tip of the longest finger (e.g. 7.5). This sets the size."
          },
          "fingerLengthIn": {
            "type": "number",
            "minimum": 1,
            "maximum": 6,
            "description": "Longest finger in inches, from its base to its tip (e.g. 3.25). Optional: over 3 inches adds one wrap."
          },
          "gloveSize": {
            "type": "string",
            "maxLength": 20,
            "description": "Optional glove size (e.g. M, ML, L, Cadet L). Recorded only: the hand chart sizes from hand length."
          },
          "handedness": {
            "type": "string",
            "enum": [
              "right",
              "left"
            ],
            "description": "Optional. Grip size does not depend on handedness."
          },
          "includeGrips": {
            "type": "boolean",
            "description": "Include catalog grips offered in the recommended size (default true)"
          },
          "gripLimit": {
            "type": "integer",
            "minimum": 1,
            "maximum": 10,
            "description": "Catalog grips to return, 1-10 (default 5)"
          }
        },
        "additionalProperties": false
      },
      "GripSizeResponse": {
        "type": "object",
        "description": "status \"recommended\" carries the recommendation; \"needs_hand_length\" has recommendation null and howToMeasure.",
        "properties": {
          "success": {
            "const": true
          },
          "status": {
            "type": "string",
            "enum": [
              "recommended",
              "needs_hand_length"
            ]
          },
          "recommendation": {
            "type": [
              "object",
              "null"
            ],
            "properties": {
              "size": {
                "type": "string",
                "enum": [
                  "junior",
                  "undersize",
                  "standard",
                  "midsize",
                  "jumbo"
                ]
              },
              "sizeLabel": {
                "type": "string"
              },
              "wraps": {
                "type": "integer",
                "enum": [
                  0,
                  1,
                  2
                ]
              },
              "wrapsLabel": {
                "type": "string"
              },
              "label": {
                "type": "string",
                "examples": [
                  "Midsize + 1 wrap"
                ]
              },
              "storeGripSize": {
                "type": [
                  "string",
                  "null"
                ],
                "description": "gripSize for POST /v1/store/link (Undersize, Standard, Midsize, Jumbo); null for junior"
              },
              "chart": {
                "type": "object",
                "properties": {
                  "hand": {
                    "type": "string"
                  },
                  "finger": {
                    "type": [
                      "string",
                      "null"
                    ]
                  }
                }
              },
              "flags": {
                "type": "object",
                "properties": {
                  "beyondChart": {
                    "type": "boolean"
                  },
                  "fingerWrap": {
                    "type": "boolean"
                  }
                }
              }
            }
          },
          "reasoning": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "measurements": {
            "type": "object"
          },
          "grips": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "gripId": {
                  "type": "string"
                },
                "name": {
                  "type": "string"
                },
                "brand": {
                  "type": [
                    "string",
                    "null"
                  ]
                },
                "size": {
                  "type": [
                    "string",
                    "null"
                  ]
                },
                "price": {
                  "type": [
                    "number",
                    "null"
                  ]
                }
              }
            }
          },
          "howToMeasure": {
            "type": "object"
          },
          "rules": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "notes": {
            "type": "array",
            "items": {
              "type": "string"
            }
          }
        }
      },
      "BagGappingRequest": {
        "type": "object",
        "properties": {
          "clubs": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "club": {
                  "type": "string",
                  "minLength": 1,
                  "maxLength": 40,
                  "description": "The club, e.g. Driver, 3W, 5-wood, 4H, 4-iron, 7, PW, GW, SW, LW, 54 (a wedge by loft), Putter"
                },
                "type": {
                  "type": "string",
                  "enum": [
                    "driver",
                    "fairway",
                    "hybrid",
                    "iron",
                    "wedge",
                    "putter"
                  ],
                  "description": "driver, fairway, hybrid, iron, wedge or putter, when the label alone is unclear"
                },
                "loft": {
                  "type": "number",
                  "minimum": 5,
                  "maximum": 64,
                  "description": "Loft in degrees, if known (a stock loft is assumed otherwise, and marked)"
                },
                "carry": {
                  "type": "number",
                  "minimum": 10,
                  "maximum": 400,
                  "description": "Typical carry in yards, if known (estimated otherwise, and marked)"
                },
                "total": {
                  "type": "number",
                  "minimum": 10,
                  "maximum": 450,
                  "description": "Typical total distance (carry plus roll) in yards, if known"
                }
              },
              "required": [
                "club"
              ],
              "additionalProperties": false
            },
            "minItems": 2,
            "maxItems": 20,
            "description": "Every club in the bag, in any order"
          },
          "driverSpeed": {
            "type": "number",
            "minimum": 40,
            "maximum": 150,
            "description": "Driver swing speed in mph, if known (used to estimate missing carries)"
          },
          "sevenIronSpeed": {
            "type": "number",
            "minimum": 30,
            "maximum": 130,
            "description": "7-iron swing speed in mph, if known"
          },
          "attackAngle": {
            "type": "number",
            "minimum": -15,
            "maximum": 15,
            "description": "Optional wedge attack angle in degrees, for the bounce of suggested wedges"
          },
          "lobWedgeFullSwing": {
            "type": "string",
            "enum": [
              "yes",
              "no",
              "unsure"
            ],
            "description": "Optional: does the golfer hit the lob wedge with a full swing often? (sets the SW → LW gap)"
          }
        },
        "required": [
          "clubs"
        ],
        "additionalProperties": false
      },
      "BagGappingResponse": {
        "type": "object",
        "properties": {
          "success": {
            "const": true
          },
          "clubs": {
            "type": "array",
            "description": "In bag order (by loft), putter last.",
            "items": {
              "type": "object",
              "properties": {
                "label": {
                  "type": "string"
                },
                "type": {
                  "type": "string"
                },
                "loft": {
                  "type": [
                    "number",
                    "null"
                  ]
                },
                "loftSource": {
                  "type": [
                    "string",
                    "null"
                  ],
                  "enum": [
                    "given",
                    "label",
                    "engine_stock",
                    "assumed",
                    "iron_chart",
                    null
                  ]
                },
                "carry": {
                  "type": [
                    "number",
                    "null"
                  ]
                },
                "carrySource": {
                  "type": [
                    "string",
                    "null"
                  ],
                  "enum": [
                    "given",
                    "estimated",
                    null
                  ]
                },
                "total": {
                  "type": [
                    "number",
                    "null"
                  ]
                }
              }
            }
          },
          "gaps": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "from": {
                  "type": "string"
                },
                "to": {
                  "type": "string"
                },
                "loftGap": {
                  "type": [
                    "number",
                    "null"
                  ]
                },
                "carryGap": {
                  "type": [
                    "number",
                    "null"
                  ]
                },
                "estimated": {
                  "type": "boolean"
                },
                "flags": {
                  "type": "array",
                  "items": {
                    "$ref": "#/components/schemas/BagFlag"
                  }
                }
              }
            }
          },
          "flags": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/BagFlag"
            }
          },
          "suggestions": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "action": {
                  "type": "string",
                  "enum": [
                    "add",
                    "drop",
                    "replace",
                    "change_loft"
                  ]
                },
                "club": {
                  "type": "string"
                },
                "loft": {
                  "type": [
                    "number",
                    "null"
                  ]
                },
                "replaces": {
                  "type": [
                    "string",
                    "null"
                  ]
                },
                "reason": {
                  "type": "string"
                },
                "basis": {
                  "type": "string",
                  "enum": [
                    "engine_rule",
                    "eldrick_guidance",
                    "rules_of_golf"
                  ]
                },
                "bounce": {
                  "type": [
                    "object",
                    "null"
                  ]
                },
                "fitWith": {
                  "type": "object",
                  "description": "The analyze_fitting (POST /v1/fitting/analyze) club type, and loft or pwLoft, for this club",
                  "properties": {
                    "tool": {
                      "const": "analyze_fitting"
                    },
                    "clubType": {
                      "type": "string",
                      "enum": [
                        "driver",
                        "fairway",
                        "hybrid",
                        "iron",
                        "wedge"
                      ]
                    },
                    "loft": {
                      "type": "number"
                    },
                    "pwLoft": {
                      "type": "number"
                    },
                    "fairwayDesignation": {
                      "type": "string"
                    }
                  }
                }
              }
            }
          },
          "wedgeSet": {
            "type": [
              "object",
              "null"
            ],
            "description": "The recommended wedge set from the PW loft (master fitter rules), with the alternative makeup"
          },
          "speeds": {
            "type": [
              "object",
              "null"
            ]
          },
          "clubCount": {
            "type": "integer"
          },
          "clubCountAfter": {
            "type": "integer"
          },
          "estimatedCarries": {
            "type": "integer"
          },
          "thresholds": {
            "type": "object",
            "description": "Yardage thresholds applied (ELDRICK guidance, scaled by driver speed)"
          },
          "summary": {
            "type": "string"
          },
          "guidance": {
            "type": "array",
            "items": {
              "type": "string"
            }
          }
        }
      },
      "BagFlag": {
        "type": "object",
        "properties": {
          "code": {
            "type": "string",
            "enum": [
              "duplicate_loft",
              "wedge_gap_too_wide",
              "overlap",
              "gap_too_small",
              "gap_too_big",
              "too_many_clubs",
              "no_distance_data"
            ]
          },
          "severity": {
            "type": "string",
            "enum": [
              "high",
              "medium",
              "low"
            ]
          },
          "basis": {
            "type": "string",
            "enum": [
              "engine_rule",
              "eldrick_guidance",
              "rules_of_golf"
            ]
          },
          "message": {
            "type": "string"
          },
          "clubs": {
            "type": "array",
            "items": {
              "type": "string"
            }
          }
        }
      },
      "Fitter": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string"
          },
          "name": {
            "type": "string"
          },
          "label": {
            "type": "string",
            "const": "ELDRICK partner",
            "description": "Present for ELDRICK partners only"
          },
          "partner": {
            "type": "boolean"
          },
          "distanceMiles": {
            "type": "number"
          },
          "businessType": {
            "type": [
              "string",
              "null"
            ]
          },
          "address": {
            "type": "object",
            "properties": {
              "street": {
                "type": [
                  "string",
                  "null"
                ]
              },
              "city": {
                "type": [
                  "string",
                  "null"
                ]
              },
              "state": {
                "type": [
                  "string",
                  "null"
                ]
              },
              "postalCode": {
                "type": [
                  "string",
                  "null"
                ]
              },
              "country": {
                "type": "string"
              }
            }
          },
          "phone": {
            "type": [
              "string",
              "null"
            ]
          },
          "website": {
            "type": [
              "string",
              "null"
            ]
          },
          "bookingUrl": {
            "type": [
              "string",
              "null"
            ]
          },
          "recognition": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "rating": {
            "type": [
              "object",
              "null"
            ],
            "description": "Only when a published rating and its source are on file",
            "properties": {
              "value": {
                "type": "number"
              },
              "reviewCount": {
                "type": [
                  "integer",
                  "null"
                ]
              },
              "source": {
                "type": "string"
              }
            }
          },
          "services": {
            "type": "array",
            "items": {
              "type": "string",
              "enum": [
                "driver",
                "fairway_hybrid",
                "irons",
                "wedges",
                "putter",
                "full_bag"
              ]
            }
          },
          "brands": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "verifiedAt": {
            "type": [
              "string",
              "null"
            ],
            "format": "date"
          },
          "sourceUrls": {
            "type": "array",
            "items": {
              "type": "string"
            }
          }
        }
      },
      "FittersResponse": {
        "type": "object",
        "properties": {
          "success": {
            "const": true
          },
          "origin": {
            "type": "object",
            "properties": {
              "label": {
                "type": "string"
              },
              "lat": {
                "type": "number"
              },
              "lng": {
                "type": "number"
              },
              "source": {
                "type": "string",
                "enum": [
                  "coordinates",
                  "zcta",
                  "fitter_postal_codes",
                  "fitter_city"
                ]
              }
            }
          },
          "radiusMiles": {
            "type": "number"
          },
          "filters": {
            "type": "object"
          },
          "count": {
            "type": "integer"
          },
          "totalInRadius": {
            "type": "integer"
          },
          "partnersInRadius": {
            "type": "integer"
          },
          "fitters": {
            "type": "array",
            "description": "ELDRICK partners within the radius first, then by distance",
            "items": {
              "$ref": "#/components/schemas/Fitter"
            }
          },
          "nearestOutsideRadius": {
            "type": "array",
            "description": "Only when nothing is within the radius: the 3 nearest",
            "items": {
              "$ref": "#/components/schemas/Fitter"
            }
          },
          "summary": {
            "type": "string"
          },
          "dataNote": {
            "type": "string"
          }
        }
      }
    }
  }
}
