REST API
The daemon's HTTP API on localhost:11500, plus the MCP endpoint.
The daemon serves JSON over HTTP on localhost:11500. It listens on localhost only and has no auth in v1; do not expose it beyond the machine. Requests from other hosts or browser origins are refused with 403.
POST /api/query
Search. This is the endpoint your scripts want.
curl -s -X POST localhost:11500/api/query \
-d '{"query": "termination clause", "tag": "", "limit": 5}'Request:
| Field | Type | Meaning |
|---|---|---|
query | string | Required. Search text |
tag | string | Optional. Restrict to folders with this tag |
limit | int | Optional. Max results, default 10, cap 100 |
mode | string | Optional. search (default) returns only results that clear the relevance floors, possibly none. answer falls back to the nearest chunks instead of returning empty; it is what hay ask uses for retrieval |
Response:
{
"results": [
{
"path": "/Users/you/Documents/contracts/msa.pdf",
"page": 4,
"chunk": 11,
"snippet": "Either party may terminate...",
"text": "Either party may terminate this Agreement...",
"score": 0.0322
}
]
}page is 1-based and present for paginated formats; 0 means the format has no pages and chunk is the citation. snippet is the short display excerpt; text is the chunk's full text, for callers that answer or reason from the result.
POST /api/ask
Retrieval plus a streamed answer from your local LLM, as server-sent events. This is what hay web uses; point curl -N or an SSE client at it.
curl -sN -X POST localhost:11500/api/ask \
-d '{"question": "what notice period does the vendor agreement require?"}'Request:
| Field | Type | Meaning |
|---|---|---|
question | string | Required. The question to answer |
tag | string | Optional. Restrict retrieval to folders with this tag |
limit | int | Optional. Passages given to the model, default 6, cap 20 |
The response is text/event-stream with four event types, in order:
| Event | Data | Meaning |
|---|---|---|
sources | array of results with label | The retrieved passages, always first |
token | JSON string | One piece of the answer, repeated |
error | {"message": "..."} | Generation failed; the stream ends after this |
done | {} | Always last on success |
Requires an OpenAI-compatible server, same as hay ask; when none answers, the response is 503 before any stream starts, so clients can branch on the status code.
GET /api/chunk
The passage behind a citation, with its neighbors for context. The web UI's "read in place" view.
curl -s "localhost:11500/api/chunk?path=/Users/you/Documents/msa.pdf&chunk=11&window=1"| Param | Meaning |
|---|---|
path | Required. Source file path as returned by /api/query |
chunk | Required. Chunk ordinal from the result |
window | Neighboring chunks on each side, 0 to 5, default 1 |
GET /api/health
Liveness plus identity. Clients use db to confirm they are talking to the daemon for the right index.
{ "ok": true, "version": "0.3.1", "db": "/Users/you/.haypile/haypile.db", "model": "bundled/all-MiniLM-L6-v2" }GET /api/status
Everything hay status shows: the health fields plus uptime, sources, counts, pending indexing jobs, and the measured outbound connection count.
| Field | Meaning |
|---|---|
uptime_seconds | Seconds since the daemon started |
sources | Indexed folders, each with path, tag, files, chunks |
files, chunks | Totals across all sources |
pending_jobs | Watcher changes queued for re-indexing |
outbound_connections | The daemon's live non-listening TCP connections, measured |
outbound_note | Present only when the count could not be measured; explains why |
indexing | Present only while an add pass runs: phase (extracting or embedding), files_done/files_total, bytes_done/bytes_total, chunks_done/chunks_total |
indexing is what hay add polls to draw its live progress bar; scripts can poll it the same way.
POST /api/shutdown
Asks the daemon to exit gracefully; this is what hay stop calls. Responds {"stopping": true}, finishes in-flight requests, closes the index cleanly, and removes the runtime file on the way out.
GET /api/sources
Indexed folders with counts.
POST /api/sources
Index and watch a folder or file. Runs synchronously and returns the indexing stats.
curl -s -X POST localhost:11500/api/sources \
-d '{"path": "/Users/you/Documents", "tag": "personal"}'DELETE /api/sources
Un-index and un-watch. Body: {"path": "..."}. Returns {"removed": true} or false if the path was not indexed.
GET /api/browse and POST /api/pick
Helpers for the web UI's folder picking; scripts rarely need them. GET /api/browse?path=/abs/dir lists a directory's subfolders and indexable files (defaults to the home directory). POST /api/pick?kind=folder|file opens the native OS file dialog and returns the chosen path; 204 means the user canceled, 501 means the platform has no dialog helper.
POST /mcp
The MCP endpoint (Streamable HTTP transport, JSON-RPC 2.0). Supports initialize, tools/list, tools/call, and ping. Tools:
| Tool | Arguments | Returns |
|---|---|---|
search_documents | query (required), tag, limit | Cited passages as text |
list_sources | none | Indexed folders with counts |
Point any MCP client at http://localhost:11500/mcp, or use hay mcp-stdio for stdio-transport clients.
Errors
Non-200 responses carry {"error": "message"}. The codes in use:
| Code | When |
|---|---|
400 | Malformed request |
403 | Request from another host or browser origin |
404 | /api/browse on an unreadable path, /api/chunk for a chunk that does not exist |
405 | Wrong method, e.g. GET /mcp |
409 | /api/pick while a dialog is already open |
503 | /api/ask when no LLM endpoint answers |
500 | Server fault |
MCP tool failures come back inside the JSON-RPC result with isError: true so agent models can read and react to them.