REST Streaming Responses
How GoodMem streams REST responses using SSE and NDJSON
REST Streaming Responses
GoodMem REST streaming endpoints return events over HTTP in one of two formats:
- SSE (Server-Sent Events) —
text/event-stream - NDJSON (newline-delimited JSON) —
application/x-ndjson
These formats are used by endpoints such as:
POST /v1/ping:streamGET /v1/memories:retrievePOST /v1/memories:retrieve
The auto-generated API reference documents the request and response schemas, but it does not fully describe the streaming framing. This page captures the wire format in detail.
Choosing a streaming format
Select the format with the Accept header:
- Prefer SSE:
Accept: text/event-stream - Prefer NDJSON:
Accept: application/x-ndjson
If no Accept header is provided, the server defaults to NDJSON.
Local TLS note: for https://localhost use a locally trusted cert (mkcert) and pass --cacert in examples
instead of disabling verification. See "Local TLS with mkcert" in the Security reference.
Practical tradeoffs:
- SSE: named events (
event:) and native browser support (EventSource). - NDJSON: simplest framing, easy line-by-line parsing and piping (e.g.,
jq), good for CLI/server consumers. - Both: identical payload schemas; only the framing differs.
SSE framing
Each event is sent as an SSE frame with event: item and a JSON payload in data:. The server sends a terminal
close event when the stream completes.
Example (ping stream):
curl -sS \
--cacert /path/to/mkcert/rootCA.pem \
-H "x-api-key: $GOODMEM_API_KEY" \
-H "Accept: text/event-stream" \
-H "Content-Type: application/json" \
-d '{"targetId":"36cd638d-965e-4524-9cd9-0c40261f73e2","targetTypeHint":"EMBEDDER"}' \
https://localhost:8080/v1/ping:streamSample SSE output:
event: item
data: {"result":{...}}
event: item
data: {"summary":{...}}
event: close
data: {"type":"completed"}NDJSON framing
NDJSON streams one JSON object per line, with no SSE framing. Each line is a complete event.
Example (ping stream):
curl -sS \
--cacert /path/to/mkcert/rootCA.pem \
-H "x-api-key: $GOODMEM_API_KEY" \
-H "Accept: application/x-ndjson" \
-H "Content-Type: application/json" \
-d '{"targetId":"36cd638d-965e-4524-9cd9-0c40261f73e2","targetTypeHint":"EMBEDDER"}' \
https://localhost:8080/v1/ping:streamSample NDJSON output:
{"result":{...}}
{"summary":{...}}Stream completion
-
SSE: the server sends an explicit terminal frame:
event: close data: {"type":"completed"} -
NDJSON: there is no explicit terminal object. Treat the stream as complete when the HTTP connection closes. For ping, the final logical event is typically a
summary. For memory retrieval, the last logical event is usually aresultSetBoundarywithkind: "END"(plus any finalabstractReply). If the connection closes before you see that terminal event, assume the stream ended early and results may be partial.
Errors during streaming
- Before any events are sent: the server returns a normal HTTP error response (JSON body + status code).
- After streaming has started: the server cannot send a structured error event. The connection is closed and you may have received a partial stream.
Non-fatal warnings do appear as normal events:
- Ping:
noticeevents - Memory retrieval:
statusevents
Example event payloads
Only one of the top-level fields (result, summary, notice, etc.) is present per event.
PingEvent examples
{
"result": {
"endpoint": {
"targetType": "EMBEDDER",
"targetId": "36cd638d-965e-4524-9cd9-0c40261f73e2",
"resolvedEndpoint": "https://api.example.com/v1/embeddings",
"provider": "OPENAI",
"modelIdentifier": "text-embedding-3-small"
},
"seq": 1,
"bytesSent": 64,
"bytesReceived": 64,
"ok": true,
"httpStatus": 200,
"rttMs": 12.34,
"timing": {
"clientSendTimeUnixNanos": 1712082300123456789,
"serverReceivedTimeUnixNanos": 1712082301123456789,
"serverSendTimeUnixNanos": 1712082302123456789,
"clientReceiveTimeUnixNanos": 1712082303123456789
},
"metadata": {
"request_id": "req_123"
}
}
}{
"notice": {
"code": "COUNT_CLAMPED",
"message": "Probe count reduced to maximum allowed value",
"details": {
"requested": "100",
"applied": "20"
}
}
}{
"summary": {
"endpoint": {
"targetType": "EMBEDDER",
"targetId": "36cd638d-965e-4524-9cd9-0c40261f73e2",
"resolvedEndpoint": "https://api.example.com/v1/embeddings",
"provider": "OPENAI",
"modelIdentifier": "text-embedding-3-small"
},
"requests": 4,
"responses": 4,
"ok": 4,
"timeouts": 0,
"errors": 0,
"rttMinMs": 10.1,
"rttAvgMs": 12.3,
"rttP50Ms": 12.0,
"rttP90Ms": 14.8,
"rttP99Ms": 15.0,
"rttMaxMs": 15.3,
"requestsPerSecond": 3.9,
"bytesPerSecond": 512.0,
"appliedLimits": {
"max_count": "20"
}
}
}RetrieveMemoryEvent examples
{
"resultSetBoundary": {
"resultSetId": "550e8400-e29b-41d4-a716-446655440000",
"kind": "BEGIN",
"stageName": "vector_search",
"expectedItems": 10
}
}{
"retrievedItem": {
"memory": {
"memoryId": "b2cfc3b1-6a7b-4ed8-8f7f-2fe20e0ab8c1",
"spaceId": "8a445f3e-2d1d-4a0f-9a30-5f1a6bb4e3b9",
"contentType": "text/plain",
"processingStatus": "COMPLETED",
"metadata": {
"source": "docs",
"author": "Example"
},
"createdAt": 1712000000000,
"updatedAt": 1712000000000,
"createdById": "9f1f3e2f-3b3f-4a1a-9b1e-1f1f1f1f1f1f",
"updatedById": "9f1f3e2f-3b3f-4a1a-9b1e-1f1f1f1f1f1f"
}
}
}{
"status": {
"code": "PARTIAL_RESULTS",
"message": "Some embedders were unavailable, returning partial results"
}
}Event schemas
The payload of each event is a JSON object that matches the endpoint’s response schema:
- Ping stream uses
PingEvent(one ofresult,summary,notice) - Memory retrieve stream uses
RetrieveMemoryEvent(result boundaries, items, summaries, notices)
For field-level details, see the REST API reference under Reference → API Reference. The schemas are accurate, but
the framing (SSE vs NDJSON) is described here.
Client tips
- Browsers: SSE works directly with
EventSource. - CLI / servers: NDJSON is easy to parse line-by-line.
- Streaming reads: ensure your HTTP client exposes the body incrementally (e.g.,
curl --no-buffer, Pythonrequestswithstream=True+iter_lines(), or JSfetch()withresponse.body.getReader()). - Timeouts: Streams can be long-lived; avoid client timeouts that assume a fixed-length response.