Skip to main content

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

ScenarioHow A2A Helps
Multi-Agent WorkflowsYour orchestrator AI can delegate tasks to specialized ToothFairyAI agents and get structured responses back
External System IntegrationConnect ToothFairyAI agents to your existing AI infrastructure using a standard protocol
Agent MarketplacesDiscover and use agents from different providers through standardized agent cards
Cross-Platform AutomationBuild automation pipelines that span multiple AI platforms

When to Use A2A vs Regular API

Use CaseRecommendation
Simple chatbot widget on your websiteUse the Widget or REST API
Single agent, straightforward Q&AUse the REST API
Multi-agent orchestrationUse A2A
Integration with other A2A-compatible systemsUse A2A
Building an AI agent that calls other AI agentsUse A2A
Standardized task tracking across multiple AI platformsUse A2A
Quick Decision

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:

  1. API Key - Found in your ToothFairyAI workspace settings
  2. 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:

  1. A new Task is created (or an existing one is continued)
  2. The Task tracks all messages between you and the agent
  3. The Task has a lifecycle: activecompleted (or failed/canceled)

Messages = Chat Messages

Messages are the individual exchanges within a Task. Each message has:

  • A role: user (you) or agent (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:

RegionEndpoint
Australiahttps://ais.toothfairyai.com
Europehttps://ais.eu.toothfairyai.com
United Stateshttps://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
Subscription Required

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
}
ParameterTypeDescription
taskIdstringRequired. The task ID
historyLengthnumberLimit 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
}
ParameterTypeDescription
pageSizenumberMax results (1-100, default: 50)
statusstringFilter: active, completed, failed, canceled
includeArtifactsbooleanInclude 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

StateDescription
activeTask is being processed
completedTask finished successfully
failedAn error occurred
canceledUser canceled the task

Statuses

StatusWhen
submittedTask received, queued for processing
workingAgent is actively processing
input_requiredAgent needs more information
completedSuccessfully 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

ErrorCauseSolution
Missing x-api-key headerNo API key providedAdd x-api-key header
Invalid credentialsWrong API keyCheck your API key in workspace settings
agentId in configuration for new tasksMissing agent IDInclude agentId in configuration
Task not foundInvalid task IDVerify 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

CodeMeaning
-32700Invalid JSON
-32600Invalid request format
-32601Unknown method
-32602Invalid parameters
-32603Internal server error
-32000Task 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:

  1. The task ID is incorrect
  2. The task belongs to a different workspace
  3. 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:

  1. Invalid agent ID
  2. Agent is disabled
  3. Agent doesn't have the required capabilities

Solution: Use agent/getExtendedCard to verify the agent exists and check its capabilities.


Need Help?