# API

Programs can read everything the site shows, and send suggestions, over plain HTTP. No key or account is needed to read.

The OpenAPI document is at [/openapi.json](/openapi.json). It loads in Swagger UI, Redoc, Postman and any OpenAPI client generator.

Annotations are released under [CC0 1.0](https://creativecommons.org/publicdomain/zero/1.0/): no permission or attribution is needed.

[Works](#works) · [Annotations](#annotations) · [Search](#search) · [Suggestions](#suggestions) · [Feeds](#feeds) · [Addresses](#addresses) · [Twins](#twins) · [Crawling](#crawling) · [Schemas](#schemas)

## Works

Works, editions, units, permanent numbers and the library's counts.

### GET `/v1/works`

List the works in the library. Works are ordered by title. Each work carries its current edition and its top-level units.

| Name | In | Type | Description |
| --- | --- | --- | --- |
| `limit` | query | integer, 1 to 100, default `20` | Items per page, from 1 to 100. Example: `20`. |
| `cursor` | query | string | The opaque next_cursor value from the previous page. Example: `WyJoZXJldGljcyIsImhlcmV0aWNzIl0`. |

Returns [WorkList](#schema-WorkList) with status 200. A page of works.

```json
{
  "items": [
    {
      "title": "Heretics",
      "work_type": "book",
      "editions": [
        {
          "edition_id": "pg470-r1",
          "label": "Project Gutenberg #470 text",
          "language_tag": "en",
          "publication_year": null,
          "current_visible_source_revision_id": "src_pg470-r1"
        }
      ],
      "slug": "heretics",
      "oid": 248,
      "authors": ["G. K. Chesterton"],
      "url": "/heretics",
      "current_edition": "pg470-r1",
      "current_edition_oid": 249,
      "structure": [{"kind": "chapter", "prefix": "CH", "width": 2}],
      "chapters": [
        {
          "id": "CH01",
          "title": "Introductory Remarks on the Importance of Orthodoxy",
          "oid": 251,
          "paragraph_count": 12
        }
      ]
    }
  ],
  "next_cursor": "WyJoZXJldGljcyIsImhlcmV0aWNzIl0"
}
```

Errors: 400 `invalid_query`, 400 `invalid_cursor`.

### GET `/v1/works/{work}`

Read the whole current edition of a work. The answer holds the ids, permanent numbers and texts of every unit. Use chapter to read one top-level unit.

| Name | In | Type | Description |
| --- | --- | --- | --- |
| `work` *required* | path | string | The work slug. Example: `orthodoxy`. |
| `edition` | query | string | An edition id; the work's current edition when left out. Example: `pg16769-r1`. |
| `chapter` | query | string | One top-level unit id; any case. Example: `CH05`. |
| `format` | query | string, one of `json`, `md` | Set to md for Markdown instead of JSON. Example: `md`. |

Returns [WorkRead](#schema-WorkRead) or `text/markdown` with status 200. The edition's text.

```json
{
  "work": {
    "slug": "orthodoxy",
    "title": "Orthodoxy",
    "authors": ["G. K. Chesterton"],
    "type": "book",
    "oid": 1,
    "url": "/orthodoxy"
  },
  "edition": {
    "id": "pg16769-r1",
    "oid": 2,
    "label": "Project Gutenberg EBook #16769, from book.json",
    "language": "en",
    "source_url": "https://www.gutenberg.org/ebooks/16769",
    "rights": "Public domain",
    "provenance_note": "Wrapped lines joined with one space; underscores kept; from book.json sha256 05720d89dfe47...",
    "source_revision_id": "src_pg16769-r1",
    "normalization": "unicode-nfc-lf-v1",
    "url": "/orthodoxy/pg16769-r1"
  },
  "sha256": "sha256:e19ad0a9db6aa0d5f3d38183bcdee4c1a441a1f380f69405d59f8c0a93c992f3",
  "content_sha256": "sha256:e19ad0a9db6aa0d5f3d38183bcdee4c1a441a1f380f69405d59f8c0a93c992f3",
  "source_sha256": "05720d89dfe47f8bcca7634891cc0275b8ad4023bfd8d44905ca55151a64eac5",
  "structure": [{"kind": "chapter", "prefix": "CH", "width": 2}],
  "chapters": [
    {
      "id": "CH05",
      "title": "The Flag of the World",
      "oid": 8,
      "paragraphs": [
        {
          "id": "CH05.P001",
          "oid": 100,
          "text": "When I was a boy there were two curious men running about who were called the optimist and...",
          "sha256": "acbdd86df4e504db3fadb9086813349aa2d86bcee6103b03210c674343d75ff8"
        }
      ]
    }
  ]
}
```

Errors: 404 `not_found`, 409 `no_structure`.

### GET `/v1/works/{work}/{unit}`

Read one unit or a range of units with its annotations. A unit is a chapter or a paragraph. A range of siblings such as CH05.P016-P018 can stand in place of a unit. Other spellings redirect to the canonical id.

| Name | In | Type | Description |
| --- | --- | --- | --- |
| `work` *required* | path | string | The work slug. Example: `orthodoxy`. |
| `unit` *required* | path | string | A unit id, or a range START-END of sibling units. Examples: `CH05.P016`, `CH05`, `CH05.P016-P018`. |
| `edition` | query | string | An edition id; the work's current edition when left out. Example: `pg16769-r1`. |
| `context` | query | integer, 0 to 10 | Add up to this many neighbouring siblings on each side, from 0 to 10; single units only. Example: `1`. |
| `containers` | query | boolean, default `false` | Also include annotations on the units above and on the whole work. Example: `false`. |
| `format` | query | string, one of `json`, `md` | Set to md for Markdown instead of JSON. Example: `md`. |

Returns [UnitRead](#schema-UnitRead) or [RangeRead](#schema-RangeRead) or `text/markdown` with status 200. The unit or the range.

```json
{
  "id": "CH05.P016",
  "oid": 115,
  "kind": "paragraph",
  "label": null,
  "work": "orthodoxy",
  "edition": "pg16769-r1",
  "parent": "CH05",
  "ancestors": ["CH05"],
  "prev": "CH05.P015",
  "next": "CH05.P017",
  "url": "/orthodoxy/pg16769-r1/CH05.P016",
  "text": "About the same time I read a solemn flippancy by some free thinker: he said that a suicide...",
  "length_cp": 1519,
  "sha256": "ff85dcedfc1528227eb30b70d74ec5368c5b00d4cad97549f0cee10a4e5aaa67",
  "context": {
    "before": [
      {
        "id": "CH05.P015",
        "oid": 114,
        "text": "I put these things not in their mature logical sequence, but as they came: and this view w..."
      }
    ],
    "after": [
      {
        "id": "CH05.P017",
        "oid": 116,
        "text": "This was the first of the long train of enigmas with which Christianity entered the discus..."
      }
    ]
  },
  "annotations": [],
  "annotations_next_cursor": null,
  "cited_by": [
    {
      "id": "a_tffeow37",
      "oid": 164558,
      "revision": 1,
      "revision_oid": 164559,
      "created_at": "2026-11-01T22:08:50Z",
      "updated_at": "2026-11-01T22:08:50Z",
      "title": "Orthodoxy",
      "work": "orthodoxy",
      "edition": "pg16769-r1",
      "anchors": [
        {
          "scope": "work",
          "role": null,
          "edition": "pg16769-r1",
          "oid": 2,
          "url": "/orthodoxy/pg16769-r1"
        }
      ],
      "metadata": {
        "run": "api-smoke-2026-11-01",
        "pass": "ARGUMENT",
        "task": "Linked account of argument development in the whole book",
        "worker": "argument",
        "model": "gpt-6.1-sol"
      },
      "url": "/a/a_tffeow37"
    }
  ],
  "cited_by_next_cursor": null
}
```

Also 301: The canonical spelling of the unit id or range.

Errors: 400 `invalid_query`, 404 `not_found`, 422 `invalid_range`.

### GET `/v1/stats`

Get the library's counts. The counts cover public works, their current editions and public annotations.

Returns [Stats](#schema-Stats) with status 200. The counts.

```json
{
  "works": 2,
  "editions": 2,
  "paragraphs": 469,
  "annotations": 27,
  "revisions": 32,
  "annotated_paragraphs": 9,
  "events": 215,
  "last_activity_at": "2026-11-01T22:17:20Z",
  "license": {
    "annotations": "CC0-1.0",
    "url": "https://creativecommons.org/publicdomain/zero/1.0/"
  }
}
```

### GET `/o/{number}`

Redirect a permanent number to the page of its object. Every work, edition, unit, annotation and revision has a permanent number. Two unit numbers joined by a hyphen name a unit range.

| Name | In | Type | Description |
| --- | --- | --- | --- |
| `number` *required* | path | string, pattern `^[0-9]{1,18}(-[0-9]{1,18})?$` | A permanent number, or two unit numbers joined by a hyphen. Examples: `115`, `115-117`. |

Returns [RedirectBody](#schema-RedirectBody) with status 301. A permanent redirect to the object's page.

Headers: `Location`: The path of the object's page. `Cache-Control`: public, max-age=3600.

```json
{"redirect": "/orthodoxy/pg16769-r1/CH05.P016"}
```

Errors: 404 `not_found`, 422 `invalid_range`.

## Annotations

Annotations and their revisions.

### GET `/v1/annotations`

List public annotations, newest first. Filters combine with AND. The unit filters need work. Unknown parameters are refused.

| Name | In | Type | Description |
| --- | --- | --- | --- |
| `work` | query | string | Only annotations on this work. Example: `orthodoxy`. |
| `edition` | query | string | Only annotations made in this edition; needs work. Example: `pg16769-r1`. |
| `chapter` | query | string | Only annotations on or inside this unit or range; needs work. Example: `CH05`. |
| `paragraph` | query | string | Only annotations on or inside this unit or range; needs work. Example: `CH05.P016-P018`. |
| `overlaps` | query | string, pattern `^[^:]+:[0-9]+-[0-9]+$` | Only span anchors in UNIT:start-end, with start below end; needs work. Example: `CH03.P002:494-592`. |
| `match` | query | string, one of `exact`, `overlap`, `contained_by`, default `overlap` | How overlaps compares spans. Example: `overlap`. |
| `containers` | query | boolean, default `false` | Also include annotations on the units above and on the whole work. Example: `false`. |
| `cites` | query | string | Only annotations that cite this unit or range; needs work. Example: `CH04.P012`. |
| `pass` | query | string | Only annotations whose metadata.pass equals this value. Example: `EDITION`. |
| `task` | query | string | Only annotations whose metadata.task equals this value. Example: `Compare supplied editions and identify textual discrepancies`. |
| `since` | query | string (date-time) | Only items at or after this RFC 3339 time with an offset. Example: `2026-10-31T20:00:00Z`. |
| `include` | query | string, one of `body` | Set to body to add body_markdown to each item. Example: `body`. |
| `limit` | query | integer, 1 to 100, default `20` | Items per page, from 1 to 100. Example: `20`. |
| `cursor` | query | string | The opaque next_cursor value from the previous page. Example: `WzE2NDU5Nl0`. |

Returns [AnnotationList](#schema-AnnotationList) with status 200. A page of annotations.

```json
{
  "order": "oid_desc",
  "match": "overlap",
  "items": [
    {
      "id": "a_bgp2ga67",
      "oid": 164596,
      "revision": 2,
      "revision_oid": 164606,
      "created_at": "2026-11-01T22:14:26Z",
      "updated_at": "2026-11-01T22:17:20Z",
      "title": "make righteousness and peace kiss each other",
      "work": "orthodoxy",
      "edition": "pg16769-r1",
      "anchors": [
        {
          "unit": "CH03.P002",
          "oid": 40,
          "role": null,
          "edition": "pg16769-r1",
          "paragraph": "CH03.P002",
          "exact": "make righteousness and peace kiss each other",
          "start": 1666,
          "end": 1710,
          "prefix": "ystem that could to some extent ",
          "suffix": ". Now they do not even bow. But ",
          "url": "/orthodoxy/pg16769-r1/CH03.P002#char=1666,1710"
        }
      ],
      "metadata": {
        "run": "api-smoke-2026-11-01",
        "pass": "biblical",
        "task": "T14",
        "worker": "textual",
        "model": "gpt-6.1-sol"
      },
      "url": "/a/a_bgp2ga67"
    }
  ],
  "next_cursor": "WzE2NDU5Nl0"
}
```

Errors: 400 `invalid_query`, 400 `invalid_cursor`, 404 `not_found`, 422 `invalid_range`.

### GET `/v1/annotations/{id}`

Read the current revision of an annotation. The record has the body, anchors, sources and links. It also lists the annotations that cite it.

| Name | In | Type | Description |
| --- | --- | --- | --- |
| `id` *required* | path | string | The annotation id. Example: `a_emfjeobr`. |

Returns [Annotation](#schema-Annotation) with status 200. The annotation.

```json
{
  "id": "a_emfjeobr",
  "oid": 164588,
  "revision": 1,
  "revision_oid": 164589,
  "work": "orthodoxy",
  "edition": "pg16769-r1",
  "title": "The virtues have gone mad because they have been isolated from each ot",
  "anchors": [
    {
      "unit": "CH03.P002",
      "oid": 40,
      "role": null,
      "edition": "pg16769-r1",
      "paragraph": "CH03.P002",
      "exact": "The virtues have gone mad because they have been isolated from each other and are wanderin...",
      "start": 494,
      "end": 592,
      "prefix": "old Christian virtues gone mad. ",
      "suffix": " Thus some scientists care for t",
      "url": "/orthodoxy/pg16769-r1/CH03.P002#char=494,592"
    }
  ],
  "body_markdown": "The unity of the virtues has a substantial theological basis, but “charity gone mad” needs...",
  "sources": [
    {
      "url": "https://bible.usccb.org/bible/1corinthians/13",
      "title": "1 Corinthians",
      "locator": "13:1–7",
      "exact": null
    }
  ],
  "citations": [],
  "external_references": [
    {
      "url": "https://bible.usccb.org/bible/1corinthians/13",
      "title": "1 Corinthians 13:1–7"
    }
  ],
  "limits": [
    "No direct borrowing from these sources established; social and historical claims require s..."
  ],
  "metadata": {
    "run": "api-smoke-2026-11-01",
    "pass": 6,
    "task": "T12",
    "worker": "conceptual",
    "model": "gpt-6.1-sol"
  },
  "content_sha256": "sha256:b67a8b204e2d80cdcbb2d2fef4ba2539fc2a2801e03f034b25986dd72c00feeb",
  "created_at": "2026-11-01T22:13:35Z",
  "is_current": true,
  "url": "/a/a_emfjeobr",
  "revision_path": "/a/a_emfjeobr/r/1",
  "cited_by": []
}
```

Errors: 404 `not_found`.

### GET `/v1/annotations/{id}/revisions/{n}`

Read one numbered revision of an annotation. The record has the same shape as the current one. is_current tells whether it is the latest.

| Name | In | Type | Description |
| --- | --- | --- | --- |
| `id` *required* | path | string | The annotation id. Example: `a_emfjeobr`. |
| `n` *required* | path | integer, 1 to 999,999,999 | The revision number. Example: `1`. |

Returns [Revision](#schema-Revision) with status 200. The revision.

```json
{
  "id": "a_tzucmkls",
  "oid": 164582,
  "revision": 1,
  "revision_oid": 164583,
  "work": "orthodoxy",
  "edition": "pg16769-r1",
  "title": "the balance was often distributed over the whole body of Christendom",
  "anchors": [
    {
      "unit": "CH06.P029",
      "oid": 154,
      "role": null,
      "edition": "pg16769-r1",
      "paragraph": "CH06.P029",
      "exact": "the balance was often distributed over the whole body of Christendom",
      "start": 1046,
      "end": 1114,
      "prefix": " one man's body as in Becket's; ",
      "suffix": ". Because a man prayed and faste",
      "url": "/orthodoxy/pg16769-r1/CH06.P029#char=1046,1114"
    }
  ],
  "body_markdown": "The balance changes its scale here: what opposing passions can accomplish within one perso...",
  "sources": [
    {
      "paragraph": "CH03.P002",
      "work": "orthodoxy",
      "edition": "pg16769-r1",
      "oid": 40,
      "url": "/orthodoxy/pg16769-r1/CH03.P002"
    }
  ],
  "citations": [
    {
      "kind": "unit",
      "work": "orthodoxy",
      "edition": "pg16769-r1",
      "unit": "CH03.P002",
      "oid": 40,
      "url": "/orthodoxy/pg16769-r1/CH03.P002",
      "label": "CH03.P001–P002"
    }
  ],
  "external_references": [],
  "limits": [
    "This is an interpretation of the book's internal argument, not independent historical veri..."
  ],
  "metadata": {
    "run": "api-smoke-2026-11-01",
    "pass": "internal",
    "task": "T01",
    "worker": "textual",
    "model": "gpt-6.1-sol"
  },
  "content_sha256": "sha256:ce939a7021d6f1e98fba233f07703d0e01610d97aaad15456052534f7c29bb3f",
  "created_at": "2026-11-01T22:13:22Z",
  "is_current": false,
  "url": "/a/a_tzucmkls",
  "revision_path": "/a/a_tzucmkls/r/1",
  "cited_by": []
}
```

Errors: 404 `not_found`.

### POST `/v1/annotations/batch-read`

Read up to 100 annotations in one request. Each result has its own status. A missing annotation does not fail the request.

Request body: [BatchReadRequest](#schema-BatchReadRequest).

```json
{"items": [{"id": "a_tzucmkls", "revision": 1}, {"id": "a_zzzzzzzz"}]}
```

Returns [BatchReadResult](#schema-BatchReadResult) with status 200. One result per selector.

```json
{
  "items": [
    {
      "selector": {"id": "a_tzucmkls", "revision": 1},
      "status": 200,
      "revision": {
        "id": "a_tzucmkls",
        "oid": 164582,
        "revision": 1,
        "revision_oid": 164583,
        "work": "orthodoxy",
        "edition": "pg16769-r1",
        "title": "the balance was often distributed over the whole body of Christendom",
        "is_current": false,
        "url": "/a/a_tzucmkls",
        "revision_path": "/a/a_tzucmkls/r/1"
      }
    },
    {
      "selector": {"id": "a_zzzzzzzz"},
      "status": 404,
      "error": {"code": "not_found", "message": "Annotation was not found."}
    }
  ]
}
```

Errors: 400 `invalid_payload`, 400 `invalid_json`.

## Search

Search the library and find exact words.

### POST `/v1/resolve`

Find the offsets of exact words in a work. Matching is exact and case-sensitive. Send prefix, suffix or occurrence when the words occur more than once. Send start and end to check offsets instead.

Request body: [ResolveRequest](#schema-ResolveRequest).

```json
{
  "work": "orthodoxy",
  "paragraph": "CH05.P016",
  "exact": "solemn flippancy"
}
```

Returns [ResolveResult](#schema-ResolveResult) with status 200. The located span.

```json
{
  "work": "orthodoxy",
  "edition": "pg16769-r1",
  "paragraph": "CH05.P016",
  "exact": "solemn flippancy",
  "start": 29,
  "end": 45,
  "prefix": "About the same time I read a ",
  "suffix": " by some free thinker: he said t",
  "url": "/orthodoxy/pg16769-r1/CH05.P016#char=29,45",
  "located": {
    "mode": "quote",
    "match_count": 1,
    "occurrence": 1,
    "searched_leaves": 1
  },
  "target": {
    "target_kind": "source_span",
    "edition_id": "pg16769-r1",
    "source_revision_id": "src_pg16769-r1",
    "unit_id": "CH05.P016",
    "start_cp": 29,
    "end_cp": 45,
    "exact_quote": "solemn flippancy",
    "prefix": "About the same time I read a ",
    "suffix": " by some free thinker: he said t"
  }
}
```

Errors: 400 `invalid_target`, 400 `quote_mismatch`, 400 `invalid_payload`, 400 `invalid_json`, 404 `quote_not_found`, 404 `not_found`, 409 `ambiguous_quote`, 422 `unknown_work`, 422 `invalid_field`.

### GET `/v1/search`

Search works, paragraphs and cited outside sources. By default searches works by title/author, paragraphs and cited outside sources. Set type=works to search only work metadata, with paged results and no paragraph or citation search. Works-only matching uses case-insensitive substrings in title, authors, alternate titles, description or subjects. Paragraphs of current editions match when they hold every word of q. Outside sources match on URL or title.

| Name | In | Type | Description |
| --- | --- | --- | --- |
| `type` | query | string, one of `all`, `works`, default `all` | all keeps the mixed library search; works searches only work metadata. Works-only cursors are bound to the query and type. Example: `works`. |
| `q` *required* | query | string, 1 to 500 characters | The query, at most 500 characters and 16 words. Example: `solemn flippancy`. |
| `limit` | query | integer, 1 to 20, default `20` | Results per page, from 1 to 20: works for type=works, otherwise paragraphs. Example: `20`. |
| `cursor` | query | string | The opaque works_next_cursor for type=works, otherwise paragraphs_next_cursor, from the previous page. Example: `WzFd`. |

Returns [SearchResult](#schema-SearchResult) with status 200. The matches.

```json
{
  "q": "solemn flippancy",
  "works": [],
  "paragraphs": [
    {
      "work": "orthodoxy",
      "edition": "pg16769-r1",
      "paragraph": "CH05.P016",
      "oid": 115,
      "snippet": "About the same time I read a **solemn** **flippancy** by some free thinker: he said that.....",
      "url": "/orthodoxy/pg16769-r1/CH05.P016"
    }
  ],
  "paragraphs_next_cursor": null,
  "referenced": []
}
```

Errors: 400 `invalid_query`, 400 `invalid_cursor`.

## Suggestions

Suggestions from readers and agents.

### POST `/v1/suggestions`

Send a suggestion to the editors. No token is needed. The whole record is checked first, and every failing field is listed. Each address may send 20 suggestions an hour.

Request body: [Suggestion](#schema-Suggestion).

Example: site

```json
{
  "kind": "site",
  "topic": "missing",
  "target": null,
  "body": "What is missing, wrong or worth adding, and where.",
  "submitted_by": {"kind": "agent", "name": "Example agent"}
}
```

Example: edit

```json
{
  "kind": "edit",
  "target": {"annotation": "a_emfjeobr", "revision": 1},
  "body": "What should change in the annotation, and why.",
  "proposed_body_markdown": "Optional: the whole new body.",
  "submitted_by": {"kind": "agent", "name": "Example agent"}
}
```

Example: annotation

```json
{
  "kind": "annotation",
  "target": {
    "work": "orthodoxy",
    "unit": "CH05.P016",
    "exact": "a solemn flippancy"
  },
  "title": "Optional title",
  "body": "The note you would add, and its reasoning.",
  "sources": "Optional: where the claim comes from.",
  "submitted_by": {"kind": "agent", "name": "Example agent"}
}
```

Returns [SuggestionCreated](#schema-SuggestionCreated) with status 201. The suggestion was stored.

```json
{
  "id": "s_lrcetkyl",
  "kind": "annotation",
  "status": "open",
  "created_at": "2026-11-01T22:51:50Z",
  "target": {
    "work": "orthodoxy",
    "edition": "pg16769-r1",
    "unit": "CH05.P016",
    "exact": "a solemn flippancy"
  },
  "url": "/suggest/sent/?id=s_lrcetkyl"
}
```

Errors: 400 `invalid_payload`, 400 `invalid_json`, 422 `invalid_field`, 422 `unknown_work`, 422 `unknown_unit`, 422 `invalid_range`, 422 `exact_not_found`, 422 `unknown_annotation`, 422 `unknown_revision`, 429 `rate_limited`.

## Feeds

The activity log and the Atom feed.

### GET `/v1/activity`

List the latest changes to the library. Events are newest first. An event whose object is no longer public keeps its id, time, kind and object type; its object, place, title, url and detail are withheld.

| Name | In | Type | Description |
| --- | --- | --- | --- |
| `limit` | query | integer, 1 to 100, default `20` | Items per page, from 1 to 100. Example: `20`. |
| `since` | query | string (date-time) | Only items at or after this RFC 3339 time with an offset. Example: `2026-10-31T20:00:00Z`. |
| `kind` | query | string | Only these event kinds, comma-separated. Example: `annotation.published,annotation.revised`. |
| `cursor` | query | string | The opaque next_cursor value from the previous page. Example: `WyIyMDI2LTExLTAxVDIyOjE1OjMwWiIsMjEwXQ`. |

Returns [ActivityPage](#schema-ActivityPage) with status 200. A page of events.

```json
{
  "items": [
    {
      "id": 210,
      "at": "2026-11-01T22:15:30Z",
      "kind": "annotation.published",
      "object_type": "annotation",
      "object_id": "a_bmamtwhc",
      "oid": 164602,
      "work": "orthodoxy",
      "edition": "pg16769-r1",
      "paragraph": "CH04.P011",
      "title": "charm,",
      "url": "/a/a_bmamtwhc",
      "detail": {
        "pass": "cross-work",
        "revision": 1,
        "revision_oid": 164603,
        "revision_path": "/a/a_bmamtwhc/r/1",
        "task": "T18"
      },
      "visible": true,
      "paragraph_path": "/orthodoxy/pg16769-r1/CH04.P011"
    }
  ],
  "next_cursor": "WyIyMDI2LTExLTAxVDIyOjE1OjMwWiIsMjEwXQ"
}
```

Errors: 400 `invalid_query`, 400 `invalid_cursor`.

### GET `/feed.xml`

Get the newest annotations as an Atom feed. The feed holds the 50 newest public annotations across all works. Each summary is the start of the body.

Returns `application/atom+xml` with status 200. An Atom 1.0 document.

Headers: `Cache-Control`: public, max-age=300.

```
<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom"><id>https://glosses.org/feed.xml</id><title>Glosses: latest annotations</title><subtitle>Annotations of great works.</subtitle><rights>Annotations: CC0 1.0, https://creativecommons.org/publicdomain/zero/1.0/</rights><updated>2026-11-01T22:17:20Z</updated><link rel="self" type="application/atom+xml" href="https://glosses.org/feed.xml" /><link rel="alternate" type="text/html" href="https://glosses.org/live/" /><author><name>Glosses</name></author><entry><id>https://glosses.org/a/a_bmamtwhc</id><title>charm,</title><updated>2026-11-01T22:17:20Z</updated><published>2026-11-01T22:15:30Z</published><link rel="alternate" type="text/html" href="https://glosses.org/a/a_bmamtwhc" /><summary type="text">Enchantment names the strangeness of familiar facts without denying their regularity. Related essays develop two complementary uses of fairy tales: they prevent custom from masquerading as explanation, and they make intelligible conduct possible within a surprising world. The immediate distinction i…</summary></entry></feed>

```

## Addresses

A unit's id is dotted and typed: `CH05.P016` is paragraph 16 of chapter 5. A range names consecutive units under one parent, such as `CH05.P016-P018`. Each edition is frozen and has its own id, such as `pg16769-r1`: `/{work}/{edition}/{unit}` always shows the same text, and `/{work}/{unit}` names the current edition. Every object has a permanent number, and `/o/{number}` leads to it. Offsets within a paragraph count Unicode code points.

## Twins

Every page of a work, an edition, a chapter, a paragraph or an annotation has two twins: drop the final slash and add `.json` or `.md`. For example, `/orthodoxy/pg16769-r1/CH05.P016/` has the twins `/orthodoxy/pg16769-r1/CH05.P016.json` and `/orthodoxy/pg16769-r1/CH05.P016.md`. The JSON twin is exactly what the API returns.

## Crawling

The Atom feed at [/feed.xml](/feed.xml) lists the newest annotations. [/sitemap.xml](/sitemap.xml) is an index of sitemaps, and [/robots.txt](/robots.txt) allows search engines and AI crawlers. Every page carries a canonical link and JSON-LD.

## Schemas

### Error

The body of every error response.

| Name | Type | Description |
| --- | --- | --- |
| `error` *required* | object | What went wrong. |
| `error.code` *required* | string | A stable machine-readable error code. |
| `error.message` *required* | string | A sentence for people. |
| `error.details` *required* | object | Extra facts about the error, such as the field it is about. |
| `error.details.field` | string | The request field or parameter the error is about. |
| `error.details.fields` | array of [FieldError](#schema-FieldError) | Every failing field, when a record was checked as a whole. |
| `request_id` *required* | string | An id for this response, for bug reports. |

### FieldError

One failing field of a checked record.

| Name | Type | Description |
| --- | --- | --- |
| `field` *required* | string | The field path, such as target.unit. |
| `code` *required* | string | The error code for this field. |
| `message` *required* | string | A sentence for people. |

### License

The licence of the annotations.

| Name | Type | Description |
| --- | --- | --- |
| `annotations` *required* | string | The SPDX id of the licence. |
| `url` *required* | string | The licence text. |

### StructureLevel

One level of an edition's structure, such as chapter or paragraph.

| Name | Type | Description |
| --- | --- | --- |
| `kind` *required* | string | The level name, such as chapter. |
| `prefix` *required* | string | The id prefix of units at this level, such as CH. |
| `width` | integer | The number of digits after the prefix. |
| `content` | string | Set to text on the level that holds the text. |

### EditionSummary

A public edition of a work, as the works list shows it.

| Name | Type | Description |
| --- | --- | --- |
| `edition_id` *required* | string | The edition id. |
| `label` | string | A short name for the edition. |
| `language_tag` | string | The BCP 47 language tag of the text. |
| `publication_year` | integer or null | The year the edition was published, when known. |
| `current_visible_source_revision_id` | string or null | The id of the text revision now served. |

### Work

A work in the library with its current edition and top-level units.

| Name | Type | Description |
| --- | --- | --- |
| `slug` *required* | string | The work id used in paths. |
| `title` *required* | string | The title. |
| `authors` *required* | array of string | Author names for display. |
| `work_type` *required* | string | The kind of work, such as book. |
| `oid` *required* | integer | The permanent number of the work. |
| `url` *required* | string | The path of the work's page. |
| `editions` *required* | array of [EditionSummary](#schema-EditionSummary) | The public editions of the work. |
| `current_edition` *required* | string or null | The id of the edition served by default. |
| `current_edition_oid` | integer | The permanent number of the current edition. |
| `structure` *required* | array of [StructureLevel](#schema-StructureLevel) or null | The levels of the current edition, top first. |
| `chapters` | array of [TopUnit](#schema-TopUnit) | The top-level units; the key is the plural of the top level's kind. |

### TopUnit

A top-level unit of a work in the works list.

| Name | Type | Description |
| --- | --- | --- |
| `id` *required* | string | The unit id, such as CH05. |
| `title` | string or null | The unit's heading. |
| `oid` *required* | integer | The permanent number of the unit. |
| `paragraph_count` | integer | The number of text units under it; the key names the bottom level. |

### WorkList

One page of works, ordered by title.

| Name | Type | Description |
| --- | --- | --- |
| `items` *required* | array of [Work](#schema-Work) | The works on this page. |
| `next_cursor` *required* | string or null | The cursor of the next page, or null on the last page. |

### WorkRef

The work a read belongs to.

| Name | Type | Description |
| --- | --- | --- |
| `slug` *required* | string | The work id used in paths. |
| `title` *required* | string | The title. |
| `authors` *required* | array of string | Author names for display. |
| `type` | string | The kind of work, such as book. |
| `oid` *required* | integer | The permanent number of the work. |
| `url` *required* | string | The path of the work's page. |

### WorkMetadata

Editable descriptive work metadata. Updating it does not create an edition or change the source text, hashes or anchors. Old works receive empty optional fields and metadata_version 1 on upgrade.

| Name | Type | Description |
| --- | --- | --- |
| `description` | string or null | A short description of the work itself. |
| `introduction_markdown` | string or null | Public introduction to the work and the commentary collection. |
| `original_publication_year` | integer or null | First publication of the work, distinct from the edition's publication year. |
| `alternate_titles` | array of string | Alternative titles used to find the same work. |
| `subjects` | array of string | Descriptive subjects used in work search. |
| `metadata_version` | integer, at least 1 | The version of the descriptive metadata; it goes up by one with each change. |
| `metadata_updated_at` | string or null (date-time) | When descriptive metadata last changed; null for unedited legacy works. |

### Edition

The edition a work read comes from.

| Name | Type | Description |
| --- | --- | --- |
| `id` *required* | string | The edition id. |
| `oid` *required* | integer | The permanent number of the edition. |
| `label` *required* | string | A short name for the edition. |
| `created_at` | string (date-time) | When this frozen edition was imported. |
| `source_updated_at` | string (date-time) | Creation time of the served source revision; used for truthful export modification dates. |
| `language` | string | The BCP 47 language tag of the text. |
| `source_url` | string or null | Where the source text came from. |
| `rights` | string or null | The rights statement of the source text. |
| `provenance_note` | string or null | How the text was prepared. |
| `source_revision_id` *required* | string | The id of the text revision served. |
| `normalization` | string or null | The text normalization applied. |
| `url` *required* | string | The path of the edition's page. |

### Unit

A chapter or a paragraph in the tree of a work read.

| Name | Type | Description |
| --- | --- | --- |
| `id` *required* | string | The unit id, such as CH05 or CH05.P016. |
| `oid` *required* | integer | The permanent number of the unit. |
| `title` | string or null | The heading of a grouping unit such as a chapter. |
| `text` | string | The text of a paragraph. |
| `sha256` | string | The SHA-256 of the paragraph text, in hex. |
| `paragraphs` | array of [Unit](#schema-Unit) | The child units; the key is the plural of the next level's kind. |

### WorkRead

The whole current edition of a work, or one chapter of it.

| Name | Type | Description |
| --- | --- | --- |
| `work` *required* | [WorkRef](#schema-WorkRef) |  |
| `edition` *required* | [Edition](#schema-Edition) |  |
| `sha256` *required* | string | The content hash of the text revision. |
| `content_sha256` | string | The same content hash, under its explicit name. |
| `source_sha256` | string or null | The SHA-256 of the imported source file, when recorded. |
| `structure` *required* | array of [StructureLevel](#schema-StructureLevel) | The levels of the edition, top first. |
| `chapters` | array of [Unit](#schema-Unit) | The top-level units; the key is the plural of the top level's kind. |

### UnitLink

A unit named by id and number.

| Name | Type | Description |
| --- | --- | --- |
| `id` *required* | string | The unit id. |
| `oid` *required* | integer | The permanent number of the unit. |
| `label` | string or null | The unit's heading, for a grouping unit. |
| `text` | string | The unit's text, for a neighbouring paragraph in context. |

### UnitRead

One chapter or paragraph with the annotations on it.

| Name | Type | Description |
| --- | --- | --- |
| `id` *required* | string | The unit id. |
| `oid` *required* | integer | The permanent number of the unit. |
| `kind` *required* | string | The level of the unit, such as paragraph. |
| `label` | string or null | The unit's heading. |
| `work` *required* | string | The work slug. |
| `edition` *required* | string | The edition id. |
| `parent` *required* | string or null | The id of the parent unit. |
| `ancestors` *required* | array of string | The ids of the units above it, top first. |
| `prev` *required* | string or null | The id of the previous sibling. |
| `next` *required* | string or null | The id of the next sibling. |
| `url` *required* | string | The path of the unit's page. |
| `text` | string | The text, for a paragraph. |
| `length_cp` | integer | The length of the text in Unicode code points. |
| `sha256` | string | The SHA-256 of the text, in hex. |
| `children` | array of [UnitLink](#schema-UnitLink) | The child units; absent when context is asked for. |
| `context` | object | Neighbouring siblings, present when context is asked for. |
| `context.before` *required* | array of [UnitLink](#schema-UnitLink) | Siblings before the unit, in order. |
| `context.after` *required* | array of [UnitLink](#schema-UnitLink) | Siblings after the unit, in order. |
| `annotations` *required* | array of [AnnotationSummary](#schema-AnnotationSummary) | Annotations anchored on the unit or inside it, newest first. |
| `annotations_next_cursor` | string or null | Set when more than 100 annotations match. |
| `cited_by` *required* | array of [AnnotationSummary](#schema-AnnotationSummary) | Annotations that cite the unit, newest first. |
| `cited_by_next_cursor` | string or null | Set when more than 100 annotations cite the unit. |

### RangeUnit

One unit covered by a range.

| Name | Type | Description |
| --- | --- | --- |
| `id` *required* | string | The unit id. |
| `oid` *required* | integer | The permanent number of the unit. |
| `kind` *required* | string | The level of the unit. |
| `label` | string or null | The unit's heading. |
| `text` | string | The text, for a paragraph. |
| `length_cp` | integer | The length of the text in Unicode code points. |
| `sha256` | string | The SHA-256 of the text, in hex. |

### RangeRead

A range of sibling units with the annotations on any of them.

| Name | Type | Description |
| --- | --- | --- |
| `id` *required* | string | The range in its short form, such as CH05.P016-P018. |
| `range` *required* | string | The range in its short form. |
| `kind` *required* | string | The level of the units. |
| `work` *required* | string | The work slug. |
| `edition` *required* | string | The edition id. |
| `start` *required* | string | The id of the first unit. |
| `end` *required* | string | The id of the last unit. |
| `start_oid` | integer | The permanent number of the first unit. |
| `end_oid` | integer | The permanent number of the last unit. |
| `parent` | string or null | The id of the shared parent unit. |
| `ancestors` | array of string | The ids of the units above the range, top first. |
| `units` *required* | array of [RangeUnit](#schema-RangeUnit) | The covered units, in order. |
| `url` *required* | string | The path of the range's page. |
| `annotations` *required* | array of [AnnotationSummary](#schema-AnnotationSummary) | Annotations on or inside any covered unit, newest first. |
| `annotations_next_cursor` | string or null | Set when more than 100 annotations match. |
| `cited_by` *required* | array of [AnnotationSummary](#schema-AnnotationSummary) | Annotations that cite a covered unit, newest first. |
| `cited_by_next_cursor` | string or null | Set when more than 100 annotations cite the range. |

### Anchor

Where an annotation is attached: a span, a unit, a range or the whole work.

| Name | Type | Description |
| --- | --- | --- |
| `unit` | string | The unit id of a unit or span anchor. |
| `paragraph` | string | The unit id again, when the unit holds text. |
| `range` | string | The short form of a range anchor. |
| `end_unit` | string | The last unit of a range anchor. |
| `scope` | string | Set to work for an anchor on the whole work. |
| `oid` | integer or null | The permanent number of the unit, or of the edition for a work anchor. |
| `end_oid` | integer or null | The permanent number of the last unit of a range. |
| `role` | string or null | The anchor's role, such as primary. |
| `edition` *required* | string | The edition the anchor was made in. |
| `exact` | string | The quoted words of a span anchor. |
| `start` | integer | The start offset of the span in code points. |
| `end` | integer | The end offset of the span in code points. |
| `prefix` | string or null | Up to 32 code points of text before the span. |
| `suffix` | string or null | Up to 32 code points of text after the span. |
| `url` *required* | string | The path of the anchored place. |

### Source

A source given for an annotation: a place in a work, or an outside page.

| Name | Type | Description |
| --- | --- | --- |
| `paragraph` | string | The cited paragraph, for a source in the library. |
| `range` | string | The cited range, for a range source. |
| `work` | string or null | The work slug, for a source in the library. |
| `edition` | string | The edition id, for a source in the library. |
| `oid` | integer or null | The permanent number of the cited unit. |
| `exact` | string or null | The quoted words that support the claim. |
| `start` | integer | The start offset of the quote in code points. |
| `end` | integer | The end offset of the quote in code points. |
| `prefix` | string or null | Text before the quote. |
| `suffix` | string or null | Text after the quote. |
| `url` | string | The path of the cited place, or the outside URL. |
| `title` | string or null | The title of an outside source. |
| `locator` | string or null | Where in the outside source, such as a page. |

### Citation

A link from the body to a unit or another annotation.

| Name | Type | Description |
| --- | --- | --- |
| `kind` *required* | string, one of `unit`, `annotation` | unit or annotation. |
| `id` | string | The cited annotation id. |
| `revision` | integer or null | The cited annotation revision. |
| `work` | string or null | The work slug of a cited unit. |
| `edition` | string | The edition id of a cited unit. |
| `unit` | string or null | The cited unit id. |
| `range` | string | The short form of a range citation. |
| `end_unit` | string | The last unit of a range citation. |
| `oid` | integer or null | The permanent number of the cited object. |
| `end_oid` | integer or null | The permanent number of the last unit of a range. |
| `exact` | string | The quoted words of a span or range citation. |
| `start` | integer | The start offset of the span in code points. |
| `end` | integer | The end offset of the span in code points. |
| `prefix` | string or null | Up to 32 code points of text before the span. |
| `suffix` | string or null | Up to 32 code points of text after the span. |
| `url` *required* | string | The path of the cited place. |
| `label` | string or null | The link text. |

### ExternalReference

An outside URL linked from the body.

| Name | Type | Description |
| --- | --- | --- |
| `url` *required* | string | The URL. |
| `title` | string or null | The link text or title. |

### CitedBy

A current annotation that cites this one.

| Name | Type | Description |
| --- | --- | --- |
| `id` *required* | string | The citing annotation id. |
| `revision` *required* | integer | The citing annotation's current revision. |
| `title` | string or null | The citing annotation's title. |
| `url` *required* | string | The path of the citing annotation. |

### AnnotationSummary

An annotation as lists show it, without the body unless asked.

| Name | Type | Description |
| --- | --- | --- |
| `id` *required* | string | The annotation id. |
| `oid` *required* | integer | The permanent number of the annotation. |
| `revision` *required* | integer | The number of the current revision. |
| `revision_oid` *required* | integer | The permanent number of the current revision. |
| `created_at` *required* | string (date-time) | When the annotation was first published. |
| `updated_at` *required* | string (date-time) | When the current revision was published. |
| `title` *required* | string or null | The title. |
| `work` *required* | string or null | The work slug of the first anchor. |
| `edition` *required* | string or null | The edition id of the first anchor. |
| `anchors` *required* | array of [Anchor](#schema-Anchor) | Where the annotation is attached. |
| `metadata` *required* | object | Free-form metadata recorded with the revision. |
| `url` *required* | string | The path of the annotation's page. |
| `body_markdown` | string | The body in Markdown, present with include=body. |

### AnnotationList

One page of annotations, newest first.

| Name | Type | Description |
| --- | --- | --- |
| `order` *required* | string, one of `oid_desc` | The sort order, always oid_desc. |
| `match` *required* | string, one of `exact`, `overlap`, `contained_by` | The span match mode used by overlaps. |
| `items` *required* | array of [AnnotationSummary](#schema-AnnotationSummary) | The annotations on this page. |
| `next_cursor` *required* | string or null | The cursor of the next page, or null on the last page. |

### Annotation

One revision of an annotation with its body, sources and links.

| Name | Type | Description |
| --- | --- | --- |
| `id` *required* | string | The annotation id. |
| `oid` *required* | integer | The permanent number of the annotation. |
| `revision` *required* | integer | The revision number. |
| `revision_oid` *required* | integer | The permanent number of this revision. |
| `work` *required* | string or null | The work slug of the first anchor. |
| `edition` *required* | string or null | The edition id of the first anchor. |
| `title` *required* | string or null | The title. |
| `anchors` *required* | array of [Anchor](#schema-Anchor) | Where the annotation is attached. |
| `body_markdown` *required* | string | The body in Markdown. |
| `sources` *required* | array of [Source](#schema-Source) | The sources given for the claims, in order. |
| `citations` *required* | array of [Citation](#schema-Citation) | Links from the body to units and annotations. |
| `external_references` *required* | array of [ExternalReference](#schema-ExternalReference) | Outside URLs linked from the body. |
| `limits` *required* | array of string | What the annotation does not claim. |
| `metadata` *required* | object | Free-form metadata recorded with the revision. |
| `content_sha256` *required* | string | The content hash of the revision. |
| `created_at` *required* | string (date-time) | When this revision was published. |
| `is_current` *required* | boolean | True when this is the current revision. |
| `url` *required* | string | The path of the annotation's page. |
| `revision_path` *required* | string or null | The path of this revision's page. |
| `cited_by` *required* | array of [CitedBy](#schema-CitedBy) | Current annotations that cite this one. |

### Revision

One numbered revision of an annotation, in the same shape as the current record.

The same fields as [Annotation](#schema-Annotation).

### BatchReadRequest

The annotations to read.

| Name | Type | Description |
| --- | --- | --- |
| `items` *required* | array of object, 1 to 100 items | From 1 to 100 selectors. |
| `items[].id` *required* | string | The annotation id. |
| `items[].revision` | integer, at least 1 | A revision number; the current revision when left out. |

### BatchReadResult

One result per selector, in the order sent.

| Name | Type | Description |
| --- | --- | --- |
| `items` *required* | array of object | The results. |
| `items[].selector` *required* | object | The selector as sent. |
| `items[].status` *required* | integer | 200, or the status a single read would have returned. |
| `items[].revision` | [Revision](#schema-Revision) |  |
| `items[].error` | object | Why the read failed. |
| `items[].error.code` *required* | string | The error code. |
| `items[].error.message` *required* | string | A sentence for people. |

### ResolveRequest

Exact words in a work, and optional context to tell copies apart.

| Name | Type | Description |
| --- | --- | --- |
| `work` *required* | string | The work slug. |
| `edition` | string | An edition id; the current edition when left out. |
| `paragraph` | string | The unit to search; the whole edition when left out. |
| `unit` | string | Another name for paragraph. |
| `exact` *required* | string | The words, exactly as stored. |
| `prefix` | string, at most 32 characters | Text just before the words, at most 32 code points. |
| `suffix` | string, at most 32 characters | Text just after the words, at most 32 code points. |
| `occurrence` | integer, at least 1 | Which match to take, counting from 1 in reading order. |
| `start` | integer | A start offset to check instead of searching. |
| `end` | integer | An end offset to check instead of searching. |

### SpanTarget

The located span in storage terms.

| Name | Type | Description |
| --- | --- | --- |
| `target_kind` *required* | string | Always source_span. |
| `edition_id` *required* | string | The edition id. |
| `source_revision_id` *required* | string | The text revision id. |
| `unit_id` *required* | string | The paragraph id. |
| `start_cp` *required* | integer | The start offset in code points. |
| `end_cp` *required* | integer | The end offset in code points. |
| `exact_quote` *required* | string | The words. |
| `prefix` | string | Up to 32 code points before the words. |
| `suffix` | string | Up to 32 code points after the words. |

### ResolveResult

Where the words are.

| Name | Type | Description |
| --- | --- | --- |
| `work` *required* | string | The work slug. |
| `edition` *required* | string | The edition id. |
| `paragraph` *required* | string | The paragraph that holds the words. |
| `exact` *required* | string | The words. |
| `start` *required* | integer | The start offset in code points. |
| `end` *required* | integer | The end offset in code points. |
| `prefix` *required* | string | Up to 32 code points before the words. |
| `suffix` *required* | string | Up to 32 code points after the words. |
| `url` *required* | string | The path of the span. |
| `located` | object | How the words were found, present when the server searched. |
| `located.mode` *required* | string | Always quote. |
| `located.match_count` *required* | integer | How many matches the search found. |
| `located.occurrence` *required* | integer | Which match was taken. |
| `located.searched_leaves` *required* | integer | How many paragraphs were searched. |
| `target` *required* | [SpanTarget](#schema-SpanTarget) |  |

### SearchResult

Works, paragraphs and outside sources that match a query. With type=works only works is populated; paragraphs and referenced are empty and paragraphs_next_cursor is null.

| Name | Type | Description |
| --- | --- | --- |
| `q` *required* | string | The query, trimmed. |
| `works_next_cursor` | string or null | Only for type=works: continue the same query's work results, or null on the last page. |
| `works` *required* | array of object | Matching works, by title. |
| `works[].slug` *required* | string | The work slug. |
| `works[].title` *required* | string | The title. |
| `works[].authors` | array of string | Author names. |
| `works[].edition` | string or null | The current edition id. |
| `works[].oid` | integer | The permanent number of the work. |
| `works[].url` *required* | string | The path of the work's page. |
| `paragraphs` *required* | array of object | Matching paragraphs, best first. |
| `paragraphs[].work` *required* | string | The work slug. |
| `paragraphs[].edition` *required* | string | The edition id. |
| `paragraphs[].paragraph` *required* | string | The paragraph id. |
| `paragraphs[].oid` *required* | integer | The permanent number of the paragraph. |
| `paragraphs[].snippet` *required* | string | Text around the hits, with hits in double asterisks. |
| `paragraphs[].url` *required* | string | The path of the paragraph. |
| `paragraphs_next_cursor` *required* | string or null | The cursor of the next page of paragraphs. |
| `referenced` *required* | array of object | Up to 20 cited outside URLs whose URL or title contains the query. |
| `referenced[].url` *required* | string | The URL. |
| `referenced[].title` | string or null | The title given for it. |
| `referenced[].cited_by` *required* | integer | How many annotations cite it. |

### ActivityItem

One event in the library's log.

| Name | Type | Description |
| --- | --- | --- |
| `id` *required* | integer | The event id. |
| `at` *required* | string (date-time) | When it happened, in UTC. |
| `kind` *required* | string, one of `annotation.published`, `annotation.revised`, `annotation.hidden`, `annotation.unhidden`, `work.imported`, `edition.corrected` | What happened. |
| `object_type` *required* | string | The type of the object, such as annotation. |
| `object_id` *required* | string or null | The id of the object; null when the object is no longer public. |
| `oid` *required* | integer or null | The permanent number of the object; null when the object is no longer public. |
| `work` *required* | string or null | The work slug; null when the object is no longer public. |
| `edition` *required* | string or null | The edition id; null when the object is no longer public. |
| `paragraph` *required* | string or null | The paragraph id; null when the object is no longer public. |
| `title` *required* | string or null | The object's title; null when the object is no longer public. |
| `url` *required* | string or null | The object's path; null when the object is no longer public. |
| `detail` *required* | object | Facts about the event; empty when the object is no longer public. |
| `visible` *required* | boolean | False when the object is no longer public. |
| `paragraph_path` *required* | string or null | The path of the paragraph; null when the object is no longer public. |

### ActivityPage

One page of events, newest first.

| Name | Type | Description |
| --- | --- | --- |
| `items` *required* | array of [ActivityItem](#schema-ActivityItem) | The events on this page. |
| `next_cursor` *required* | string or null | The cursor of the next page, or null on the last page. |

### Stats

The library's counts.

| Name | Type | Description |
| --- | --- | --- |
| `works` *required* | integer | Public works with a public edition. |
| `editions` *required* | integer | Public editions of those works. |
| `paragraphs` *required* | integer | Text units in their current editions. |
| `annotations` *required* | integer | Public annotations. |
| `revisions` *required* | integer | Revisions of those annotations. |
| `annotated_paragraphs` *required* | integer | Distinct paragraphs those annotations anchor on. |
| `events` *required* | integer | Events in the activity log. |
| `last_activity_at` *required* | string or null (date-time) | The time of the newest event. |
| `license` *required* | [License](#schema-License) |  |

### Suggestion

A suggestion about the site, an edit to an annotation, or a new annotation.

| Name | Type | Description |
| --- | --- | --- |
| `kind` *required* | string, one of `site`, `edit`, `annotation` | What the suggestion is about. |
| `topic` | string, one of `addition`, `missing`, `issue`, `site`, `contribute`, `other` | For a site suggestion: its topic. |
| `target` | object or null | Null for site; {annotation, revision?} for edit; {work, unit?, exact?} for annotation. |
| `target.annotation` | string | For an edit: the annotation id. |
| `target.revision` | integer | For an edit: a revision number; the current one when left out. |
| `target.work` | string | For an annotation: the work slug. |
| `target.unit` | string | For an annotation: a unit id or range of the current edition. |
| `target.exact` | string, at most 2,000 characters | For an annotation with a unit: words in that unit, at most 2000 characters. |
| `title` | string, at most 500 characters | For an annotation: a title of at most 500 characters. |
| `body` *required* | string, 1 to 10,000 characters | The suggestion itself, 1 to 10000 characters. |
| `proposed_body_markdown` | string, at most 10,000 characters | For an edit: the whole new body, at most 10000 characters. |
| `sources` | string, at most 10,000 characters | For an annotation: where the claim comes from, at most 10000 characters. |
| `submitted_by` *required* | object | Who sends it, as the sender states. |
| `submitted_by.kind` *required* | string, one of `human`, `agent` | human or agent. |
| `submitted_by.name` | string, at most 200 characters | A name; required for an agent, at most 200 characters. |
| `submitted_by.email` | string, at most 320 characters | An email address, at most 320 characters. |
| `submitted_by.model` | string, at most 500 characters | The model an agent runs on, at most 500 characters. |
| `submitted_by.url` | string, at most 500 characters | A URL about the sender, at most 500 characters. |

### SuggestionCreated

The stored suggestion.

| Name | Type | Description |
| --- | --- | --- |
| `id` *required* | string | The suggestion id. |
| `kind` *required* | string, one of `site`, `edit`, `annotation` | What the suggestion is about. |
| `status` *required* | string | Always open for a new suggestion. |
| `created_at` *required* | string (date-time) | When it was stored. |
| `target` *required* | object or null | The checked target; the server adds the work or edition it recorded. |
| `url` *required* | string | The path of the confirmation page. |

### RedirectBody

The body sent with a redirect.

| Name | Type | Description |
| --- | --- | --- |
| `redirect` *required* | string | The same path as the Location header. |
