The Service Contract

Your service answers one HTTP endpoint. This page describes that endpoint, what Neo sends it, and what Neo expects back. How your agent produces its answer is entirely up to you.

The Endpoint

POST {Agent Service URL}/run?stream=true|false
Content-Type: application/json

The platform appends /run to the Agent Service URL you registered and sets a single query parameter:

stream

Behaviour

false

Return one complete JSON response. Neo passes your JSON back to the caller unchanged.

true

Stream the answer. Neo forwards what you write, as you write it, to the waiting client.

Implement /run and handle both values of the parameter.

The Request Body

{
  "input": "What is my account balance?",
  "agent_id": "3f9a1c72-5e44-4b18-9c1e-7d2a6b0f8e51",
  "session_id": "9b7e2d10-4c3f-4a56-8f21-1e6c5b90a3d7",
  "internal_user_id": "priya@example.com",
  "config": {}
}

Field

Always sent

Meaning

input

Yes

The user’s message. An empty string if the caller sent no input.

agent_id

Yes

The Neo Code Agent’s own identifier in Neo — the same ID that appears in the page URL.

session_id

Yes

Identifies the conversation. Use it to group turns and to correlate your logs with the platform’s records.

internal_user_id

No

The signed-in user’s email address. Omitted entirely when the caller has no email — for example an API-key request. Never assume this key is present.

config

No

Whatever the caller passed as tweaks on the run request, forwarded verbatim. Present only when the caller supplied it.

Treat unknown or missing optional keys defensively — read config and internal_user_id with a default rather than indexing them directly.

Responding

Non-streaming (stream=false) — reply 200 with a JSON body. The shape is yours to define; Neo returns it to the caller as-is without inspecting or reshaping it.

Streaming (stream=true) — reply 200 and write the answer as you generate it. Neo reads your response bytes as they arrive and re-emits each chunk to the client as a token event, then sends a final end event when your response closes. Because Neo forwards raw bytes, write plain incremental text rather than your own event envelope.

Errors and Timeouts

Condition

What the caller sees

Your service returns a non-200

The same status code, with your response body in the error detail. Return meaningful status codes and readable messages.

Neo cannot connect at all

502 Bad Gateway. Usually a wrong URL, a DNS failure, or a service that is down or unreachable from the platform.

No response within 300 seconds

The request is abandoned. Five minutes is the ceiling for a single turn; stream long answers so the caller sees progress.

Securing Your Endpoint

Neo sends the request with Content-Type: application/json as its only header, so secure an External URL service at the network layer:

  • Place the service on a private network reachable only by the platform.

  • Restrict inbound traffic to the platform’s egress addresses — ask your platform administrator for these.

  • Put a gateway or proxy in front of it that applies your own authentication before traffic reaches the agent.

  • Use internal_user_id to personalise a response, not to authorise one. It arrives as an unsigned value in the request body.

With an EKS Deployment, the container runs inside the platform’s cluster and its address is not published externally. Supply any secrets your agent needs through Environment Variables on the deployment configuration — see Creating a Neo Code Agent.

A Minimal Implementation

from fastapi import FastAPI, Request
from fastapi.responses import StreamingResponse

app = FastAPI()

@app.post("/run")
async def run(request: Request, stream: bool = False):
    body = await request.json()

    user_input = body.get("input", "")
    session_id = body.get("session_id")
    user_email = body.get("internal_user_id")   # may be absent
    config = body.get("config", {})             # may be absent

    answer = await my_agent(user_input, session_id, config, user_email)

    if stream:
        async def chunks():
            for piece in answer_pieces(answer):
                yield piece                      # plain text, no envelope
        return StreamingResponse(chunks(), media_type="text/plain")

    return {"output": answer}

Exposing /openapi.json — as FastAPI does automatically — lets the builder’s API endpoints panel list your operations when you click Fetch, which is a quick way to confirm you have registered the right URL.

Checklist Before Registering

1

POST /run accepts a JSON body and returns 200 with JSON.

2

The stream query parameter is honoured in both states.

3

Missing internal_user_id and config do not cause an error.

4

Responses complete well within 300 seconds, or stream.

5

The URL is reachable from the platform and secured at the network layer.

6

Failures return a meaningful status code and a readable message.