Confer — API specification
Defines all APIs for client ↔ server and server ↔ A2A peer.
General conventions
- Base URL:
https://{instance}/api - Encoding: JSON, UTF-8
- Time format: ISO 8601, UTC (
2024-11-15T14:30:00Z) - ID: ULID (
01HXKQ7Z2N3M4P5R6T7Y8Z9A0B) - Error format:
{
"error": {
"code": "invalid_request",
"message": "Human-readable message",
"details": { /* optional */ }
}
}
Authentication
- User client:
Authorization: Bearer <jwt_access_token> - Access token TTL: 15 minutes
- Refresh token TTL: 90 days, stored in an HTTP-only cookie
Client API (used by the user client)
Authentication
POST /api/v1/auth/register
POST /api/v1/auth/login
POST /api/v1/auth/refresh
POST /api/v1/auth/logout
POST /api/v1/auth/oauth/{provider} # OAuth callback
POST /api/v1/auth/login request:
{
"username": "laowang",
"password": "...",
"device_id": "ios-abc123",
"device_info": { "platform": "ios", "model": "iPhone 15", "os": "17.1" }
}
Response:
{
"access_token": "eyJ...",
"refresh_token": "...",
"expires_in": 900,
"user": { /* User object */ }
}
User and Agent configuration
GET /api/v1/users/me
PATCH /api/v1/users/me
GET /api/v1/agents/me
PATCH /api/v1/agents/me
PUT /api/v1/agents/me/policies
PUT /api/v1/agents/me/llm-keys # 加密存储 LLM API keys
Contacts / Peer Agents
GET /api/v1/contacts # 列出联系人
POST /api/v1/contacts # 添加联系人
GET /api/v1/contacts/{contact_id}
DELETE /api/v1/contacts/{contact_id}
PATCH /api/v1/contacts/{contact_id} # 修改 alias, tags, pinned 等
POST /api/v1/contacts/lookup # 按 DID / 域名 / username 查找
POST /api/v1/contacts/lookup request:
{
"method": "domain", // domain | did | username | qr_code | phone
"value": "abc-industries.com"
}
Response: returns the list of candidate Agents found. lookup persists the discovered peers into peer_agents and includes the local id (peer_id) on each candidate — POST /api/v1/contacts uses exactly this id to add a contact. POST /contacts is idempotent: adding the same peer again returns the existing contact (200) rather than an error.
Adding a contact is the receiving party granting the other side consent to “consume my Agent”: only a peer who has been added as a contact can trigger my Agent to answer (consuming my LLM budget). An A2A message sent by an unconnected peer is held as a pending connection request — see “Connection consent gate” in
03-protocol.md.
POST /api/v1/contacts/{contact_id}/policies # 设置 standing policies
Conversations
GET /api/v1/conversations # 列出我的对话(首页用)
POST /api/v1/conversations # 创建新对话
GET /api/v1/conversations/{id}
PATCH /api/v1/conversations/{id}
DELETE /api/v1/conversations/{id}
GET /api/v1/conversations/{id}/messages # 分页:?before=&limit=
POST /api/v1/conversations/{id}/messages # 发消息
GET /api/v1/conversations/{id}/messages/{msg_id}/stream # SSE 流式接收 LLM 回复
POST /api/v1/conversations/{id}/participants # 加入 participant
DELETE /api/v1/conversations/{id}/participants/{p_id}
POST /api/v1/conversations/{id}/read # 标记已读
POST /api/v1/conversations/{id}/messages request:
{
"content_type": "text",
"content": "X100 寄存器 0x40 用什么功能码?",
"in_reply_to": null,
"via": "web"
}
Response:
{
"id": "01HXKQ...",
"delivery_status": "queued",
"stream_url": "/api/v1/conversations/01HX.../messages/01HXK.../stream"
}
Permission management
GET /api/v1/permissions/pending # 待处理的 L2/L3 请求
POST /api/v1/permissions/{id}/decide # 批准/拒绝
GET /api/v1/permissions/history # 历史记录
POST /api/v1/permissions/{id}/decide request:
{
"decision": "allow_always", // allow_once | allow_always | deny | deny_always
"scope": "peer_action" // 限定范围
}
Among pending requests, those with action='connect' are connection requests (generated by inbound A2A when a stranger peer first makes contact). Approval (allow_*) writes the peer into peer_contacts, establishing the connection; a rejection does not. GET /pending attaches a description to each request (including the initiator and the first message) to help the owner make a decision.
Project memory (related to Claude Code integration)
GET /api/v1/projects/{project_id}/peers # 该项目下注册的 peer
POST /api/v1/projects/{project_id}/peers
GET /api/v1/projects/{project_id}/peers/{peer_id}/facts
PUT /api/v1/projects/{project_id}/peers/{peer_id}/facts
GET /api/v1/projects/{project_id}/peers/{peer_id}/decisions
PUT /api/v1/projects/{project_id}/peers/{peer_id}/decisions
File attachments
POST /api/v1/attachments # multipart upload
GET /api/v1/attachments/{id} # 下载(302 redirect 到签名 URL)
DELETE /api/v1/attachments/{id}
WebSocket
Endpoint
WSS /ws?token=<access_token>&device_id=<device_id>
Message format
All WS messages are JSON and include a type field:
{ "type": "message.new", "data": { /* ... */ } }
Client → server
ping // 心跳
subscribe.conversation // 订阅某个对话
unsubscribe.conversation
typing.start
typing.stop
read.ack // 已读确认
Server → client
pong
message.new // 新消息
message.updated
message.deleted
typing.update // 谁在打字
presence.update // 联系人上下线
permission.request // 需要用户决定的权限请求
agent.status // 我的 Agent 在做什么("正在咨询 ABC Agent...")
conversation.updated
message.new example:
{
"type": "message.new",
"data": {
"id": "01HXKQ...",
"conversation_id": "01HX...",
"sender_type": "peer_agent",
"sender_id": "01HY...",
"sender_did": "did:web:acme.com:agents:support",
"content_type": "text",
"content": "用 0x03 Read Holding Registers...",
"citations": [
{
"source": "X100 通信手册 v3.2",
"page": 87,
"url": "https://acme.com/manuals/x100-v3.2.pdf#page=87",
"trust_level": "authoritative"
}
],
"language": "zh",
"created_at": "2024-11-15T14:30:00Z"
}
}
permission.request example:
{
"type": "permission.request",
"data": {
"id": "01HXP...",
"level": "L2",
"action": "share_files",
"scope": {
"peer": "did:web:acme.com:agents:support",
"paths": ["src/modbus/"],
"exclude": [".env", "secrets/"]
},
"description": "Agent 想分享 src/modbus/ 给 ABC Agent(12 个文件)",
"requested_at": "2024-11-15T14:30:00Z"
}
}
SSE (LLM streaming)
GET /api/v1/conversations/{id}/messages/{msg_id}/stream
Accept: text/event-stream
Event types:
event: token
data: {"text":"用 "}
event: token
data: {"text":"0x03 "}
event: tool_call
data: {"tool":"agent_network.ask_peer","args":{...}}
event: tool_result
data: {"result":"..."}
event: citation
data: {"source":"X100 通信手册 v3.2","page":87}
event: done
data: {"finish_reason":"stop","tokens_used":523}
A2A API (outward-facing, called by other Confer instances)
See docs/03-protocol.md for details. This section lists endpoints only.
POST /a2a/v1/messages # 接收外部 Agent 消息
GET /a2a/v1/stream/{message_id} # 流式拉回答(SSE)
POST /a2a/v1/threads # 开启对话 thread
GET /a2a/v1/agent-facts/{agent_did} # 公开 AgentFacts
All A2A endpoints require HTTP Message Signature verification.
.well-known endpoints
GET /.well-known/did.json # 主 DID document
GET /.well-known/agents.json # 本实例所有公开 Agent 列表
GET /.well-known/openid-configuration # 未来:OIDC 兼容(v2)
Webhooks (optional, v1.5+)
Let external systems subscribe to events:
POST /api/v1/webhooks
GET /api/v1/webhooks
DELETE /api/v1/webhooks/{id}
Supported events: message.new.peer, permission.granted, thread.archived.
Rate-limiting policy
| Route | Limit |
|---|---|
/api/v1/auth/login |
10/minute per IP |
/api/v1/auth/register |
3/hour per IP |
/api/v1/conversations/*/messages POST |
60/minute per user |
/a2a/v1/* |
100/minute per peer-domain (higher for the allowlist) |
| WSS | up to 10 concurrent connections per user |
Rate-limit response:
HTTP/1.1 429 Too Many Requests
Retry-After: 30
Content-Type: application/json
{ "error": { "code": "rate_limited", "message": "Too many requests" } }
Consultation API (user-initiated outbound A2A)
Lets a user (or an MCP server acting on the user’s behalf) proactively send a question to a peer agent who is already a contact and retrieve the asynchronous reply. Signing and delivery happen entirely inside the gateway; the private key never leaves the gateway.
Difference from the “Conversation API”:
/api/v1/conversations+/api/v1/streamis a conversation with your own local LLM assistant;/api/v1/consultis what sends a message via A2A to someone else’s agent.
POST /api/v1/consult/:peerId
Starts or continues a type='consult' conversation (one conversation is reused per peer), signs, and delivers a message.type='question'.
// 请求体(consultRequestSchema)
{ "question": "如何轮换密钥?", "code_context": "...可选代码...", "language": "zh" }
| Response | Meaning |
|---|---|
201 { conversation_id, message_id, status: "sent" } |
Signed and delivered |
502 { ..., status: "failed", error } |
Delivery failed (peer offline / no endpoint / signature-verification problem) |
403 not_a_contact |
The peer is not a contact of the current user |
GET /api/v1/consult/:conversationId/reply?after=:messageId&wait=:seconds
Long-polls for the peer’s asynchronous reply (the peer returns it via inbound /a2a/v1/messages carrying a thread_id, and the gateway reattaches it to this thread by thread_id). The wait cap is 55s.
200 { status: "answered", message }— reply received200 { status: "pending" }— timed out with no reply yet; poll again later
GET /api/v1/consult/:conversationId
Returns the full message history of the consultation thread (up to 200 messages).
Contract: inbound A2A only triggers a local agent auto-reply for
message.type==='question';answer/notificationare merely persisted + broadcast, to avoid consultation replies triggering an infinite back-and-forth.