The HTTP API is served by devlogbusd on 127.0.0.1:7423 by default. Browser
Tap uses this API, and public tools may use it when an HTTP boundary is simpler
than the Go client socket protocol.
All HTTP JSON responses use Content-Type: application/json. The daemon also
sets permissive CORS headers because Browser Tap and local development pages
publish from browser contexts.
{
"id": "optional-daemon-assigned-id",
"time": "2026-05-31T12:00:00Z",
"level": "INFO",
"source": "billing_svc",
"message": "payment method updated",
"attrs": {
"request_id": "abc123",
"tenant": "demo"
}
}
Required fields for publishing:
sourcemessageRecommended fields:
timelevelattrsIf time is omitted through the HTTP API, the zero value is accepted by the
current daemon but publishers should send a real timestamp. Go client publishers
validate that time is present.
Level names are normalized:
| Input | Stored |
|---|---|
debug, dbg |
DEBUG |
info, empty |
INFO |
warn, warning |
WARN |
error, err |
ERROR |
Unknown levels are uppercased and treated like INFO for filtering.
Filtering order:
DEBUG < INFO < WARN < ERROR
source is the record owner used for filtering and expunge.
Browser publishers can set attrs.sourceGroup to make a parent group while
preserving child sources:
{
"source": "chrome:api.localhost:8080",
"message": "GET /api/orders -> 500",
"attrs": {
"sourceGroup": "chrome:localhost:5173",
"tabTitle": "Local Checkout"
}
}
The UI and TUI use sourceGroup for grouped panes. The daemon does not give
sourceGroup special retention or expunge behavior; expunge still operates on
real sources.
Publish one record:
curl -X POST http://127.0.0.1:7423/api/records \
-H 'Content-Type: application/json' \
-d '{"level":"info","source":"demo","message":"hello"}'
Publish an array:
curl -X POST http://127.0.0.1:7423/api/records \
-H 'Content-Type: application/json' \
-d '[{"level":"info","source":"demo","message":"one"},{"level":"warn","source":"demo","message":"two"}]'
Publish a batch object:
curl -X POST http://127.0.0.1:7423/api/records \
-H 'Content-Type: application/json' \
-d '{"records":[{"level":"info","source":"demo","message":"one"}]}'
Response:
{"published":1}
Limits:
Replay matching records:
curl 'http://127.0.0.1:7423/api/records?source=demo&level=warn&replay=20'
Query parameters:
| Parameter | Meaning |
|---|---|
source |
Source to include. Repeatable or comma-separated. |
sources |
Alias for multiple sources. Repeatable or comma-separated. |
level |
Minimum level. |
minLevel |
Alias for level. |
replay |
Number of matching records to replay. |
replayPerSource |
Number of records to replay per source. |
replay-per-source |
Alias for replayPerSource. |
replay_per_source |
Alias for replayPerSource. |
Response:
[
{
"id": "1",
"time": "2026-05-31T12:00:00Z",
"level": "WARN",
"source": "demo",
"message": "hello"
}
]
Open a Server-Sent Events stream:
curl -N 'http://127.0.0.1:7423/api/stream?replay=20'
Each event is:
event: record
data: {"id":"1","time":"2026-05-31T12:00:00Z","level":"INFO","source":"demo","message":"hello"}
The stream sends replay records first, then live records. A keepalive comment is sent periodically while the stream is idle:
: keepalive
Delete replay-buffer records for one source:
curl -X DELETE 'http://127.0.0.1:7423/api/records/expunge?source=demo'
Delete all replay-buffer records by omitting source:
curl -X DELETE http://127.0.0.1:7423/api/records/expunge
Response:
{"expunged":12}
curl http://127.0.0.1:7423/api/health
Response:
{"ok":true}
curl http://127.0.0.1:7423/api/about
Response includes API status, broker settings, and build metadata:
{
"api": {"ok": true},
"broker": {
"endpoint": "/tmp/devlogbus/devlogbus.sock",
"httpListenAddress": "127.0.0.1:7423",
"tcpListenAddress": "",
"maxRecords": 5000,
"echo": true
},
"build": {
"app": "devlogbusd",
"version": "v1.3.1",
"commit": "abc123",
"buildDate": "2026-05-31T12:00:00Z"
}
}
The Go client and CLI speak newline-delimited JSON envelopes over Unix sockets or TCP sockets.
Envelope shape:
{
"type": "log",
"record": {
"time": "2026-05-31T12:00:00Z",
"level": "INFO",
"source": "demo",
"message": "hello"
}
}
Envelope types:
| Type | Direction | Payload |
|---|---|---|
log |
client to daemon, daemon to subscribers | record |
subscribe |
client to daemon | subscribe |
replay_complete |
daemon to subscriber | none |
expunge |
client to daemon | expunge |
expunge_result |
daemon to client | expungeResult |
error |
daemon to client | error |
Subscribe payload:
{
"type": "subscribe",
"subscribe": {
"sources": ["demo"],
"minLevel": "WARN",
"replay": 20,
"replayPerSource": 100
}
}
Expunge payload:
{
"type": "expunge",
"expunge": {"source": "demo"}
}
An empty expunge source means all replay records.
The v1 API should preserve existing JSON field names and endpoint behavior. New optional fields may be added. Consumers should ignore unknown fields and avoid depending on exact object key order.
See Compatibility.