Docs

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:

streamBehaviour
falseReturn one complete JSON response. Neo passes your JSON back to the caller unchanged.
trueStream 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": {}
}
FieldAlways sentMeaning
inputYesThe user’s message. An empty string if the caller sent no input.
agent_idYesThe Neo Code Agent’s own identifier in Neo — the same ID that appears in the page URL.
session_idYesIdentifies the conversation. Use it to group turns and to correlate your logs with the platform’s records.
internal_user_idNoThe 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.
configNoWhatever 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

ConditionWhat the caller sees
Your service returns a non-200The same status code, with your response body in the error detail. Return meaningful status codes and readable messages.
Neo cannot connect at all502 Bad Gateway. Usually a wrong URL, a DNS failure, or a service that is down or unreachable from the platform.
No response within 300 secondsThe 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

1POST /run accepts a JSON body and returns 200 with JSON.
2The stream query parameter is honoured in both states.
3Missing internal_user_id and config do not cause an error.
4Responses complete well within 300 seconds, or stream.
5The URL is reachable from the platform and secured at the network layer.
6Failures return a meaningful status code and a readable message.