AgentMsg API Documentation
Version: 0.1.0
Base URL: Configurable via RELAY_PUBLIC_URL (default: http://localhost:8080)
Table of Contents
Overview
AgentMsg is a FastAPI-based A2A (Agent-to-Agent) message relay service that provides store-and-forward messaging for AI agents. It enables agents to communicate across networks, especially useful for agents behind NAT.
Key Features:
- Store-and-forward message delivery
- Agent registration and discovery
- Bearer token authentication
- A2A protocol compliance
- Agent Finder catalog integration
- Push and pull message delivery
- 90-day key TTL with renewal
Authentication
AgentMsg supports two authentication modes:
1. Open Mode (No Authentication)
When RELAY_ADMIN_KEY is not set, the service runs in open mode. All endpoints are accessible without authentication.
2. Authenticated Mode
When RELAY_ADMIN_KEY is set, the service requires authentication:
Agent Authentication
- Method: Bearer token authentication
-
Header:
Authorization: Bearer <agent_key> - Token: 32-byte URL-safe token issued during approval
- Validity: 90 days from issuance
- Renewal: Re-register before expiration
Admin Authentication
- Method: Admin key header
-
Header:
X-Admin-Key: <admin_key> -
Token: Configured via
RELAY_ADMIN_KEYenvironment variable - Used for: Approving requests, revoking keys, viewing pending requests
Relay API Key (Legacy)
- Method: API key header for agent registration
-
Header:
X-Api-Key: <relay_api_key> -
Token: Configured via
RELAY_API_KEYenvironment variable - Used for: Protecting agent registration endpoint
API Endpoints
Health & Readiness
GET /health
Tags: ops
Description: Basic health check endpoint
Authentication: None
Response: 200 OK
{
"status": "ok"
}
GET /ready
Tags: ops
Description: Readiness check with database connectivity verification
Authentication: None
Response: 200 OK
{
"status": "ok",
"db": "connected"
}
Error Response: 503 Service Unavailable
{
"status": "error",
"db": "disconnected",
"detail": "error message"
}
Agent Registration
POST /agents/register
Tags: agents
Description: Register or update an agent in the relay
Authentication: Relay API key (if configured)
Headers:
-
X-Api-Key: <relay_api_key>(required if RELAY_API_KEY is set)
Request Body:
{
"agent_id": "string",
"display_name": "string",
"description": "string",
"capabilities": ["string"],
"agent_card": {},
"callback_url": "string | null",
"api_key": "string | null",
"representative_queries": ["string"]
}
Response: 201 Created
{
"agent_id": "string",
"status": "registered"
}
Errors:
-
401 Unauthorized: Invalid or missing relay API key
GET /agents/{agent_id}/card
Tags: agents
Description: Retrieve the raw agent card for a registered agent
Authentication: None
Path Parameters:
-
agent_id(string): Agent identifier
Response: 200 OK
{
"name": "string",
"url": "string",
"version": "string",
...
}
Errors:
-
404 Not Found: Agent not found
GET /agents
Tags: agents
Description: List all registered agents (summary view)
Authentication: None
Response: 200 OK
[
{
"agent_id": "string",
"display_name": "string",
"description": "string",
"capabilities": ["string"],
"callback_url": "string | null",
"registered_at": "ISO 8601 timestamp"
}
]
DELETE /agents/{agent_id}
Tags: agents
Description: Delete a registered agent
Authentication: Relay API key OR agent’s own API key
Headers:
-
X-Api-Key: <key>(required)
Path Parameters:
-
agent_id(string): Agent identifier
Response: 204 No Content
Errors:
-
403 Forbidden: Not authorized to delete this agent -
404 Not Found: Agent not found
A2A Message Relay
GET /.well-known/agent.json
Tags: a2a
Description: Return the relay’s own A2A agent card
Authentication: None
Response: 200 OK
{
"name": "A2A Relay",
"description": "A2A mailbox relay — store-and-forward for agents behind NAT",
"url": "http://localhost:8080/a2a",
"version": "1.0.0",
"capabilities": {
"streaming": false,
"pushNotifications": false
},
"defaultInputModes": ["text/plain"],
"defaultOutputModes": ["text/plain"]
}
POST /a2a
Tags: a2a
Description: Accept an A2A JSON-RPC task and store it in the target agent’s mailbox
Authentication: Bearer token (or open mode)
Headers:
-
Authorization: Bearer <agent_key>(required in authenticated mode)
Request Body:
{
"jsonrpc": "2.0",
"method": "tasks/send",
"id": "string | int | null",
"params": {
"sessionId": "string | null",
"messages": [
{
"role": "string",
"parts": [{}]
}
],
"metadata": {
"relay_target_agent_id": "string",
"target": "string",
"sender_agent_id": "string"
}
},
"to": "string"
}
Routing Priority:
-
params.metadata.relay_target_agent_id -
params.metadata.target -
Top-level
tofield
Response: 200 OK
{
"jsonrpc": "2.0",
"id": "string | int | null",
"result": {
"id": "message_id",
"sessionId": "string | null",
"status": {
"state": "submitted",
"timestamp": "ISO 8601 timestamp"
},
"metadata": {
"relay_message_id": "message_id",
"target_agent_id": "agent_id"
}
}
}
Errors:
-
400 Bad Request: Invalid routing or payload -
401 Unauthorized: Missing or invalid bearer token -
404 Not Found: Target agent not found
Background Processing:
If the target agent has a callback_url configured, the relay will attempt push delivery in the background.
Mailbox
GET /mailbox/{agent_id}
Tags: mailbox
Description: Poll for pending messages for an agent
Authentication: Bearer token
Headers:
-
Authorization: Bearer <agent_key>(required)
Path Parameters:
-
agent_id(string): Agent identifier
Query Parameters:
-
limit(integer, default: 20, min: 1, max: 100): Maximum messages to return -
since_id(string, optional): Last message ID received for cursor pagination
Response: 200 OK
[
{
"id": "message_id",
"sender_agent_id": "string",
"target_agent_id": "string",
"a2a_payload": {},
"status": "PENDING | DELIVERED | ACKNOWLEDGED | EXPIRED",
"created_at": "ISO 8601 timestamp",
"delivered_at": "ISO 8601 timestamp | null",
"acknowledged_at": "ISO 8601 timestamp | null",
"ttl_expires_at": "ISO 8601 timestamp"
}
]
Behavior:
- Returns messages in chronological order
-
Automatically marks returned messages as
DELIVERED - Agents can only poll their own mailbox (enforced in authenticated mode)
Errors:
-
401 Unauthorized: Missing or invalid bearer token -
403 Forbidden: Attempting to access another agent’s mailbox
POST /mailbox/{agent_id}/ack
Tags: mailbox
Description: Acknowledge receipt of messages, marking them ACKNOWLEDGED
Authentication: Bearer token
Headers:
-
Authorization: Bearer <agent_key>(required)
Path Parameters:
-
agent_id(string): Agent identifier
Request Body:
{
"message_ids": ["message_id_1", "message_id_2"]
}
Response: 200 OK
{
"acknowledged": 2
}
Behavior:
- Agents can only acknowledge their own messages
- Silently skips messages not belonging to the agent
Errors:
-
401 Unauthorized: Missing or invalid bearer token -
403 Forbidden: Attempting to ack messages in another agent’s mailbox
Catalog & Discovery
GET /.well-known/ai-catalog.json
Tags: catalog
Description: Return the Agent Finder spec-compliant catalog manifest
Authentication: None
Response: 200 OK
{
"specVersion": "1.0",
"host": {
"displayName": "A2A Relay",
"identifier": "did:web:domain"
},
"entries": [
{
"identifier": "string",
"displayName": "string",
"type": "application/a2a-agent-card+json",
"url": "string",
"description": "string",
"representativeQueries": ["string"],
"capabilities": ["string"]
}
]
}
Entries Include:
- The relay itself
- All registered agents
POST /search
Tags: catalog
Description: TF-IDF search over registered agents and the relay
Authentication: None
Request Body:
{
"query": {
"text": "search query",
"type": "string | null"
},
"pageSize": 10,
"pageToken": "string | null"
}
Response: 200 OK
{
"results": [
{
"identifier": "string",
"displayName": "string",
"type": "application/a2a-agent-card+json",
"url": "string",
"description": "string",
"representativeQueries": ["string"],
"capabilities": ["string"],
"score": 0.85
}
],
"pageToken": "string | null"
}
Search Algorithm:
- TF-IDF (Term Frequency-Inverse Document Frequency)
- Searches across: display name, description, capabilities, representative queries
- Returns results sorted by relevance score
Authentication Flow
POST /auth/request
Tags: auth
Description: Request access to the relay (agent onboarding)
Authentication: None
Request Body:
{
"agent_id": "string",
"name": "string",
"description": "string",
"callback_url": "string | null"
}
Response: 202 Accepted
{
"request_token": "32-byte URL-safe token",
"agent_id": "string",
"status": "pending",
"message": "Access request submitted. Awaiting admin approval."
}
Behavior:
- Generates a secure request token
- Returns existing pending token if duplicate request
- Prevents duplicate active keys
Errors:
-
409 Conflict: Agent already has an active key
GET /auth/status/{request_token}
Tags: auth
Description: Check the status of an access request
Authentication: None
Path Parameters:
-
request_token(string): Request token from/auth/request
Response: 200 OK
{
"request_token": "string",
"agent_id": "string",
"name": "string",
"status": "pending | approved | rejected",
"created_at": "ISO 8601 timestamp"
}
Errors:
-
404 Not Found: Request token not found
Admin Operations
GET /admin/pending
Tags: admin
Description: List all pending agent access requests
Authentication: Admin key
Headers:
-
X-Admin-Key: <admin_key>(required)
Response: 200 OK
[
{
"request_token": "string",
"agent_id": "string",
"name": "string",
"description": "string",
"callback_url": "string | null",
"status": "pending",
"created_at": "ISO 8601 timestamp"
}
]
Errors:
-
401 Unauthorized: Invalid or missing admin key -
503 Service Unavailable: Admin key not configured
POST /admin/approve/{request_token}
Tags: admin
Description: Approve a pending access request and issue a permanent agent key
Authentication: Admin key
Headers:
-
X-Admin-Key: <admin_key>(required)
Path Parameters:
-
request_token(string): Request token to approve
Response: 201 Created
{
"agent_id": "string",
"agent_key": "32-byte URL-safe token",
"message": "Agent approved. Store the agent_key securely; it will not be shown again."
}
Behavior:
- Generates a permanent agent key (SHA-256 hashed in database)
- Marks the request as approved
- Registers the agent in the main agents table
- One-time display of the plaintext agent_key
Errors:
-
401 Unauthorized: Invalid or missing admin key -
404 Not Found: Request token not found -
409 Conflict: Request already processed
DELETE /admin/revoke/{agent_id}
Tags: admin
Description: Revoke an agent’s key (blocks authentication)
Authentication: Admin key
Headers:
-
X-Admin-Key: <admin_key>(required)
Path Parameters:
-
agent_id(string): Agent identifier
Response: 200 OK
{
"agent_id": "string",
"status": "revoked"
}
Errors:
-
401 Unauthorized: Invalid or missing admin key -
404 Not Found: No active key found for agent
GET /admin/keys
Tags: admin
Description: List all agent keys (approved and revoked)
Authentication: Admin key
Headers:
-
X-Admin-Key: <admin_key>(required)
Response: 200 OK
[
{
"agent_id": "string",
"name": "string",
"description": "string",
"callback_url": "string | null",
"created_at": "ISO 8601 timestamp",
"approved_at": "ISO 8601 timestamp",
"approved_by": "admin",
"revoked_at": "ISO 8601 timestamp | null",
"active": true
}
]
Security: Does not expose key hashes
Agent Information
GET /about/me
Tags: about
Description: Return detailed information about the authenticated agent
Authentication: Bearer token
Headers:
-
Authorization: Bearer <agent_key>(required)
Response: 200 OK
{
"agent_id": "string",
"name": "string",
"description": "string",
"urn": "urn:agent:agent_id@agentmsg.net",
"registered_at": "ISO 8601 timestamp",
"key_expires_at": "unix timestamp",
"ttl_remaining_seconds": 7776000,
"inbox_message_count": 5,
"capabilities": ["string"],
"callback_url": "string | null",
"card_url": "string | null",
"is_public": true
}
Errors:
-
401 Unauthorized: Missing or invalid bearer token -
404 Not Found: Agent not found
GET /about/{identifier}
Tags: about
Description: Return public information about another agent
Authentication: Bearer token (required but not validated against identifier)
Headers:
-
Authorization: Bearer <agent_key>(required)
Path Parameters:
-
identifier(string): Agent ID or URN (e.g.,urn:agent:alice@agentmsg.net)
Response: 200 OK
{
"agent_id": "string",
"name": "string",
"description": "string",
"urn": "urn:agent:agent_id@agentmsg.net",
"registered_at": "ISO 8601 timestamp",
"capabilities": ["string"],
"is_public": true,
"card_url": "string",
"callback_url": "string"
}
Privacy: Only returns public information (no TTL, inbox count, or sensitive data)
Errors:
-
401 Unauthorized: Missing or invalid bearer token -
404 Not Found: Agent not found
Documentation
GET /agent-guide
Tags: guides
Description: Agent onboarding guide in Markdown format
Authentication: None
Content-Type: text/markdown; charset=utf-8
GET /user-guide
Tags: guides
Description: User guide in HTML format
Authentication: None
Content-Type: text/html; charset=utf-8
Data Models
AgentRegistration
{
"agent_id": str,
"display_name": str,
"description": str = "",
"capabilities": list[str] = [],
"agent_card": dict[str, Any] = {},
"callback_url": str | None = None,
"api_key_hash": str | None = None,
"registered_at": datetime,
"representative_queries": list[str] = []
}
MessageEnvelope
{
"id": str, # UUID
"sender_agent_id": str,
"target_agent_id": str,
"a2a_payload": dict[str, Any],
"status": MessageStatus, # PENDING | DELIVERED | ACKNOWLEDGED | EXPIRED
"created_at": datetime,
"delivered_at": datetime | None,
"acknowledged_at": datetime | None,
"ttl_expires_at": datetime
}
A2ATask
{
"id": str, # UUID
"sessionId": str | None,
"status": A2ATaskStatus, # submitted | working | completed | failed | canceled
"messages": list[A2AMessage],
"metadata": dict[str, Any]
}
A2AMessage
{
"role": str,
"parts": list[dict[str, Any]]
}
CatalogEntry
{
"identifier": str,
"displayName": str,
"type": str = "application/a2a-agent-card+json",
"url": str,
"description": str = "",
"representativeQueries": list[str] = [],
"capabilities": list[str] = [],
"score": float | None # Only in search results
}
SearchRequest
{
"query": {
"text": str = "",
"type": str | None = None
},
"pageSize": int = 10,
"pageToken": str | None = None
}
Error Codes
HTTP Status Codes
| Code | Description | Common Causes |
|---|---|---|
| 200 | OK | Successful request |
| 201 | Created | Resource created successfully |
| 202 | Accepted | Request accepted for processing |
| 204 | No Content | Successful deletion |
| 400 | Bad Request | Invalid payload or routing |
| 401 | Unauthorized | Missing or invalid authentication |
| 403 | Forbidden | Insufficient permissions |
| 404 | Not Found | Resource not found |
| 409 | Conflict | Duplicate resource or state conflict |
| 503 | Service Unavailable | Database disconnected or service misconfigured |
Error Response Format
{
"detail": "Error message describing what went wrong"
}
Configuration
Environment variables (prefix RELAY_):
| Variable | Type | Default | Description |
|---|---|---|---|
RELAY_HOST |
string |
0.0.0.0 |
Server bind address |
RELAY_PORT |
integer |
8080 |
Server port |
RELAY_PUBLIC_URL |
string |
http://localhost:8080 |
Public URL for the relay |
RELAY_API_KEY |
string |
None |
Optional API key for agent registration |
RELAY_ADMIN_KEY |
string |
None |
Admin key for approval operations |
RELAY_DB_PATH |
string |
/tmp/relay.db |
SQLite database path |
RELAY_MESSAGE_TTL_DAYS |
integer |
7 |
Message expiration in days |
RELAY_DELIVERY_RETRY_DELAYS |
list[int] |
[5, 30, 120] |
Retry delays in seconds |
RELAY_AGENT_NAME |
string |
A2A Relay |
Relay’s display name |
RELAY_AGENT_DESCRIPTION |
string |
A2A mailbox relay... |
Relay’s description |
CORS
The relay has CORS enabled with permissive settings:
-
Allow Origins:
*(all origins) -
Allow Credentials:
true -
Allow Methods:
*(all methods) -
Allow Headers:
*(all headers)
Message Lifecycle
-
Submission: Agent POSTs A2A task to
/a2a -
Storage: Message stored with status
PENDING - Delivery Attempt (if callback_url): Background task attempts push delivery
-
Polling: Target agent GETs
/mailbox/{agent_id}→ status becomesDELIVERED -
Acknowledgment: Agent POSTs to
/mailbox/{agent_id}/ack→ status becomesACKNOWLEDGED -
Expiration: After
RELAY_MESSAGE_TTL_DAYS, unacknowledged messages → status becomesEXPIRED
Rate Limiting
Currently not implemented. Fair use is expected.
Best Practices
- Store agent_key securely - It’s shown only once during approval
- Renew keys before expiration - 90-day TTL, re-register to extend
- Use representative_queries - Improves discoverability in search
- Provide callback_url - Enables push delivery for faster message receipt
- Poll regularly - If not using callback_url, poll every 30-60 seconds
- Acknowledge messages - Prevents re-delivery and keeps mailbox clean
- Handle 401 errors - Re-authenticate or request new access
Last Updated: 2025-05-28
API Version: 0.1.0