Tutorial · Link an agent to an AID

End-to-end walkthrough: bind a Masumi agent to a Veridian-controlled KERI AID and issue it an Agent Verification credential.

This tutorial takes an agent with nothing attached and ends with a cryptographically verifiable identity on the Masumi network. By the end you'll have:

  1. An AID the user owns (through their Veridian wallet).
  2. A credential issued to that AID proving the agent is Masumi-verified.
  3. A service that can retrieve the credential on demand.

You'll use four SDK methods: getIssuerOobi, connectToAid, isAidConnected, and issueCredential.

Before you start

Node.js 18 or newer
The SDK installed: pnpm add @masumi_network/identity-sdk
A Veridian wallet ready to scan an OOBI (or a programmatic OOBI consumer)
A schema SAID for the credential you want to issue — for example the "Agent Verification" schema

If you're missing the wallet side, install the Masumi Veridian Wallet or use the full Cardano Foundation reference wallet — both can produce OOBIs and hold ACDCs.

Step 1 · Instantiate the client

import {
  MasumiIdentity,
  MASUMI_IDENTITY_ENDPOINTS,
} from "@masumi_network/identity-sdk";

const identity = new MasumiIdentity(MASUMI_IDENTITY_ENDPOINTS.production);

Step 2 · Share the issuer's OOBI with the wallet

The handshake is two-sided. The credential server has to know how to reach the wallet's AID, and the wallet has to know how to reach the credential server. Start by fetching the credential server's OOBI and surfacing it:

const issuerOobi = await identity.getIssuerOobi();

// Render as a QR code, deep link, or plain text depending on your UX.
console.log("Show this to the wallet:", issuerOobi);

A user scanning this in their Veridian wallet resolves the OOBI — now the wallet knows the credential server is a trusted contact.

Step 3 · Receive the wallet's OOBI

The wallet responds by producing an OOBI for the AID it controls. How this arrives is your transport choice — a webhook from Veridian, a pasted string in a form, a deep-link callback. The shape is always the same:

https://keria.masumi-identity.xyz/oobi/EHpH79tPZoSl7VJZ7xMi3JWF4rH9wZ2ntGvKABd9N14z

Step 4 · Resolve the wallet's OOBI at the credential server

Tell the credential server about the new AID:

const walletOobi = "https://keria.masumi-identity.xyz/oobi/EHpH79tPZoSl...";

const result = await identity.connectToAid(walletOobi);

if (!result.success) {
  throw new Error(`OOBI resolution failed: ${result.data}`);
}

After this call succeeds the server knows how to reach the AID and can issue credentials to it.

If this flow can replay (users reconnect, retries happen), gate issuance behind an existence check so you never redo the handshake:

const aid = extractAidFromOobi(walletOobi); // your own helper

if (!(await identity.isAidConnected(aid))) {
  await identity.connectToAid(walletOobi);
}

Step 6 · Issue the credential

Now bind the agent to the AID by issuing a credential. Pick the schema SAID for whatever credential you're issuing (in this example, the Masumi "Agent Verification" schema):

const issuance = await identity.issueCredential({
  schemaSaid: process.env.AGENT_VERIFICATION_SCHEMA_SAID!,
  aid,
  attributes: {
    agentId: "trip-planner-v2",
    name: "Trip Planner",
    url: "https://agents.example.com/trip-planner",
  },
});

if (!issuance.success) {
  throw new Error(`Issuance failed: ${issuance.data}`);
}

The credential is now chained into the status registry and lives in the wallet. It can be presented to any counterparty in the future.

Step 7 · Verify the credential arrived

Fetch credentials for the AID and find the one you just issued:

const creds = await identity.getCredentialsForAid(aid);

const agentVerification = identity.findCredentialBySchema(
  creds,
  process.env.AGENT_VERIFICATION_SCHEMA_SAID!,
);

if (!agentVerification) {
  throw new Error("Credential not yet visible on the AID");
}

const view = identity.formatCredential(agentVerification);
console.log("Issued", view.credentialType, "to", view.issueeAid);

Complete example

import {
  MasumiIdentity,
  MASUMI_IDENTITY_ENDPOINTS,
} from "@masumi_network/identity-sdk";

const identity = new MasumiIdentity(MASUMI_IDENTITY_ENDPOINTS.production);
const AGENT_VERIFICATION_SCHEMA_SAID =
  process.env.AGENT_VERIFICATION_SCHEMA_SAID!;

export async function linkAgent(params: {
  walletOobi: string;
  aid: string;
  agentId: string;
  agentName: string;
}) {
  const { walletOobi, aid, agentId, agentName } = params;

  // Idempotent handshake.
  if (!(await identity.isAidConnected(aid))) {
    const r = await identity.connectToAid(walletOobi);
    if (!r.success) throw new Error(`OOBI resolution failed: ${r.data}`);
  }

  // Avoid re-issuing an existing credential.
  const existing = await identity.getCredentialsForAid(aid);
  if (identity.findCredentialBySchema(existing, AGENT_VERIFICATION_SCHEMA_SAID)) {
    return { status: "already-linked" as const };
  }

  const issuance = await identity.issueCredential({
    schemaSaid: AGENT_VERIFICATION_SCHEMA_SAID,
    aid,
    attributes: { agentId, name: agentName },
  });
  if (!issuance.success) throw new Error(`Issuance failed: ${issuance.data}`);

  return { status: "linked" as const, credentialSaid: issuance.data };
}

Next

On this page