GoodMem
Reference

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:stream
  • GET /v1/memories:retrieve
  • POST /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:stream

Sample 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:stream

Sample 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 a resultSetBoundary with kind: "END" (plus any final abstractReply). 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: notice events
  • Memory retrieval: status events

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 of result, 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, Python requests with stream=True + iter_lines(), or JS fetch() with response.body.getReader()).
  • Timeouts: Streams can be long-lived; avoid client timeouts that assume a fixed-length response.