krz/hutch-stats
clone: git clone https://gitbay.org/krz/hutch-stats.git
main: API.txt · raw
1# API Reference
2
3`srht-contrib` exposes a small HTTP JSON API for health checks, contribution calendar reads, manual polling, and optional 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- Public contribution reads also register the actor for background indexing. A first lookup may therefore return an empty graph while the scheduler catches up.
47- Clients may add `prioritize_self=true` on contribution read endpoints to explicitly request temporary indexing priority for the signed-in user's own graph.
48- Incremental indexing and one-year backfill are separate. An actor can be recently indexed before the retained one-year window is fully filled in.
49- The service only retains and backfills the most recent 365 days of activity.
50
51Background polling:
52
53- When `ENABLE_SCHEDULER=true`, the service runs one poll immediately at startup and then continues polling on `POLL_INTERVAL_SECONDS`.
54- The scheduler always seeds `DEFAULT_ACTOR` as a known actor.
55- Public contribution reads register additional actors for later background polling.
56- The scheduler only processes due actors, up to `DISCOVERY_BATCH_SIZE` per pass.
57- Scheduled first indexing skips bounded one-year backfill work, then drains backfill in later scheduled passes so newly requested actors become indexed sooner.
58- Manual polling remains available through `POST /api/contributions/poll`.
59
60Repository names:
61
62- Repository create/update accepts either:
63 - shorthand `repo-name`
64 - canonical `~owner/repo-name`
65- Stored repository names are normalized to canonical `~owner/repo-name` form.
66- Git polling auto-discovers repositories owned by the actor through SourceHut.
67- Tracked repositories are optional force-includes for git polling; they are not required for normal owned-repository discovery.
68
69## Health
70
71### `GET /health`
72
73Returns a basic service health response.
74
75Auth:
76
77- Public
78
79Response `200 OK`:
80
81```json
82{
83 "status": "ok"
84}
85```
86
87## Contributions
88
89### `GET /api/contributions/{actor}`
90
91Returns a contribution calendar for an actor over a year or explicit date range.
92
93Auth:
94
95- Public
96
97Path parameters:
98
99- `actor` string: SourceHut actor, for example `~your-user`
100
101Query parameters:
102
103- `year` integer, optional
104- `from` string `YYYY-MM-DD`, optional
105- `to` string `YYYY-MM-DD`, optional
106- `prioritize_self` boolean, optional
107
108Rules:
109
110- Provide either `year`
111- Or provide both `from` and `to`
112- Do not combine `year` with `from`/`to`
113
114Behavior notes:
115
116- This endpoint resolves aliases to a canonical actor before querying data.
117- This endpoint also registers the actor for background indexing and updates the actor's `last_requested_at` timestamp.
118- When `prioritize_self=true`, registration also applies a temporary scheduler boost so that due polls for that actor run ahead of the normal due queue.
119- The response is always immediate; it does not wait for SourceHut polling to finish.
120- One-year backfill runs in bounded background batches and may take multiple scheduler passes to complete.
121
122Example by year:
123
124```bash
125curl "http://127.0.0.1:8000/api/contributions/~your-user?year=2026"
126```
127
128Example by range:
129
130```bash
131curl "http://127.0.0.1:8000/api/contributions/~your-user?from=2026-03-01&to=2026-04-15"
132```
133
134Response `200 OK`:
135
136```json
137{
138 "actor": "~your-user",
139 "from": "2026-03-01",
140 "to": "2026-04-15",
141 "is_indexed": false,
142 "last_polled_at": null,
143 "indexing_state": "pending",
144 "is_recent_window_backfilled": false,
145 "recent_backfill_state": "in_progress",
146 "recent_backfill_completed_at": null,
147 "days": [
148 { "date": "2026-03-01", "count": 0, "score": 0.0 },
149 { "date": "2026-03-02", "count": 3, "score": 2.5 }
150 ]
151}
152```
153
154Response fields:
155
156- `actor` string: canonical actor after alias resolution
157- `from` string: inclusive start date
158- `to` string: inclusive end date
159- `is_indexed` boolean: whether the service has already completed at least one successful recent/incremental poll for this actor
160- `last_polled_at` string or `null`: most recent successful poll time, if any
161- `indexing_state` string: one of `pending`, `indexed`, or `error`
162- `is_recent_window_backfilled` boolean: whether the service has finished filling the retained one-year history window
163- `recent_backfill_state` string: one of `pending`, `in_progress`, `completed`, or `error`
164- `recent_backfill_completed_at` string or `null`: when one-year backfill completed, if it has
165- `days` array:
166 - `date` string `YYYY-MM-DD`
167 - `count` integer contribution count for the day
168 - `score` float weighted score for the day
169
170Indexing state semantics:
171
172- `pending`: the actor is known but has not completed a successful poll yet
173- `indexed`: at least one successful poll has completed for the actor
174- `error`: the most recent poll attempt for the actor failed
175
176Recent backfill semantics:
177
178- the service only retains and backfills the most recent 365 days of activity
179- `pending`: the actor has not started one-year backfill yet
180- `in_progress`: one-year backfill is actively progressing in bounded background batches
181- `completed`: the retained one-year window is fully backfilled
182- `error`: the most recent backfill attempt failed
183
184Retention notes:
185
186- activity older than 365 days is not retained
187- scheduled polling periodically prunes contribution rows older than the retained window
188
189Possible errors:
190
191- `400 Bad Request` for invalid or conflicting date input
192
193Example `400`:
194
195```json
196{
197 "detail": "Provide `year` or both `from` and `to`."
198}
199```
200
201### `GET /api/contributions/{actor}/stats`
202
203Returns aggregated stats for the same date selection rules as the calendar endpoint.
204
205Auth:
206
207- Public
208
209Path parameters:
210
211- `actor` string
212
213Query parameters:
214
215- `year` integer, optional
216- `from` string `YYYY-MM-DD`, optional
217- `to` string `YYYY-MM-DD`, optional
218- `prioritize_self` boolean, optional
219
220Behavior notes:
221
222- This endpoint has the same actor-registration and alias-resolution behavior as the calendar endpoint.
223- When `prioritize_self=true`, registration also applies the same temporary scheduler boost as the calendar endpoint.
224- This endpoint returns immediately and does not block on SourceHut polling.
225- This endpoint also reflects whether the retained one-year history window has been fully backfilled yet.
226
227Example:
228
229```bash
230curl "http://127.0.0.1:8000/api/contributions/~your-user/stats?from=2026-03-01&to=2026-04-15"
231```
232
233Response `200 OK`:
234
235```json
236{
237 "actor": "~your-user",
238 "from": "2026-03-01",
239 "to": "2026-04-15",
240 "is_indexed": true,
241 "last_polled_at": "2026-04-11T18:05:00Z",
242 "indexing_state": "indexed",
243 "is_recent_window_backfilled": true,
244 "recent_backfill_state": "completed",
245 "recent_backfill_completed_at": "2026-04-11T18:02:00Z",
246 "total_events": 126,
247 "total_score": 116.75,
248 "active_days": 14,
249 "longest_streak": 5,
250 "current_streak": 0
251}
252```
253
254Response fields:
255
256- `actor` string
257- `from` string
258- `to` string
259- `is_indexed` boolean
260- `last_polled_at` string or `null`
261- `indexing_state` string
262- `is_recent_window_backfilled` boolean
263- `recent_backfill_state` string
264- `recent_backfill_completed_at` string or `null`
265- `total_events` integer
266- `total_score` float
267- `active_days` integer
268- `longest_streak` integer
269- `current_streak` integer
270
271Possible errors:
272
273- `400 Bad Request` for invalid or conflicting date input
274
275### `POST /api/contributions/poll`
276
277Triggers a manual SourceHut poll for the given actor and stores any newly discovered events.
278
279Auth:
280
281- Requires `X-API-Key`
282
283Query parameters:
284
285- `actor` string: SourceHut actor to poll
286
287Example:
288
289```bash
290curl -X POST \
291 -H "X-API-Key: your-api-key" \
292 "http://127.0.0.1:8000/api/contributions/poll?actor=~your-user"
293```
294
295Response `200 OK`:
296
297```json
298{
299 "actor": "~your-user",
300 "inserted_events": 57,
301 "services": ["todo", "git"]
302}
303```
304
305Response fields:
306
307- `actor` string: canonical actor after alias resolution
308- `inserted_events` integer: number of newly inserted normalized events
309- `services` array of strings: currently `["todo", "git"]`
310
311Behavior notes:
312
313- Manual polling also updates the actor's indexing metadata.
314- Manual polling also advances one-year backfill by bounded batches for each supported service.
315- Git polling auto-discovers the actor's owned repositories and unions in any configured tracked repositories.
316
317Possible errors:
318
319- `401 Unauthorized` if the API key is missing or invalid
320- `502 Bad Gateway` if polling SourceHut fails
321
322Example `401`:
323
324```json
325{
326 "detail": "Invalid API key."
327}
328```
329
330Example `502`:
331
332```json
333{
334 "detail": "SourceHut polling failed: HTTP error from SourceHut: 502"
335}
336```
337
338## Tracked Repositories
339
340All repository endpoints are protected and require `X-API-Key`.
341
342Tracked repositories are optional force-includes for git polling. Each repository is associated with an actor and stored in canonical `~owner/repo` form.
343
344### `GET /api/repositories`
345
346Lists tracked git repositories.
347
348Auth:
349
350- Requires `X-API-Key`
351
352Query parameters:
353
354- `actor` string, optional: filter to a canonical actor or alias
355
356Example:
357
358```bash
359curl \
360 -H "X-API-Key: your-api-key" \
361 "http://127.0.0.1:8000/api/repositories?actor=~your-user"
362```
363
364Response `200 OK`:
365
366```json
367[
368 {
369 "id": 1,
370 "service": "git",
371 "actor": "~your-user",
372 "repo_name": "~your-user/your-repo"
373 }
374]
375```
376
377### `GET /api/repositories/{repository_id}`
378
379Fetches one tracked repository by numeric ID.
380
381Auth:
382
383- Requires `X-API-Key`
384
385Path parameters:
386
387- `repository_id` integer
388
389Example:
390
391```bash
392curl \
393 -H "X-API-Key: your-api-key" \
394 "http://127.0.0.1:8000/api/repositories/1"
395```
396
397Response `200 OK`:
398
399```json
400{
401 "id": 1,
402 "service": "git",
403 "actor": "~your-user",
404 "repo_name": "~your-user/your-repo"
405}
406```
407
408Possible errors:
409
410- `404 Not Found` if the repository ID does not exist
411
412### `POST /api/repositories`
413
414Creates a tracked repository entry.
415
416Auth:
417
418- Requires `X-API-Key`
419
420Request body:
421
422```json
423{
424 "actor": "~your-user",
425 "repo_name": "your-repo"
426}
427```
428
429Example:
430
431```bash
432curl -X POST \
433 -H "X-API-Key: your-api-key" \
434 -H "Content-Type: application/json" \
435 -d '{"actor":"~your-user","repo_name":"your-repo"}' \
436 "http://127.0.0.1:8000/api/repositories"
437```
438
439Response `201 Created`:
440
441```json
442{
443 "id": 1,
444 "service": "git",
445 "actor": "~your-user",
446 "repo_name": "~your-user/your-repo"
447}
448```
449
450Possible errors:
451
452- `401 Unauthorized` if the API key is missing or invalid
453- `409 Conflict` if the normalized repository already exists for that actor
454- `422 Unprocessable Content` if `actor` or `repo_name` is blank or malformed
455
456### `PATCH /api/repositories/{repository_id}`
457
458Updates an existing tracked repository.
459
460Auth:
461
462- Requires `X-API-Key`
463
464Path parameters:
465
466- `repository_id` integer
467
468Request body:
469
470```json
471{
472 "actor": "~your-user",
473 "repo_name": "~your-user/your-other-repo"
474}
475```
476
477Body rules:
478
479- At least one of `actor` or `repo_name` must be present
480
481Example:
482
483```bash
484curl -X PATCH \
485 -H "X-API-Key: your-api-key" \
486 -H "Content-Type: application/json" \
487 -d '{"repo_name":"~your-user/your-other-repo"}' \
488 "http://127.0.0.1:8000/api/repositories/1"
489```
490
491Response `200 OK`:
492
493```json
494{
495 "id": 1,
496 "service": "git",
497 "actor": "~your-user",
498 "repo_name": "~your-user/your-other-repo"
499}
500```
501
502Possible errors:
503
504- `404 Not Found`
505- `409 Conflict`
506- `422 Unprocessable Content`
507
508### `DELETE /api/repositories/{repository_id}`
509
510Deletes a tracked repository.
511
512Auth:
513
514- Requires `X-API-Key`
515
516Path parameters:
517
518- `repository_id` integer
519
520Example:
521
522```bash
523curl -X DELETE \
524 -H "X-API-Key: your-api-key" \
525 "http://127.0.0.1:8000/api/repositories/1"
526```
527
528Response `204 No Content`
529
530Possible errors:
531
532- `404 Not Found`
533
534## Error Summary
535
536Common status codes:
537
538- `200 OK` successful read or manual poll
539- `201 Created` successful repository creation
540- `204 No Content` successful repository deletion
541- `400 Bad Request` invalid date parameters
542- `401 Unauthorized` missing or invalid API key
543- `404 Not Found` missing repository record
544- `409 Conflict` duplicate repository after normalization
545- `422 Unprocessable Content` invalid repository payload
546- `502 Bad Gateway` upstream SourceHut failure during poll
547
548## OpenAPI
549
550FastAPI also serves an OpenAPI document at:
551
552```text
553/openapi.json
554```
555
556If interactive docs are enabled by your deployment, the standard FastAPI docs may also be available at:
557
558```text
559/docs
560```