API Reference
Complete reference for the A2A protocol implementation: architecture, methods, types, scopes, security, and the ohwow-connect relay.
Note
How It Works#
ohwow operates in two modes simultaneously. As a server, it accepts incoming A2A requests from external agents. As a client, it makes outbound requests to external A2A agents.
┌─────────────────────────────────────────────────────────┐
│ ohwow Platform │
│ │
│ ┌──────────────┐ ┌──────────────┐ ┌────────────┐ │
│ │ A2A Server │ │ Agent Core │ │ A2A Client │ │
│ │ │───▶│ │──▶│ │ │
│ │ /api/a2a │ │ Execution │ │ Outbound │ │
│ │ Agent Card │ │ Engine │ │ Requests │ │
│ └──────┬───────┘ └──────────────┘ └─────┬──────┘ │
│ │ │ │
│ Auth │ Scopes Discovery │
│ Rate │ Limit & Tasks │
│ │ │ │
└─────────┼─────────────────────────────────────┼─────────┘
│ │
▼ ▼
┌──────────────┐ ┌──────────────┐
│ External │ │ External │
│ Agents │ │ A2A Agents │
│ (inbound) │ │ (outbound) │
└──────────────┘ └──────────────┘Inbound Request Flow#
When an external agent calls your ohwow instance, the request flows through several layers:
External Agent
│
▼
┌─────────┐ ┌─────────┐ ┌──────────┐ ┌──────────┐
│ Auth │───▶│ Scope │───▶│ Rate │───▶│ Agent │
│ Check │ │ Check │ │ Limit │ │ Execute │
│ │ │ │ │ │ │ │
│ Bearer │ │ Verify │ │ Sliding │ │ Run the │
│ Token │ │ scopes │ │ window │ │ task │
└─────────┘ └─────────┘ └──────────┘ └────┬─────┘
│
▼
┌──────────┐
│ Sanitize │
│ Response │
│ │
│ Strip │
│ metadata │
└──────────┘- Authentication: Bearer token with
ohw_a2a_prefix is validated against hashed keys in the database. - Scope check: The requested method is checked against the key's allowed scopes. For example,
tasks.createis required fortasks/send. - Rate limiting: Sliding window rate limiter enforces per-minute and per-hour limits per API key.
- Execution: The matched agent processes the task using the standard execution engine.
- Sanitization: Internal metadata (workspace IDs, costs, model config) is stripped before the response is returned.
Outbound Request Flow#
When your agents need to call external A2A agents, the outbound flow handles discovery, authentication, and result storage:
Your ohwow Agent
│
▼
┌──────────┐ ┌──────────┐ ┌──────────┐ ┌──────────┐
│ Discover │───▶│ Auth │───▶│ Send │───▶│ Store │
│ │ │ │ │ Task │ │ Result │
│ Fetch │ │ Use the │ │ │ │ │
│ Agent │ │ saved │ │ JSON-RPC │ │ Summary │
│ Card │ │ creds │ │ request │ │ only │
└──────────┘ └──────────┘ └──────────┘ └──────────┘- Discovery: Fetch and cache the remote agent's Agent Card to understand its capabilities.
- Authentication: Use the credentials configured in the connection (API key, OAuth, bearer token, or mTLS).
- Task send: Dispatch the JSON-RPC request and wait for the result.
- Result storage: By default, only a summary (max 500 characters) is stored. Raw results are not persisted unless
store_resultsis enabled.
Agent Card Schema#
The Agent Card is published at /.well-known/agent.jsonand describes your agents' capabilities.
interface A2AAgentCard {
name: string; // Display name
description: string; // What this agent system does
url: string; // A2A endpoint URL
version: string; // Protocol version (e.g. "0.3")
capabilities: {
streaming: boolean; // Supports SSE streaming
pushNotifications: boolean; // Supports push notifications
stateTransitionHistory: boolean; // Returns task history
};
authentication: {
schemes: string[]; // e.g. ["bearer"]
credentials?: string; // How to get credentials
};
defaultInputModes: string[]; // e.g. ["text"]
defaultOutputModes: string[]; // e.g. ["text"]
skills: A2ASkill[]; // List of agent capabilities
}Skill Schema#
interface A2ASkill {
id: string; // Unique identifier (e.g. "content-writing")
name: string; // Human-readable name
description: string; // What this skill does
tags?: string[]; // Searchable tags
examples?: string[]; // Example prompts
inputModes?: string[]; // Override default input modes
outputModes?: string[]; // Override default output modes
}Authentication#
All A2A requests require a Bearer token in the Authorization header. Keys use the ohw_a2a_ prefix.
POST /api/a2a HTTP/1.1
Content-Type: application/json
Authorization: Bearer ohw_a2a_your_key_hereJSON-RPC Methods#
All requests use JSON-RPC 2.0 format. The endpoint is /api/a2a.
| Method | Required Scope | Description |
|---|---|---|
| tasks/send | tasks.create | Send a message to create or continue a task |
| tasks/sendSubscribe | tasks.stream | Send a message with SSE streaming response |
| tasks/get | tasks.read | Get the current status and result of a task |
| tasks/cancel | tasks.cancel | Cancel a running task |
| tasks/pushNotification/set | tasks.create | Register a webhook for task updates |
| tasks/pushNotification/get | tasks.read | Get push notification config for a task |
tasks/send#
{
"jsonrpc": "2.0",
"id": "1",
"method": "tasks/send",
"params": {
"message": {
"role": "user",
"parts": [
{ "type": "text", "text": "Your task description here" }
]
},
"sessionId": "optional-session-id"
}
}{
"jsonrpc": "2.0",
"id": "1",
"result": {
"id": "task_abc123",
"status": {
"state": "completed",
"message": {
"role": "agent",
"parts": [{ "type": "text", "text": "Here are the results..." }]
}
},
"artifacts": []
}
}tasks/get#
{
"jsonrpc": "2.0",
"id": "2",
"method": "tasks/get",
"params": {
"id": "task_abc123",
"historyLength": 10
}
}Scopes#
Each API key has a set of scopes that determine what operations it can perform.
| Scope | Description |
|---|---|
| tasks.read | Read task status and results |
| tasks.create | Create new tasks via tasks/send |
| tasks.cancel | Cancel running tasks |
| tasks.stream | Use streaming (SSE) responses |
| agents.read | Read individual agent details |
| agents.list | List available agents |
| results.read | Read task result data |
| results.files | Access file artifacts from results |
Trust & Security#
Trust Levels#
Every A2A API key and connection is assigned a trust level that controls what actions are allowed. Higher levels include all permissions from lower levels.
| Level | What It Allows | Best For |
|---|---|---|
| read_only | List agents, read task status, read results | Dashboards, monitoring, analytics |
| execute | Everything above + create new tasks | Partner integrations, limited automation |
| autonomous | Everything above + cancel tasks, access file artifacts | Full automation, CI/CD pipelines |
| admin | All scopes including streaming | Internal tools only (not for external use) |
Warning
PII Redaction#
Before any response leaves ohwow via the A2A protocol, the sanitizer automatically strips internal metadata that should never be exposed to external agents:
- workspace_id
- user_id
- internal_task_id
- system_prompt
- cost_breakdown
- model_config
- browser_session_id
- memory_document
- api_key_id
This happens at the protocol layer, so agents and task handlers don't need to worry about accidentally leaking internal data.
Summaries-Only Mode#
For outbound A2A connections, ohwow stores only a summary of each result by default, not the raw data. Summaries are capped at 500 characters.
This means even if the external agent returns large payloads, only a concise human-readable summary is persisted in your workspace. You can enable full result storage per connection by setting store_results: true.
Data Sovereignty#
You have full control over what data leaves your workspace via A2A:
- Outbound connections: Each connection has configurable
allowed_data_typesthat restrict what categories of data can be sent. - Result retention: Set
result_retention_hoursper connection. After this window, stored results are deleted. - Agent-level permissions: Control which of your agents an external key or connection can access.
Rate Limiting#
Every A2A API key has two rate limits enforced via a sliding window:
- Per-minute limit: Prevents burst abuse. Default: 60 requests/minute.
- Per-hour limit: Prevents sustained abuse. Default: 1,000 requests/hour.
When rate limited, the API returns error code -32004 (RATE_LIMITED). Callers should implement exponential backoff.
Key Rotation#
To rotate an API key:
- Create a new key with the same scopes in your dashboard settings.
- Update your external systems to use the new key.
- Revoke the old key. Revoked keys are immediately rejected with no grace period.
Tip
expires_at when creating a key for automatic rotation.Error Codes#
| Code | Name | Description |
|---|---|---|
| -32700 | PARSE_ERROR | Invalid JSON in request body |
| -32600 | INVALID_REQUEST | Missing required JSON-RPC fields |
| -32601 | METHOD_NOT_FOUND | Unknown method name |
| -32602 | INVALID_PARAMS | Missing or invalid parameters |
| -32603 | INTERNAL_ERROR | Server-side error during execution |
| -32001 | TASK_NOT_FOUND | No task with the given ID |
| -32002 | TASK_NOT_CANCELABLE | Task is already completed or failed |
| -32003 | UNAUTHORIZED | Invalid or missing API key |
| -32004 | RATE_LIMITED | Too many requests; try again later |
| -32005 | AGENT_NOT_FOUND | No agent matches the request |
| -32006 | SCOPE_INSUFFICIENT | API key lacks the required scope |
Key Types#
Task#
interface A2ATask {
id: string;
sessionId?: string;
status: {
state: 'submitted' | 'working' | 'input-required'
| 'completed' | 'canceled' | 'failed';
message?: A2AMessage;
timestamp?: string;
};
artifacts?: A2AArtifact[];
history?: A2AMessage[];
metadata?: Record<string, unknown>;
}Message & Parts#
interface A2AMessage {
role: 'user' | 'agent';
parts: A2APart[];
metadata?: Record<string, unknown>;
}
type A2APart = A2ATextPart | A2AFilePart | A2ADataPart;
interface A2ATextPart {
type: 'text';
text: string;
}
interface A2AFilePart {
type: 'file';
file: {
name?: string;
mimeType?: string;
bytes?: string; // base64
uri?: string;
};
}
interface A2ADataPart {
type: 'data';
data: Record<string, unknown>;
}Artifact#
interface A2AArtifact {
name?: string;
description?: string;
parts: A2APart[];
index: number;
append?: boolean;
lastChunk?: boolean;
}Note
src/lib/a2a/types.ts.ohwow-connect Relay#
What Is It?#
ohwow-connect is a lightweight relay that runs on your machine or private network. It creates a secure tunnel to the ohwow platform, allowing your locally-running agents to receive and respond to A2A tasks without exposing them to the public internet.
This is ideal for agents that access internal databases, run on private infrastructure, or need to stay behind a firewall.
Docker Setup#
The fastest way to get started:
docker run -d \
--name ohwow-connect \
-e OHWOW_API_KEY=ohw_a2a_your_key_here \
-e OHWOW_WORKSPACE_ID=your_workspace_id \
-e LOCAL_AGENT_URL=http://host.docker.internal:3001 \
-p 9090:9090 \
ghcr.io/ohwow/connect:latestThe relay exposes a local admin UI at http://localhost:9090 where you can monitor connection status and task logs.
npm Setup#
If you prefer running it directly with Node.js:
npx ohwow-connect initThis creates an ohwow-connect.yaml config file. Edit it, then start the relay:
npx ohwow-connect startConfiguration#
The config file defines your workspace, agents, and routing:
# ohwow-connect configuration
workspace_id: "your_workspace_id"
api_key: "ohw_a2a_your_key_here"
# Local agents to expose via A2A
agents:
- name: "Internal Analytics"
description: "Runs queries against our private data warehouse"
local_url: "http://localhost:3001/api/agent"
skills:
- id: "sql-query"
name: "SQL Query"
description: "Execute read-only SQL queries"
- id: "report-gen"
name: "Report Generation"
description: "Generate formatted reports from query results"
- name: "Document Processor"
description: "Processes internal documents and contracts"
local_url: "http://localhost:3002/api/agent"
skills:
- id: "doc-summarize"
name: "Summarize Document"
description: "Extract key points from a document"
# Optional settings
health_check_interval: 30 # seconds
retry_attempts: 3
log_level: "info"Register in Dashboard#
After starting the relay, register your local agents in the ohwow dashboard:
- Go to Dashboard Settings → Connections.
- Click "Add Connection" and select "ohwow-connect Relay".
- Enter your workspace ID and the relay will auto-discover your local agents.
- Set trust level and permissions for each agent.
Troubleshooting#
| Issue | Solution |
|---|---|
| Relay can't connect to ohwow | Check your API key and workspace ID. Ensure outbound HTTPS is allowed. |
| Local agent not responding | Verify the local_url is reachable from the relay container. Use host.docker.internal for Docker. |
| Tasks timing out | Increase timeout in config. Check that your local agent responds within 30 seconds. |
| Connection drops frequently | Check network stability. The relay auto-reconnects with exponential backoff. |
| Agent not appearing in dashboard | Click "Refresh" in the connections panel. The relay sends agent cards on startup. |
| "UNAUTHORIZED" errors | Your API key may be revoked or expired. Create a new key in Dashboard Settings. |
Tip
log_level: "debug" in your config file or pass -e LOG_LEVEL=debug to Docker.