Developers
The same published releases these pages read, answered by a machine surface: what it serves, how to reach it, what comes back, and how to pin a release so a figure you cite does not move under you. Everything on this page is read from the contract this build generates, so it cannot describe an API we do not serve.
What this API is
One surface, version 1.1.0, in two transports. 15 operations answer to an ordinary web request. 8 of those 15 are also reachable as tools, and a tool call is dispatched through the same operation the equivalent web request would have reached — so the scopes, the quota, the ordering, the paging and the error envelope are not a second implementation of themselves. The transport differs; nothing about the answers does.
It serves the same immutable releases these pages read. A figure you fetch here is the figure on the page, from the same publication, and both can be cited by the same release identifier.
How to reach it
The tool endpoint
POST/mcp
It answers on this site’s own address, so the endpoint above is the whole address. Messages are JSON over a single endpoint, with the protocol version 2025-06-18. The server calls itself akashic.
- Methods it answers
initialize,notifications/initialized,tools/list,tools/call,ping. Anything else is answered as a method it does not have, which is how a client discovers that an optional capability is absent.- Largest result
- 1,024 KiB. A result above that is refused with the address that serves the bytes instead. It is never truncated — a truncated document is one that parses and is wrong.
The web addresses
The versioned operations below are served by the gateway at a path beginning /v1; the two liveness probes sit outside the version, at /health.
Those addresses are not reachable from this site yet. Available: the whole declared surface on this page, and the tool endpoint, which answers the same questions with the same schemas. Unavailable: every one of those addresses under this host: the route that carries API traffic here begins /api/v1, one segment away from the paths the gateway serves, so a request answers 404. As of . Coming back: the address is a deployment change, not a rebuild; the operations themselves are built.
Your first call
Ask what has been published. No account, no key, no sign-up: reading published reference data needs none of them, and that is a commitment rather than a convenience.
{
"jsonrpc": "2.0",
"id": 1,
"method": "tools/call",
"params": { "name": "list_releases", "arguments": { "limit": 1 } }
}Post that to /mcp with a JSON content type. The reply summarizes one release: its identifier, when it was published, the licences behind it, how much it holds, and where its full list of files is, with the checksum to verify that list by. Take the identifier out of it — every later call can pin to it.
What it returns
Two body shapes and no third. Both carry the same metadata block, and you should read it before you use the figures.
{ "data": …, "meta": … }- One thing, under `data`, with the metadata that says where it came from.
{ "items": [ … ], "page": …, "meta": … }- A page of rows under `items`, with the cursor and ordering under `page`.
The same facts as headers
Every successful response repeats its metadata in headers, so a request that only asks for the headers tells you which publication your cached copy is current against without downloading the body — and a revalidation that comes back with no body still says which release it agreed with.
| Header | Field | What it says |
|---|---|---|
x-akashic-release | releaseId | The immutable release this response came from. |
x-akashic-freshness | freshness | published, candidate, live or stale — the same value the body carries. |
x-akashic-generated-at | generatedAt | When the payload was generated. |
x-akashic-schema-version | schemaVersion | The version of the contract the payload is shaped by. |
link | citationUrl | The persistent address to cite this response by. |
When it fails
A failure is one envelope with one code, over either transport. It names what happened and correlates to a request identifier; it never returns anything about our internals.
| Code | Status | Meaning |
|---|---|---|
BAD_REQUEST | 400 | a parameter failed validation; details name the field |
UNAUTHORIZED | 401 | the request needs an identity it did not present |
FORBIDDEN | 403 | the identity is not allowed this resource or scope |
NOT_FOUND | 404 | no resource matches the identifier at the requested release |
RATE_LIMITED | 429 | quota exceeded; Retry-After states the horizon |
INTERNAL_ERROR | 500 | an unexpected failure |
SERVICE_NOT_CONFIGURED | 503 | the service is not ready to serve; Retry-After states the horizon |
Identity and rate limits
Every operation this surface declares asks for public:read, which a caller presenting nothing already holds. So a key is never a precondition for reading published reference data, and a price of zero behind a login would still be a wall.
A key does two things and no others. It raises how many requests a minute you may make, and it carries permissions for material outside the free public corpus. Keys are issued from your account, shown once, and presented as Authorization: Bearer …. A key looks like ^ak_(live|test)_[A-Za-z0-9]{8,16}$ — it is bound to the environment that issued it, so a test key cannot read production by accident.
Rate plans
| Plan | Ceiling | Counted against | Applies to |
|---|---|---|---|
| anonymous | 60 requests a minute | client_ip | a caller presenting no credential |
| standard | 600 requests a minute | api_key_id | iam.api_key.rate_plan = 'standard' (the column default) |
Over the ceiling, the answer says how long to wait rather than leaving you to guess. A tool call is one request and is metered exactly once — never twice, never free.
Permissions
A caller presenting nothing holds public:read. The rest exist for material that is not published reference data.
| Permission | Grants |
|---|---|
public:read | Published reference data. Anonymous callers hold it implicitly. |
releases:read | Release manifests and their artifacts metadata. |
bulk:read | Redirects to immutable bulk artifacts (API-007). |
workspace:read | A workspace the caller is a member of. |
workspace:write | Mutating a workspace the caller may write. |
content:read | Unpublished editorial content. |
content:write | Authoring editorial content. |
content:publish | Publishing editorial content. |
jobs:read | Job status. |
jobs:write | Submitting jobs. |
live:operate | Live operator actions. |
admin | Internal administration. Never held by a personal key at launch. |
Bounds
A page is never larger than 100 rows, a query string never longer than 2,048 bytes, and a request that has not answered within 10 seconds is ended. Asking for more than the maximum is refused rather than quietly reduced, so you always know how much you got.
Pinning a release
Ask for nothing and you get the current stable release, which moves when a new one is published. Name a release and you get exactly that publication, for as long as the archive exists. A correction never edits a release: it publishes a new one that supersedes it, and the superseded release keeps its own address so a citation written against it still answers.
So a figure you quote does not move under you — provided you quote the release it came from. Every response names it, in the body and in a header, whether or not you asked.
12 of these 15 operations take a release directly: /v1/releases, /v1/releases/{releaseId}, /v1/releases/{releaseId}/artifacts, /v1/places/{placeId}, /v1/places/{placeId}/history, /v1/places/{placeId}/records, /v1/elections, /v1/elections/{electionId}/contests, /v1/contests/{contestId}, /v1/results, /v1/records/{recordType}, /v1/downloads/{artifactId}. The rest answer from the release the surface resolved for the call, and say which one that was.
Every operation
The whole declared surface, read from the contract this build generates. Nothing is omitted and nothing here is aspirational: what is declared and not built is in its own section below.
| Address | What it answers | Accepts | Tool |
|---|---|---|---|
GET /v1/releases | List published releases, newest first, as bounded summaries; each names the address, length and digest of its whole manifest. | cursor, limit, releaseId | list_releases |
GET /v1/releases/{releaseId} | One release's summary — identity, supersession, quality, counts, licences, bulk tables and citation — with the address, length and digest of its whole manifest. | releaseId (required) | none yet |
GET /v1/releases/{releaseId}/artifacts | One release's artifacts, a page at a time, each with its role, length, digest and sources and the id getDownload answers for. | releaseId (required), role, cursor, limit | none yet |
GET /v1/places/{placeId} | The Place View Model for one geography at one release. | placeId (required), releaseId | get_place |
GET /v1/places/{placeId}/history | The Place's history series, one page at a time. | placeId (required), cursor, limit, releaseId | none yet |
GET /v1/places/{placeId}/records | The records this Place holds, with universe, tie rule and method. | placeId (required), cursor, limit, releaseId | none yet |
GET /v1/elections | Elections in the published universe, one page at a time. | jurisdictionId, year, cursor, limit, releaseId | none yet |
GET /v1/elections/{electionId} | One election with the single contest this release can show for it — that contest's options, results, reporting, coverage and sources. Its whole ballot is at listContests. | electionId (required) | get_election |
GET /v1/elections/{electionId}/contests | Every contest on this election's ballot, one page at a time — including the contests whose results this release does not hold, each row saying which it is. | electionId (required), cursor, limit, releaseId | list_contests |
GET /v1/contests/{contestId} | One contest — options, results at the requested scope, reporting, coverage and sources. | contestId (required), releaseId | get_contest_results |
GET /v1/results | Result facts at the canonical grain for one contest — the roll-up and every reporting unit beneath it, each row naming its unit — one page at a time. | contestId (required), reportingUnitId, voteType, cursor, limit, releaseId | list_results |
GET /v1/records/{recordType} | A record ranking of one type across the published universe. | recordType (required), cursor, limit, releaseId | get_record |
GET /v1/downloads/{artifactId} | Metadata for, or a redirect to, an immutable bulk artifact (API-007). | artifactId (required), releaseId | get_download |
GET /health/live | Liveness — the process answers. | No parameters. | none yet |
GET /health/ready | Readiness — every declared dependency answers; 503 with the same shape otherwise. | No parameters. | none yet |
The tools
Each tool binds to the operation that answers the same question and carries that operation’s response schema, from the same generator run. A tool that bound to nothing would be a tool with no schema to carry, and the generator refuses one.
| Tool | Answers through | What it answers | Requires |
|---|---|---|---|
get_place | /v1/places/{placeId} | The Place View Model for one geography at one release. | placeId |
get_election | /v1/elections/{electionId} | One election with the single contest this release can show for it — that contest's options, results, reporting, coverage and sources. Its whole ballot is at listContests. | electionId |
list_contests | /v1/elections/{electionId}/contests | Every contest on this election's ballot, one page at a time — including the contests whose results this release does not hold, each row saying which it is. | electionId |
get_contest_results | /v1/contests/{contestId} | One contest — options, results at the requested scope, reporting, coverage and sources. | contestId |
list_results | /v1/results | Result facts at the canonical grain for one contest — the roll-up and every reporting unit beneath it, each row naming its unit — one page at a time. | contestId |
get_record | /v1/records/{recordType} | A record ranking of one type across the published universe. | recordType |
list_releases | /v1/releases | List published releases, newest first, as bounded summaries; each names the address, length and digest of its whole manifest. | Nothing. |
get_download | /v1/downloads/{artifactId} | Metadata for, or a redirect to, an immutable bulk artifact (API-007). | artifactId |
Deprecations and withdrawals
Nothing that was ever served has been taken away. What has left the declared surface is listed here anyway, because an absence decided on purpose still has to be announced like one.
get_citation— withdrawn- It was declared and never served, so no caller could have used it and there is no migration window to honour. Use the citation address on every response, and the download tool for an artifact’s full citation.
What is not built yet
These are declared and not built. They are listed so the inventory is complete, and written in the future tense because that is what they are — a page that described them as though they answered would cost you an afternoon.
/v1/search— Search places and elections by name, alias, identifier and hierarchy context. Not built./v1/sources/{sourceId}— One source — publisher, rights, authority class and its releases. Not built./v1/methods/{methodId}— One method or metric definition with its version, inputs and limitations. Not built.search_places— declared as a tool and not built.compare_places— declared as a tool and not built.get_source— declared as a tool and not built.get_method— declared as a tool and not built.- The web addresses above, from this site’s own host. The operations are built; the address is not routed to them here.
- The longer guides — concepts, paging, bulk downloads, worked examples — are written and are not published at this address yet. This page carries what a first integration needs.