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.
- arccos_api-0.1.0/MANIFEST.in +5 -0
- arccos_api-0.1.0/PKG-INFO +346 -0
- arccos_api-0.1.0/README.md +312 -0
- arccos_api-0.1.0/arccos/__init__.py +31 -0
- arccos_api-0.1.0/arccos/__main__.py +5 -0
- arccos_api-0.1.0/arccos/_http.py +98 -0
- arccos_api-0.1.0/arccos/auth.py +263 -0
- arccos_api-0.1.0/arccos/cli.py +719 -0
- arccos_api-0.1.0/arccos/client.py +156 -0
- arccos_api-0.1.0/arccos/exceptions.py +75 -0
- arccos_api-0.1.0/arccos/resources/__init__.py +1 -0
- arccos_api-0.1.0/arccos/resources/clubs.py +146 -0
- arccos_api-0.1.0/arccos/resources/courses.py +74 -0
- arccos_api-0.1.0/arccos/resources/handicap.py +62 -0
- arccos_api-0.1.0/arccos/resources/rounds.py +184 -0
- arccos_api-0.1.0/arccos/resources/stats.py +112 -0
- arccos_api-0.1.0/arccos_api.egg-info/PKG-INFO +346 -0
- arccos_api-0.1.0/arccos_api.egg-info/SOURCES.txt +29 -0
- arccos_api-0.1.0/arccos_api.egg-info/dependency_links.txt +1 -0
- arccos_api-0.1.0/arccos_api.egg-info/entry_points.txt +2 -0
- arccos_api-0.1.0/arccos_api.egg-info/requires.txt +12 -0
- arccos_api-0.1.0/arccos_api.egg-info/top_level.txt +1 -0
- arccos_api-0.1.0/pyproject.toml +74 -0
- arccos_api-0.1.0/setup.cfg +4 -0
- arccos_api-0.1.0/tests/test_auth.py +158 -0
- arccos_api-0.1.0/tests/test_cli.py +497 -0
- arccos_api-0.1.0/tests/test_client.py +90 -0
- arccos_api-0.1.0/tests/test_exceptions.py +105 -0
- arccos_api-0.1.0/tests/test_http.py +132 -0
- arccos_api-0.1.0/tests/test_resources.py +214 -0
- arccos_api-0.1.0/tests/test_rounds.py +79 -0
|
@@ -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
|
+
]
|