REST API - AI assistants (AHG Archives MCP)
The AHG Archives MCP server lets Claude, ChatGPT and other assistants search and read a catalogue, and lets a signed-in archivist's assistant propose changes for review. It reads PSIS (AtoM with the AHG extensions) and Heratio through the same calls to REST API v2. This page lists those calls as Heratio answers them (heratio#1558).
Keys
Every call sends a key in X-API-Key (or Authorization: Bearer).
- Your own keys: make and revoke them at
/api/keysonce you are signed in. - Anyone's keys: an administrator manages them from that user's page (
/user/{slug}, section API keys). This is how to give a service account, such as the MCP's user with no groups, its key without signing in as it.
Both places use the same form and show each key's scopes, last use, expiry and daily limit. Each key has its own scopes, and a key can never hold a scope its owner's role does not allow:
| Scope | Allows |
|---|---|
read |
Reading the catalogue |
write |
Creating and updating records, and proposing changes for review |
delete |
Deleting records |
publish:write |
Publishing and unpublishing records |
batch |
Batch operations |
oai |
OAI-PMH harvesting, when OAI authentication is on |
The REST and OAI-PMH key fields that older versions showed on the user page are not accepted by the API. Remove them on the user's edit page, and create a key as above.
A key acts as the person who made it:
- A user with no groups (the MCP's public service key) gets a key for the public catalogue only. It reads the description, authority, repository, taxonomy, exhibition and search routes, which show only what a visitor may see. Every other route answers 403, "This key may read the public catalogue only."
- Staff get the scopes their own role holds, never more.
Each key has an hourly limit and, optionally, a daily limit. Past either one, the API answers 429 with a Retry-After header in seconds.
What a key may see
Every read applies the same gate as the public site:
- published records only;
- no record under an active embargo, a security classification above public, an ICIP/TK protocol, or an ODRL "use" prohibition.
This holds for single reads, lists, search results, a record's ancestors and children count, and an authority record's related descriptions. A hidden record answers exactly like a missing one: 404, {"message": "Description '...' not found."}. Authority records follow the public rule too: published and not embargoed.
Calls
| Call | Answers |
|---|---|
GET /api/v2/descriptions?limit=&skip=&parent=<id> |
data.results[], data.total. Newest first; a parent's children come in archival order. |
GET /api/v2/descriptions/{slug}?full=true |
The record, plus dates, names, subjects, places, notes, hierarchy[], children_count, provenance and digital_objects[]. |
GET /api/v2/descriptions/{slug}/text?start=&chars= |
texts[]: extracted text, OCR and transcriptions of the record's files, paged by character. |
POST /api/v2/search {query, limit, skip} |
data.results[] of {slug, title, level_of_description}, plus data.total. |
GET /api/v2/authorities?search=&entity_type=person|organization|family&limit= |
data.results[]. Every word of search must appear in the name, in any order. |
GET /api/v2/authorities/{slug} |
The authority record. |
GET /api/v2/taxonomies/{id}/terms?search= |
data.terms[], data.total. Places are taxonomy 42. |
GET /iiif/manifest/{slug} |
IIIF Presentation 3.0 manifest, gated like the reads. |
Lists answer in one shape: data = {total, limit, skip, results} (terms: terms), with meta and links for page-based clients. Before heratio#1558, data was the bare list.
digital_objects[] link only to copies the caller may open, as plain paths on this site: thumbnail_url, reference_url, and master_url (only for staff who may read masters). A record with redaction regions offers no reference or master, and no file text.
Privacy
Field-level redaction rules apply to the full read and to file text, for everyone except administrators. A rule on the field transcript masks file text.
Proposals
An archivist's assistant, signed in with the archivist's own key, may propose additions to a record. Nothing on the record changes: every proposal waits for a person to approve it. The key needs the write scope, and its owner must be allowed to edit the record.
POST /api/v2/descriptions/{slug}/proposals with:
{"source": "glam-mcp",
"entities": [{"type": "PERSON", "value": "Debora Engelbrecht",
"evidence": "Named on the reverse of the photograph",
"sources": ["https://..."],
"authority": {"dates_of_existence": "1890-1950", "history": "...", "places": "..."},
"actor_id": 123, "link_reason": "same name and dates"}]}
typeis PERSON, ORG, GPE (a place) or DATE. Each request takes at most 50.actor_id(people and organisations) orterm_id(places) names the existing record the proposal most likely is.- Values already waiting or linked for the record are skipped and counted in
skipped_existing.
Names and places go to AI > NER review and on to the Authority Resolution queue. There the reviewer sees the evidence, the sources, who proposed it, and the proposed record first, marked "proposed". Creating a new authority record pre-fills it from the proposed details. Dates stay in the NER dialog. Re-running NER on a record leaves proposals alone.
Text goes to AI > Suggest review (/admin/ai/suggest-review):
fields-[{field, text, culture?, evidence?, sources?}], at most 10 per request.fieldis title, scope_and_content, archival_history, extent_and_medium, arrangement, access_conditions or reproduction_conditions. Acultureother than the record's makes it a translation into that language.transcripts-[{text, digital_object_id?, evidence?}], at most 5, each up to 65,000 bytes. This is the text of one of the record's files, such as a scanned page with no text layer.
The reviewer sees the current text, or the page image for a transcript, beside the proposal with its evidence and sources. They may edit the text before approving. Approval writes the field, the field in the other language (creating that language's row when needed), or the file's transcript, which GET /descriptions/{slug}/text then returns. The outcome shows in GET /api/v2/proposals as approved, edited or rejected, with the reviewer's note.
Relationships go to AI > Relationship review (/admin/ai/relationship-review):
relationships-[{subject_name, subject_actor_id?, relation, object_name, object_actor_id?, date?, start_date?, end_date?, evidence?, sources?}], at most 20 per request.relationis an actor relation type by name or id, such as "is the spouse of" or "is the child of" (GET /api/v2/taxonomies/55/terms). The four category terms are refused.- Each side is an existing authority record (
*_actor_id) or a person or organisation proposed for the same record. Any other name comes back withqueued: falseand the reason.
The reviewer approves once both names are linked to authority records, in NER review or Authority Resolution. Approval writes the relation, with its date, and it shows on both authority records. A relation already present in either direction is reused rather than duplicated.
GET /api/v2/proposals?mine=1&status=&count=&start= lists the caller's proposals, newest first, with each one's status (pending, linked, rejected, and so on) and the record's slug and title. mine=0 lists everyone's and needs an administrator or editor.