krz/hutch-stats

Server-side utility for calculating contributions for sourcehut users. contributions server sourcehut stats

Commit b6d1348abf

b6d1348abf907b8b9c16ab87487eb9384dc4cc89

parent: 965c060dca

Verified · cmc

cmc <hello@cleberg.net> · 2026-04-11 18:34 UTC

chore: add API.md

Layout: unified · split

API.md added +479
@@ -0,0 +1,479 @@
1# API Reference
2
3`srht-contrib` exposes a small HTTP JSON API for health checks, contribution calendar reads, manual polling, and tracked repository management.
4
5Base URL examples:
6
7- Local development: `http://127.0.0.1:8000`
8- Deployed example: `https://hutch-stats.example.com`
9
10Content type:
11
12- Request bodies: `application/json`
13- Response bodies: `application/json`
14
15Authentication:
16
17- Public endpoints:
18 - `GET /health`
19 - `GET /api/contributions/{actor}`
20 - `GET /api/contributions/{actor}/stats`
21- Protected endpoints require `X-API-Key`:
22 - `POST /api/contributions/poll`
23 - all `/api/repositories*`
24
25Example protected header:
26
27```http
28X-API-Key: your-api-key
29```
30
31## Common Conventions
32
33Actors:
34
35- Actors are SourceHut canonical names such as `~your-user`.
36- Actor aliases may resolve to the canonical actor through configured alias mappings.
37
38Dates:
39
40- Query date format is `YYYY-MM-DD`.
41- `year` and `from`/`to` are mutually exclusive on contribution endpoints.
42
43Contribution ranges:
44
45- Contribution read endpoints return zero-filled days, so clients do not need to patch missing dates.
46
47Repository names:
48
49- Repository create/update accepts either:
50 - shorthand `repo-name`
51 - canonical `~owner/repo-name`
52- Stored repository names are normalized to canonical `~owner/repo-name` form.
53
54## Health
55
56### `GET /health`
57
58Returns a basic service health response.
59
60Auth:
61
62- Public
63
64Response `200 OK`:
65
66```json
67{
68 "status": "ok"
69}
70```
71
72## Contributions
73
74### `GET /api/contributions/{actor}`
75
76Returns a contribution calendar for an actor over a year or explicit date range.
77
78Auth:
79
80- Public
81
82Path parameters:
83
84- `actor` string: SourceHut actor, for example `~your-user`
85
86Query parameters:
87
88- `year` integer, optional
89- `from` string `YYYY-MM-DD`, optional
90- `to` string `YYYY-MM-DD`, optional
91
92Rules:
93
94- Provide either `year`
95- Or provide both `from` and `to`
96- Do not combine `year` with `from`/`to`
97
98Example by year:
99
100```bash
101curl "http://127.0.0.1:8000/api/contributions/~your-user?year=2026"
102```
103
104Example by range:
105
106```bash
107curl "http://127.0.0.1:8000/api/contributions/~your-user?from=2026-03-01&to=2026-04-15"
108```
109
110Response `200 OK`:
111
112```json
113{
114 "actor": "~your-user",
115 "from": "2026-03-01",
116 "to": "2026-04-15",
117 "days": [
118 { "date": "2026-03-01", "count": 0, "score": 0.0 },
119 { "date": "2026-03-02", "count": 3, "score": 2.5 }
120 ]
121}
122```
123
124Response fields:
125
126- `actor` string: canonical actor after alias resolution
127- `from` string: inclusive start date
128- `to` string: inclusive end date
129- `days` array:
130 - `date` string `YYYY-MM-DD`
131 - `count` integer contribution count for the day
132 - `score` float weighted score for the day
133
134Possible errors:
135
136- `400 Bad Request` for invalid or conflicting date input
137
138Example `400`:
139
140```json
141{
142 "detail": "Provide `year` or both `from` and `to`."
143}
144```
145
146### `GET /api/contributions/{actor}/stats`
147
148Returns aggregated stats for the same date selection rules as the calendar endpoint.
149
150Auth:
151
152- Public
153
154Path parameters:
155
156- `actor` string
157
158Query parameters:
159
160- `year` integer, optional
161- `from` string `YYYY-MM-DD`, optional
162- `to` string `YYYY-MM-DD`, optional
163
164Example:
165
166```bash
167curl "http://127.0.0.1:8000/api/contributions/~your-user/stats?from=2026-03-01&to=2026-04-15"
168```
169
170Response `200 OK`:
171
172```json
173{
174 "actor": "~your-user",
175 "from": "2026-03-01",
176 "to": "2026-04-15",
177 "total_events": 126,
178 "total_score": 116.75,
179 "active_days": 14,
180 "longest_streak": 5,
181 "current_streak": 0
182}
183```
184
185Response fields:
186
187- `actor` string
188- `from` string
189- `to` string
190- `total_events` integer
191- `total_score` float
192- `active_days` integer
193- `longest_streak` integer
194- `current_streak` integer
195
196Possible errors:
197
198- `400 Bad Request` for invalid or conflicting date input
199
200### `POST /api/contributions/poll`
201
202Triggers a manual SourceHut poll for the given actor and stores any newly discovered events.
203
204Auth:
205
206- Requires `X-API-Key`
207
208Query parameters:
209
210- `actor` string: SourceHut actor to poll
211
212Example:
213
214```bash
215curl -X POST \
216 -H "X-API-Key: your-api-key" \
217 "http://127.0.0.1:8000/api/contributions/poll?actor=~your-user"
218```
219
220Response `200 OK`:
221
222```json
223{
224 "actor": "~your-user",
225 "inserted_events": 57,
226 "services": ["todo", "git"]
227}
228```
229
230Response fields:
231
232- `actor` string: canonical actor after alias resolution
233- `inserted_events` integer: number of newly inserted normalized events
234- `services` array of strings: currently `["todo", "git"]`
235
236Possible errors:
237
238- `401 Unauthorized` if the API key is missing or invalid
239- `502 Bad Gateway` if polling SourceHut fails
240
241Example `401`:
242
243```json
244{
245 "detail": "Invalid API key."
246}
247```
248
249Example `502`:
250
251```json
252{
253 "detail": "SourceHut polling failed: HTTP error from SourceHut: 502"
254}
255```
256
257## Tracked Repositories
258
259All repository endpoints are protected and require `X-API-Key`.
260
261Tracked repositories are used by git polling. Each repository is associated with an actor and stored in canonical `~owner/repo` form.
262
263### `GET /api/repositories`
264
265Lists tracked git repositories.
266
267Auth:
268
269- Requires `X-API-Key`
270
271Query parameters:
272
273- `actor` string, optional: filter to a canonical actor or alias
274
275Example:
276
277```bash
278curl \
279 -H "X-API-Key: your-api-key" \
280 "http://127.0.0.1:8000/api/repositories?actor=~your-user"
281```
282
283Response `200 OK`:
284
285```json
286[
287 {
288 "id": 1,
289 "service": "git",
290 "actor": "~your-user",
291 "repo_name": "~your-user/your-repo"
292 }
293]
294```
295
296### `GET /api/repositories/{repository_id}`
297
298Fetches one tracked repository by numeric ID.
299
300Auth:
301
302- Requires `X-API-Key`
303
304Path parameters:
305
306- `repository_id` integer
307
308Example:
309
310```bash
311curl \
312 -H "X-API-Key: your-api-key" \
313 "http://127.0.0.1:8000/api/repositories/1"
314```
315
316Response `200 OK`:
317
318```json
319{
320 "id": 1,
321 "service": "git",
322 "actor": "~your-user",
323 "repo_name": "~your-user/your-repo"
324}
325```
326
327Possible errors:
328
329- `404 Not Found` if the repository ID does not exist
330
331### `POST /api/repositories`
332
333Creates a tracked repository entry.
334
335Auth:
336
337- Requires `X-API-Key`
338
339Request body:
340
341```json
342{
343 "actor": "~your-user",
344 "repo_name": "your-repo"
345}
346```
347
348Example:
349
350```bash
351curl -X POST \
352 -H "X-API-Key: your-api-key" \
353 -H "Content-Type: application/json" \
354 -d '{"actor":"~your-user","repo_name":"your-repo"}' \
355 "http://127.0.0.1:8000/api/repositories"
356```
357
358Response `201 Created`:
359
360```json
361{
362 "id": 1,
363 "service": "git",
364 "actor": "~your-user",
365 "repo_name": "~your-user/your-repo"
366}
367```
368
369Possible errors:
370
371- `401 Unauthorized` if the API key is missing or invalid
372- `409 Conflict` if the normalized repository already exists for that actor
373- `422 Unprocessable Content` if `actor` or `repo_name` is blank or malformed
374
375### `PATCH /api/repositories/{repository_id}`
376
377Updates an existing tracked repository.
378
379Auth:
380
381- Requires `X-API-Key`
382
383Path parameters:
384
385- `repository_id` integer
386
387Request body:
388
389```json
390{
391 "actor": "~your-user",
392 "repo_name": "~your-user/your-other-repo"
393}
394```
395
396Body rules:
397
398- At least one of `actor` or `repo_name` must be present
399
400Example:
401
402```bash
403curl -X PATCH \
404 -H "X-API-Key: your-api-key" \
405 -H "Content-Type: application/json" \
406 -d '{"repo_name":"~your-user/your-other-repo"}' \
407 "http://127.0.0.1:8000/api/repositories/1"
408```
409
410Response `200 OK`:
411
412```json
413{
414 "id": 1,
415 "service": "git",
416 "actor": "~your-user",
417 "repo_name": "~your-user/your-other-repo"
418}
419```
420
421Possible errors:
422
423- `404 Not Found`
424- `409 Conflict`
425- `422 Unprocessable Content`
426
427### `DELETE /api/repositories/{repository_id}`
428
429Deletes a tracked repository.
430
431Auth:
432
433- Requires `X-API-Key`
434
435Path parameters:
436
437- `repository_id` integer
438
439Example:
440
441```bash
442curl -X DELETE \
443 -H "X-API-Key: your-api-key" \
444 "http://127.0.0.1:8000/api/repositories/1"
445```
446
447Response `204 No Content`
448
449Possible errors:
450
451- `404 Not Found`
452
453## Error Summary
454
455Common status codes:
456
457- `200 OK` successful read or manual poll
458- `201 Created` successful repository creation
459- `204 No Content` successful repository deletion
460- `400 Bad Request` invalid date parameters
461- `401 Unauthorized` missing or invalid API key
462- `404 Not Found` missing repository record
463- `409 Conflict` duplicate repository after normalization
464- `422 Unprocessable Content` invalid repository payload
465- `502 Bad Gateway` upstream SourceHut failure during poll
466
467## OpenAPI
468
469FastAPI also serves an OpenAPI document at:
470
471```text
472/openapi.json
473```
474
475If interactive docs are enabled by your deployment, the standard FastAPI docs may also be available at:
476
477```text
478/docs
479```