Skip to content

HTTP API

The dirsql server (default localhost:7117) exposes two endpoints: POST /query and GET /events.

POST /query

Run a read-only SQL query — statements SQLite classifies as writes are rejected. That rule governs SQL against the index; dirsql separately never modifies the files it indexes, which is permanent by design. Request body is JSON:

json
{"sql": "SELECT title, author FROM posts WHERE draft = 0"}

The 200 response is a JSON array of row objects keyed by column name:

json
[
  {"title": "Hello World", "author": "alice"},
  {"title": "Second Post", "author": "bob"}
]
bash
curl -s http://localhost:7117/query \
  -H 'content-type: application/json' \
  -d '{"sql":"SELECT COUNT(*) AS n FROM files"}' \
  | jq

Value serialization

SQLite valueJSON
NULLnull
INTEGERnumber
REALnumber; NaN / ±Infinity become null
TEXTstring
BLOBlowercase hex string (e.g. "deadbeef")

Internal tracking columns (_dirsql_file_path, _dirsql_row_index) are excluded from SELECT * results.

Status codes

StatusWhen
200Query succeeded. Body: array of row objects (or the post-query hook's JSON).
400Malformed JSON body, missing or empty sql field, or a SQL error (syntax error, unknown table, or a statement SQLite classifies as a write — queries are read-only).
405GET /query. Plain-text body method not allowed.
408The query exceeded the 30-second per-query timeout.
500Internal server fault, or a failed pre-query / post-query hook.
503The server is in degraded mode (the config file exists but failed to load).

All error responses (except 405) are application/json:

json
{"error": "syntax error near \"SLECT\""}

Hook interactions

  • With [dirsql].pre-query configured, the request body is not parsed as {"sql": …}; the raw body is passed to the hook, which prints the SQL to run. Hook failure → 500.
  • With [dirsql].post-query configured, the 200 body is whatever JSON the hook prints instead of the bare row array. Hook failure or non-JSON output → 500.

GET /events

Opens a Server-Sent Events stream of row-change events.

bash
curl -N http://localhost:7117/events

On open, the server emits a single ready frame signalling the subscription is attached. Every subsequent frame is named row:

event: ready
data: {}

event: row
data: {"action":"insert","table":"posts","file_path":"posts/hello.json","row":{"title":"Hello World"},"old_row":null}

Event payloads

The data: payload is one JSON object per row-level change:

Fieldinsertupdatedeleteerror
action"insert""update""delete""error"
tabletable nametable nametable nametable name, or null when the failure isn't tied to one table
file_pathpath relative to the rootsamesamesame
rowthe new rowthe new rowthe deleted row— (absent)
old_rownullthe previous rownull— (absent)
errormessage string

Row values serialize as in POST /query.

Stream semantics

  • Errors do not terminate the stream. A malformed file produces an error event; the stream continues.
  • Slow consumers skip, not crash. A subscriber that lags behind the event buffer silently misses the overflowed events and keeps receiving new ones.
  • The server sends periodic SSE keep-alive comments.
  • The stream closes when the server shuts down.

Status codes

StatusWhen
200Stream opened (text/event-stream).
405POST /events. Plain-text body method not allowed.
503The server is in degraded mode. JSON {"error": …} body.

Released under the MIT License.