Agent2Agent.

Houston agents speak the Agent2Agent (A2A) protocol, so you can point any A2A-capable agent, such as LangGraph, CrewAI, or Google's ADK, straight at a Houston agent and delegate work to it. A2A is JSON-RPC 2.0 over HTTP, authenticated with your hst_... API key.

The agent card

Every Houston agent publishes an A2A agent card describing its name, capabilities, and endpoint. Fetch it (authenticated) at:

GET /a2a/:org/:agent/.well-known/agent-card.json
curl https://gateway.gethouston.ai/a2a/8c41e7a0d39b2f16/3f9a1c07b2e84d65/.well-known/agent-card.json \
  -H "Authorization: Bearer hst_9f2c1a7b4e8d0364f1a2b3c4d5e6f7089f2c1a7b4e8d0364f1a2b3c4d5e6f708"

Feed that card URL to your A2A client. The card's url field is the JSON-RPC endpoint below.

:org must match the organization your request acts in, or the gateway answers 403 with { "error": "org mismatch" }. Without a header, a request acts in your personal organization. For an agent in a team organization, put its Organization ID in the path and send it as x-houston-org on every request, card fetch included (see Organizations):

curl https://gateway.gethouston.ai/a2a/8c41e7a0d39b2f16/3f9a1c07b2e84d65/.well-known/agent-card.json \
  -H "Authorization: Bearer hst_9f2c1a7b4e8d0364f1a2b3c4d5e6f7089f2c1a7b4e8d0364f1a2b3c4d5e6f708" \
  -H "x-houston-org: 8c41e7a0d39b2f16"

The JSON-RPC endpoint

POST /a2a/:org/:agent

All A2A methods are JSON-RPC 2.0 calls to this one endpoint. Houston supports message/send, message/stream, tasks/get, and tasks/cancel.

message/send

Send a message to the agent and open a task. The response contains the task, whose status.state you then poll with tasks/get or follow with message/stream. The task id is the Houston mission id and contextId is its conversation id, both 32 hex characters. Only text parts are read.

curl -X POST https://gateway.gethouston.ai/a2a/8c41e7a0d39b2f16/3f9a1c07b2e84d65 \
  -H "Authorization: Bearer hst_9f2c1a7b4e8d0364f1a2b3c4d5e6f7089f2c1a7b4e8d0364f1a2b3c4d5e6f708" \
  -H "Content-Type: application/json" \
  -d '{
    "jsonrpc": "2.0",
    "id": "1",
    "method": "message/send",
    "params": {
      "message": {
        "role": "user",
        "parts": [
          { "kind": "text", "text": "Run a market pulse for Lisbon next weekend." }
        ]
      }
    }
  }'
{
  "jsonrpc": "2.0",
  "id": "1",
  "result": {
    "id": "a7f3c91e0b2d4856e19c3f70b4a2d8e5",
    "contextId": "5d0e8b3a6c1f4927b8e2a4c6d9f01b37",
    "status": { "state": "working", "timestamp": "2026-07-12T09:31:07Z" },
    "kind": "task",
    "history": []
  }
}

message/stream

Same call, but the response is a Server-Sent Events stream. Each event is a JSON-RPC response: first the task, then artifact-update events carrying the agent's text as it is written, and finally a status-update with "final": true, after which the stream closes. Use this for live progress. Honor each update's append flag: true adds its text to the end of the result, and false replaces the whole result (Houston sends one when it resyncs the text written so far). Concatenating every event blindly can duplicate text.

curl -N -X POST https://gateway.gethouston.ai/a2a/8c41e7a0d39b2f16/3f9a1c07b2e84d65 \
  -H "Authorization: Bearer hst_9f2c1a7b4e8d0364f1a2b3c4d5e6f7089f2c1a7b4e8d0364f1a2b3c4d5e6f708" \
  -H "Content-Type: application/json" \
  -d '{
    "jsonrpc": "2.0",
    "id": "1",
    "method": "message/stream",
    "params": {
      "message": {
        "role": "user",
        "parts": [ { "kind": "text", "text": "Summarize this week for Lisbon." } ]
      }
    }
  }'

Multi-turn with taskId

To continue an existing task, for example to answer a question the agent asked, send another message and set taskId on the message (params.message.taskId). Houston adds the message as a new turn in the same mission and conversation instead of starting a new one.

This works on finished tasks too. A follow-up on a completed, failed, or canceled task puts it back to working and clears the previous result. While the agent is still working on a turn, a follow-up is refused with -32000 (agent busy); send it once the current turn ends.

{
  "jsonrpc": "2.0",
  "id": "2",
  "method": "message/send",
  "params": {
    "message": {
      "role": "user",
      "taskId": "a7f3c91e0b2d4856e19c3f70b4a2d8e5",
      "parts": [ { "kind": "text", "text": "Yes, include Porto too." } ]
    }
  }
}

tasks/get & tasks/cancel

Fetch a task's current state:

{
  "jsonrpc": "2.0",
  "id": "3",
  "method": "tasks/get",
  "params": { "id": "a7f3c91e0b2d4856e19c3f70b4a2d8e5" }
}

A completed task carries the agent's final text as artifacts[0], named result. history is always an empty array; read the conversation in the Houston app.

{
  "jsonrpc": "2.0",
  "id": "3",
  "result": {
    "id": "a7f3c91e0b2d4856e19c3f70b4a2d8e5",
    "contextId": "5d0e8b3a6c1f4927b8e2a4c6d9f01b37",
    "status": { "state": "completed", "timestamp": "2026-07-12T09:34:51Z" },
    "kind": "task",
    "artifacts": [
      {
        "artifactId": "a7f3c91e0b2d4856e19c3f70b4a2d8e5-result",
        "name": "result",
        "parts": [ { "kind": "text", "text": "Market pulse for Lisbon is ready..." } ]
      }
    ],
    "history": []
  }
}

Cancel a running task. The response is the task in the canceled state:

{
  "jsonrpc": "2.0",
  "id": "4",
  "method": "tasks/cancel",
  "params": { "id": "a7f3c91e0b2d4856e19c3f70b4a2d8e5" }
}

Task states

A2A task states map directly onto Houston mission status:

A2A task stateHouston missionMeaning
workingrunningThe agent is actively working.
completedcompletedTerminal. The result is in artifacts[0].
failedfailedTerminal. The mission errored. A follow-up message can restart it.
canceledcanceledTerminal. The task was canceled. A follow-up message can restart it.

Error codes

JSON-RPC errors come back as HTTP 200 with an error object. Besides the standard codes (-32700 parse error, -32600 invalid request, -32601 method not found, -32603 internal error), Houston returns:

CodeMessageWhen
-32001task not foundNo task with that id exists for this agent in this organization.
-32002task is not cancelableThe task already reached a terminal state.
-32000the agent is busy with another turnThe agent is already running a turn. Retry once it finishes.
-32029message limit reachedYour plan's message limit is reached. error.data carries code (message_limit), limit, and resetsAt.
-32602agent not foundNo agent with that slug exists in this organization.
-32602variesNo AI provider is connected, so the agent cannot run. error.data.code is no_provider.
-32602variesInvalid params, for example an empty message (missing 'input') or a missing id.
{
  "jsonrpc": "2.0",
  "id": "4",
  "error": { "code": -32002, "message": "task is not cancelable" }
}

A few failures happen before JSON-RPC and use HTTP status codes: 401 for a missing or invalid key, 403 org mismatch when :org is not the organization the request acts in, 403 not_member when x-houston-org names an organization you are not in, and 404 for a malformed agent slug or an agent card for an agent that does not exist.