For golf apps, clubs and federations

HandiRoam API

Let your players bring their handicap with them. A player approves your service once; you can then read their latest handicap and, if they allow it, publish updates.

This documentation is in English. The API and its error messages are English in every language.

Getting started

Requests go to https://handiroam.com over HTTPS and use JSON. Calls that identify your service carry your API key as a bearer token. Keep the key on your server and never put it in a mobile app or web page.

Authorization: Bearer hrk_xxxxxxxxxxxxxxxxxxxxxxxx

Two ways to get a key, both on the developer page.

A sandbox key is instant. It comes with three made-up players to read and publish against, plus a connect link and a share-code endpoint that run the whole approve-and-exchange flow. It can't see any real golfer: it lives in a separate database and its key starts hrs_ rather than hrk_. It is deleted after 30 days.

A live key is by application. Give us your service name, a return address (an https URL on your server that players are sent back to) and what you need. A person reviews it. Live keys start read-only, and you collect the key once when the application is approved. Every live service gets a free tier; the limits are under Errors and limits. Questions: support@handiroam.com.

The two things you store

For each connected player, store the reference we give you (it looks like hrp_K7Q93D2MX8RTBN4F) and the grants (what they allowed). That is all. A reference is private to your service: it only works with your API key, and another service gets a different one for the same player. You never see a HandiRoam ID, an email address or any contact details.

Rotating your key

Change your key whenever you like, and straight away if it may have leaked. Call this with your current key:

POST/key/rotate

curl -X POST https://handiroam.com/key/rotate -H "Authorization: Bearer $HANDIROAM_KEY"

{ "api_key": "hrk_newkeyxxxxxxxxxxxxxxxxxxxxxxxx", "old_key_valid_hours": 24 }

The new key is shown once. Your old key keeps working for 24 hours so you can deploy the new one without downtime, then stops. Only your current key can rotate; the old one can't. If a key has leaked and you want it stopped at once, email support@handiroam.com and we will rotate it with no grace period.

Connect a player

This is the normal way to connect a player: they approve you on HandiRoam and come back to your site.

  1. Create a random, unguessable state value, store it against the player's session on your side, and send the player's browser to the authorise page.
  2. The player signs in (or signs up), sees exactly which kinds of data you are asking for, and ticks what they allow. For each kind they can allow reading, and, if you are set up for it, publishing.
  3. They are sent back to your return address with a one-time code.
  4. Check that state matches, then exchange the code on your server.
# 1. Send the player's browser here
https://handiroam.com/authorise?consumer=your-service-id&state=RANDOM_STATE_VALUE

# 3. They come back to your return address
https://your-app.example.com/handiroam/callback?code=ONE_TIME_CODE&state=RANDOM_STATE_VALUE

POST/link/exchange

curl -X POST https://handiroam.com/link/exchange \
  -H "Authorization: Bearer $HANDIROAM_KEY" \
  -H "Content-Type: application/json" \
  -d '{"code": "ONE_TIME_CODE"}'

{
  "reference": "hrp_K7Q93D2MX8RTBN4F",
  "grants": { "handicap": { "publish": true } }
}

The code works once and expires after five minutes, so exchange it straight away. grants lists each kind of data the player allowed and whether you may also publish it. A player can allow less than you asked for, so always check grants rather than assuming. If the same player connects again later, you get the same reference.

Players can disconnect you at any time from their HandiRoam dashboard. When they do, calls with that reference return 403. Treat that as "disconnected": stop using the reference and show your own "connect HandiRoam" button again.

Read a handicap

GET/players/{reference}/handicap

curl https://handiroam.com/players/hrp_K7Q93D2MX8RTBN4F/handicap \
  -H "Authorization: Bearer $HANDIROAM_KEY"

{
  "reference": "hrp_K7Q93D2MX8RTBN4F",
  "type": "handicap",
  "value": 12.4,
  "source": "self",
  "verified": false,
  "verified_by": null,
  "updated_at": "2026-10-29",
  "federation": { "code": "ENG", "name": "England Golf", "country": "England" },
  "federation_self_declared": true,
  "federation_verified": false
}
FieldMeaning
valueThe handicap, or null if the player has not published one yet. Plus handicaps are negative: a +2.1 player is -2.1. Values run from -10.0 to 54.0 with one decimal place.
sourceself if the player entered it; your own service id if you published it; the id of a verified source (named in verified_by); otherwise service. You are not told which other app a player uses: that, with an exact timestamp, would let two services work out they hold the same person (which is also why updated_at is a day, not a time).
verified, verified_byTrue, with the name of the source, when the value came from a verified source such as the player's federation. See Verified sources. Otherwise false and null.
sync_activePresent only when verified is true. true while the verified source is still connected and keeping the value up to date; false once the player has disconnected it.
updated_atThe day the value was last set (UTC), as YYYY-MM-DD.
federationThe federation the player belongs to, or null. List the codes with GET https://handiroam.com/federations.
federation_self_declaredTrue when the player chose the federation themselves. It is not checked. federation_verified is true instead when a connected federation sets it.
HandiRoam does not check any number. A value is what the player, or a service they allowed, put there. Unless verified is true, treat it as self-reported and not an official handicap record. If you need certainty, for example to enter a competition, check it with the player's federation or club.

GET/players/{reference}

Returns everything you are allowed to read for that player in one call:

{
  "reference": "hrp_K7Q93D2MX8RTBN4F",
  "federation": { "code": "ENG", "name": "England Golf", "country": "England" },
  "federation_self_declared": true,
  "federation_verified": false,
  "records": {
    "handicap": { "value": 12.4, "source": "self", "verified": false, "verified_by": null, "updated_at": "2026-10-29" }
  }
}

Caching

Successful reads send Cache-Control: private, max-age=60. You may reuse a value for up to a minute. Don't keep it longer without asking again: the player may have changed it, or disconnected you.

Publish a handicap

Only if the player allowed you to publish. Send the new value whenever you calculate one; it becomes the player's latest value and is visible to every service they have connected.

PUT/players/{reference}/handicap

curl -X PUT https://handiroam.com/players/hrp_K7Q93D2MX8RTBN4F/handicap \
  -H "Authorization: Bearer $HANDIROAM_KEY" \
  -H "Content-Type: application/json" \
  -d '{"value": 11.9}'

{ "reference": "hrp_K7Q93D2MX8RTBN4F", "type": "handicap", "value": 11.9, "source": "your-service-id" }

A value outside -10.0 to 54.0, or with more than one decimal place, returns 400 with an explanation. Publish only values your own calculation stands behind: the player sees your service named as the source, and their other services see that a service set it.

Share codes

If your service can't send players through the redirect flow (it has no website, or it is a closed system), the player can instead create a share code in HandiRoam and give it to you, by typing it into your app, pasting it into a form, or reading it out over the counter. It works once, expires after ten minutes, and only ever gives read access.

POST/share/redeem

curl -X POST https://handiroam.com/share/redeem \
  -H "Authorization: Bearer $HANDIROAM_KEY" \
  -H "Content-Type: application/json" \
  -d '{"code": "K7Q9-3D2M"}'

{
  "reference": "hrp_K7Q93D2MX8RTBN4F",
  "grants": { "handicap": { "publish": false } }
}

The result is the same as the redirect flow, so reading works exactly as above. Codes are not case-sensitive and the dash is optional. A wrong, used or expired code returns 400. If your service isn't set up for the kind of data in the code, or is at its connection limit, you get 403. Redeeming a code for a player who is already connected returns their existing reference and keeps their existing permissions.

Verified sources (federations)

Golf federations and similar authorities can be set up as a verified source. They connect like any other service, and then publish the player's official handicap. While a verified source is connected for a player:

  • values it publishes are marked verified: true with its name in verified_by;
  • the player can't type their own handicap, so it can't be overwritten by hand;
  • the player's federation is set from the source and reported as federation_verified: true.

If the player disconnects the source, those locks lift. The last value stays, but records from a verified source carry sync_active, which becomes false, so you can tell it is no longer being kept up to date. Verified status is granted by us after checking the organisation; it isn't something a service can claim for itself. To be set up as one, get in touch.

Errors and limits

StatusMeaning
400The request is malformed, a value is out of range, or a code is wrong or expired.
401Your API key is missing or invalid.
403Not allowed. This covers an unknown reference, a reference that belongs to a different service, a player who has disconnected you, and a kind of data you weren't granted. We deliberately return the same response for all of them, so a reference can't be probed.
429Either you have used your plan's calls for this month, or your address sent 30 requests with a wrong API key within 5 minutes. In the second case every request from that address is refused for a while, even with a correct key, so don't retry a 401 blindly. Both carry a Retry-After header.

Errors look like {"error": "not authorised"}. Each plan has two limits: the number of players you can have connected, and calls per month (UTC calendar month). Responses on any plan with a monthly call limit, including the free plan, carry X-RateLimit-Limit, X-RateLimit-Remaining and X-RateLimit-Reset (a Unix time). Calls that fail are counted too. Check your own usage at any time (the numbers here are only an example):

GET/usage

curl https://handiroam.com/usage -H "Authorization: Bearer $HANDIROAM_KEY"

{
  "plan": "free",
  "month": "2026-10",
  "types": { "handicap": { "publish": true } },
  "calls": { "used": 412, "limit": 5000, "remaining": 4588 },
  "linked_players": { "used": 37, "limit": 100, "remaining": 63 }
}

types lists the kinds of data your service may ask for and whether it may publish them.

Need more? Tell us what you are building and we will move you to a plan that fits.

GET/federations

The list of federation codes a player can choose from, with no key needed:

curl https://handiroam.com/federations

{ "federations": [ { "code": "ENG", "name": "England Golf", "country": "England" }, ... ] }

Privacy rules for services

  • Never ask players for, or store, their HandiRoam ID. You don't need it, and you won't be given it.
  • Use a reference only for the player it was issued for, and only with your own key.
  • Read and publish only what the player's grants allow, and only for the purpose they connected you for.
  • If a call returns 403 for a reference, treat the player as disconnected. Don't keep trying.
  • Once you've read a handicap, you hold it under your own privacy policy. Disconnecting does not erase what you already received, so tell your players what you keep.

Reporting a security problem

If you find a way to read a player you were not granted, to learn who a player is, or anything else that should not be possible, please email security@handiroam.com before you tell anyone else. Say what you found and how to reproduce it. We will reply within three working days, fix confirmed problems as a priority, and credit you if you would like that. Please don't test against real players' data: the sandbox exists for this, and you may create as many sandboxes as the limits allow. This address is also published at /.well-known/security.txt.

HandiRoam is independent and not affiliated with, endorsed by or authorised by any golf association or handicapping authority. The values it carries are entered by players or published by services they allow, and are not official handicap records. Values marked verified were sent by a verified source such as the player's federation, and HandiRoam relays them without checking them. Describe them that way in your own product.

Questions, or something here doesn't match what you see? Email support@handiroam.com.