4 min read

Keep a golf club database current with a nightly GET /changes job

Store the release header from your last full scan, ask GET /changes what each later release added, updated or removed, and refetch only those clubs.

A timeline of four catalog releases above three cards for clubs added, updated and removed.

Once you've stored the catalog, you don't have to rescan it to stay current. GET /changes lists the clubs each release added, updated or removed since the one you stored, and you refetch only those.

Store the release with your scan

Every authenticated catalog response names the catalog release it was read from, in two headers:

text
Golfapi-Catalog-Release: bld_01M77S54P0FCDDQVVAB2DCKPY5
Golfapi-Catalog-Published: 2026-11-12T11:45:14Z

Run your first full scan as described in Catalog sync. Save the Golfapi-Catalog-Release value with the documents. Read it from the first page and check that the last page names the same release. If a release was published during the scan, the two differ; scan again or start the change feed from the earlier one.

Ask what changed

Send the stored release as after:

Shell
curl 'https://api.golfapi.dev/changes?after=bld_01M6F0VDZH8HGAJAG815AJAGQC&limit=2' \
  -H "Authorization: Bearer $GOLFDEV_API_KEY"

Run export GOLFDEV_API_KEY='sk_test_…' with your key in the same terminal first. Leave $GOLFDEV_API_KEY as written; the shell replaces it with your key. How to set your key

The response lists the releases published after that one, oldest first, with club ids per resource. Response on 12 November 2026, cut to two releases and fewer ids:

JSON
{
  "after": "bld_01M6F0VDZH8HGAJAG815AJAGQC",
  "data": [
    {
      "release": "bld_01M6H19R5Z93JRF43G4X40QWMF",
      "published_at": "2026-11-03T15:44:33.739102Z",
      "changes": {
        "drivers": {
          "added": [],
          "updated": [
            "clb_01M403FZ82QC5AJR2VQ1G2ESYP",
            "clb_01M462H41MSVDT5PGWTT60ZE7B"
          ],
          "removed": []
        },
        "hybrids": {
          "added": [],
          "updated": [
            "clb_01M408GWX27G0EVF721ADKTAN3"
          ],
          "removed": []
        }
      }
    },
    {
      "release": "bld_01M6H29WQ7DJVV0Q3EGESHNY5N",
      "published_at": "2026-11-03T16:02:24.486920Z",
      "changes": {
        "fairway-woods": {
          "added": [],
          "updated": [
            "clb_01M3XEAXTQZ1PVN4AAZPTFSSQ6"
          ],
          "removed": []
        },
        "drivers": {
          "added": [],
          "updated": [
            "clb_01M5CN3MJZHAHSBVK1BAGR1P0N"
          ],
          "removed": []
        }
      }
    }
  ],
  "page": {
    "limit": 2,
    "has_more": true,
    "next": "cur_eyJ5IjowLCJp…"
  }
}

/changes lists only the club types your key can read. A key without fairway-woods access never sees fairway-wood ids.

In a chat, an assistant connected to the MCP server answers the same question with get_changes. Ask "What changed in the catalog since release bld_01M6F0VDZH8HGAJAG815AJAGQC?" to check a release by hand. That release is in the live catalog, so the prompt needs a live key on Hobby or Pro; a Free account's test key reads the sample's own releases.

Apply each release

For each release in order:

  • Fetch every id in added and updated by id and replace the stored document. If you store a view or a fields list, send the same one.
  • Delete every id in removed. A request for a removed id returns 410.
  • After the last release, store its release as your new after.

Follow page.next as cursor until page.has_more is false. Send after with every page; limit is at most 50. An id that appears in several releases needs only one fetch.

python
import os
import requests
 
API = "https://api.golfapi.dev"
H = {"Authorization": f"Bearer {os.environ['GOLFDEV_API_KEY']}"}
 
def sync(after, store):
    params = {"after": after, "limit": 50}
    newest = after
    while True:
        r = requests.get(f"{API}/changes", headers=H, params=params, timeout=30)
        if r.status_code == 400:
            return None  # the history does not cover this release: rescan
        r.raise_for_status()
        body = r.json()
        for release in body["data"]:
            for resource, c in release["changes"].items():
                for club_id in c["added"] + c["updated"]:
                    doc = requests.get(f"{API}/{resource}/{club_id}", headers=H, timeout=30)
                    doc.raise_for_status()
                    store.put(resource, club_id, doc.json())
                for club_id in c["removed"]:
                    store.delete(resource, club_id)
            newest = release["release"]
        if not body["page"]["has_more"]:
            return newest
        params = {"after": after, "limit": 50, "cursor": body["page"]["next"]}

When to rescan

/changes answers from a stored change history. If your after is older than that history, or is not a release id, the request returns 400 with param: after, and the message names the oldest release it can answer from. Run a full scan and start again from its release.

Rescan also when you add a club type to your key, change the view you store, or change your fields list. /changes reports changes to documents, so it says nothing about clubs you never stored.

How often to call it

A release is published when a signed data change merges. Several can land on one day and none on another. Calling /changes once a day keeps a store within a day of the live catalog, at one request when nothing changed. Each request counts toward your plan's monthly quota. The changelog has a readable weekly note of the same changes. To handle a saved id that stops resolving, see build a club comparison.

More posts

All posts