Coelevate Documentation

A2A Reference

How an external client talks to a deployed Coelevate agent over the A2A protocol — authentication, requests, task states and how to answer an agent's questions.

A deployed Coelevate agent speaks A2A, the open agent-to-agent protocol. Use it to run an agent from your own code: authenticate with an API key, send a request, follow the task as it works, answer if it asks you something, and read the result.

Everything below was checked against the platform's own code and the A2A SDK it is built on. The examples use shell variables so you can paste them as they are:

export API_URL="<the agent address your key panel shows>"   # no trailing slash
export TOKEN_URL="<the token address your key panel shows>"
export CLIENT_ID="<your key id>"
export CLIENT_SECRET="<your key secret>"

1. Authenticate

Create an API key with the Agent execution permission for the project. You receive the key id and secret once; keep them out of source control. Exchange them for a short-lived bearer token with the standard OAuth 2.0 client-credentials grant:

curl -sS -X POST "$TOKEN_URL" \
  -u "$CLIENT_ID:$CLIENT_SECRET" \
  -d "grant_type=client_credentials"
{
  "access_token": "eyJ…",
  "token_type": "Bearer",
  "expires_in": 600,
  "scope": "a2a:invoke",
  "project_id": "proj_…"
}
  • Send the key as HTTP Basic, as above, or as the form fields client_id and client_secret — not both.
  • The token lasts 600 seconds and there is no refresh token. Exchange the key again shortly before the token's own exp claim.
  • The only scope is a2a:invoke. It opens the agents of the key's project and nothing else.
  • project_id comes back in the reply. You never send it to the token endpoint.

Send the token on every call as Authorization: Bearer <access_token>.

Token-endpoint failures follow RFC 6749 ({"error": "...", "error_description": "..."}):

StatuserrorMeaning
401invalid_clientThe key id or secret is wrong, or the key was revoked.
400invalid_requestA required field is missing or both credential forms were sent.
400unsupported_grant_typeOnly client_credentials is accepted.
400invalid_scopeA scope the key does not hold was requested.
503temporarily_unavailableTry again with a short back-off.

Revoking a key stops new tokens; a token already issued stays valid until it expires.

2. Find an agent

List the agents deployed in your project:

curl -sS "$API_URL/agents" -H "Authorization: Bearer $TOKEN"

Each entry has an id and a card_url. Only deployed agents are listed; an agent that is not deployed answers 404 for its card, its contract and any new session.

No address names a project: your access token does. The same URL serves every project, and the token decides which one a call is for:

WhatAddress
Agent directoryGET /agents
Agent cardGET /agents/{agent_id}/.well-known/agent-card.json
ContractGET /agents/{agent_id}/contract
Every A2A callPOST /agents/{agent_id}/

Keep the trailing slash on the POST address. A redirect would drop the request body.

The agent card is a standard A2A AgentCard: the agent's name and description, the one skill it offers, its capabilities (streaming is supported), the security schemes it accepts (HTTP bearer, and OAuth 2.0 client credentials with the a2a:invoke scope) and the extensions it uses.

The contract tells you what to send. request.input_schema is a JSON Schema for the params of a request (below); a request that does not satisfy it is rejected. Read it each time rather than caching it: it describes the version of the agent that is deployed now.

3. Send a message

Every call is one JSON-RPC 2.0 POST to the agent's address. The method in the body picks the operation:

methodUse
message/sendSend a message; the reply comes back when the agent next needs you or finishes.
message/streamThe same, as a stream of task updates.
tasks/getRead a task's current state.
tasks/cancelStop a running task.
tasks/resubscribeRe-attach to the stream of a task that is still running.

tasks/get, tasks/cancel and tasks/resubscribe keep working for a task that already exists even if its agent has since been undeployed, and on an archived project tasks/get and tasks/cancel still answer while message/send is refused.

A message has two forms, chosen by its part type. Do not mix them in one message.

A question: a text part

A message of one text part is a conversation turn. The agent answers directly with a message, and no task is created. Use it to ask what the agent can do.

curl -sS -X POST "$API_URL/agents/$AGENT_ID/" \
  -H "Authorization: Bearer $TOKEN" -H "Content-Type: application/json" \
  -d '{
    "jsonrpc": "2.0",
    "id": "req-1",
    "method": "message/send",
    "params": {
      "message": {
        "kind": "message",
        "messageId": "11111111-1111-4111-8111-111111111111",
        "role": "user",
        "parts": [{"kind": "text", "text": "What can you do?"}]
      }
    }
  }'

The reply carries a contextId. Send it back on later messages to stay in the same conversation.

A request: a data part

To have the agent do its work, send one data part whose data is an envelope of a type and a payload. For a new request, type is new_request and the payload carries your free-text message, the agent's id, and params that satisfy the contract's input_schema:

{
  "jsonrpc": "2.0",
  "id": "req-2",
  "method": "message/send",
  "params": {
    "message": {
      "kind": "message",
      "messageId": "33333333-3333-4333-8333-333333333333",
      "role": "user",
      "contextId": "ctx-123",
      "parts": [
        {
          "kind": "data",
          "data": {
            "type": "new_request",
            "payload": {
              "message": "Qualify this lead and tell me what to do next.",
              "agent_id": "lead_qualification",
              "params": { "…": "as the contract's input_schema requires" }
            }
          }
        }
      ]
    }
  }
}

Put the free text inside the payload as message; a separate text part next to the data part is rejected rather than silently dropped.

Rules for the fields you choose:

  • messageId — a fresh UUID for every message.
  • contextId — optional. Omit it and one is created and returned. If you supply one it must be a non-blank printable string of at most 256 characters; anything else is rejected with JSON-RPC error -32602. Wait for a reply before sending the next message on the same contextId.
  • X-Persona-Id (header) — optional. It selects the role the agent acts for; it is not a credential. Leave it off to use the project's default.

The agent can also be told whom it acts for and which context to use; both travel as metadata on the message, using the extension addresses listed on the agent card (identity-claims and context-selection).

4. Follow the task

A request creates a task. Its status.state moves through:

StateMeaning
submittedAccepted.
workingIn progress. A sub_status in the update's metadata names the phase.
input-requiredThe agent needs you; see below. Not final.
completedFinished; the result was delivered. Final.
failedCould not be completed. Final.
canceledStopped by a tasks/cancel. Final.

A run that has produced its result — whether it succeeded or not — pauses at input-required and asks for feedback before the task reaches its final state. That is when the result is available, as described next.

Read the result

The result is the task's response artifact. Its first part is a text part with the agent's reply. Its second is a data part:

{ "status": "success", "output": { "…": "…" }, "deliveries": [], "issues": [] }

status tells you how far to trust it: success is safe to use automatically, partial is for a person to review first, and failed means do not use the output.

Answer the agent

A task in input-required carries a data part on its status message whose type says what is being asked:

typeAsks forAnswer with
question_requestA piece of information (question, optional suggestions).question_response
confirmation_requestApproval to proceed (summary, risks).confirmation_response
closure_reviewFeedback on the finished result.closure

Answer by sending a new message/send with the same contextId and taskId and one data part of the matching type. A text reply to a paused task is not an answer; it is treated as a new conversation turn and the run does not advance.

{
  "jsonrpc": "2.0",
  "id": "req-3",
  "method": "message/send",
  "params": {
    "message": {
      "kind": "message",
      "messageId": "22222222-2222-4222-8222-222222222222",
      "role": "user",
      "contextId": "ctx-123",
      "taskId": "task-456",
      "parts": [
        {
          "kind": "data",
          "data": { "type": "question_response", "payload": { "answer": "Yes, the budget is approved." } }
        }
      ]
    }
  }
}
Answerpayload
question_response{"answer": "…"} — answer may be null to decline.
confirmation_response{"confirmed": true, "comments": "…"} — comments is optional.
closure{"feedback": {"verdict": "success", "sentiment": "…", "comments": "…", "improvement_suggestions": ["…"]}} — every field is optional. verdict is success, partial or failure.

A question or confirmation can carry an expires_at time. Answering after it fails the session instead of resuming it.

Closing the task with closure is what moves it to its final state, and it is also how your verdict reaches the agent's learning loop: failure feedback is the most useful signal you can give.

Read or cancel a task

{ "jsonrpc": "2.0", "id": "req-4", "method": "tasks/get", "params": { "id": "task-456" } }
{ "jsonrpc": "2.0", "id": "req-5", "method": "tasks/cancel", "params": { "id": "task-456" } }

Errors

StatusWhenBody or header
401No tokenmissing platform token, with WWW-Authenticate: Bearer
401Expired or invalid tokeninvalid or expired platform token
401Not a bearer tokenmalformed bearer token
403The token lacks the scopethis token does not carry the a2a:invoke scope
403A back-office key was usedA back-office key cannot invoke agents; create an agent-execution key and exchange that.
404The agent is not deployed, or the token belongs to a different projectagent_not_found for an undeployed agent
413The request body is too largeThe limit is just under 1 MB.

Protocol-level problems — an invalid contextId, a message that mixes part types, params that do not satisfy the contract — come back as JSON-RPC errors in the body of a 200 response, not as HTTP errors.

Limits

  • Tokens last 600 seconds and cannot be refreshed; exchange the key again.
  • Request bodies are limited to just under 1 MB.
  • A key lasts 90 days by default.
  • No request-rate limit is documented yet. Back off and retry on 503.