arccos-api 0.1.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,5 @@
1
+ include LICENSE
2
+ include README.md
3
+ include pyproject.toml
4
+ recursive-exclude __pycache__ *
5
+ global-exclude *.pyc *.pyo
@@ -0,0 +1,346 @@
1
+ Metadata-Version: 2.4
2
+ Name: arccos-api
3
+ Version: 0.1.0
4
+ Summary: Unofficial Python CLI and client library for the Arccos Golf API
5
+ Author: Paul Frederiksen
6
+ License: MIT
7
+ Project-URL: Homepage, https://github.com/pfrederiksen/arccos-api
8
+ Project-URL: Repository, https://github.com/pfrederiksen/arccos-api
9
+ Project-URL: Issues, https://github.com/pfrederiksen/arccos-api/issues
10
+ Keywords: golf,arccos,api,cli,golf-stats
11
+ Classifier: Development Status :: 4 - Beta
12
+ Classifier: Environment :: Console
13
+ Classifier: Intended Audience :: Developers
14
+ Classifier: Intended Audience :: End Users/Desktop
15
+ Classifier: License :: OSI Approved :: MIT License
16
+ Classifier: Programming Language :: Python :: 3
17
+ Classifier: Programming Language :: Python :: 3.11
18
+ Classifier: Programming Language :: Python :: 3.12
19
+ Classifier: Programming Language :: Python :: 3.13
20
+ Classifier: Topic :: Software Development :: Libraries :: Python Modules
21
+ Requires-Python: >=3.11
22
+ Description-Content-Type: text/markdown
23
+ Requires-Dist: requests>=2.31
24
+ Requires-Dist: click>=8.1
25
+ Requires-Dist: rich>=13.0
26
+ Provides-Extra: dev
27
+ Requires-Dist: pytest>=8.0; extra == "dev"
28
+ Requires-Dist: pytest-cov; extra == "dev"
29
+ Requires-Dist: ruff; extra == "dev"
30
+ Requires-Dist: mypy; extra == "dev"
31
+ Requires-Dist: types-requests; extra == "dev"
32
+ Requires-Dist: build>=0.10.0; extra == "dev"
33
+ Requires-Dist: twine>=4.0.0; extra == "dev"
34
+
35
+ # arccos-api
36
+
37
+ Unofficial Python client and CLI for the [Arccos Golf](https://arccosgolf.com) platform.
38
+
39
+ Arccos provides no public API. This library was reverse-engineered from the Arccos Dashboard web app. It gives you full programmatic access to your own golf data — rounds, handicap, club distances, pace of play, and more.
40
+
41
+ > **Unofficial.** Not affiliated with Arccos Golf LLC. Use with your own account only. Respect their servers (no aggressive polling).
42
+
43
+ ## Installation
44
+
45
+ ```bash
46
+ pip install arccos-api
47
+ ```
48
+
49
+ Or install from source:
50
+
51
+ ```bash
52
+ git clone https://github.com/pfrederiksen/arccos-api.git
53
+ cd arccos-api
54
+ pip install -e .
55
+ ```
56
+
57
+ Requires Python >= 3.11. Dependencies: `requests`, `click`, `rich`.
58
+
59
+ ## CLI
60
+
61
+ ### Quick start
62
+
63
+ ```bash
64
+ arccos login # authenticate and save credentials
65
+ arccos rounds # your recent rounds
66
+ arccos handicap # handicap breakdown
67
+ arccos clubs # smart club distances
68
+ ```
69
+
70
+ ### Commands
71
+
72
+ | Command | Description |
73
+ |---------|-------------|
74
+ | `arccos login` | Authenticate and cache credentials to `~/.arccos_creds.json` |
75
+ | `arccos rounds` | List recent rounds with date, score, holes, course |
76
+ | `arccos round <id>` | Hole-by-hole detail (score, putts, FIR, GIR) with totals |
77
+ | `arccos handicap` | Handicap breakdown by category (overall, driving, approach, etc.) |
78
+ | `arccos clubs` | Smart club distances with make/model, range, and shot count |
79
+ | `arccos courses` | List all courses you've played |
80
+ | `arccos pace` | Pace of play analysis by course (color-coded) |
81
+ | `arccos stats <id>` | Strokes gained analysis for a round |
82
+ | `arccos export` | Export rounds to JSON, CSV, or NDJSON |
83
+ | `arccos logout` | Clear cached credentials |
84
+
85
+ Every command supports `--json` for raw JSON output and `--help` for usage info.
86
+
87
+ ### Usage examples
88
+
89
+ ```bash
90
+ # Rounds
91
+ arccos rounds # last 20 rounds
92
+ arccos rounds -n 50 # last 50
93
+ arccos rounds --after 2025-01-01 # filter by date
94
+ arccos rounds --json # raw JSON
95
+
96
+ # Round detail
97
+ arccos round 26685289 # hole-by-hole breakdown
98
+
99
+ # Handicap
100
+ arccos handicap # category breakdown
101
+ arccos handicap --history # revision history
102
+
103
+ # Club distances
104
+ arccos clubs # active clubs with make/model
105
+ arccos clubs --after 2025-01-01 # distances from this year only
106
+
107
+ # Courses
108
+ arccos courses # all courses played
109
+
110
+ # Pace of play
111
+ arccos pace # all rounds, slowest courses first
112
+ arccos pace -n 20 # last 20 rounds only
113
+
114
+ # Export
115
+ arccos export -f csv -o rounds.csv # export to CSV file
116
+ arccos export -f json # dump JSON to stdout
117
+ arccos export -f ndjson # newline-delimited JSON
118
+ ```
119
+
120
+ ### Example output
121
+
122
+ ```
123
+ $ arccos clubs
124
+ Smart Club Distances
125
+ ╭──────┬────────────────┬───────┬─────────┬──────────┬───────╮
126
+ │ Club │ Model │ Smart │ Longest │ Range │ Shots │
127
+ ├──────┼────────────────┼───────┼─────────┼──────────┼───────┤
128
+ │ Dr │ PING G425 SFT │ 244y │ 285y │ 241–259y │ 517 │
129
+ │ 3w │ PING G440 SFT │ 203y │ 223y │ 194–210y │ 71 │
130
+ │ 5w │ PING G425 │ 170y │ 206y │ 164–177y │ 285 │
131
+ │ 4i │ Callaway Elyte │ 141y │ 171y │ 137–151y │ 35 │
132
+ │ 9i │ Callaway Elyte │ 125y │ 149y │ 117–128y │ 43 │
133
+ │ Pw │ Callaway Elyte │ 98y │ 113y │ 95–101y │ 25 │
134
+ │ Aw │ Callaway Elyte │ 87y │ 104y │ 87–91y │ 49 │
135
+ │ 56 │ Callaway Opus │ 69y │ 186y │ 67–71y │ 644 │
136
+ ╰──────┴────────────────┴───────┴─────────┴──────────┴───────╯
137
+
138
+ $ arccos round 26685289
139
+ ╭─── Round 26685289 ───╮
140
+ │ Date: 2026-03-08 │
141
+ │ Course: Las Vegas GC │
142
+ │ Score: 85 (+13) │
143
+ │ Holes: 18 │
144
+ ╰──────────────────────╯
145
+ Hole-by-Hole
146
+ ╭──────┬───────┬───────┬─────┬─────╮
147
+ │ Hole │ Score │ Putts │ FIR │ GIR │
148
+ ├──────┼───────┼───────┼─────┼─────┤
149
+ │ 1 │ 5 │ 2 │ T │ F │
150
+ │ 2 │ 5 │ 2 │ F │ T │
151
+ │ ... │ ... │ ... │ ... │ ... │
152
+ ├──────┼───────┼───────┼─────┼─────┤
153
+ │ Tot │ 85 │ 34 │ │ │
154
+ ╰──────┴───────┴───────┴─────┴─────╯
155
+
156
+ $ arccos handicap
157
+ Handicap Breakdown
158
+ ╭────────────┬───────╮
159
+ │ Category │ HCP │
160
+ ├────────────┼───────┤
161
+ │ Overall │ -17.3 │
162
+ │ Driving │ -20.4 │
163
+ │ Approach │ -25.6 │
164
+ │ Short Game │ -21.2 │
165
+ │ Putting │ -8.5 │
166
+ ╰────────────┴───────╯
167
+
168
+ $ arccos pace -n 10
169
+ Pace of Play — 10 rounds across 9 courses Overall avg: 4h 39m
170
+
171
+ By Course (slowest first)
172
+ ╭────┬──────────┬───────────────────────┬────────╮
173
+ │ │ Avg Time │ Course │ Rounds │
174
+ ├────┼──────────┼───────────────────────┼────────┤
175
+ │ 🔴 │ 5h 05m │ Las Vegas GC │ 2 │
176
+ │ 🟡 │ 5h 00m │ Siena GC │ 1 │
177
+ │ 🟢 │ 3h 27m │ Furnace Creek Ranch │ 1 │
178
+ ╰────┴──────────┴───────────────────────┴────────╯
179
+ ```
180
+
181
+ ### Environment variables
182
+
183
+ Skip the login prompt by setting:
184
+
185
+ ```bash
186
+ export ARCCOS_EMAIL="you@example.com"
187
+ export ARCCOS_PASSWORD="your_password"
188
+ arccos rounds
189
+ ```
190
+
191
+ ## Python Library
192
+
193
+ ```python
194
+ from arccos import ArccosClient
195
+
196
+ client = ArccosClient(email="you@example.com", password="your_password")
197
+ # Credentials cached in ~/.arccos_creds.json. Auto-refreshes silently.
198
+
199
+ # Rounds
200
+ rounds = client.rounds.list(limit=10)
201
+ for r in rounds:
202
+ print(f"{r['startTime'][:10]} score={r['noOfShots']} {r.get('courseName', '')}")
203
+
204
+ # Round detail (includes embedded hole-by-hole data)
205
+ rd = client.rounds.get(rounds[0]["roundId"])
206
+ for hole in rd["holes"]:
207
+ print(f" Hole {hole['holeId']}: {hole['noOfShots']} shots, {hole['putts']} putts")
208
+
209
+ # Handicap breakdown
210
+ hcp = client.handicap.current()
211
+ print(f"Overall: {hcp['userHcp']:.1f}")
212
+ print(f"Driving: {hcp['driveHcp']:.1f}")
213
+
214
+ # Club distances (returns clubId as bag slot — see bag API for name mapping)
215
+ for club in client.clubs.smart_distances():
216
+ dist = club["smartDistance"]["distance"]
217
+ print(f" Club {club['clubId']}: {dist:.0f}y")
218
+
219
+ # Bag configuration (maps clubId → clubType + make/model)
220
+ profile = client._http.get(f"/users/{client.user_id}")
221
+ bag = client.clubs.bag(str(profile["bagId"]))
222
+ for club in bag["clubs"]:
223
+ if club.get("isDeleted") != "T":
224
+ print(f" {club['clubMakeOther']} {club['clubModelOther']}")
225
+
226
+ # Courses played
227
+ for course in client.courses.played():
228
+ print(f" {course.get('courseName', course['courseId'])}")
229
+
230
+ # Pace of play analysis
231
+ pace = client.rounds.pace_of_play()
232
+ print(f"Overall avg: {pace['overall_avg_display']}")
233
+ for c in pace["course_averages"][:5]:
234
+ print(f" {c['avg_display']} {c['course']}")
235
+
236
+ # Personal bests
237
+ bests = client.stats.personal_bests()
238
+ ```
239
+
240
+ ## API Reference
241
+
242
+ Full OpenAPI 3.1 spec: [`docs/openapi.yaml`](docs/openapi.yaml)
243
+
244
+ ### Authentication
245
+
246
+ All auth calls go to `https://authentication.arccosgolf.com`.
247
+
248
+ | Step | Endpoint | Body | Returns |
249
+ |------|----------|------|---------|
250
+ | 1. Get access key | `POST /accessKeys` | `{"email", "password", "signedInByFacebook": "F"}` | `{"userId", "accessKey", "secret"}` |
251
+ | 2. Get JWT | `POST /tokens` | `{"userId", "accessKey"}` | `{"userId", "token"}` |
252
+
253
+ The JWT expires in ~3 hours. The `accessKey` is valid for ~180 days. Refresh by calling `POST /tokens` again.
254
+
255
+ ### Endpoints
256
+
257
+ All data calls go to `https://api.arccosgolf.com` with `Authorization: Bearer <token>`.
258
+
259
+ | Resource | Method | Path |
260
+ |----------|--------|------|
261
+ | User profile | GET | `/users/{userId}` (includes `bagId`, `bags[]`) |
262
+ | Bag / clubs | GET | `/users/{userId}/bags/{bagId}` (club config with make/model) |
263
+ | Rounds | GET | `/users/{userId}/rounds?offSet=0&limit=200&roundType=flagship` |
264
+ | Round detail | GET | `/users/{userId}/rounds/{roundId}` (includes `holes[]` with `shots[]`) |
265
+ | Handicap | GET | `/users/{userId}/handicaps/latest` |
266
+ | Handicap history | GET | `/users/{userId}/handicaps?rounds=20` |
267
+ | Smart distances | GET | `/v4/clubs/user/{userId}/smart-distances` |
268
+ | Courses played | GET | `/users/{userId}/coursesPlayed` |
269
+ | Course metadata | GET | `/courses/{courseId}?courseVersion=1` |
270
+ | Course search | GET | `/v2/courses?search={query}` |
271
+ | Personal bests | GET | `/users/{userId}/personalBests?tags=allTimeBest` |
272
+
273
+ ### Key data structures
274
+
275
+ **Round** (from `GET /users/{id}/rounds/{roundId}`):
276
+ ```json
277
+ {
278
+ "roundId": 26685289,
279
+ "courseId": 10769,
280
+ "courseName": "Las Vegas GC",
281
+ "startTime": "2026-03-08T19:17:28.000000Z",
282
+ "endTime": "2026-03-09T00:46:31.000000Z",
283
+ "noOfShots": 85,
284
+ "noOfHoles": 18,
285
+ "overUnder": 13,
286
+ "holes": [
287
+ {
288
+ "holeId": 1, "noOfShots": 5, "putts": 2,
289
+ "isGir": "F", "isFairWay": "T",
290
+ "shots": [{"clubId": 1, "clubType": 1, "distance": 237.0}]
291
+ }
292
+ ]
293
+ }
294
+ ```
295
+
296
+ **Smart distance** (from `GET /v4/clubs/user/{id}/smart-distances`):
297
+ ```json
298
+ {
299
+ "clubId": 1,
300
+ "smartDistance": {"distance": 243.9, "unit": "yd"},
301
+ "longest": {"distance": 285.4, "unit": "yd"},
302
+ "range": {"low": 241.1, "high": 259.1, "unit": "yd"},
303
+ "usage": {"count": 517}
304
+ }
305
+ ```
306
+
307
+ **Bag club** (from `GET /users/{id}/bags/{bagId}`):
308
+ ```json
309
+ {
310
+ "clubId": 1, "clubType": 1,
311
+ "clubMakeOther": "PING", "clubModelOther": "G425 SFT",
312
+ "isDeleted": "F"
313
+ }
314
+ ```
315
+
316
+ > `clubId` is a user-specific bag slot. Resolve to a display name via: bag's `clubType` → standard name (1=Dr, 2=3w, 3=5w, 5=4i, ..., 11=Pw, 12=Putter, 43=Aw, 53=56°).
317
+
318
+ ## Development
319
+
320
+ ```bash
321
+ python3 -m venv .venv
322
+ source .venv/bin/activate
323
+ pip install -e ".[dev]"
324
+
325
+ pytest # tests with coverage (90%+, 96 tests)
326
+ ruff check arccos/ # lint
327
+ mypy arccos/ # type check
328
+ ```
329
+
330
+ Coverage report: `htmlcov/index.html`. Minimum threshold: 80% (configured in `pyproject.toml`).
331
+
332
+ ## Known Gaps
333
+
334
+ - [ ] SGA endpoint auth (`/v2/sga/shots/` returns 40102 — may need HMAC via `secret`)
335
+ - [ ] Social/feed endpoints (`/users/{id}/feed`, follows, etc.)
336
+ - [ ] Driving range session data (`roundType=range`)
337
+ - [ ] Async client (`httpx`)
338
+
339
+ PRs welcome.
340
+
341
+ ## Disclaimer
342
+
343
+ Not affiliated with, endorsed by, or connected to Arccos Golf LLC.
344
+ Use your own account credentials only. This project is for personal data access and research.
345
+
346
+ MIT License.
@@ -0,0 +1,312 @@
1
+ # arccos-api
2
+
3
+ Unofficial Python client and CLI for the [Arccos Golf](https://arccosgolf.com) platform.
4
+
5
+ Arccos provides no public API. This library was reverse-engineered from the Arccos Dashboard web app. It gives you full programmatic access to your own golf data — rounds, handicap, club distances, pace of play, and more.
6
+
7
+ > **Unofficial.** Not affiliated with Arccos Golf LLC. Use with your own account only. Respect their servers (no aggressive polling).
8
+
9
+ ## Installation
10
+
11
+ ```bash
12
+ pip install arccos-api
13
+ ```
14
+
15
+ Or install from source:
16
+
17
+ ```bash
18
+ git clone https://github.com/pfrederiksen/arccos-api.git
19
+ cd arccos-api
20
+ pip install -e .
21
+ ```
22
+
23
+ Requires Python >= 3.11. Dependencies: `requests`, `click`, `rich`.
24
+
25
+ ## CLI
26
+
27
+ ### Quick start
28
+
29
+ ```bash
30
+ arccos login # authenticate and save credentials
31
+ arccos rounds # your recent rounds
32
+ arccos handicap # handicap breakdown
33
+ arccos clubs # smart club distances
34
+ ```
35
+
36
+ ### Commands
37
+
38
+ | Command | Description |
39
+ |---------|-------------|
40
+ | `arccos login` | Authenticate and cache credentials to `~/.arccos_creds.json` |
41
+ | `arccos rounds` | List recent rounds with date, score, holes, course |
42
+ | `arccos round <id>` | Hole-by-hole detail (score, putts, FIR, GIR) with totals |
43
+ | `arccos handicap` | Handicap breakdown by category (overall, driving, approach, etc.) |
44
+ | `arccos clubs` | Smart club distances with make/model, range, and shot count |
45
+ | `arccos courses` | List all courses you've played |
46
+ | `arccos pace` | Pace of play analysis by course (color-coded) |
47
+ | `arccos stats <id>` | Strokes gained analysis for a round |
48
+ | `arccos export` | Export rounds to JSON, CSV, or NDJSON |
49
+ | `arccos logout` | Clear cached credentials |
50
+
51
+ Every command supports `--json` for raw JSON output and `--help` for usage info.
52
+
53
+ ### Usage examples
54
+
55
+ ```bash
56
+ # Rounds
57
+ arccos rounds # last 20 rounds
58
+ arccos rounds -n 50 # last 50
59
+ arccos rounds --after 2025-01-01 # filter by date
60
+ arccos rounds --json # raw JSON
61
+
62
+ # Round detail
63
+ arccos round 26685289 # hole-by-hole breakdown
64
+
65
+ # Handicap
66
+ arccos handicap # category breakdown
67
+ arccos handicap --history # revision history
68
+
69
+ # Club distances
70
+ arccos clubs # active clubs with make/model
71
+ arccos clubs --after 2025-01-01 # distances from this year only
72
+
73
+ # Courses
74
+ arccos courses # all courses played
75
+
76
+ # Pace of play
77
+ arccos pace # all rounds, slowest courses first
78
+ arccos pace -n 20 # last 20 rounds only
79
+
80
+ # Export
81
+ arccos export -f csv -o rounds.csv # export to CSV file
82
+ arccos export -f json # dump JSON to stdout
83
+ arccos export -f ndjson # newline-delimited JSON
84
+ ```
85
+
86
+ ### Example output
87
+
88
+ ```
89
+ $ arccos clubs
90
+ Smart Club Distances
91
+ ╭──────┬────────────────┬───────┬─────────┬──────────┬───────╮
92
+ │ Club │ Model │ Smart │ Longest │ Range │ Shots │
93
+ ├──────┼────────────────┼───────┼─────────┼──────────┼───────┤
94
+ │ Dr │ PING G425 SFT │ 244y │ 285y │ 241–259y │ 517 │
95
+ │ 3w │ PING G440 SFT │ 203y │ 223y │ 194–210y │ 71 │
96
+ │ 5w │ PING G425 │ 170y │ 206y │ 164–177y │ 285 │
97
+ │ 4i │ Callaway Elyte │ 141y │ 171y │ 137–151y │ 35 │
98
+ │ 9i │ Callaway Elyte │ 125y │ 149y │ 117–128y │ 43 │
99
+ │ Pw │ Callaway Elyte │ 98y │ 113y │ 95–101y │ 25 │
100
+ │ Aw │ Callaway Elyte │ 87y │ 104y │ 87–91y │ 49 │
101
+ │ 56 │ Callaway Opus │ 69y │ 186y │ 67–71y │ 644 │
102
+ ╰──────┴────────────────┴───────┴─────────┴──────────┴───────╯
103
+
104
+ $ arccos round 26685289
105
+ ╭─── Round 26685289 ───╮
106
+ │ Date: 2026-03-08 │
107
+ │ Course: Las Vegas GC │
108
+ │ Score: 85 (+13) │
109
+ │ Holes: 18 │
110
+ ╰──────────────────────╯
111
+ Hole-by-Hole
112
+ ╭──────┬───────┬───────┬─────┬─────╮
113
+ │ Hole │ Score │ Putts │ FIR │ GIR │
114
+ ├──────┼───────┼───────┼─────┼─────┤
115
+ │ 1 │ 5 │ 2 │ T │ F │
116
+ │ 2 │ 5 │ 2 │ F │ T │
117
+ │ ... │ ... │ ... │ ... │ ... │
118
+ ├──────┼───────┼───────┼─────┼─────┤
119
+ │ Tot │ 85 │ 34 │ │ │
120
+ ╰──────┴───────┴───────┴─────┴─────╯
121
+
122
+ $ arccos handicap
123
+ Handicap Breakdown
124
+ ╭────────────┬───────╮
125
+ │ Category │ HCP │
126
+ ├────────────┼───────┤
127
+ │ Overall │ -17.3 │
128
+ │ Driving │ -20.4 │
129
+ │ Approach │ -25.6 │
130
+ │ Short Game │ -21.2 │
131
+ │ Putting │ -8.5 │
132
+ ╰────────────┴───────╯
133
+
134
+ $ arccos pace -n 10
135
+ Pace of Play — 10 rounds across 9 courses Overall avg: 4h 39m
136
+
137
+ By Course (slowest first)
138
+ ╭────┬──────────┬───────────────────────┬────────╮
139
+ │ │ Avg Time │ Course │ Rounds │
140
+ ├────┼──────────┼───────────────────────┼────────┤
141
+ │ 🔴 │ 5h 05m │ Las Vegas GC │ 2 │
142
+ │ 🟡 │ 5h 00m │ Siena GC │ 1 │
143
+ │ 🟢 │ 3h 27m │ Furnace Creek Ranch │ 1 │
144
+ ╰────┴──────────┴───────────────────────┴────────╯
145
+ ```
146
+
147
+ ### Environment variables
148
+
149
+ Skip the login prompt by setting:
150
+
151
+ ```bash
152
+ export ARCCOS_EMAIL="you@example.com"
153
+ export ARCCOS_PASSWORD="your_password"
154
+ arccos rounds
155
+ ```
156
+
157
+ ## Python Library
158
+
159
+ ```python
160
+ from arccos import ArccosClient
161
+
162
+ client = ArccosClient(email="you@example.com", password="your_password")
163
+ # Credentials cached in ~/.arccos_creds.json. Auto-refreshes silently.
164
+
165
+ # Rounds
166
+ rounds = client.rounds.list(limit=10)
167
+ for r in rounds:
168
+ print(f"{r['startTime'][:10]} score={r['noOfShots']} {r.get('courseName', '')}")
169
+
170
+ # Round detail (includes embedded hole-by-hole data)
171
+ rd = client.rounds.get(rounds[0]["roundId"])
172
+ for hole in rd["holes"]:
173
+ print(f" Hole {hole['holeId']}: {hole['noOfShots']} shots, {hole['putts']} putts")
174
+
175
+ # Handicap breakdown
176
+ hcp = client.handicap.current()
177
+ print(f"Overall: {hcp['userHcp']:.1f}")
178
+ print(f"Driving: {hcp['driveHcp']:.1f}")
179
+
180
+ # Club distances (returns clubId as bag slot — see bag API for name mapping)
181
+ for club in client.clubs.smart_distances():
182
+ dist = club["smartDistance"]["distance"]
183
+ print(f" Club {club['clubId']}: {dist:.0f}y")
184
+
185
+ # Bag configuration (maps clubId → clubType + make/model)
186
+ profile = client._http.get(f"/users/{client.user_id}")
187
+ bag = client.clubs.bag(str(profile["bagId"]))
188
+ for club in bag["clubs"]:
189
+ if club.get("isDeleted") != "T":
190
+ print(f" {club['clubMakeOther']} {club['clubModelOther']}")
191
+
192
+ # Courses played
193
+ for course in client.courses.played():
194
+ print(f" {course.get('courseName', course['courseId'])}")
195
+
196
+ # Pace of play analysis
197
+ pace = client.rounds.pace_of_play()
198
+ print(f"Overall avg: {pace['overall_avg_display']}")
199
+ for c in pace["course_averages"][:5]:
200
+ print(f" {c['avg_display']} {c['course']}")
201
+
202
+ # Personal bests
203
+ bests = client.stats.personal_bests()
204
+ ```
205
+
206
+ ## API Reference
207
+
208
+ Full OpenAPI 3.1 spec: [`docs/openapi.yaml`](docs/openapi.yaml)
209
+
210
+ ### Authentication
211
+
212
+ All auth calls go to `https://authentication.arccosgolf.com`.
213
+
214
+ | Step | Endpoint | Body | Returns |
215
+ |------|----------|------|---------|
216
+ | 1. Get access key | `POST /accessKeys` | `{"email", "password", "signedInByFacebook": "F"}` | `{"userId", "accessKey", "secret"}` |
217
+ | 2. Get JWT | `POST /tokens` | `{"userId", "accessKey"}` | `{"userId", "token"}` |
218
+
219
+ The JWT expires in ~3 hours. The `accessKey` is valid for ~180 days. Refresh by calling `POST /tokens` again.
220
+
221
+ ### Endpoints
222
+
223
+ All data calls go to `https://api.arccosgolf.com` with `Authorization: Bearer <token>`.
224
+
225
+ | Resource | Method | Path |
226
+ |----------|--------|------|
227
+ | User profile | GET | `/users/{userId}` (includes `bagId`, `bags[]`) |
228
+ | Bag / clubs | GET | `/users/{userId}/bags/{bagId}` (club config with make/model) |
229
+ | Rounds | GET | `/users/{userId}/rounds?offSet=0&limit=200&roundType=flagship` |
230
+ | Round detail | GET | `/users/{userId}/rounds/{roundId}` (includes `holes[]` with `shots[]`) |
231
+ | Handicap | GET | `/users/{userId}/handicaps/latest` |
232
+ | Handicap history | GET | `/users/{userId}/handicaps?rounds=20` |
233
+ | Smart distances | GET | `/v4/clubs/user/{userId}/smart-distances` |
234
+ | Courses played | GET | `/users/{userId}/coursesPlayed` |
235
+ | Course metadata | GET | `/courses/{courseId}?courseVersion=1` |
236
+ | Course search | GET | `/v2/courses?search={query}` |
237
+ | Personal bests | GET | `/users/{userId}/personalBests?tags=allTimeBest` |
238
+
239
+ ### Key data structures
240
+
241
+ **Round** (from `GET /users/{id}/rounds/{roundId}`):
242
+ ```json
243
+ {
244
+ "roundId": 26685289,
245
+ "courseId": 10769,
246
+ "courseName": "Las Vegas GC",
247
+ "startTime": "2026-03-08T19:17:28.000000Z",
248
+ "endTime": "2026-03-09T00:46:31.000000Z",
249
+ "noOfShots": 85,
250
+ "noOfHoles": 18,
251
+ "overUnder": 13,
252
+ "holes": [
253
+ {
254
+ "holeId": 1, "noOfShots": 5, "putts": 2,
255
+ "isGir": "F", "isFairWay": "T",
256
+ "shots": [{"clubId": 1, "clubType": 1, "distance": 237.0}]
257
+ }
258
+ ]
259
+ }
260
+ ```
261
+
262
+ **Smart distance** (from `GET /v4/clubs/user/{id}/smart-distances`):
263
+ ```json
264
+ {
265
+ "clubId": 1,
266
+ "smartDistance": {"distance": 243.9, "unit": "yd"},
267
+ "longest": {"distance": 285.4, "unit": "yd"},
268
+ "range": {"low": 241.1, "high": 259.1, "unit": "yd"},
269
+ "usage": {"count": 517}
270
+ }
271
+ ```
272
+
273
+ **Bag club** (from `GET /users/{id}/bags/{bagId}`):
274
+ ```json
275
+ {
276
+ "clubId": 1, "clubType": 1,
277
+ "clubMakeOther": "PING", "clubModelOther": "G425 SFT",
278
+ "isDeleted": "F"
279
+ }
280
+ ```
281
+
282
+ > `clubId` is a user-specific bag slot. Resolve to a display name via: bag's `clubType` → standard name (1=Dr, 2=3w, 3=5w, 5=4i, ..., 11=Pw, 12=Putter, 43=Aw, 53=56°).
283
+
284
+ ## Development
285
+
286
+ ```bash
287
+ python3 -m venv .venv
288
+ source .venv/bin/activate
289
+ pip install -e ".[dev]"
290
+
291
+ pytest # tests with coverage (90%+, 96 tests)
292
+ ruff check arccos/ # lint
293
+ mypy arccos/ # type check
294
+ ```
295
+
296
+ Coverage report: `htmlcov/index.html`. Minimum threshold: 80% (configured in `pyproject.toml`).
297
+
298
+ ## Known Gaps
299
+
300
+ - [ ] SGA endpoint auth (`/v2/sga/shots/` returns 40102 — may need HMAC via `secret`)
301
+ - [ ] Social/feed endpoints (`/users/{id}/feed`, follows, etc.)
302
+ - [ ] Driving range session data (`roundType=range`)
303
+ - [ ] Async client (`httpx`)
304
+
305
+ PRs welcome.
306
+
307
+ ## Disclaimer
308
+
309
+ Not affiliated with, endorsed by, or connected to Arccos Golf LLC.
310
+ Use your own account credentials only. This project is for personal data access and research.
311
+
312
+ MIT License.
@@ -0,0 +1,31 @@
1
+ """
2
+ arccos — Unofficial Python client for the Arccos Golf API.
3
+
4
+ Quick start:
5
+ from arccos import ArccosClient
6
+
7
+ client = ArccosClient(email="you@example.com", password="secret")
8
+ rounds = client.rounds.list()
9
+ print(client.handicap.current())
10
+ """
11
+
12
+ from .auth import ArccosAuth
13
+ from .client import ArccosClient
14
+ from .exceptions import (
15
+ ArccosAuthError,
16
+ ArccosError,
17
+ ArccosForbiddenError,
18
+ ArccosNotFoundError,
19
+ ArccosRateLimitError,
20
+ )
21
+
22
+ __version__ = "0.1.0"
23
+ __all__ = [
24
+ "ArccosClient",
25
+ "ArccosAuth",
26
+ "ArccosError",
27
+ "ArccosAuthError",
28
+ "ArccosForbiddenError",
29
+ "ArccosNotFoundError",
30
+ "ArccosRateLimitError",
31
+ ]