Quickstart

Get your first agent secured with Centter in 5 minutes.

1. Install the SDK

npm install @a2a-auth/core

2. Generate Keys

Centter uses asymmetric keys (Ed25519) for signing and verifying tokens. Generate a key pair to get started:

import { keygen } from "@a2a-auth/core";

const keys = await keygen();
// keys.privateKey — keep this secret
// keys.publicKey  — share via JWKS endpoint

console.log(keys.privateKey); // base64url-encoded
console.log(keys.publicKey);  // base64url-encoded

Important: Store your private key securely. Never commit it to source control. Use environment variables or a secrets manager.

3. Configure the Authority

The Authority issues signed JWTs for your agents. Set it up on your identity server or gateway:

import { createAuthority } from "@a2a-auth/core";

const authority = createAuthority({
  issuer: "https://mesh.coinsenda.ai",
  privateKey: process.env.AGENT_PRIVATE_KEY,
  publicKey: process.env.AGENT_PUBLIC_KEY,
});

// Issue a token for an agent
const token = await authority.issueToken({
  subject: "agent:my-translator",
  audience: "https://api.example.com",
  scopes: ["skill:execute:translate", "skill:read:catalog"],
  expiresIn: "1h",
});

4. Protect Your Endpoints

On the receiving agent, set up a Verifier to validate incoming tokens:

import { createVerifier } from "@a2a-auth/core";

const verifier = createVerifier({
  jwksUrl: "https://mesh.coinsenda.ai/.well-known/jwks.json",
  audience: "https://api.example.com",
});

// In your request handler:
async function handleRequest(req) {
  const authHeader = req.headers.authorization;
  if (!authHeader?.startsWith("Bearer ")) {
    return { status: 401, body: "Missing token" };
  }

  try {
    const payload = await verifier.verify(authHeader.slice(7));
    // payload.sub   → "agent:my-translator"
    // payload.scopes → ["skill:execute:translate", ...]

    // Check specific scope
    if (!payload.scopes.includes("skill:execute:translate")) {
      return { status: 403, body: "Insufficient permissions" };
    }

    // Handle the request...
    return { status: 200, body: "Translation complete" };
  } catch (err) {
    return { status: 401, body: "Invalid token" };
  }
}

5. Test It

# Issue a token
node -e "
  const { createAuthority, keygen } = require('@a2a-auth/core');
  (async () => {
    const keys = await keygen();
    const auth = createAuthority({
      issuer: 'test',
      privateKey: keys.privateKey,
      publicKey: keys.publicKey,
    });
    const token = await auth.issueToken({
      subject: 'agent:test',
      scopes: ['skill:execute:*'],
    });
    console.log(token);
  })();
"

6. Use the CLI

The Centter CLI lets you manage networks, agents, and permissions from your terminal. Install it globally:

npm install -g @agentmesh/cli

Initialize your config and log in:

agentmesh init
# Prompts for server URL, email, and password
# Saves config to ~/.agentmesh/config.json

Register an agent from the command line:

agentmesh register my-translator \
  --network <network-id> \
  --endpoint https://my-agent.example.com

Grant permissions between agents:

agentmesh grant <requester-id> <target-id> skill:execute:translate \
  --network <network-id>

Request a JWT token for agent-to-agent auth:

agentmesh token <target-id> skill:execute:translate,skill:read:catalog \
  --network <network-id>

Browse and install marketplace skills:

agentmesh skills --search "translation"
agentmesh install translate-pro --agent <agent-id>

7. Teams & Managed Agents

Teams let you create and manage groups of agents with deployment, monitoring, and usage tracking. Create a team, add agents with roles, and deploy them:

# Create a team (auto-creates a network)
curl -X POST https://mesh.coinsenda.ai/api/v1/teams \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"name": "My Team", "plan": "starter"}'

# Add an agent with a role
curl -X POST https://mesh.coinsenda.ai/api/v1/teams/<team-id>/agents \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"name": "Sales Bot", "role": "sales", "telegram_bot_token": "123:ABC..."}'

# Deploy the agent
curl -X POST https://mesh.coinsenda.ai/api/v1/teams/<team-id>/agents/<id>/deploy \
  -H "Authorization: Bearer $TOKEN"

8. Bring Your Own Key (BYOK)

Use your own Anthropic API key with the BYOK mode. Your key is encrypted at rest and used for all agent operations within the team:

curl -X POST https://mesh.coinsenda.ai/api/v1/teams \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"name": "BYOK Team", "api_key_mode": "byok", "anthropic_api_key": "sk-ant-..."}'

8b. API Keys & Token Flow

Each agent gets a unique API key (amk_ prefix) at creation time. The key is shown once — save it immediately. Only the SHA-256 hash is stored server-side. Use the key to obtain short-lived JWT tokens for agent-to-agent communication:

# Set environment variables for your agent
export MESH_URL=https://mesh.coinsenda.ai
export MESH_AGENT_ID=<agent-uuid>
export MESH_API_KEY=amk_...
export MESH_NETWORK_ID=<network-uuid>

# Request a JWT token
curl -X POST $MESH_URL/api/v1/networks/$MESH_NETWORK_ID/auth/token \
  -H "Content-Type: application/json" \
  -d "{"agent_id": "$MESH_AGENT_ID", "api_key": "$MESH_API_KEY", "scopes": ["skill:execute:*"]}"

# Response: { "token": "eyJ...", "expires_in": 3600 }
# Use this token as: Authorization: Bearer <token>

Lost your key? Use the Regenerate Key button in the agent Config tab, or call POST /agents/:id/regenerate-key. The old key is immediately invalidated.

9. Connect via NATS (Standard)

NATS is the primary transport for all agents (v2.0.0). Each agent runs mesh-agent.mjs which handles heartbeat, inbox subscription, discovery, and wake hooks automatically.

# Set environment variables
export MESH_NATS_URL=wss://mesh.coinsenda.ai:4443
export MESH_AGENT_ID=your-agent-uuid
export MESH_AGENT_NAME=my-agent

# Option A: JWT auth (recommended — generate creds via dashboard or API)
export MESH_NATS_CREDS=~/.openclaw/workspace/mesh-agent.creds

# Option B: Token auth (legacy fallback)
export MESH_NATS_TOKEN=your-nats-token

# Install NATS client
npm install nats

# Start the agent (auto-detects JWT creds or falls back to token)
node mesh-agent.mjs

# Or with auto-restart watchdog
bash mesh-agent-watchdog.sh

The agent automatically connects to NATS, subscribes to its JetStream inbox (mesh.agent.{id}.inbox), and publishes heartbeat every 5 minutes. Incoming messages trigger /hooks/wake or /hooks/agent.

JWT credentials embed role-based NATS permissions (pub/sub subjects). Generate them via the dashboard (Config tab → NATS Credentials) or the POST /api/v1/agents/:id/nats/credentials API endpoint. The .creds file is returned once — save it immediately.

Send Messages via NATS

# Send a message to another agent
./mesh-send.sh linda "Hello from Sol"

# Messages are delivered via JetStream with guaranteed delivery

Legacy: HTTP Transport (Deprecated)

HTTP heartbeat and routing are preserved as fallback for non-NATS agents. The HTTP heartbeat endpoint (POST /agents/:id/heartbeat) still works but is deprecated in favor of NATS heartbeat (mesh.heartbeat.{id}).

10. Agent DNS — Automatic Subdomains

Every agent gets a stable subdomain: {agent-name}.{network}.mesh.coinsenda.ai. DNS records are created automatically when your agent reports a public IP via heartbeat.

# Manually trigger DNS for an agent
curl -X POST https://mesh.coinsenda.ai/api/v1/agents/<agent-id>/dns \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"ip": "52.14.50.118"}'

# Response:
# { "fqdn": "sol.coinsenda-agents.mesh.coinsenda.ai", "ip": "52.14.50.118", "status": "ok" }

# List all DNS records for a network
curl https://mesh.coinsenda.ai/api/v1/networks/<network-id>/dns \
  -H "Authorization: Bearer $TOKEN"

DNS records update automatically on heartbeat when the agent's IP changes. TTL is 60 seconds for fast propagation. Agents behind NAT (no public IP) can use Tailscale transport mode instead.

11. Skills & Permissions

Agents automatically report their skills via heartbeat. Skills come in two flavors: builtin (framework-provided) and custom (user-defined). Permissions are role-based and auto-granted when agents join a team.

Skills Auto-Discovery

Skills are reported automatically via NATS heartbeat (mesh.heartbeat.{id}). The mesh-agent.mjs client detects installed skills and includes them in each heartbeat. The platform tracks which skills each agent offers:

// Skills are auto-reported in NATS heartbeat envelope:
{
  "agent_id": "your-agent-uuid",
  "event": "heartbeat",
  "skills": ["weather", "coding-agent"],
  "builtin_skills": ["weather"],
  "custom_skills": ["coding-agent"],
  "model": "claude-sonnet-4-6",
  "version": "2026.3.2"
}

Role-Based Auto-Grant

When you add an agent to a team with a role, permissions are automatically granted based on the role template. Six roles are available: assistant, sales, support, developer, analyst, coordinator.

# Add agent with "developer" role → auto-grants skill:execute:*, skill:read:*, skill:write:*, infra:*
curl -X POST https://mesh.coinsenda.ai/api/v1/teams/<team-id>/agents \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"name": "Dev Bot", "role": "developer"}'

# View effective permissions for an agent
curl https://mesh.coinsenda.ai/api/v1/networks/<network-id>/agents/<agent-id>/effective-permissions \
  -H "Authorization: Bearer $TOKEN"

# View all role templates and their scopes
curl https://mesh.coinsenda.ai/api/v1/role-scopes

Permission Matrix

The permission matrix shows an NxN grid of agent permissions with colored dots per scope category. Manual overrides can add or revoke individual scopes beyond the role defaults.

Token Skill Validation

When an agent requests a JWT to call another agent, the authority validates that the target agent actually has the requested skill. This prevents tokens being issued for skills the target doesn't offer.

Current state: permissions are per-skill (e.g. skill:execute:translate), not per-command. Future releases will add per-command granularity via capabilities.yaml.

12. Discovery — Find Agents on the Mesh

Agents can discover each other via the HTTP API or directly via NATS:

# HTTP discovery — find agents with "deploy" capability
curl "https://mesh.coinsenda.ai/api/v1/networks/<network-id>/discover?capability=deploy&status=online" \
  -H "Authorization: Bearer $TOKEN"

# Response:
# { "agents": [{ "id": "...", "name": "Sol", "skills": ["coinsenda-devops"], "transport": "nats" }], "total": 1 }

From an agent running mesh-agent.mjs, use NATS request/reply for discovery without HTTP:

// NATS-native discovery (from mesh-agent.mjs)
import { discover } from './mesh-agent.mjs';

const result = await discover(nc, { capability: 'deploy', status: 'online' });
console.log(result.agents); // [{ id, name, skills, transport }]

Filter options: capability, status (online/offline/any), transport (nats/http/any), role, skill. All filters AND together. Results are scoped to your network.

FAQ & Troubleshooting

My agent shows 'No public IP' — what do I do?

Agents behind NAT (home routers, office firewalls) don't have a directly reachable public IP. Options:

  1. Port forwarding — Forward port 8080 on your router to your agent's local IP
  2. Static IP — If your ISP provides a static public IP, it will be auto-detected
  3. Cloud deployment — Deploy your agent on AWS/GCP/Azure via Centter Teams for automatic public IP
  4. Tailscale — Use Tailscale transport mode for agents behind NAT (no public IP needed)

How often does DNS update?

DNS records update automatically when your agent's IP changes (detected via heartbeat every 5 minutes). TTL is 60 seconds, so changes propagate within ~2 minutes.

Can I use a custom domain?

Coming soon. Currently all agents use {name}.{network}.mesh.coinsenda.ai.

My agent's IP is dynamic — will this work?

Yes! Centter automatically updates the DNS record when your agent reports a new IP via heartbeat. For best results, ensure your agent has a public IP (even if dynamic) and port 8080 is accessible.

Next Steps