The four endpoints
All four areGET, and all four are unauthenticated.
Every response from all four carries
api_version, which is 1 today, and generated_at, the moment the response was built. Every response is sent with cache-control: no-store, so what you read is what the log says now rather than something a cache kept.
What a public row contains
Chat transcripts stay private. They are never on the public routes, and neither is the report body, your email address, your display name, a conversation id, a pairing code or a token of any kind. The full list is on File a report.
Windows
since and until are RFC 3339 timestamps, and they behave the same way on the leaderboard and on the reports listing.
sinceis inclusive.untilis exclusive. So consecutive windows can share a bound without double-counting a row.- Both filter on
adjudicated_at, the moment the verdict was recorded. Neither filters on filing time. A report filed in one window and judged in the next belongs to the second. until <= sinceis refused. It is not answered with an empty page, and the reason is worth understanding: an empty page reads exactly like “this hotkey earned nothing”.- An
adjudicated_atvalue taken from a response is reusable as a bound byte for byte. It is truncated to microseconds. A+00:00offset needs percent-encoding in a query string, though the un-encoded form is accepted too.
Paging the reports listing
The reports endpoint is the one that enumerates completely, and it is cursor-paged.limitis clamped to 100. Asking for more gets you 100.- The response reports
has_more. While it is true, sendnext_cursorback verbatim ascursorand read the next page. - The cursor is opaque, and it is keyed on
adjudicated_atand thenid. Do not build one yourself and do not try to decode it. idis assigned at filing time, not at verdict time, so ids are not in verdict order. That is exactly why the cursor exists: you cannot page this log by sorting ids.
has_more when there are more than that. If you need a complete enumeration, walk the reports endpoint instead. The leaderboard is a summary, and the reports listing is the record.
Reading zero correctly
This is the part to get right before you conclude anything from an empty result. A zero on the leaderboard has three different meanings, and the status endpoint is what tells them apart.
Two further facts make the reading safe. A backend that cannot answer at all fails the request; it never answers an empty list, so an empty list is always a real answer from a working backend. And
last_adjudicated_at on the status response tells you how stale the log is, which is the difference between “nothing has happened” and “nothing has happened lately”.
Verify one bounty end to end
1
Check that the log is being kept
Read
/v1/bounty/public/status. Confirm that an operator exists and that the backlog is zero. If it is not, stop here: anything you count next is incomplete by definition.2
Read the leaderboard for the window
Call
/v1/bounty/public/leaderboard with the since and until bounds of the window you care about. Note the valid-report count for the hotkey you are checking.3
Walk the reports listing over the same window
Call
/v1/bounty/public/reports with the same hotkey, since and until, and follow next_cursor until has_more is false. Count the valid rows as you go.4
Fetch one row on its own
Take an id from the walk and read
/v1/bounty/public/reports/{id}. You get the same row, which is the one you can quote to somebody else.5
Compare the two counts
The count from the leaderboard and the rows you walked must agree. A disagreement means the walk was truncated, so check
has_more and page again. It does not mean the log is wrong.There is no public API for anything else in Cortex. These four endpoints exist because the bounty log has to be verifiable by outsiders; they are not a general platform API, and there is no inference endpoint, no API key and no client library. See Platform API.