On the internet, you usually identify yourself with a login and a password: an identifier and a secret. That works well for humans. Programs that access services get even less: most of the time it’s just an API key, even for autonomous agents acting on behalf of people or companies.

In this post I’ll quickly go over what makes up a digital identity, how identification works for software and autonomous agents, and how to give those agents verifiable identities.

What makes up an identity

An identity has three parts:

  • an identifier: it points to one unique user, like a login or an email address;
  • a proof: the secret the user has to prove they are who they say they are, like a password or a code sent by email;
  • attributes: everything that describes the person, like their first name, last name, date of birth…

You usually find them in a database. It looks more or less like this JSON:

{
  "email": "alice@example.com",
  "password_hash": "$argon2id$v=19$m=65536,t=3,p=4$...",
  "name": "Alice Martin",
  "role": "accountant",
  "email_verified": true
}

Here, email is the identifier. password_hash holds the hash of the password, the secret that proves at sign-in that the user controls the account. The other fields are attributes that describe the user.

Most of the time the user provides the attributes themselves, and the service has probably never checked them, or has no way to. One way to get reliable attributes is to rely on a third party that did check them (an employer for a role, a university for a degree, the state for a civil identity). One way to get verifiable identities without a third-party identity provider is discussed in this post on emailless account architecture.

The identity of non-humans

Programs need an identity too. A website, a backup script or an integration between two pieces of software must prove who they are before reaching a service. These are known as non-human identities.

The most common example is the HTTPS certificate. When a browser connects to a site, the server presents a certificate that binds its domain name to a public key. You can print this blog’s certificate with openssl:

openssl s_client -connect blog.kaizensphere.dev:443 \
  -servername blog.kaizensphere.dev </dev/null 2>/dev/null \
  | openssl x509 -noout -subject -issuer -dates

At the time of writing, the command prints:

subject=CN = blog.kaizensphere.dev
issuer=C = US, O = Let's Encrypt, CN = YR2
notBefore=Aug 27 02:03:17 2026 GMT
notAfter=Nov 25 02:03:16 2026 GMT

The subject line holds the identifier, here the domain name. The issuer line names the certification authority that signed the certificate. notBefore and notAfter are the validity limits. You can see the main parts of an identity are all here: an identifier, a proof through the private key that only the server has, and an attestation signed by a third party the browser trusts.

Accessing an API is much simpler: an API key. For example, a script that imports invoices into an accounting tool gets a configuration like this:

{
  "name": "invoice-import",
  "api_key": "7f3a9c1e...",
  "scopes": ["*"],
  "expires_at": null,
  "owner": null
}

Here the key is both the identifier and the secret. Whoever has the key can act in place of the script it was made for.

tied to no owner ("owner": null). Any person or program that gets hold of this key can act in place of the script.

There are three differences with a person’s identity:

  • programs act on behalf of people (individuals or companies), and the service needs to know who is responsible;
  • a program or an autonomous agent can be created for one task and deleted a few minutes later, and its rights should last just as long;
  • there are a lot of identities for programs and autonomous agents: in October 2025, the World Economic Forum noted that AI agents were multiplying the need for program identities, often with broader and longer-lasting access.

The case of autonomous agents

These differences become critical when the program decides for itself what to do. Given some goals, it picks the steps and takes actions: reading invoices, checking a balance, preparing a payment. Its behaviour changes with the situation, and it would use the same key for all of it.

Say an agent is in charge of processing a company’s invoices. In the classic setup with an API key, the accounting service it calls only knows the request comes from someone who has the key. It has no idea it’s an agent, or which service it’s acting for.

The service would need to know which agent is talking to it, on behalf of which service, and what it’s allowed to do. These checks go by the name Know Your Agent, by analogy with banks’ Know Your Customer, and the Decentralized Identity Foundation has a specification for them.

The principle of verifiable credentials

A Verifiable Credential (VC) is a document signed by an issuer that states attributes about a subject. There are three roles:

  • the issuer, who signs and issues the credential;
  • the holder, who keeps it;
  • the verifier, who checks the signature to confirm the attributes are valid.

Any organisation can issue VCs. It works a bit like the HTTPS certificate, where the browser accepts a signature because it trusts the authority behind it. With VCs, the verifier decides which issuers it accepts, depending on the attributes.

Since a credential always names its subject by an identifier, you can then give that agent rights that only it holds, and not, say, the sub-agents it might use.

Giving an autonomous agent an identifier

The W3C defines a standard, Decentralized Identifiers (DID), whose specification has been a recommendation since 2022. A DID points to a document that publishes its holder’s public keys. One of the methods is did:web, which in our case could look like this:

did:web:example.com:agents:invoices

The did:web method says the document is published on a website. The document contains the following:

{
  "@context": [
    "https://www.w3.org/ns/did/v1",
    "https://w3id.org/security/suites/jws-2020/v1"
  ],
  "id": "did:web:example.com:agents:invoices",
  "controller": "did:web:example.com",
  "verificationMethod": [
    {
      "id": "did:web:example.com:agents:invoices#key-1",
      "type": "JsonWebKey2020",
      "controller": "did:web:example.com:agents:invoices",
      "publicKeyJwk": { "kty": "EC", "crv": "P-256", "x": "...", "y": "..." }
    }
  ],
  "authentication": ["did:web:example.com:agents:invoices#key-1"]
}

The KYA-OS specification also accepts the did:key method, which derives the identifier from the public key itself instead of fetching the document from another address like did:web does.

So when the agent signs a request with its private key, the service can fetch this document with did:web, or verify directly with did:key.

Describing the agent’s rights in a mandate

The agent’s rights are described in a Verifiable Credential. The KYA-OS specification calls it a delegation credential, because it carries delegation capabilities. The example below is based on ZCAP-LD:

{
  "@context": [
    "https://www.w3.org/ns/credentials/v2",
    "https://w3id.org/security/zcap/v1",
    "https://kya-os.org/ns/delegation/v1"
  ],
  "type": ["VerifiableCredential", "DelegationCredential"],
  "issuer": "did:web:example.com",
  "validUntil": "2026-09-22T18:00:00Z",
  "credentialSubject": {
    "id": "urn:zcap:del_2026_09_22_001",
    "invoker": "did:web:example.com:agents:invoices",
    "invocationTarget": "did:web:accounting.example.com",
    "parentCapability": "did:web:accounting.example.com",
    "allowedAction": ["invoices.read", "payments.propose"],
    "caveats": [
      { "type": "MaxAmount", "limit": "500.00", "currency": "EUR" }
    ]
  },
  "credentialStatus": {
    "type": "BitstringStatusListEntry",
    "statusPurpose": "revocation",
    "statusListIndex": "94567",
    "statusListCredential": "https://example.com/status/delegations"
  }
}

ZCAP-LD is used to prove what the agent is allowed to do on a remote resource. It also makes it possible to securely delegate the right to perform actions to sub-agents, while following the principle of least privilege.

I’d like to point out a small difference with HTTPS. In HTTPS, the certification authority is involved in certifying the identity. Delegation credentials are based on the Object Capability (OCAP) model, which lets an agent issue a delegation credential to another agent.

There’s also a crucial difference with regular Verifiable Credentials: those are optimised for selective disclosure, while a delegation credential gives all the information. We want to know exactly which agent it is and what it can do on the resources.

In practice, how does this identification work?

To send its request, the autonomous agent adds three headers: the delegation credential in KYA-OS-Delegation-Credential, the proof that it holds the key in KYA-OS-Delegation-Proof, and its identity in KYA-OS-Agent-DID. There are three more headers that play no part in the authorization decision: KYA-OS-Session-Id, a session identifier, KYA-OS-Delegation-Chain, the delegation chain, useful for routing, and KYA-OS-Granted-Scopes, the granted rights in plain text, useful for logging or debugging.

The service then has to do the following:

1. Resolve the agent's DID in KYA-OS-Agent-DID and fetch its public key
2. Check the proof of key possession in KYA-OS-Delegation-Proof
3. Resolve the issuer's DID and check the signature on the mandate in KYA-OS-Delegation-Credential
4. Check that the invoker field names this agent
5. Check that invocationTarget names the resource being called
6. Check the validUntil date
7. Look up the status list to confirm the mandate is still active
8. Compare the requested action with allowedAction and the caveats

Once all these checks have passed, we can vouch for the identifier of the agent’s principal, whether it’s a person or an organisation.

The principal stores its credentials in a digital wallet. The European Union is preparing a standardised version of this wallet for companies, presented in the June 2026 EUDI Wallet news.

Limits

Agents are unpredictable, so nothing guarantees they’ll stick to what they’re allowed to do. Decision engines, like the one presented in the article on ODRL, help the service reduce that risk.

ZCAP-LD hasn’t been adopted yet. For now, discussions about the format are ongoing at the W3C.

To try it out, the KYA-OS specification has an online demo.