kaman.ai

Docs

Documentation

Guides, use cases & API reference

  • Overview
    • Getting Started
    • Platform Overview
  • Features
    • Features Overview
    • AI Assistant
    • Workflow Automation
    • Intelligent Memory
    • Data Management
    • Universal Integrations
    • Communication Channels
    • Security & Control
  • Use Cases Overview
  • Financial Services
  • Fraud Detection
  • Supply Chain
  • Technical Support
  • Software Development
  • Smart ETL
  • Data Governance
  • ESG Reporting
  • TAC Management
  • Reference
    • API Reference
  • Guides
    • Getting Started
    • Authentication
  • Endpoints
    • Workflows API
    • Tools API
    • KDL (Data Lake) API
    • OpenAI-Compatible API
    • A2A Protocol
    • Skills API
    • Knowledge Base (RAG) API
    • Communication Channels

A2A Protocol (Agent-to-Agent)

Kaman implements the A2A protocol (v0.3), enabling agent-to-agent communication over JSON-RPC 2.0. This allows external agents, orchestrators, and multi-agent systems to discover and interact with Kaman agents using a standardized protocol.

Base URL

/api/a2a

For self-hosted installations: http://kaman.ai/api/a2a

Authentication

All endpoints (except public agent card discovery) require a Bearer token:

Authorization: Bearer <your-kaman-token>

Endpoints Overview

MethodPathAuthDescription
GET/api/a2a/agentsRequiredList all available agents
GET/api/a2a/{expertId}/.well-known/agent-card.jsonOptionalAgent discovery card
POST/api/a2a/{expertId}RequiredJSON-RPC 2.0 dispatcher
GET/api/a2a/tasks/{taskId}RequiredGet task status

Agent Discovery

List All Agents

bash
curl http://kaman.ai/api/a2a/agents \
  -H "Authorization: Bearer $KAMAN_TOKEN"

Response:

json
{
  "agents": [
    {
      "id": "42_0",
      "name": "Sales Assistant",
      "description": "Handles sales inquiries and CRM operations",
      "url": "http://kaman.ai/api/a2a/42_0",
      "cardUrl": "http://kaman.ai/api/a2a/42_0/.well-known/agent-card.json"
    }
  ],
  "total": 1
}

Agent Card (Discovery)

The agent card is the A2A standard mechanism for discovering agent capabilities. It can be accessed without authentication (returns minimal info) or with a token (returns full capabilities).

bash
curl http://kaman.ai/api/a2a/42_0/.well-known/agent-card.json

Response (Authenticated):

json
{
  "name": "Kaman Agent 42_0",
  "description": "Sales Assistant — Handles sales inquiries and CRM operations",
  "version": "1.0",
  "url": "http://kaman.ai/api/a2a/42_0",
  "provider": {
    "organization": "Yoctotta",
    "url": "http://kaman.ai"
  },
  "capabilities": {
    "streaming": true,
    "pushNotifications": false,
    "extendedAgentCard": true
  },
  "defaultInputModes": ["text/plain"],
  "defaultOutputModes": ["text/plain", "application/json"],
  "skills": [
    {
      "id": "getQuarterlySales",
      "name": "getQuarterlySales",
      "description": "Retrieves quarterly sales data",
      "tags": ["sales", "reporting"],
      "examples": ["What were Q3 sales?"]
    },
    {
      "id": "sendEmail",
      "name": "sendEmail",
      "description": "Sends an email to specified recipients",
      "tags": ["email", "communication"]
    }
  ],
  "securitySchemes": {
    "bearer": {
      "type": "http",
      "scheme": "bearer",
      "bearerFormat": "JWT"
    }
  },
  "security": [{"bearer": []}]
}

Unauthenticated: Returns a minimal card with empty skills and generic description.


JSON-RPC 2.0 Methods

All methods are sent as POST requests to /api/a2a/{expertId} using JSON-RPC 2.0 format.

Message Format

Messages use A2A's multi-part format:

json
{
  "role": "user",
  "parts": [
    {"kind": "text", "text": "Analyze the sales data"},
    {"kind": "file", "file": {"url": "https://...", "mimeType": "text/csv"}},
    {"kind": "data", "data": {"quarter": "Q3", "year": 2024}}
  ],
  "messageId": "msg-123",
  "contextId": "ctx-456",
  "taskId": "task-789"
}

Part types:

KindFieldsDescription
texttextPlain text content
filefile.url, file.bytes, file.mimeType, file.nameFile attachment (URL or base64)
datadataStructured JSON data

message/send — Non-Streaming

Send a message and wait for the complete response.

Request:

json
{
  "jsonrpc": "2.0",
  "id": "req-1",
  "method": "message/send",
  "params": {
    "message": {
      "role": "user",
      "parts": [
        {"kind": "text", "text": "What were last quarter's sales?"}
      ],
      "messageId": "msg-001"
    }
  }
}

Response:

json
{
  "jsonrpc": "2.0",
  "id": "req-1",
  "result": {
    "id": "task-abc123",
    "contextId": "ctx-def456",
    "status": {
      "state": "completed",
      "timestamp": "2024-02-25T10:30:00Z"
    },
    "artifacts": [
      {
        "artifactId": "art-001",
        "name": "response",
        "parts": [
          {"kind": "text", "text": "Last quarter's total sales were $2.4M, up 12% from Q2."}
        ]
      }
    ],
    "history": [
      {
        "role": "user",
        "parts": [{"kind": "text", "text": "What were last quarter's sales?"}],
        "messageId": "msg-001"
      },
      {
        "role": "agent",
        "parts": [{"kind": "text", "text": "Last quarter's total sales were $2.4M, up 12% from Q2."}],
        "messageId": "msg-002"
      }
    ],
    "kind": "task"
  }
}

message/stream — Streaming

Send a message and receive Server-Sent Events as the agent works.

Request: Same as message/send, but use method message/stream.

json
{
  "jsonrpc": "2.0",
  "id": "req-2",
  "method": "message/stream",
  "params": {
    "message": {
      "role": "user",
      "parts": [{"kind": "text", "text": "Summarize the latest report"}]
    }
  }
}

SSE Response:

data: {"jsonrpc":"2.0","id":"req-2","result":{"kind":"status-update","taskId":"task-xyz","contextId":"ctx-abc","status":{"state":"working","timestamp":"2024-02-25T10:30:00Z"},"final":false}} data: {"jsonrpc":"2.0","id":"req-2","result":{"kind":"artifact-update","taskId":"task-xyz","contextId":"ctx-abc","artifact":{"artifactId":"art-001","parts":[{"kind":"text","text":"The report shows "}],"lastChunk":false,"append":true}}} data: {"jsonrpc":"2.0","id":"req-2","result":{"kind":"artifact-update","taskId":"task-xyz","contextId":"ctx-abc","artifact":{"artifactId":"art-001","parts":[{"kind":"text","text":"revenue growth of 15% year-over-year."}],"lastChunk":true,"append":true}}} data: {"jsonrpc":"2.0","id":"req-2","result":{"kind":"status-update","taskId":"task-xyz","contextId":"ctx-abc","status":{"state":"completed","timestamp":"2024-02-25T10:30:05Z"},"final":true}}

Stream event types:

Event KindDescription
status-updateTask state change (working, completed, failed, etc.)
artifact-updatePartial content chunks (use append: true for incremental)

tasks/get — Get Task Status

Retrieve a previously created task by ID.

json
{
  "jsonrpc": "2.0",
  "id": "req-3",
  "method": "tasks/get",
  "params": {"id": "task-abc123"}
}

You can also use the REST endpoint:

bash
curl http://kaman.ai/api/a2a/tasks/task-abc123 \
  -H "Authorization: Bearer $KAMAN_TOKEN"

tasks/cancel — Cancel a Task

Cancel a running task. Only works for tasks in non-terminal states.

json
{
  "jsonrpc": "2.0",
  "id": "req-4",
  "method": "tasks/cancel",
  "params": {"id": "task-abc123"}
}

Response: Updated task with state: "canceled".


Task Lifecycle

StateDescription
submittedTask received, queued for processing
workingAgent is actively processing
completedTask finished successfully
failedTask encountered an error
canceledTask was canceled by client
input_requiredAgent needs human input (interrupt)
auth_requiredAgent needs auth confirmation
rejectedTask was rejected

Error Handling

Errors follow JSON-RPC 2.0 format:

json
{
  "jsonrpc": "2.0",
  "id": "req-1",
  "error": {
    "code": -32000,
    "message": "Internal server error",
    "data": {"detail": "Agent timeout after 300s"}
  }
}
CodeMeaning
-32700Parse error — invalid JSON
-32600Invalid request — missing required fields
-32601Method not found
-32602Invalid params
-32001Task not found
-32002Task in terminal state (cannot cancel)
-32000Server error

Code Examples

Python

python
import requests
import json
import sseclient  # pip install sseclient-py

BASE_URL = "http://kaman.ai/api/a2a"
TOKEN = "your-kaman-token"
HEADERS = {
    "Authorization": f"Bearer {TOKEN}",
    "Content-Type": "application/json",
}

# 1. Discover agents
agents = requests.get(f"{BASE_URL}/agents", headers=HEADERS).json()
agent_id = agents["agents"][0]["id"]
print(f"Using agent: {agents['agents'][0]['name']}")

# 2. Get agent card
card = requests.get(
    f"{BASE_URL}/{agent_id}/.well-known/agent-card.json",
    headers=HEADERS,
).json()
print(f"Skills: {[s['name'] for s in card['skills']]}")

# 3. Send message (non-streaming)
response = requests.post(
    f"{BASE_URL}/{agent_id}",
    headers=HEADERS,
    json={
        "jsonrpc": "2.0",
        "id": "1",
        "method": "message/send",
        "params": {
            "message": {
                "role": "user",
                "parts": [{"kind": "text", "text": "Hello, what can you do?"}],
            }
        },
    },
)
result = response.json()["result"]
print(f"Status: {result['status']['state']}")
for artifact in result.get("artifacts", []):
    for part in artifact["parts"]:
        if part["kind"] == "text":
            print(part["text"])

# 4. Stream message
response = requests.post(
    f"{BASE_URL}/{agent_id}",
    headers=HEADERS,
    json={
        "jsonrpc": "2.0",
        "id": "2",
        "method": "message/stream",
        "params": {
            "message": {
                "role": "user",
                "parts": [{"kind": "text", "text": "Analyze Q3 performance"}],
            }
        },
    },
    stream=True,
)
client = sseclient.SSEClient(response)
for event in client.events():
    data = json.loads(event.data)
    result = data.get("result", {})
    if result.get("kind") == "artifact-update":
        for part in result["artifact"]["parts"]:
            if part["kind"] == "text":
                print(part["text"], end="", flush=True)
    elif result.get("kind") == "status-update":
        if result["status"]["state"] == "completed":
            print("\n[Done]")

TypeScript

typescript
const BASE_URL = "http://kaman.ai/api/a2a";
const TOKEN = "your-kaman-token";

// Discover agents
const agents = await fetch(`${BASE_URL}/agents`, {
  headers: { Authorization: `Bearer ${TOKEN}` },
}).then((r) => r.json());

const agentId = agents.agents[0].id;

// Send message (non-streaming)
const response = await fetch(`${BASE_URL}/${agentId}`, {
  method: "POST",
  headers: {
    Authorization: `Bearer ${TOKEN}`,
    "Content-Type": "application/json",
  },
  body: JSON.stringify({
    jsonrpc: "2.0",
    id: "1",
    method: "message/send",
    params: {
      message: {
        role: "user",
        parts: [{ kind: "text", text: "Hello!" }],
      },
    },
  }),
});
const { result } = await response.json();
console.log(result.artifacts[0].parts[0].text);

// Streaming
const stream = await fetch(`${BASE_URL}/${agentId}`, {
  method: "POST",
  headers: {
    Authorization: `Bearer ${TOKEN}`,
    "Content-Type": "application/json",
  },
  body: JSON.stringify({
    jsonrpc: "2.0",
    id: "2",
    method: "message/stream",
    params: {
      message: {
        role: "user",
        parts: [{ kind: "text", text: "Summarize report" }],
      },
    },
  }),
});

const reader = stream.body!.getReader();
const decoder = new TextDecoder();

while (true) {
  const { done, value } = await reader.read();
  if (done) break;

  const text = decoder.decode(value);
  for (const line of text.split("\n")) {
    if (!line.startsWith("data: ")) continue;
    const data = JSON.parse(line.slice(6));
    const event = data.result;

    if (event?.kind === "artifact-update") {
      for (const part of event.artifact.parts) {
        if (part.kind === "text") process.stdout.write(part.text);
      }
    }
  }
}

cURL

bash
# List agents
curl http://kaman.ai/api/a2a/agents \
  -H "Authorization: Bearer $KAMAN_TOKEN"

# Get agent card
curl http://kaman.ai/api/a2a/42_0/.well-known/agent-card.json

# Send message
curl -X POST http://kaman.ai/api/a2a/42_0 \
  -H "Authorization: Bearer $KAMAN_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "jsonrpc": "2.0",
    "id": "1",
    "method": "message/send",
    "params": {
      "message": {
        "role": "user",
        "parts": [{"kind": "text", "text": "Hello!"}]
      }
    }
  }'

# Stream message
curl -N -X POST http://kaman.ai/api/a2a/42_0 \
  -H "Authorization: Bearer $KAMAN_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "jsonrpc": "2.0",
    "id": "2",
    "method": "message/stream",
    "params": {
      "message": {
        "role": "user",
        "parts": [{"kind": "text", "text": "Analyze Q3 data"}]
      }
    }
  }'

Comparison: OpenAI API vs A2A

FeatureOpenAI APIA2A Protocol
ProtocolREST + SSEJSON-RPC 2.0 + SSE
SDK supportOpenAI SDK (Python, TS, etc.)Any HTTP client
Agent discoveryGET /modelsAgent Cards (.well-known)
Message format{role, content}{role, parts[]} (multi-modal)
Task trackingStateless (per-request)Persistent tasks with status
CancellationNot supportedtasks/cancel
Multi-modalText onlyText, files, structured data
Best forOpenAI SDK compatibility, LLM modeAgent orchestration, multi-agent systems

Timeouts & Limits

SettingValue
Request timeout5 minutes
Task TTL24 hours
Max concurrent tasks10,000
CORSOpen (*)

Next Steps

  • OpenAI-Compatible API — Use standard OpenAI SDK
  • Authentication — API authentication guide
  • Tools API — Search and execute individual tools

On this page

  • Base URL
  • Authentication
  • Endpoints Overview
  • Agent Discovery
  • List All Agents
  • Agent Card (Discovery)
  • JSON-RPC 2.0 Methods
  • Message Format
  • `message/send` — Non-Streaming
  • `message/stream` — Streaming
  • `tasks/get` — Get Task Status
  • `tasks/cancel` — Cancel a Task
  • Task Lifecycle
  • Error Handling
  • Code Examples
  • Python
  • TypeScript
  • cURL
  • Comparison: OpenAI API vs A2A
  • Timeouts & Limits
  • Next Steps