A2A Protocol Integration
What is A2A and Why Should I Use It?
A2A (Agent-to-Agent) is a standardized way for AI agents to talk to each other. Think of it like a universal translator that allows different AI systems to communicate seamlessly.
The Problem A2A Solves
Imagine you have:
- A customer service AI from ToothFairyAI
- A scheduling AI from another platform
- An analytics AI running in your own system
Without A2A, these systems can't easily work together. Each has its own API format, authentication method, and way of tracking conversations.
A2A provides a universal standard so any A2A-compatible agent can communicate with any other A2A-compatible agent - regardless of who built them.
Real-World Use Cases
| Scenario | How A2A Helps |
|---|---|
| Multi-Agent Workflows | Your orchestrator AI can delegate tasks to specialized ToothFairyAI agents and get structured responses back |
| External System Integration | Connect ToothFairyAI agents to your existing AI infrastructure using a standard protocol |
| Agent Marketplaces | Discover and use agents from different providers through standardized agent cards |
| Cross-Platform Automation | Build automation pipelines that span multiple AI platforms |
When to Use A2A vs Regular API
| Use Case | Recommendation |
|---|---|
| Simple chatbot widget on your website | Use the Widget or REST API |
| Single agent, straightforward Q&A | Use the REST API |
| Multi-agent orchestration | Use A2A ✓ |
| Integration with other A2A-compatible systems | Use A2A ✓ |
| Building an AI agent that calls other AI agents | Use A2A ✓ |
| Standardized task tracking across multiple AI platforms | Use A2A ✓ |
If you just need to send messages to a ToothFairyAI agent and get responses, the REST API is simpler. Use A2A when you need standardized inter-agent communication or multi-agent orchestration.
Quick Start Guide
Follow these steps to make your first A2A request:
Step 1: Get Your Credentials
You need two things:
- API Key - Found in your ToothFairyAI workspace settings
- Agent ID - The UUID of the agent you want to communicate with
Step 2: Discover the Agent
First, verify your agent is available and check its capabilities:
curl -H "x-api-key: YOUR_API_KEY" \
"https://ais.toothfairyai.com/.well-known/agent.json?agentId=YOUR_AGENT_ID"
This returns an Agent Card describing what the agent can do.
Step 3: Send Your First Message
curl -X POST "https://ais.toothfairyai.com/" \
-H "Content-Type: application/json" \
-H "x-api-key: YOUR_API_KEY" \
-d '{
"jsonrpc": "2.0",
"method": "message/send",
"params": {
"message": {
"role": "user",
"parts": [{"text": "Hello! What can you help me with?"}]
},
"configuration": {
"agentId": "YOUR_AGENT_ID"
}
},
"id": 1
}'
You'll receive a Task object with an id. Save this ID for follow-up messages.
Step 4: Continue the Conversation
To send follow-up messages, include the task ID:
curl -X POST "https://ais.toothfairyai.com/" \
-H "Content-Type: application/json" \
-H "x-api-key: YOUR_API_KEY" \
-d '{
"jsonrpc": "2.0",
"method": "message/send",
"params": {
"message": {
"id": "TASK_ID_FROM_STEP_3",
"role": "user",
"parts": [{"text": "Tell me more about that"}]
}
},
"id": 2
}'
Step 5: Retrieve the Full Conversation
To get all messages and any generated artifacts:
curl -X POST "https://ais.toothfairyai.com/" \
-H "Content-Type: application/json" \
-H "x-api-key: YOUR_API_KEY" \
-d '{
"jsonrpc": "2.0",
"method": "tasks/get",
"params": {
"taskId": "TASK_ID_FROM_STEP_3"
},
"id": 3
}'
Understanding Key Concepts
Before diving into the technical details, understand these core concepts:
Tasks = Conversations
In A2A terminology, a Task is equivalent to a conversation or chat session. When you send a message:
- A new Task is created (or an existing one is continued)
- The Task tracks all messages between you and the agent
- The Task has a lifecycle:
active→completed(orfailed/canceled)
Messages = Chat Messages
Messages are the individual exchanges within a Task. Each message has:
- A role:
user(you) oragent(the AI) - Parts: The content (text, files, or structured data)
Artifacts = Generated Outputs
When an agent creates something tangible (an image, chart, document), it's called an Artifact. These are attached to the Task.
Agent Cards = Agent Profiles
An Agent Card is a standardized description of an agent's capabilities, like a profile or resume. It tells you what the agent can do before you interact with it.
Endpoints
Connect to ToothFairyAI's A2A server using these regional endpoints:
| Region | Endpoint |
|---|---|
| Australia | https://ais.toothfairyai.com |
| Europe | https://ais.eu.toothfairyai.com |
| United States | https://ais.us.toothfairyai.com |
Authentication
All requests require your API key in the header:
POST / HTTP/1.1
Host: ais.toothfairyai.com
Content-Type: application/json
x-api-key: your-api-key
A2A protocol access requires a Business or Enterprise subscription. See API Integration for details.
JSON-RPC Methods Reference
A2A uses JSON-RPC 2.0 format. Every request follows this structure:
{
"jsonrpc": "2.0",
"method": "method_name",
"params": { ... },
"id": 1
}
message/send
Send a message and receive a Task response.
Starting a new conversation:
{
"jsonrpc": "2.0",
"method": "message/send",
"params": {
"message": {
"role": "user",
"parts": [
{ "text": "Analyze the quarterly sales report" }
]
},
"configuration": {
"agentId": "your-agent-uuid",
"taskName": "Sales Analysis"
}
},
"id": 1
}
Continuing an existing conversation:
{
"jsonrpc": "2.0",
"method": "message/send",
"params": {
"message": {
"id": "existing-task-id",
"role": "user",
"parts": [
{ "text": "Can you compare with last quarter?" }
]
}
},
"id": 2
}
Response:
{
"jsonrpc": "2.0",
"result": {
"id": "task-uuid-123",
"name": "Sales Analysis",
"contextId": "task-uuid-123",
"state": "completed",
"status": "completed",
"messages": [
{
"role": "user",
"parts": [{ "text": "Analyze the quarterly sales report" }]
},
{
"role": "agent",
"parts": [{ "text": "Based on the quarterly sales data, I can see..." }]
}
],
"created_at": "2025-01-25T10:30:00Z",
"updated_at": "2025-01-25T10:35:00Z"
},
"id": 1
}
message/stream
For real-time streaming responses (useful for long outputs):
{
"jsonrpc": "2.0",
"method": "message/stream",
"params": {
"message": {
"role": "user",
"parts": [{ "text": "Write a detailed analysis" }]
},
"configuration": {
"agentId": "your-agent-uuid"
}
},
"id": 1
}
Response: Server-Sent Events (SSE) stream:
data: {"type": "task_initial", "taskId": "task-uuid"}
data: {"type": "task_status_update", "status": "working", "progress": 1}
data: {"type": "task_status_update", "status": "completed", "final": true}
data: {"type": "task_complete", "task": {...}}
data: {"type": "stream_end"}
tasks/get
Retrieve a task's current state, messages, and artifacts:
{
"jsonrpc": "2.0",
"method": "tasks/get",
"params": {
"taskId": "task-uuid-123",
"historyLength": 10
},
"id": 1
}
| Parameter | Type | Description |
|---|---|---|
taskId | string | Required. The task ID |
historyLength | number | Limit number of messages returned |
tasks/list
List all tasks with optional filtering:
{
"jsonrpc": "2.0",
"method": "tasks/list",
"params": {
"pageSize": 20,
"status": "completed"
},
"id": 1
}
| Parameter | Type | Description |
|---|---|---|
pageSize | number | Max results (1-100, default: 50) |
status | string | Filter: active, completed, failed, canceled |
includeArtifacts | boolean | Include artifacts in response |
Response:
{
"jsonrpc": "2.0",
"result": {
"tasks": [...],
"nextPageToken": "",
"pageSize": 20,
"totalSize": 45
},
"id": 1
}
tasks/cancel
Cancel a running task:
{
"jsonrpc": "2.0",
"method": "tasks/cancel",
"params": {
"taskId": "task-uuid-123"
},
"id": 1
}
agent/getExtendedCard
Get detailed information about an agent's capabilities:
{
"jsonrpc": "2.0",
"method": "agent/getExtendedCard",
"params": {
"agentId": "your-agent-uuid"
},
"id": 1
}
Task Lifecycle
Tasks follow a defined state machine:
┌─────────────┐ ┌─────────────┐ ┌─────────────┐
│ submitted │ ──▶ │ working │ ──▶ │ completed │
└─────────────┘ └─────────────┘ └─────────────┘
│
▼
┌─────────────┐
│ failed │
│ canceled │
└─────────────┘
States
| State | Description |
|---|---|
active | Task is being processed |
completed | Task finished successfully |
failed | An error occurred |
canceled | User canceled the task |
Statuses
| Status | When |
|---|---|
submitted | Task received, queued for processing |
working | Agent is actively processing |
input_required | Agent needs more information |
completed | Successfully finished |
Code Examples
Python
import requests
class ToothFairyA2A:
def __init__(self, api_key: str, region: str = "au"):
regions = {
"au": "https://ais.toothfairyai.com",
"eu": "https://ais.eu.toothfairyai.com",
"us": "https://ais.us.toothfairyai.com"
}
self.base_url = regions.get(region, regions["au"])
self.headers = {
"Content-Type": "application/json",
"x-api-key": api_key
}
def _request(self, method: str, params: dict) -> dict:
response = requests.post(
f"{self.base_url}/",
headers=self.headers,
json={
"jsonrpc": "2.0",
"method": method,
"params": params,
"id": 1
}
)
return response.json()
def send_message(self, agent_id: str, text: str, task_id: str = None) -> dict:
"""Send a message to an agent. Returns the task."""
params = {
"message": {
"role": "user",
"parts": [{"text": text}]
},
"configuration": {"agentId": agent_id}
}
if task_id:
params["message"]["id"] = task_id
return self._request("message/send", params)
def get_task(self, task_id: str) -> dict:
"""Get task details including all messages."""
return self._request("tasks/get", {"taskId": task_id})
def list_tasks(self, page_size: int = 20) -> dict:
"""List all tasks."""
return self._request("tasks/list", {"pageSize": page_size})
def cancel_task(self, task_id: str) -> dict:
"""Cancel a running task."""
return self._request("tasks/cancel", {"taskId": task_id})
# Example usage
client = ToothFairyA2A(api_key="your-api-key")
# Start a conversation
result = client.send_message(
agent_id="your-agent-id",
text="What can you help me with?"
)
task_id = result["result"]["id"]
print(f"Task created: {task_id}")
# Continue the conversation
result = client.send_message(
agent_id="your-agent-id",
text="Tell me more about document analysis",
task_id=task_id
)
# Get the full conversation
task = client.get_task(task_id)
for msg in task["result"]["messages"]:
role = msg["role"]
text = msg["parts"][0].get("text", "")
print(f"{role}: {text[:100]}...")
JavaScript/TypeScript
class ToothFairyA2A {
private baseUrl: string;
private headers: Record<string, string>;
constructor(apiKey: string, region: 'au' | 'eu' | 'us' = 'au') {
const regions = {
au: 'https://ais.toothfairyai.com',
eu: 'https://ais.eu.toothfairyai.com',
us: 'https://ais.us.toothfairyai.com'
};
this.baseUrl = regions[region];
this.headers = {
'Content-Type': 'application/json',
'x-api-key': apiKey
};
}
private async request(method: string, params: any): Promise<any> {
const response = await fetch(`${this.baseUrl}/`, {
method: 'POST',
headers: this.headers,
body: JSON.stringify({
jsonrpc: '2.0',
method,
params,
id: Date.now()
})
});
return response.json();
}
async sendMessage(agentId: string, text: string, taskId?: string) {
const params: any = {
message: {
role: 'user',
parts: [{ text }]
},
configuration: { agentId }
};
if (taskId) params.message.id = taskId;
return this.request('message/send', params);
}
async getTask(taskId: string) {
return this.request('tasks/get', { taskId });
}
async listTasks(pageSize = 20) {
return this.request('tasks/list', { pageSize });
}
async cancelTask(taskId: string) {
return this.request('tasks/cancel', { taskId });
}
}
// Example usage
const client = new ToothFairyA2A('your-api-key');
// Start conversation
const result = await client.sendMessage('agent-id', 'Hello!');
const taskId = result.result.id;
// Continue conversation
await client.sendMessage('agent-id', 'Tell me more', taskId);
// Get full conversation
const task = await client.getTask(taskId);
console.log(task.result.messages);
cURL Quick Reference
# Send a new message
curl -X POST "https://ais.toothfairyai.com/" \
-H "Content-Type: application/json" \
-H "x-api-key: YOUR_API_KEY" \
-d '{"jsonrpc":"2.0","method":"message/send","params":{"message":{"role":"user","parts":[{"text":"Hello"}]},"configuration":{"agentId":"AGENT_ID"}},"id":1}'
# Get task details
curl -X POST "https://ais.toothfairyai.com/" \
-H "Content-Type: application/json" \
-H "x-api-key: YOUR_API_KEY" \
-d '{"jsonrpc":"2.0","method":"tasks/get","params":{"taskId":"TASK_ID"},"id":1}'
# List tasks
curl -X POST "https://ais.toothfairyai.com/" \
-H "Content-Type: application/json" \
-H "x-api-key: YOUR_API_KEY" \
-d '{"jsonrpc":"2.0","method":"tasks/list","params":{"pageSize":10},"id":1}'
Error Handling
Common Errors
| Error | Cause | Solution |
|---|---|---|
Missing x-api-key header | No API key provided | Add x-api-key header |
Invalid credentials | Wrong API key | Check your API key in workspace settings |
agentId in configuration for new tasks | Missing agent ID | Include agentId in configuration |
Task not found | Invalid task ID | Verify the task ID exists |
Error Response Format
{
"jsonrpc": "2.0",
"error": {
"code": -32602,
"message": "Invalid params",
"data": {
"missing": "agentId in configuration for new tasks"
}
},
"id": 1
}
JSON-RPC Error Codes
| Code | Meaning |
|---|---|
| -32700 | Invalid JSON |
| -32600 | Invalid request format |
| -32601 | Unknown method |
| -32602 | Invalid parameters |
| -32603 | Internal server error |
| -32000 | Task not found |
Best Practices
Do's
- ✅ Store task IDs for multi-turn conversations
- ✅ Check task state before sending follow-ups
- ✅ Use streaming for long-running tasks
- ✅ Implement retry logic for transient errors
Don'ts
- ❌ Don't expose API keys in client-side code
- ❌ Don't send follow-ups to completed/canceled tasks
- ❌ Don't ignore error responses
Troubleshooting
"Task not found" Error
Possible causes:
- The task ID is incorrect
- The task belongs to a different workspace
- The task was deleted
Solution: Verify the task ID and ensure you're using the correct API key for that workspace.
Empty Messages Array
Possible cause: The task is still processing.
Solution: Check the task state. If it's active with status working, wait and poll again.
Agent Not Responding
Possible causes:
- Invalid agent ID
- Agent is disabled
- Agent doesn't have the required capabilities
Solution: Use agent/getExtendedCard to verify the agent exists and check its capabilities.
Related Documentation
- REST API - Standard API for simpler integrations
- MCP Integration - Model Context Protocol support
- Agent Configuration - Creating and configuring agents
- API Reference - Complete API documentation
- Check the API Documentation
- Test with the code examples above
- Contact support@toothfairyai.com for subscription questions