File DA-012 View as Markdown
Agent access
Double Agent's account agent lives on https://app.doubleagent.so. An AI agent registers itself, its human owner approves it by email, and it then reads the sites and managed agents of its accounts with its own daa_ token.
Endpoints
| Endpoint | What |
|---|---|
GET https://app.doubleagent.so/.well-known/agent-card.json | The A2A 1.0 Agent Card, with a 0.3 interface, signed with ES256 |
GET https://app.doubleagent.so/.well-known/jwks.json | The public key that verifies the card and every push |
POST https://app.doubleagent.so/a2a | A2A JSON-RPC, versions 1.0 and 0.3. Send A2A-Version. |
POST https://app.doubleagent.so/mcp | MCP over Streamable HTTP with JSON responses, versions 2025-11-25, 2025-06-18 and 2025-03-26 |
Both offer the same tools. Request bodies are at most 64 KiB.
Register
- Call
register_agentwithname(1–100 characters) and, optionally,owner_email,requested_role(vieweroradmin, defaultviewer; the owner decides),card_url,mcp_urlandtoken_name. Withoutpowit fails withpow_required, anddetailsholds{ challenge, difficulty, format, expires_at }. - Find a
solutionof 1–64 characters of[A-Za-z0-9_-]so that the SHA-256 of the UTF-8 stringda-agents|<challenge>|<solution>hasdifficultyleading zero bits (18 by default). The challenge expires after 10 minutes and works once; reusing it givesinvalid_pow. - Call
register_agentagain withpow: { challenge, solution }. The answer hasagent_id,registration_id(arg_…),status,token(daa_…, shown once),token_id,owner_email_masked,expires_at,next[]andowner_email_sent. - Without an owner email the status is
needs_owner_email. Callset_owner_email(at most 3 changes per registration; each change voids the previous approval link;owner_email_sent: falsemeans fix the address or retry), or on A2A reply on the registration task with the data part{ "owner_email": "…" }. - The owner gets a fixed email with no text from your agent and signs in with that address. They see the name, the unverified URLs, the request's country and the requested role, pick an account they own or administer (or a new one) and a role (
vieweroradmin, neverowner), and approve or decline. - Learn the outcome by polling
get_registrationorwhoamino faster than once a minute, with A2AGetTask(the task id is the registration id), or by push (Push notifications). Statuses:needs_owner_email,pending,approved,declined,expired. A2A task states:input-requireduntil approved, thencompleted;rejectedwhen declined;canceledwhen expired. - A registration expires after 7 days without approval, and its token is revoked.
Nothing is fetched at registration. card_url and mcp_url must be public HTTPS on the default port with a DNS name (no IP address, no credentials). The owner sees them as unverified until a proof passes; see Agent verification.
Roles
An agent is a viewer or an admin, never owner. Whatever the role, an agent never holds a team or account permission: it cannot see or change members, invitations, ownership or account settings. Person-only routes (agent approval, device approval, invitations) answer 403 with human_only.
A viewer reads sites, analytics and managed agents. An admin also manages sites, domains, keys and managed agents. Agent actions appear in the account's audit log as the agent.
People list, edit and remove agents and revoke their tokens on the portal's Team page. Agents cannot.
Tokens
A pending token works only for set_owner_email, get_registration and whoami. Every other tool answers agent_pending until the owner approves; then the same token works.
Tokens do not expire on a clock. An agent has at most 10 active tokens:
create_tokenmakes another named token.rotate_tokenreplaces a token; the old value stops working at once.revoke_tokenrevokes another token. A token cannot revoke itself.list_tokenslists tokens, never their values.
New values are shown once. A revoked token answers 401.
An approved token also authenticates the HTTP API at https://api.doubleagent.so as Authorization: Bearer daa_…, within the agent's role.
Store tokens in a file with mode 0600 or in a secret manager. Never put a token in a repository, a prompt, a log, a JSON-RPC id or MCP clientInfo.
Tools
Send the token as Authorization: Bearer daa_…. Auth none needs no token, pending takes any valid token including one awaiting approval, and token needs an approved token.
| Tool | Auth | What it does |
|---|---|---|
register_agent | none | Starts registration. Without pow it returns a proof-of-work challenge. Called again with { challenge, solution } it returns a pending token (shown once) and a registration that waits for the owner. |
set_owner_email | pending | Sets or changes the owner's email (at most 3 changes). |
get_registration | pending | Status: needs_owner_email, pending, approved, declined or expired. |
whoami | pending | The agent's id, name, URLs, registration, accounts, roles and verification labels. |
list_tokens | token | Token ids, names, status, created and last used. Never the values. |
create_token | token | Another named token (at most 10 active). The value is shown once. |
rotate_token | token | Revokes a token and returns a new one with the same name. |
revoke_token | token | Revokes another token. The token making the call cannot revoke itself. |
update_profile | token | Changes name, card_url or mcp_url. Changing a URL removes its verification at once. |
start_verification | token | Opens a challenge (72 hours) proving control of the A2A endpoint, MCP server, card key, MCP Registry key, origin or domain. Takes method (a2a_callback, mcp_callback, card_key, mcp_registry_key, well_known, dns) and optional target. The secret and the steps are in this answer only. |
check_verification | token | Runs the check for a started challenge (10 per hour). Takes proof_id; jws for card_key, signature for mcp_registry_key. A pass adds a verification label. |
list_verifications | token | This agent's challenges and labels, newest first. |
list_sites | token | Sites in the agent's accounts, within its role. |
site_summary | token | Humans, bots and agents for one site (default: last 7 days). |
list_managed_agents | token | Agents an account tracks with Double Agent observability. |
agent_summary | token | Operations, conversations, errors and latency for one tracked agent. |
For A2A, send a data part { "skill": "<tool>", …arguments }. For MCP, call tools/call.
Conversations
Each protocol's own conversation id groups your calls into one conversation.
- A2A: The portal agent tells you: Keep the
contextIdfrom my first answer and send it on every later message; to answer a task, send itstaskIdwith the samecontextId(or none). - MCP: Keep the
Mcp-Session-Idfrominitializeand send it on every request; on 404, initialize again. Anmcs_…session lasts 24 hours; after that the server answers 404.
Without a contextId, the agent mints one (ctx_…). A contextId of your own must be 1–128 of [A-Za-z0-9_.:-], but prefer the ctx_… the agent mints. A reply whose contextId differs from its task's is rejected. A conversation id grants no access.
Errors
| Code | What to do |
|---|---|
pow_required | Solve the challenge in details and call register_agent again with pow. |
invalid_pow | The solution is wrong, expired or already used. Call register_agent without pow for a new challenge. |
needs_owner_email | Give the owner's email with set_owner_email, or reply on the registration task. |
agent_pending | Wait for the owner to approve. Poll get_registration no faster than once a minute. |
unauthorized | Send Authorization: Bearer daa_…. If the token was revoked, declined or expired, tell your human; do not register again. |
forbidden | Your role does not allow it. Ask your human. |
rate_limited | Wait details.retry_after seconds once, then stop and report. |
invalid_argument | Fix the argument named in details.field. On A2A it arrives as JSON-RPC -32602, and an unknown skill lists the valid ones in details.tools. On MCP it is a tool result with isError: true, and an unknown tool is JSON-RPC -32602 without details. |
not_found | The id does not exist or is not yours. |
conflict | The state changed, for example the owner already decided or the owner email changed 3 times. Read it again with whoami. |
unavailable | Double Agent could not be reached. Try again later, once. |
internal | Something failed on our side. Try again later, once, then report it. |
HTTP API errors are { "error": { "code", "message" } }, and every 429 has Retry-After.
Limits
- Registration: 5 solved
register_agentcalls per hour per IP address. - Proof-of-work challenges: 30 per minute per IP address.
- Approval emails: 3 per day per owner address.
- Calls with one token: 120 per minute, tools and HTTP API together.
- Push config handshakes: 5 per hour per registration and 30 per hour per webhook host.
- Proofs started: 20 per hour per agent.
- Proof checks: 10 per hour per proof and 30 per hour per target host.
- Directory API: 60 requests per minute per IP address; reviews 30, check 10 and submissions 10 per minute.
- Observe event ingest (
POST https://api.doubleagent.so/v1/agent-events): 1,000 requests per minute per agent source.
Related
AI agents can install everything here as a skill:
npx skills add doubleagent-so/skills