Renly as an A2A agent
Renly speaks the Agent2Agent (A2A) protocol, version 1.0. Another agent can discover Renly, hand a task to one of your organization's Renly agents, and follow it to the result. Changes follow your organization's approval rules, and anything that needs approval, including every send, waits for a person to approve it in Renly.
At a glance. Agent Card: https://renly.ai/.well-known/agent-card.json. Endpoint: https://renly.ai/api/a2a (JSON-RPC 2.0). Protocol version 1.0. Auth: an agent access key as a Bearer token. Streaming: yes (Server-Sent Events). Push notifications: not supported.
How it works
- One endpoint, many agents. Each Renly agent you can use is an interface on the same endpoint, addressed by putting its agent id in the request's
tenantfield. - A task is a Renly conversation. Each task's
contextIdis an ordinary Renly chat owned by the person whose key made the call, so the work is visible in Renly. The task'smetadata.renly.conversationUrllinks to it. - Approvals stay with people. When the agent proposes an action that needs approval, the task moves to
TASK_STATE_INPUT_REQUIREDand an approval card waits in that conversation. The calling agent cannot approve. When a person approves in Renly, the task continues; if they decline, it endsTASK_STATE_REJECTED. - Same governance as chat. The agent runs as the key's owner, with their connectors and your organization's policies, and every action is recorded as evidence.
Connect in four steps
- Fetch the public card. It needs no key and names no customer agents.
curl https://renly.ai/.well-known/agent-card.json
- Create an access key with the
a2ascope. In Renly, open the user menu, choose Agent access, and create a key under New key. Copy it: it is shown once. See agent access keys. - List the agents you can call with
GetExtendedAgentCard. Each agent appears as asupportedInterfacesentry whosetenantis the agent id, and as a skill with the same id.curl -s https://renly.ai/api/a2a \ -H "Authorization: Bearer $RENLY_KEY" \ -H "Content-Type: application/json" \ -H "A2A-Version: 1.0" \ -d '{"jsonrpc":"2.0","id":1,"method":"GetExtendedAgentCard","params":{}}' - Send a task with
SendMessage, then follow it withGetTask(below).
SendMessage
Put the agent id in params.tenant. If your key is limited to a single agent, you can leave tenant out. The message must have role: "ROLE_USER", a messageId you choose, and at least one part.
curl -s https://renly.ai/api/a2a \
-H "Authorization: Bearer $RENLY_KEY" \
-H "Content-Type: application/json" \
-H "A2A-Version: 1.0" \
-d '{
"jsonrpc": "2.0",
"id": 1,
"method": "SendMessage",
"params": {
"tenant": "AGENT_ID",
"message": {
"messageId": "msg-001",
"role": "ROLE_USER",
"parts": [{ "text": "Summarise this week'"'"'s open incidents and draft a status update." }]
},
"configuration": { "returnImmediately": true }
}
}'
The result is { "task": ... } (abridged):
{
"jsonrpc": "2.0",
"id": 1,
"result": {
"task": {
"id": "TASK_ID",
"contextId": "CONTEXT_ID",
"status": { "state": "TASK_STATE_WORKING", "timestamp": "2026-09-23T09:14:02.511Z" },
"metadata": { "renly": { "agentId": "AGENT_ID", "conversationUrl": "https://renly.ai/chat?session=CONTEXT_ID" } }
}
}
}
- Parts accepted:
text,data(any JSON, passed to the agent as a JSON block) andurl. Inline file bytes (raw) are refused with a content-type error. Up to 100,000 characters per message. - Blocking by default. Without
returnImmediately, the call waits for the task to finish or pause, for up to about 200 seconds. If it is still running then, you get the task back inTASK_STATE_WORKINGwith a status message telling you to poll or subscribe. The run carries on either way. - Output. The answer arrives as an artifact named
responsewithmediaType: "text/markdown". If you setacceptedOutputModes, includetext/markdownortext/plain. - Continuing. To follow up after a task has finished, send a new message with the same
contextId. Only contexts that were started over A2A by the same key owner can be continued; ordinary Renly chats cannot be read or extended this way.
GetTask
curl -s https://renly.ai/api/a2a \
-H "Authorization: Bearer $RENLY_KEY" \
-H "Content-Type: application/json" \
-H "A2A-Version: 1.0" \
-d '{"jsonrpc":"2.0","id":2,"method":"GetTask","params":{"id":"TASK_ID","historyLength":0}}'
Here the result is the task itself. A task waiting on a person looks like this (abridged):
{
"jsonrpc": "2.0",
"id": 2,
"result": {
"id": "TASK_ID",
"contextId": "CONTEXT_ID",
"status": {
"state": "TASK_STATE_INPUT_REQUIRED",
"timestamp": "2026-09-23T09:15:40.020Z",
"message": {
"role": "ROLE_AGENT",
"parts": [{ "text": "Ops agent is waiting for a person to approve this in Renly before it continues:\n- Post the status update to #incidents" }]
}
},
"metadata": { "renly": { "agentId": "AGENT_ID", "conversationUrl": "https://renly.ai/chat?session=CONTEXT_ID" } }
}
}
Set historyLength to include recent messages from the conversation (default 20, maximum 100; 0 for none).
The approval pause
This is the part most callers need to design for.
- The agent proposes an action that needs approval, such as sending a message or updating a record. The task moves to
TASK_STATE_INPUT_REQUIRED, and the status message lists what is waiting. - A person approves or declines on the approval card in the linked Renly conversation. No A2A method can approve, and sending another message does not approve anything.
- If they approve and the agent finishes, the task becomes
TASK_STATE_COMPLETEDwith the answer in its artifact. If the agent then needs a second approval, the task stays inINPUT_REQUIREDwith an updated message. - If they decline, the task ends
TASK_STATE_REJECTED. - If you no longer want the result, call
CancelTask. Any approval still waiting on the task is withdrawn, so a later click in Renly cannot run an action you abandoned.
Treat INPUT_REQUIRED as normal, not as an error. Poll with GetTask or keep a SubscribeToTask stream open, and tell your own user that a person in Renly needs to act.
All methods
| Method | What it does |
|---|---|
SendMessage | Start a task (or continue a context) and return the task. |
SendStreamingMessage | Same, as Server-Sent Events: a task event, artifactUpdate events as text arrives, and a final statusUpdate. |
GetTask | Current state, artifacts and optional history of one task. |
ListTasks | Your tasks, most recently updated first. Filters: contextId, status, statusTimestampAfter; paging with pageSize (1 to 100) and pageToken. With a key, only that key's tasks are listed. |
CancelTask | Cancel a task that has not finished, and withdraw any approval still waiting on it. |
SubscribeToTask | Stream status updates for an unfinished task until it ends (up to 30 minutes per stream). |
GetExtendedAgentCard | The card for your key: one interface and one skill per agent you can call. |
For convenience, GET /api/a2a/agent-card returns the same extended card, and GET /api/a2a/agents/AGENT_ID returns one agent's card. Both need the key.
Task states
| State | Meaning in Renly |
|---|---|
TASK_STATE_WORKING | The agent is running. |
TASK_STATE_INPUT_REQUIRED | Waiting for a person to approve an action in Renly. |
TASK_STATE_COMPLETED | Finished; the answer is in the response artifact. |
TASK_STATE_REJECTED | A person declined the waiting action, or the conversation was still busy with an earlier message. |
TASK_STATE_FAILED | The agent could not complete it. A run interrupted by a service restart is marked failed; send the message again. |
TASK_STATE_CANCELED | Cancelled by the caller. |
Versions and errors
Send A2A-Version: 1.0 on every request. If you leave it out but use the 1.0 method names, the request is accepted. Version 0.3 method names such as message/send are answered with a version error rather than "method not found". Batch requests are not supported.
| Code | Meaning |
|---|---|
-32600 | Invalid request (not JSON-RPC 2.0, or a batch) |
-32601 | Method not found |
-32602 | Invalid params, for example no tenant, an unknown agent, or a bad contextId |
-32603 | Internal error |
-32001 | Task not found |
-32002 | Task cannot be cancelled in its current state |
-32003 | Push notifications are not supported |
-32004 | Unsupported operation, for example continuing a finished task or subscribing to one |
-32005 | Content type not supported (inline file bytes, or no text output mode accepted) |
-32009 | A2A version not supported |
Authentication problems come back as HTTP errors before JSON-RPC: 401 for a missing, unknown, revoked or expired key, and 403 for a key without the a2a scope or whose owner has left the organization. Each key is limited to 60 requests a minute.