Why Agents Need Protocol-Compliant Identity
An API key does not make an agent addressable. What a DID, a published agent card and a signed envelope do that a bearer token cannot.
An API key answers exactly one question: does the caller hold the secret? It says nothing about who the caller is, whose authority it acts under, or how a stranger's infrastructure could check either.
For an agent that only calls your own backend, that is enough. For an agent that has to introduce itself to a service you do not control, it is not — because there is nobody to introduce it to. A bearer token is not addressable.
What an agent carries instead#
Three things, each resolvable by someone who has never met your agent and holds none of your secrets.
A DID#
Every agent is issued a W3C did:web identifier:
did:web:agents.useanima.sh:<orgId>:<agentId>
The DID document published under it lists the agent's public key and its service endpoints — where to deliver email, where to POST a task. That is the part an API key has no equivalent for: a third party can look up where to reach your agent and which key will sign for it, without you having provisioned anything on their side.
A public agent card#
Each agent serves a card at its own subdomain, unauthenticated:
https://<slug>.useanima.sh/.well-known/agent.json
{
"name": "Outreach Agent 01",
"url": "https://useanima.sh/agents/agt_...",
"did": "did:web:agents.useanima.sh:org_...:agt_...",
"capabilities": {
"email": true,
"phone": true,
"vault": true,
"address": false,
"protocols": ["a2a"]
},
"verification": { "level": "standard", "credentials": ["AnimaEmailVerified"] },
"trustScore": 20,
"contact": { "email": "outreach-01@yourdomain.com" }
}The capabilities block is derived from what the agent actually holds, not from what you
asked for at creation — email is true because an email identity exists, not because you
passed a flag.
A signed envelope#
Agent-to-agent messages are canonicalized — keys sorted, recursively — then signed with
the agent's key and sent with the signature in an X-Agent-Signature header:
{
"from": "did:web:agents.useanima.sh:org_...:agt_...",
"to": "did:web:example.com:org_...:agt_...",
"type": "task.request",
"input": { },
"ts": "2026-07-21T10:04:11.000Z",
"nonce": "..."
}Canonicalization is what makes the signature checkable by a recipient who serializes JSON
differently than you do. The ts and nonce are what stop a captured envelope from being
replayed.
Verification level is earned. Trust score is not.#
The card's verification.level is derived from credentials the platform issues on real
events, so it means something:
| Level | What it took |
|---|---|
basic | Nothing yet — a fresh agent |
standard | A channel was verified: email OTP completed, or a real number provisioned |
premium | Standard, plus org-level verification via a completed paid checkout |
Ignore trustScore — it is a reserved field
trustScore is not computed from anything. It is a hardcoded placeholder, the same value
on every card, kept in the response because four SDKs and the CLI type it as a number.
Do not rank, filter, or gate on it. It carries no signal today, and a card showing 20 is
not telling you the agent is worse than one you scored yourself.
The DID is provisioned, but not guaranteed#
Worth knowing before you build discovery on top of it: DID provisioning at agent creation is best-effort. If it fails, the failure is logged and agent creation still succeeds.
An agent without a DID has no card — the well-known endpoint returns 404 rather than a
partial document. So treat "the card is missing" as a state to handle, not an impossibility.
Creating one#
import { Anima } from "@anima-labs/sdk";
const client = new Anima({ apiKey: process.env.ANIMA_API_KEY! });
const agent = await client.agents.create({
orgId: process.env.ANIMA_ORG_ID!,
name: "Outreach Agent 01",
slug: "outreach-01",
email: "outreach-01@yourdomain.com",
provisionPhone: true,
});There is no protocols parameter and no capabilities array. Both were invented by an
earlier version of this post; neither exists on the create input. Protocol support is not
something you opt into per agent — the DID, the card and the signing key come with the
identity.
Discovery needs no credentials at all, because the card is public:
const card = await client.a2a.discover("https://outreach-01.useanima.sh");An agent submits tasks as itself, and only as itself
discover is an unauthenticated fetch. Task submission is authenticated, and an agent's
own key is enough — but the sender is bound to the caller rather than taken on trust:
with an agent key, from must be that agent's own DID, and anything else is refused.
An org key may send on behalf of any agent in its own org.
The one thing to plan for is a newer scoped sk_ key. The A2A procedures are classified
as platform infrastructure and are not mapped to a narrow scope, so a scoped key falls
back to needing the * wildcard. Legacy ak_ and mk_ keys are exempt from scope
checks entirely, which is why an agent key works today.
The compliance gate that actually runs#
Identity gets an agent addressable. It does not get it permission to dial.
Outbound voice is gated on a one-time TCPA consent attestation your organization completes in the console. It is enforced server-side and fails closed — no attestation, no call — and your agent's code cannot route around it.
What Anima does not do is scrub the destination against the FCC Reassigned Numbers Database. That obligation stays with you. The attestation records that your org may dial; nothing checks whether this number still belongs to the person who consented.
What this is not#
Anima does not implement Google AP2
An earlier version of this post said agents must comply with "Google AP2 (Agent Protocol 2.0)" to avoid being flagged as spam. That was wrong twice over.
AP2 is Google's Agent Payments Protocol — signed mandates for agent-led purchases, extending A2A and MCP. It has nothing to do with spam classification, and Anima implements no part of it, because Anima has no payments surface. Email deliverability comes from DKIM, SPF and DMARC on the sending domain, which is a different mechanism entirely.
Why it matters#
Without a resolvable identity, an agent can only act inside systems that already hold a secret for it. Every new counterparty is a manual key exchange, and every action traces back to "whoever had the token."
A DID, a public card and a signed envelope replace that with something a stranger can verify unilaterally — which is the property that lets an agent act somewhere you have no account, no prior arrangement, and no way to hand over a key in advance.