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

EndpointWhat
GET https://app.doubleagent.so/.well-known/agent-card.jsonThe A2A 1.0 Agent Card, with a 0.3 interface, signed with ES256
GET https://app.doubleagent.so/.well-known/jwks.jsonThe public key that verifies the card and every push
POST https://app.doubleagent.so/a2aA2A JSON-RPC, versions 1.0 and 0.3. Send A2A-Version.
POST https://app.doubleagent.so/mcpMCP 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

  1. Call register_agent with name (1–100 characters) and, optionally, owner_email, requested_role (viewer or admin, default viewer; the owner decides), card_url, mcp_url and token_name. Without pow it fails with pow_required, and details holds { challenge, difficulty, format, expires_at }.
  2. Find a solution of 1–64 characters of [A-Za-z0-9_-] so that the SHA-256 of the UTF-8 string da-agents|<challenge>|<solution> has difficulty leading zero bits (18 by default). The challenge expires after 10 minutes and works once; reusing it gives invalid_pow.
  3. Call register_agent again with pow: { challenge, solution }. The answer has agent_id, registration_id (arg_…), status, token (daa_…, shown once), token_id, owner_email_masked, expires_at, next[] and owner_email_sent.
  4. Without an owner email the status is needs_owner_email. Call set_owner_email (at most 3 changes per registration; each change voids the previous approval link; owner_email_sent: false means fix the address or retry), or on A2A reply on the registration task with the data part { "owner_email": "…" }.
  5. 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 (viewer or admin, never owner), and approve or decline.
  6. Learn the outcome by polling get_registration or whoami no faster than once a minute, with A2A GetTask (the task id is the registration id), or by push (Push notifications). Statuses: needs_owner_email, pending, approved, declined, expired. A2A task states: input-required until approved, then completed; rejected when declined; canceled when expired.
  7. 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_token makes another named token.
  • rotate_token replaces a token; the old value stops working at once.
  • revoke_token revokes another token. A token cannot revoke itself.
  • list_tokens lists 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.

ToolAuthWhat it does
register_agentnoneStarts 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_emailpendingSets or changes the owner's email (at most 3 changes).
get_registrationpendingStatus: needs_owner_email, pending, approved, declined or expired.
whoamipendingThe agent's id, name, URLs, registration, accounts, roles and verification labels.
list_tokenstokenToken ids, names, status, created and last used. Never the values.
create_tokentokenAnother named token (at most 10 active). The value is shown once.
rotate_tokentokenRevokes a token and returns a new one with the same name.
revoke_tokentokenRevokes another token. The token making the call cannot revoke itself.
update_profiletokenChanges name, card_url or mcp_url. Changing a URL removes its verification at once.
start_verificationtokenOpens 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_verificationtokenRuns 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_verificationstokenThis agent's challenges and labels, newest first.
list_sitestokenSites in the agent's accounts, within its role.
site_summarytokenHumans, bots and agents for one site (default: last 7 days).
list_managed_agentstokenAgents an account tracks with Double Agent observability.
agent_summarytokenOperations, 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 contextId from my first answer and send it on every later message; to answer a task, send its taskId with the same contextId (or none).
  • MCP: Keep the Mcp-Session-Id from initialize and send it on every request; on 404, initialize again. An mcs_… 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

CodeWhat to do
pow_requiredSolve the challenge in details and call register_agent again with pow.
invalid_powThe solution is wrong, expired or already used. Call register_agent without pow for a new challenge.
needs_owner_emailGive the owner's email with set_owner_email, or reply on the registration task.
agent_pendingWait for the owner to approve. Poll get_registration no faster than once a minute.
unauthorizedSend Authorization: Bearer daa_…. If the token was revoked, declined or expired, tell your human; do not register again.
forbiddenYour role does not allow it. Ask your human.
rate_limitedWait details.retry_after seconds once, then stop and report.
invalid_argumentFix 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_foundThe id does not exist or is not yours.
conflictThe state changed, for example the owner already decided or the owner email changed 3 times. Read it again with whoami.
unavailableDouble Agent could not be reached. Try again later, once.
internalSomething 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_agent calls 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.

AI agents can install everything here as a skill:

npx skills add doubleagent-so/skills