REST API
indice serve exposes a small HTTP API alongside the human-facing pages. The base URL is wherever you serve (default http://127.0.0.1:8080); behind a reverse proxy everything sits under whatever path you mount it at.
GET /api/search
Section titled “GET /api/search”Full-text search as JSON, using the same engine behind the search page.
Query parameters
| Param | Required | Description |
|---|---|---|
q |
yes | The query. Supports the full search syntax, including field filters like site:example.com, year:2021, type:pdf, lang:en, and collection:<slug>, so facets are expressed inside q. Capped at 2048 bytes; a longer one gets 400 naming the limit. |
limit |
no | Max results to return. Default 20, capped at 200. |
curl 'http://127.0.0.1:8080/api/search?q=climate+site:example.com&limit=5'Response (illustrative, abbreviated):
{ "total": 264, "capped": false, "results": [ { "doc_type": "html", "url": "https://example.com/report", "domain": "example.com", "timestamp": "20250802185600", "title": "Climate report: annual summary", "crawl_id": "4eefdff3", "crawl_name": "example crawl", "collection": "example-collection", "snippet": "…an annual <mark>climate</mark> report…", "capture_count": 3, "status": 200 } ], "facets": [ { "field": "collection", "label": "Collection", "buckets": [ { "value": "example-collection", "count": 220 } ] }, { "field": "year", "label": "Year", "buckets": [ { "value": "2025", "count": 129 } ] } ]}Fields
total: number of matching documents;cappedistruewhen that count is approximate (very large result sets).results[]: up tolimithits, each with:doc_type(media bucket, e.g.html,pdf, orannotationfor a note),url,domain,timestamp(the capture time),title,author(set for annotation hits),crawl_id(8-char id) andcrawl_name,collection(slug),snippet(a hit-highlighted excerpt, with<mark>around matches),capture_count(how many times this URL was captured, grouped into one result), andstatus(the archived HTTP status).facets[]: facet groups, each{ field, label, buckets: [{ value, count }] }, matching the sidebar on the search page.
GET /api/annotations
Section titled “GET /api/annotations”The public read side of annotations: the notes on one capture, or every note in a collection.
Query parameters
| Param | Required | Description |
|---|---|---|
collection |
yes | The collection slug. |
url |
no | A capture URL. Provide with ts to get the notes on that one capture; omit both for every note in the collection. |
ts |
no | The 14-digit capture timestamp. Must be given together with url. |
curl 'http://127.0.0.1:8080/api/annotations?collection=example-collection'Response (abbreviated): { "can_annotate": false, "annotations": [ … ] }, where each note carries its id, author (a display name, never the login identity, since this endpoint is public), created/modified, the target url + timestamp, the selector (the quoted passage, for a passage note), and the note body as both note_md (Markdown) and note_html (sanitized). can_annotate reflects whether the current request is allowed to write.
Creating, editing, and deleting notes are management-gated write endpoints (POST /api/annotations, POST /api/annotations/{id}, POST /api/annotations/{id}/delete). See the management write API below.
GET /health
Section titled “GET /health”A liveness check: returns 200 OK with the body ok. Useful for container/orchestrator health probes.
Timeouts
Section titled “Timeouts”Routes that should answer quickly carry a 30-second budget and return 503 if they exceed it. That is a backstop rather than a service level: in practice these answer in milliseconds, and a 503 here means something is wrong on the server, not that you asked for too much.
Four kinds of request are deliberately outside it, because they have no sensible time bound: GET /files/{id} streams whole archives, the SSE progress stream follows a job that may run for hours, uploads are as slow as the link, and the Browsertrix and Archive-It browse endpoints wait on someone else’s service.
Rate limits
Section titled “Rate limits”A server limits each client. A workstation doesn’t.
- Most routes allow a burst of 100 requests per client, then 20 a second. Past that the answer is
429withRetry-After. Across all clients, at most 256 of these requests are in flight at once, and beyond that the answer is an immediate503withRetry-After. GET /files/{id}isn’t rate-limited, because one replay makes hundreds of ranged reads. Instead a client may hold 64 streams open at once, and 512 may be open in total. A stream stays open until its body has been read or dropped./healthis never limited.
A client is its IP address, and for IPv6 its /64. Behind a reverse proxy on the same machine or network, indice uses the rightmost X-Forwarded-For entry that isn’t itself internal. The shipped Caddy stack sets that header. Curators and admins are exempt from the per-client limits, so a script that reads hard needs a roster entry.
The operator sees a refusal in the server log twice: once when it starts, naming the address and the limit, and once a minute later with how many requests were refused. One line per refused request would turn a flood the limits are turning away into a flood of log lines.
Machine-readable replay endpoints
Section titled “Machine-readable replay endpoints”These serve ReplayWeb.page and are handy for tooling, though their primary consumer is the in-browser player. They answer on both the site and the replay origin; the player reads them from the replay origin:
GET /files/{id}: the raw WACZ for a crawl, honoring HTTPRangerequests (partial fetches). This is how replay reads archived resources without downloading the whole file.GET /collection/{id}/replay.json: a multi-WACZ replay manifest for a collection (every member crawl), handed to wabac.js so cross-crawl links resolve.GET /collection/{id}/pages: the collection’s page list and URL→capture resolution, scoped to the collection.
See How indice works for how these fit together.
Management write API
Section titled “Management write API”indice also carries write endpoints under /api/ (add archives, create or edit collections, delete crawls, Browsertrix and Archive-It import), plus SSE progress streams. These power the workroom UI. They are always mounted; who may call them is decided by Access, which is loopback trust on a workstation and a forward-auth proxy plus users.yaml on a server. An anonymous caller gets a 403. Treat them as an implementation detail of that UI rather than a stable public API.