Skip to main content
The Agent Graph /invoke endpoint returns a stream of newline-delimited JSON objects (NDJSON) rather than a single JSON response. Each line is a complete, self-contained JSON event. This lets your application begin processing the agent’s reply progressively rather than waiting for the entire response to arrive.

Why response.json() fails

If you try to parse the raw response body as a single JSON document it will throw an error:
response = requests.post("https://api.connectly.ai/external/v1/ai/agent_graph/invoke", ...)
data = response.json()  # ❌ JSONDecodeError
The response body contains multiple JSON objects separated by newlines β€” not a single valid JSON document. The standard response.json() method cannot handle that format.

The correct approach

Enable streaming on the request and iterate over the response line by line, parsing each non-empty line as its own JSON object:
import requests, json

response = requests.post(
    "https://api.connectly.ai/external/v1/ai/agent_graph/invoke",
    headers={"x-api-key": "YOUR_API_KEY", "Content-Type": "application/json"},
    json={
        "clientKey": "customer-123",
        "businessId": "your-business-id",
        "sessionId": "b6bed81c-5fda-486d-b96c-1ff3af36219a",
        "inputEvents": [{
            "messageEvent": {
                "role": "USER",
                "content": {"textContent": {"text": "Hi"}}
            }
        }]
    },
    stream=True          # βœ… don't buffer the full response
)

for raw_line in response.iter_lines():   # βœ… one line at a time
    if raw_line:                          # skip keep-alive empty lines
        event = json.loads(raw_line.decode("utf-8"))
        print(event)
Key differences from a standard request:
  • Pass stream=True so the response body is not buffered all at once.
  • Use response.iter_lines() to read one line at a time.
  • Skip empty lines with if raw_line: β€” these are keep-alive bytes with no data.
  • Decode each line from bytes to UTF-8, then parse with json.loads().

Parsing approaches compared

ApproachWorks with /invoke?Notes
response.json()❌Throws JSONDecodeError.
response.iter_lines() + json.loads()βœ…Correct β€” parse each line individually.
response.text.split("\n")⚠️Works but error-prone β€” prefer iter_lines().
/invoke_sync + response.json()βœ…No streaming; waits for the full response.
If you don’t need real-time streaming β€” for example, in a backend worker that waits for the complete response β€” use Invoke (sync) instead. It returns a single aggregated JSON object and works with a standard response.json() call.

What each line contains

Each parsed line is a JSON event object from the agent. The structure varies by event type β€” a messageEvent carries the agent’s text reply, a recommendationEvent carries product suggestions, an agentHandoverEvent signals a human handoff. See Invoke (stream) for the full response event schema.