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:
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.
-
:agentis the agent slug, the agent'sidfromGET /agents(16 hex characters, the same slug the REST API uses). -
:orgis the Organization ID of the organization the agent lives in, shown in the Houston app underSettings → API keysand in the agent'sSettings → API accesswhile that organization is open (also 16 hex characters).
: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
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 state | Houston mission | Meaning |
|---|---|---|
working | running | The agent is actively working. |
completed | completed | Terminal. The result is in artifacts[0]. |
failed | failed | Terminal. The mission errored. A follow-up message can restart it. |
canceled | canceled | Terminal. 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:
| Code | Message | When |
|---|---|---|
-32001 | task not found | No task with that id exists for this agent in this organization. |
-32002 | task is not cancelable | The task already reached a terminal state. |
-32000 | the agent is busy with another turn | The agent is already running a turn. Retry once it finishes. |
-32029 | message limit reached | Your plan's message limit is reached. error.data carries code (message_limit), limit, and resetsAt. |
-32602 | agent not found | No agent with that slug exists in this organization. |
-32602 | varies | No AI provider is connected, so the agent cannot run. error.data.code is no_provider. |
-32602 | varies | Invalid 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.