openapi: 3.2.0
info:
title: Arccos On-Course Data Rounds API
version: '1'
description: "## Introduction\nWelcome to The On-Course Data API, powered by Arccos. The purpose of\
\ this API is to provide direct access to the data we\nuse to power our apps. Access to this API is\
\ restricted. If you are supposed to be here, you will have been issued a\nclient id and/or client\
\ secret. More details on how to use these credentials below.\n\nThis is a living project. While backward\
\ compatibility will be preserved, updates will be regular.\n\n## Authentication\n<details>\n<summary>Details</summary>\n\
\nTo gain access to a user's data, almost all clients will use\nthe [OAuth 2.0 Authorization Code\
\ Grant Flow.](https://oauth.net/2/grant-types/authorization-code/)\nThe basic steps for this flow\
\ are:\n1. The client sends a user to `https://signin.arccosgolf.com/login?response_type=code&client_id=<client\
\ id>&redirect_uri=<redirect uri>&scope=<requested scope>`.\n For example, this swagger UI might\
\ use the url\n `https://signin.arccosgolf.com/login?response_type=code&client_id=REDACTED&redirect_uri=https%3A%2F%2Fapi.arccosgolf.com%2Foauth2-redirect.html&scope=arccos%2Fread%3Arounds%20openid`\n\
\ * `response_type`: `code` for the access code flow.\n * `client_id`: client id you have been\
\ issued by Arccos.\n * `redirect_uri`: location to send the access code upon successful authentication.\n\
\ * `scope`: list of scopes (joined by `+`) that you would like to gain on behalf of the user.\n\
\ * `client_secret` (optional): if you were issued a client secret, this parameter is required.\n\
1. The user signs into Arccos directly with us.\n1. Upon successful authentication, the user will\
\ be redirected to uri specified in step 1 with the query parameter `code=<my access code>`\n1. The\
\ client can then use the code in a `POST` request to `https://api.arccosgolf.com/oauth2/token` with\n\
\ an `x-www-form-urlencoded` body like:\n ```\n grant_type:\t\t\"authorization_code\" # must\
\ be authorization_code\n code:\t\t\t\"my_authorization_code\" # code from the query params\n \
\ client_id:\t\t\"my client id\" # same client_id from step 1\n redirect_uri:\t\"my redirect uri\"\
\ # same redirect_uri from step 1\n client_secret:\t\"my client secret\" # same client_secret from\
\ step 1\n ```\n The access code will only be valid one time. If the response is not recorded,\
\ a user will need to authenticate the\n client again by going back to step 1.\n1. Assuming everything\
\ was successful, the client will receive a response with an `application/json` body. In this\n \
\ body, the client will find two or three tokens: `id_token`, `access_token`, and `refresh_token`.\n\
\n### Tokens\n\n#### `id_token`\nThe id token is used to gain basic information about a user. **It\
\ cannot be used to authenticate requests.**\n\nAn id token will only be in the token response if\
\ you include `openid` as a requested scope. It will likely be included\nin all requests a client\
\ makes because requesting the `openid` scope is also required to make any requests that\ninclude\
\ `userId` in the path (pretty much all of them).\n\nThe most important piece of information you will\
\ find in the id token payload is `custom:arccosUserId`. The string\nlocated in this key is what you\
\ use for a request's `{userId}` path parameter.\n\n#### `access_token`\nThe access token is what\
\ is actually used to authenticate requests. It must be included in authenticated requests by\npassing\
\ a header along with the request in the form `Authorization: Bearer {access_token}`. Access tokens\
\ expire after\nX hours.\n\n#### `refresh_token`\nRefresh tokens can be used to get a fresh set of\
\ tokens. This is necessary because access tokens are meant to be\nshort-lived. Refresh tokens on\
\ the other hand can be saved and don't expire for X days. After X days, a user will need\nto re-authenticate\
\ again using the same process as outlined above.\n\nTo use a refresh token, make a `POST` request\
\ to `https://api.arccosgolf.com/oauth2/token` with an\n`x-www-form-urlencoded` body like:\n```\n\
grant_type:\t\t\"refresh_token\" # must be refresh_token\nrefresh_token:\t\"my_refresh_token\" # refresh\
\ token\nclient_id:\t\t\"my client id\" # same client_id from step 1\nclient_secret:\t\"my client\
\ secret\" # same client_secret from step 1\n```\n\nYou may also revoke a refresh token by making\
\ a `POST` request to `https://api.arccosgolf.com/oauth2/revoke` with an\n`x-www-form-urlencoded`\
\ body like:\n```\ntoken:\t\t\t\"my_refresh_token\" # refresh token\narccos_user_id:\t\"my_user_id\"\
\ # if you don't have all tokens for a user, you can optionally use this instead to revoke all tokens\
\ for a user\nclient_id:\t\t\"my client id\" # same client_id from step 1\nclient_secret:\t\"my client\
\ secret\" # same client_secret from step 1\n```\n\n### Scopes\nScopes grant specific access to different\
\ clients. If a scope is not included in the initial request for access to a\nuser's data, that scope\
\ cannot be retroactively included; a user must explicitly grant the client access to a scope.\n\n\
Most scopes are simply restricting access to endpoints, though in the future some may be used to restrict\
\ access to\ndata retrieved _from_ those endpoints, or be used to restrict access to certain other\
\ operations.\n\nThe ability to request a scope on behalf of a user is controlled by Arccos; a client\
\ must be granted the privilege of\nrequesting a scope for a user.\n\nScopes required for each endpoint\
\ can be seen described below. For example, to get access to the \"Get Rounds\" endpoint,\nthe access\
\ token must include the `arccos/read:rounds` scope. A full list of scopes and their descriptions\
\ can be\nviewed by clicking \"Authorize\" below.\n\nThe only scope that isn't formatted as `arccos/<some\
\ operation>` is the `openid` scope. As a reminder, this scope must\nbe requested for any endpoint\
\ that includes `{userId}` in its path.\n</details>\n\n## Resources\n\n### Example Project\nYou can\
\ see an example of how the authentication flow can work in [this repository](https://github.com/arccosgolf/third-party-api-example-front-end).\n\
\n### Postman Collection\nYou can gain access to our Postman collection outlining these endpoints\
\ [here](https://www.postman.com/arccosgolf/workspace/arccos-public-api/collection/14095300-9001cc03-d9dd-4c77-a42d-ca6a0417350b)\n\
\n## Webhooks\n\nClients can register HTTPS webhook URLs with the `/v5/webhooks` endpoints. Arccos\
\ sends JSON `POST` requests for these event types:\n\n- `postRound` and `patchRound`: include `userId`,\
\ `roundId`, `courseId`, `courseVersion`, and `isEnded`.\n- `deleteRound`: includes `userId` and `roundId`.\n\
- `accountDisconnected`: sent when a user disconnects your client from Arccos UI. The payload includes\
\ `eventId`, `eventVersion`, `createdAt`, and `eventBody` with `userId`, `clientId`, `disconnectedAt`,\
\ and `reason: \"user_disconnected\"`.\n\nExample `accountDisconnected` payload:\n\n```json\n{\n \
\ \"eventId\": \"uuid\",\n \"eventType\": \"accountDisconnected\",\n \"eventVersion\": 1,\n \"\
createdAt\": \"2026-05-28T18:42:13.123Z\",\n \"eventBody\": {\n \"userId\": \"arccos-user-id\"\
,\n \"clientId\": \"cognito-client-id\",\n \"disconnectedAt\": \"2026-05-28T18:42:13.123Z\"\
,\n \"reason\": \"user_disconnected\"\n }\n}\n```\n\nWebhook delivery is at-least-once, and consumers\
\ should tolerate duplicates and delays. For `accountDisconnected`, `eventId` is the idempotency key\
\ for a published webhook event and remains stable across delivery retries of that event. Repeated\
\ disconnect actions can produce another event for the same disconnected account, so consumers should\
\ also make cleanup idempotent by `userId` and `clientId`. `accountDisconnected` retries are per SQS\
\ message fan-out: if a client has multiple webhook URLs and one fails, a retry can duplicate `POST`\
\ requests to healthy URLs with the same `eventId`. Consumers must dedupe by `eventId`. Respond with\
\ a `2xx` status after durable receipt, and do downstream cleanup asynchronously. Ordering between\
\ webhook event types is not guaranteed. For received v1 payloads, ignore unknown fields. Ignore unsupported\
\ future top-level fields, and ignore unsupported future `eventVersion` values unless you have explicitly\
\ opted in to that newer contract.\n\nFor `accountDisconnected`, stop using stored Arccos tokens for\
\ the provided `userId` and disable local access for that Arccos connection. This webhook does not\
\ include tokens, email, or user PII. Webhook v1 is unsigned. Use HTTPS and a dedicated high-entropy\
\ webhook URL/path, verify that incoming requests are sent to the path you registered, and keep that\
\ URL private. HMAC signing and secret rotation are future contract upgrades, not part of the current\
\ v1 contract.\n\n### Typescript Type Files\nTODO\n\n## Support\nThis is a work in progress. Changes\
\ & updates will be made on a rolling basis. Please send any questions, concerns or suggestions to\
\ [john@arccosgolf.com](mailto:john@arccosgolf.com?subject=On%20Course%20Data%20API%20Inquiry)\n"
tags:
- name: Rounds
paths:
/v5/users/{userId}/rounds:
get:
summary: Get Rounds
description: Paged API used to get a users rounds
tags:
- Rounds
operationId: handle_search_rounds.get./v5/users/{userId}/rounds
security:
- AccessCodeAuth:
- arccos/read:rounds
parameters:
- name: userId
in: path
required: true
schema:
type: string
- in: query
name: limit
description: maximum number of results to get
required: false
schema:
type: integer
- in: query
name: offset
description: number of results to get per request
required: false
schema:
type: integer
responses:
'200':
description: 200 response
content:
application/json:
schema:
$ref: '#/components/schemas/GetRoundsResponse'
/v5/users/{userId}/rounds/{roundId}:
get:
summary: Get One Round
description: Get details for one round
tags:
- Rounds
operationId: handle_get_one_round.get./v5/users/{userId}/rounds/{roundId}
security:
- AccessCodeAuth:
- arccos/read:rounds
parameters:
- name: userId
in: path
required: true
schema:
type: string
- name: roundId
in: path
required: true
schema:
type: string
responses:
'200':
description: 200 response
content:
application/json:
schema:
$ref: '#/components/schemas/GetOneRoundResponse'
/v5/users/{userId}/rounds/{roundId}/stats:
get:
summary: Get Round Stats
description: Get traditional stats for one round with goals relative to a goalHandicap. If no handicap
is provided, 0 is assumed.
tags:
- Rounds
operationId: handle_get_round_stats.get./v5/users/{userId}/rounds/{roundId}/stats
security:
- AccessCodeAuth:
- arccos/read:rounds
parameters:
- name: userId
in: path
required: true
schema:
type: string
- name: roundId
in: path
required: true
schema:
type: string
- in: query
name: goalHandicap
description: -30 to 10. Someone with a goal of 20 over par should enter -20.
required: false
schema:
type: integer
responses:
'200':
description: 200 response
content:
application/json:
schema:
$ref: '#/components/schemas/GetOneRoundStatsResponse'
components:
schemas:
PuttStrokesGainedBreakdown:
properties:
numberOfShots:
title: PuttStrokesGainedBreakdown.numberOfShots
type: number
zeroPutts:
title: PuttStrokesGainedBreakdown.zeroPutts
type: number
onePutts:
title: PuttStrokesGainedBreakdown.onePutts
type: number
twoPutts:
title: PuttStrokesGainedBreakdown.twoPutts
type: number
threePlusPutts:
title: PuttStrokesGainedBreakdown.threePlusPutts
type: number
required:
- numberOfShots
- zeroPutts
- onePutts
- twoPutts
- threePlusPutts
additionalProperties: false
title: PuttStrokesGainedBreakdown
type: object
DrivingStrokesGainedBreakdown:
properties:
numberOfShots:
title: DrivingStrokesGainedBreakdown.numberOfShots
type: number
averageDriveDistance:
$ref: '#/components/schemas/TraditionalStatComparison'
title: DrivingStrokesGainedBreakdown.averageDriveDistance
longestDriveDistance:
title: DrivingStrokesGainedBreakdown.longestDriveDistance
type: number
hitFairwaysPercent:
$ref: '#/components/schemas/TraditionalStatComparison'
title: DrivingStrokesGainedBreakdown.hitFairwaysPercent
hitFairways:
properties:
chances:
title: DrivingStrokesGainedBreakdown.hitFairways.chances
type: number
successes:
title: DrivingStrokesGainedBreakdown.hitFairways.successes
type: number
required:
- chances
- successes
additionalProperties: false
title: DrivingStrokesGainedBreakdown.hitFairways
type: object
required:
- numberOfShots
- longestDriveDistance
- hitFairways
additionalProperties: false
title: DrivingStrokesGainedBreakdown
type: object
Tee:
properties:
teeId:
title: Tee.teeId
description: the numeric identifier for the tee. this is unique against the courseId and courseVersion,
not globally unique
type: number
teeName:
title: Tee.teeName
description: the display string for the tee that was played
type: string
required:
- teeId
- teeName
additionalProperties: false
title: Tee
type: object
RoundHole:
allOf:
- $ref: '#/components/schemas/Hole'
- properties:
stats:
items:
$ref: '#/components/schemas/HoleStat'
title: stats.[]
title: stats
type: array
startTime:
title: startTime
type: string
endTime:
title: endTime
type: string
mensPar:
title: mensPar
type: number
womensPar:
title: womensPar
type: number
pin:
$ref: '#/components/schemas/Pin'
title: pin
shots:
items:
$ref: '#/components/schemas/Shot'
title: shots.[]
title: shots
type: array
required:
- stats
- startTime
- endTime
- mensPar
- womensPar
- pin
- shots
additionalProperties: false
type: object
title: RoundHole
Round:
properties:
roundId:
title: Round.roundId
type: number
userId:
title: Round.userId
type: string
startTime:
title: Round.startTime
type: string
endTime:
title: Round.endTime
type:
- string
- 'null'
totalScore:
title: Round.totalScore
type:
- number
- 'null'
courseId:
title: Round.courseId
type: number
courseVersion:
title: Round.courseVersion
type: number
courseName:
title: Round.courseName
type: string
numberOfHoles:
title: Round.numberOfHoles
type: number
tee:
$ref: '#/components/schemas/Tee'
title: Round.tee
required:
- roundId
- userId
- startTime
- endTime
- totalScore
- courseId
- courseVersion
- courseName
- numberOfHoles
- tee
additionalProperties: false
title: Round
type: object
TraditionalStatComparison:
properties:
actual:
title: TraditionalStatComparison.actual
type: number
goal:
title: TraditionalStatComparison.goal
type: number
scratch:
title: TraditionalStatComparison.scratch
type: number
required:
- actual
- goal
- scratch
additionalProperties: false
title: TraditionalStatComparison
type: object
Terrain:
enum:
- tee
- fairway
- rough
- sand
- green
title: Terrain
type: string
GetOneRoundStatsResponse:
properties:
roundId:
title: GetOneRoundStatsResponse.roundId
type: number
courseId:
title: GetOneRoundStatsResponse.courseId
type: number
courseVersion:
title: GetOneRoundStatsResponse.courseVersion
type: number
goalHandicap:
title: GetOneRoundStatsResponse.goalHandicap
type: number
stats:
$ref: '#/components/schemas/RoundStats'
title: GetOneRoundStatsResponse.stats
required:
- roundId
- courseId
- courseVersion
- goalHandicap
- stats
additionalProperties: false
title: GetOneRoundStatsResponse
type: object
GetOneRoundResponse:
$ref: '#/components/schemas/RoundDetails'
title: GetOneRoundResponse
PagedResponseHelper:
properties:
paging:
properties:
limit:
title: PagedResponseHelper.paging.limit
type: number
offset:
title: PagedResponseHelper.paging.offset
type: number
previous:
title: PagedResponseHelper.paging.previous
type: string
next:
title: PagedResponseHelper.paging.next
type: string
required:
- limit
- offset
additionalProperties: false
title: PagedResponseHelper.paging
type: object
results:
items:
title: PagedResponseHelper.results.[]
title: PagedResponseHelper.results
type: array
required:
- paging
- results
additionalProperties: false
title: PagedResponseHelper
type: object
ApproachStrokesGainedBreakdown:
properties:
numberOfShots:
title: ApproachStrokesGainedBreakdown.numberOfShots
type: number
greensInRegulationPercent:
$ref: '#/components/schemas/TraditionalStatComparison'
title: ApproachStrokesGainedBreakdown.greensInRegulationPercent
greensInRegulation:
properties:
chances:
title: ApproachStrokesGainedBreakdown.greensInRegulation.chances
type: number
successes:
title: ApproachStrokesGainedBreakdown.greensInRegulation.successes
type: number
required:
- chances
- successes
additionalProperties: false
title: ApproachStrokesGainedBreakdown.greensInRegulation
type: object
averageShotDistance:
title: ApproachStrokesGainedBreakdown.averageShotDistance
type: number
required:
- numberOfShots
- averageShotDistance
additionalProperties: false
title: ApproachStrokesGainedBreakdown
type: object
Shot:
properties:
shotId:
title: Shot.shotId
type: number
clubId:
title: Shot.clubId
type: number
clubType:
title: Shot.clubType
type: string
startLocation:
$ref: '#/components/schemas/Location'
title: Shot.startLocation
endLocation:
anyOf:
- $ref: '#/components/schemas/Location'
title: Shot.endLocation
- properties:
latitude:
title: Shot.endLocation.latitude
longitude:
title: Shot.endLocation.longitude
required:
- latitude
- longitude
additionalProperties: false
title: Shot.endLocation
type: object
title: Shot.endLocation
shotDistance:
title: Shot.shotDistance
type: number
shotTime:
title: Shot.shotTime
type: string
startAltitude:
title: Shot.startAltitude
type:
- number
- 'null'
endAltitude:
title: Shot.endAltitude
type:
- number
- 'null'
numberOfPenalties:
title: Shot.numberOfPenalties
type: number
startTerrain:
anyOf:
- $ref: '#/components/schemas/Terrain'
title: Shot.startTerrain
- title: Shot.startTerrain
title: Shot.startTerrain
endTerrain:
anyOf:
- $ref: '#/components/schemas/Terrain'
title: Shot.endTerrain
- title: Shot.endTerrain
title: Shot.endTerrain
startMetersToCenterOfGreen:
title: Shot.startMetersToCenterOfGreen
description: The starting distance to the center of the green in meters for non-putt shots.
type:
- number
- 'null'
endMetersToCenterOfGreenMeters:
title: Shot.endMetersToCenterOfGreenMeters
description: The ending distance to the center of the green in meters for non-putt shots
type:
- number
- 'null'
endsOnFairway:
anyOf:
- enum:
- T
- F
title: Shot.endsOnFairway
type: string
- title: Shot.endsOnFairway
title: Shot.endsOnFairway
description: If the result of the shot was in the fairway, for drive or approach shots only.
isLeftOfFairway:
anyOf:
- enum:
- T
- F
title: Shot.isLeftOfFairway
type: string
- title: Shot.isLeftOfFairway
title: Shot.isLeftOfFairway
description: If the result of the shot is outside the left boundary of the fairway, for drive
or approach shots only.
isRightOfFairway:
anyOf:
- enum:
- T
- F
title: Shot.isRightOfFairway
type: string
- title: Shot.isRightOfFairway
title: Shot.isRightOfFairway
description: If the result of the shot is outside the right boundary of the fairway, for drive
or approach shots only.
isLeftOfCenterline:
anyOf:
- enum:
- T
- F
title: Shot.isLeftOfCenterline
type: string
- title: Shot.isLeftOfCenterline
title: Shot.isLeftOfCenterline
description: If the shot is left of the fairway centerline, for drive or approach shots only.
isRightOfCenterline:
anyOf:
- enum:
- T
- F
title: Shot.isRightOfCenterline
type: string
- title: Shot.isRightOfCenterline
title: Shot.isRightOfCenterline
description: If the shot is right of the fairway centerline, for drive or approach shots only.
metersFromFairwayBounds:
title: Shot.metersFromFairwayBounds
description: The distance in meters from the bounds of the fairway, for drive or approach shots
only
type:
- number
- 'null'
metersFromFairwayCenterline:
title: Shot.metersFromFairwayCenterline
description: The distance, in meters, from the centerline of the fairway, for drive or approach
shots only
type:
- number
- 'null'
required:
- shotId
- clubId
- clubType
- startLocation
- endLocation
- shotDistance
- shotTime
- startAltitude
- endAltitude
- numberOfPenalties
- startTerrain
- endTerrain
- startMetersToCenterOfGreen
- endMetersToCenterOfGreenMeters
- endsOnFairway
- isLeftOfFairway
- isRightOfFairway
- isLeftOfCenterline
- isRightOfCenterline
- metersFromFairwayBounds
- metersFromFairwayCenterline
additionalProperties: false
title: Shot
type: object
RoundDetails:
allOf:
- $ref: '#/components/schemas/Round'
- properties:
holes:
items:
$ref: '#/components/schemas/RoundHole'
title: holes.[]
title: holes
type: array
required:
- holes
additionalProperties: false
type: object
title: RoundDetails
Pin:
allOf:
- $ref: '#/components/schemas/Location'
- additionalProperties: false
type: object
title: Pin
GetRoundsResponse:
allOf:
- $ref: '#/components/schemas/PagedResponseHelper'
- properties:
results:
items:
$ref: '#/components/schemas/Round'
title: results.[]
title: results
type: array
required:
- results
additionalProperties: false
type: object
title: GetRoundsResponse
Location:
properties:
latitude:
title: Location.latitude
type: number
longitude:
title: Location.longitude
type: number
required:
- latitude
- longitude
additionalProperties: false
title: Location
type: object
RoundStats:
properties:
overall:
properties:
numberOfShots:
title: RoundStats.overall.numberOfShots
type: number
totalShotDistance:
title: RoundStats.overall.totalShotDistance
type: number
required:
- numberOfShots
- totalShotDistance
additionalProperties: false
title: RoundStats.overall
type: object
driving:
$ref: '#/components/schemas/DrivingStrokesGainedBreakdown'
title: RoundStats.driving
approach:
$ref: '#/components/schemas/ApproachStrokesGainedBreakdown'
title: RoundStats.approach
short:
$ref: '#/components/schemas/ShortStrokesGainedBreakdown'
title: RoundStats.short
putt:
$ref: '#/components/schemas/PuttStrokesGainedBreakdown'
title: RoundStats.putt
required:
- overall
- driving
- approach
- short
- putt
additionalProperties: false
title: RoundStats
type: object
HoleStat:
properties:
name:
enum:
- gir
- sand_save
- up_and_down
title: HoleStat.name
type: string
succeeded:
title: HoleStat.succeeded
type: boolean
required:
- name
- succeeded
additionalProperties: false
title: HoleStat
type: object
Hole:
properties:
holeId:
title: Hole.holeId
type: number
required:
- holeId
additionalProperties: false
title: Hole
type: object
ShortStrokesGainedBreakdown:
properties:
numberOfShots:
title: ShortStrokesGainedBreakdown.numberOfShots
type: number
upAndDown:
properties:
chances:
title: ShortStrokesGainedBreakdown.upAndDown.chances
type: number
successes:
title: ShortStrokesGainedBreakdown.upAndDown.successes
type: number
required:
- chances
- successes
additionalProperties: false
title: ShortStrokesGainedBreakdown.upAndDown
type: object
required:
- numberOfShots
- upAndDown
additionalProperties: false
title: ShortStrokesGainedBreakdown
type: object
securitySchemes:
AccessCodeAuth:
type: oauth2
flows:
authorizationCode:
scopes:
openid: Required for all endpoints that have userId in the path
arccos/read:users: Grants read users access
arccos/read:rounds: Grants read rounds access
arccos/read:clubs: Grants read clubs access
authorizationUrl: https://signin.arccosgolf.com/login
tokenUrl: https://api.arccosgolf.com/oauth2/token
BasicAuth:
type: http
scheme: basic