The Agent Auth API provides DID-based identity, delegation chains, OAuth-like login, payment authorization, and execution receipts for AI agents. Base URL: https://api.agentauth.elydora.com
All requests and responses use JSON. Rate limits are per-IP unless otherwise noted.
// DID Identity & Authentication
POST/v1/identities
Register a new agent identity. Returns a DID and verifiable credential. By default the server generates an Ed25519 keypair for you. Alternatively, supply your own public key (BYOK) and the server will bind it to a DID without ever seeing your private key.
Model identifier (e.g. claude-opus-4-6). Min 1, max 255 chars.
agent_provider
string
required
Provider name (e.g. Anthropic). Min 1, max 255 chars.
agent_purpose
string
required
What the agent intends to do. Min 1, max 500 chars.
metadata
object
optional
String key/value metadata map. Max 20 keys. Key max 64 chars, value max 256 chars.
Registration always uses the server default credential lifetime (24 hours). Website developers control credential lifetime when requesting authentication challenges via POST /v1/auth/challenge.
Supply an Ed25519 public key in JWK format to bind your own keypair to a DID. The server derives a did:key from your public key and issues a credential. Your private key never leaves your environment.
Response 201 (server-generated)
json
{
"did": "did:key:z6Mk...",
"credential": "eyJhbGciOiJFZERTQSJ9...",
"key_fingerprint": "SHA256:a1b2c3d4...",
"key_origin": "server_generated",
"private_key_jwk": { "kty": "OKP", "crv": "Ed25519", "x": "...", "d": "..." },
"_notice": "Save your private_key_jwk securely. Elydora AgentAuth does NOT store it."
}
BYOK responses omit private_key_jwk (the server never sees your private key). The key_origin field indicates whether the keypair was server-generated ("server_generated") or supplied by the agent ("client_provided"). This field is also embedded in the VC credentialSubject so verifying parties can inspect it.
Response 409
json
{
"error": "invalid_request",
"error_description": "An identity with this public key already exists."
}
Rate Limit:10 requests / hour per IP
POST/v1/auth/challenge
Request an authentication challenge. The server returns a random nonce that the agent must sign with its Ed25519 private key to prove identity.
Request Body
Name
Type
Required
Description
did
string
required
The agent's DID (e.g. "did:key:z6Mk...").
site_id
string
optional
Scope the session to a specific site.
credential_expires_in
number
optional
Credential validity in seconds, set by the website developer. Min 300 (5 min), max 2592000 (30 days). Set to 0 for no expiration. Default: 86400 (24 hours).
{
"error": "invalid_request",
"error_description": "DID not found. Register first via POST /v1/identities."
}
Rate Limit:30 requests / minute per IP
POST/v1/auth/verify
Verify a signed challenge. If the signature is valid, returns a session token and a fresh verifiable credential. The session lasts 1 hour. The credential lifetime is determined by the challenge request (default: 24 hours).
Request Body
Name
Type
Required
Description
challenge_id
string
required
The challenge ID from POST /v1/auth/challenge.
did
string
required
The agent's DID.
signature
string
required
Base64url-encoded Ed25519 signature of the challenge nonce.
{
"valid": false,
"error": "signature_invalid",
"message": "The signature does not match the registered public key for this DID."
}
Rate Limit:30 requests / minute per IP
POST/v1/credentials/verify
Verify a Verifiable Credential (VC-JWT) issued by Elydora AgentAuth. Websites call this endpoint to confirm an agent's credential is authentic and extract the verified identity. The server checks the Ed25519 signature, validates the issuer, and checks expiry.
Request Body
Name
Type
Required
Description
credential
string
required
The VC-JWT credential string received from the agent.
The key_origin field is "server_generated" when Elydora AgentAuth generated the keypair, or "client_provided" when the agent supplied their own public key (BYOK).
Response 401 (expired)
json
{
"valid": false,
"error": "credential_expired",
"message": "The credential has expired. The agent should re-authenticate via challenge-response to get a fresh credential."
}
Response 401 (invalid signature)
json
{
"valid": false,
"error": "signature_invalid",
"message": "The credential signature is invalid or the JWT is malformed."
}
Response 401 (invalid issuer)
json
{
"valid": false,
"error": "invalid_issuer",
"message": "The credential was not issued by Elydora AgentAuth."
}
Response 401 (revoked)
json
{
"valid": false,
"error": "credential_revoked",
"message": "Credential has been revoked."
}
Rate Limit:60 requests / minute per IP
GET/.well-known/did.json
Returns the server's own DID document containing its Ed25519 public key. Agents can use this to verify credentials issued by Elydora AgentAuth.
Elydora AgentAuth issues two types of tokens with different lifetimes. Credential expiration is fully configurable by developers.
Session Token vs Credential
Token
Default
Configurable
Purpose
session_token
1 hour
No
Internal session for Elydora AgentAuth. Returned from POST /v1/auth/verify.
credential
24 hours
Yes
VC-JWT for third-party verification. Developers set the lifetime.
Configuring Credential Expiration
Pass credential_expires_in (in seconds) to POST /v1/auth/challenge to control how long the issued VC-JWT credential is valid. This is set by the website developer when initiating an authentication challenge.
Website developers control this value when requesting authentication challenges. Set credential_expires_in to 0 when you explicitly want non-expiring credentials.
Name
Type
Required
Description
credential_expires_in
number
optional
Credential lifetime in seconds. Min 300 (5 min), max 2592000 (30 days). Set to 0 for no expiration. Default: 86400 (24 hours).
Examples
bash
# 1 hour credential
curl -X POST https://api.agentauth.elydora.com/v1/auth/challenge \
-H "Content-Type: application/json" \
-d '{"did":"did:key:z6Mk...","credential_expires_in":3600}'
# 7-day credential
curl -X POST https://api.agentauth.elydora.com/v1/auth/challenge \
-H "Content-Type: application/json" \
-d '{"did":"did:key:z6Mk...","credential_expires_in":604800}'
# No expiration
curl -X POST https://api.agentauth.elydora.com/v1/auth/challenge \
-H "Content-Type: application/json" \
-d '{"did":"did:key:z6Mk...","credential_expires_in":0}'
# Default (24 hours) — omit the field
curl -X POST https://api.agentauth.elydora.com/v1/auth/challenge \
-H "Content-Type: application/json" \
-d '{"did":"did:key:z6Mk..."}'
Recommended Values
Use Case
Value
Duration
High-security / single-use
300
5 minutes
Short-lived tasks
3600
1 hour
Default / general purpose
86400
24 hours
Long-running agents
604800
7 days
Background / batch jobs
2592000
30 days
Persistent identity / never expires
0
No expiration
Shorter credentials are more secure because they limit the window of exposure if a credential is leaked. Agents can always re-authenticate via challenge-response to get a fresh credential.
// OAuth-like Login
GET/oauth/authorize
Initiates the hosted agent login flow. Validates the client_id and redirect_uri against the registered developer app, then redirects to the consent page. Supports PKCE with S256 code challenge method.
Name
Type
Required
Description
client_id
string
required
Registered developer app client ID.
redirect_uri
string
required
Must match a registered redirect URI.
response_type
string
required
Must be 'code'.
state
string
required
CSRF protection state parameter.
scope
string
optional
Space-separated requested scopes.
code_challenge
string
optional
PKCE code challenge (S256).
code_challenge_method
string
optional
Must be 'S256' when code_challenge is present.
POST/oauth/token
Exchanges an authorization code for an agent session token and verifiable credential. Validates the code, PKCE verifier, client_id, and redirect_uri.
POST/oauth/revoke
Revokes an agent session token. The session becomes invalid immediately.
GET/oauth/userinfo
Returns the agent profile associated with a valid Bearer session token. Includes agent DID, display name, model, provider, site, and delegation chain.
// Delegation Chains
POST/v1/sponsors
Creates a sponsor entity (human, organization, or service account) that can authorize delegation grants for agents.
POST/v1/delegations
Creates a delegation grant from a sponsor or parent agent to a delegate agent. Enforces scope subset rules, depth limits, and parent grant validation for sub-delegations. Returns a signed delegation credential.
POST/v1/delegations/verify
Verifies a delegation credential JWT. Checks signature, issuer, expiration, credential type, and revocation status of the underlying grant.
POST/v1/delegations/:id/revoke
Revokes a delegation grant. The grant becomes invalid immediately, blocking subsequent child sessions while preserving audit references.
// Payment Authorization
POST/v1/payments/authorizations
Requests a payment authorization. Validates agent identity, delegation, and enforces approval actor independence (the agent and approver must differ). Uses idempotency keys to prevent duplicate authorizations.
POST/v1/payments/authorizations/:id/approve
Approves a pending payment authorization. Transitions the state from approval_required to approved. Checks for expiration before approving.
POST/v1/payments/authorizations/:id/execute
Initiates payment execution for an approved authorization. Transitions to provider_pending and creates an execution_initiated event.
POST/v1/payments/confirm
Confirms a provider payment. Transitions from provider_pending to paid, records the order_id and tx_hash from the payment provider.
// Execution Receipts
POST/v1/execution-receipts
Creates a signed execution receipt linking agent session, delegation, payment authorization, trace, and input/output hashes. Returns a signed receipt credential JWT.
POST/v1/execution-receipts/verify
Verifies an execution receipt credential JWT. Checks signature, issuer, expiration, and credential type.
// Reconciliation
POST/v1/reconciliation/runs
Starts a reconciliation run for a time period and provider. Matches payment authorizations against execution receipts, producing matched, missing_order, missing_receipt, and drift items.
GET/v1/reconciliation/runs/:id/items
Lists matched and drift items from a completed reconciliation run.
Use the headless flow to authenticate an AI agent directly via the API. Accept agent API logins on your site with a single verification endpoint. Or use the hosted sign-in page for browser-based agent authentication.
The primary flow for AI agents. Generate your own Ed25519 keypair (BYOK), register once with the API, then authenticate anywhere by signing a cryptographic challenge. Your private key never leaves your environment. No browser required.
01
Generate Keypair & Register (BYOK)
Generate your own Ed25519 keypair locally, then register with your public key. Your private key never leaves your environment. The server derives a DID from your public key and returns your DID and a credential.
register.js
// BYOK: Generate your own Ed25519 keypair, then register with your public key.
// Your private key never leaves your environment.
import { AuthAgents } from "auth-agents"
// npm install auth-agents
const authAgents = new AuthAgents()
// Generate your own Ed25519 keypair
const keyPair = await AuthAgents.generateKeyPair()
// keyPair.publicKeyJwk — send this to Agent Auth
// keyPair.privateKeyJwk — keep this secret, never share it
// Register with your public key
const res = await fetch("https://api.agentauth.elydora.com/v1/identities", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
agent_name: "Claude",
agent_model: "claude-opus-4-6",
agent_provider: "Anthropic",
agent_purpose: "Research assistant",
public_key_jwk: keyPair.publicKeyJwk,
}),
})
const identity = await res.json()
// Response 201 (BYOK):
// {
// "did": "did:key:z6Mk...",
// "credential": "eyJhbGciOiJFZERTQSJ9...",
// "key_fingerprint": "SHA256:a1b2c3d4...",
// "key_origin": "client_provided"
// }
// Note: No private_key_jwk returned — you already have it locally
const { did } = identity
02
Request Challenge
Before each authentication session, request a one-time challenge nonce from the server. The nonce is tied to your DID and expires in 60 seconds. You must sign and submit it before it expires.
challenge.js
// POST https://api.agentauth.elydora.com/v1/auth/challenge
// Request a one-time challenge nonce tied to your DID.
const res = await fetch("https://api.agentauth.elydora.com/v1/auth/challenge", {
method: "POST",
headers: { "Content-Type": "application/json" },
// Website developer sets credential lifetime (1 hour in this example)
body: JSON.stringify({ did, credential_expires_in: 3600 }),
})
const challenge = await res.json()
// Response 201:
// {
// "challenge_id": "ch_...",
// "nonce": "hex-encoded-random-bytes",
// "expires_in": 60
// }
//
// The nonce expires in 60 seconds. Sign and verify before it expires.
const { challenge_id, nonce } = challenge
03
Sign Nonce & Verify
Sign the nonce as a UTF-8 string using your Ed25519 private key (use the SDK's signChallenge helper), then POST the signature to /v1/auth/verify. On success, you receive a session token and a fresh Verifiable Credential with the lifetime set by the challenge request.
verify.js
// Sign the nonce with your Ed25519 private key, then verify.
// CRITICAL: Sign the nonce as a UTF-8 string (NOT hex-decoded bytes).
// Using the SDK (recommended):
const signature = await AuthAgents.signChallenge(keyPair.privateKeyJwk, nonce)
// Or manually with Web Crypto API:
// const key = await crypto.subtle.importKey(
// "jwk", keyPair.privateKeyJwk, { name: "Ed25519" }, false, ["sign"]
// )
// const raw = await crypto.subtle.sign("Ed25519", key, new TextEncoder().encode(nonce))
// const signature = btoa(String.fromCharCode(...new Uint8Array(raw)))
// .replace(/\+/g, "-").replace(/\//g, "_").replace(/=/g, "")
// POST /v1/auth/verify with the challenge_id, did, and signature
const res = await fetch("https://api.agentauth.elydora.com/v1/auth/verify", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ challenge_id, did, signature }),
})
const result = await res.json()
// Response 200:
// {
// "valid": true,
// "session_token": "sess_...",
// "credential": "eyJhbGciOiJFZERTQSJ9...",
// "agent": {
// "did": "did:key:z6Mk...",
// "agent_name": "Claude",
// "agent_model": "claude-opus-4-6",
// "agent_provider": "Anthropic",
// "agent_purpose": "Research assistant",
// "key_fingerprint": "SHA256:a1b2c3d4..."
// },
// "expires_in": 3600
// }
04
Present Credential to Websites
Include the VC-JWT credential in your requests to websites that accept Agent Auth. The site verifies it with one API call and gets your complete verified identity, including key_origin indicating whether your key was server-generated or self-provided.
present.js
// The credential is a VC-JWT you can present to any website.
// Include it in your API requests or HTTP headers.
// Option A — Authorization header
fetch("https://example.com/api/endpoint", {
headers: {
"Authorization": `Bearer ${credential}`,
"X-Agent-DID": did,
},
})
// Option B — Request body
fetch("https://example.com/api/endpoint", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ credential, did, ...yourPayload }),
})
// The site verifies by calling Agent Auth:
// POST https://api.agentauth.elydora.com/v1/credentials/verify
// { "credential": "eyJ..." }
//
// Response: { "valid": true, "did": "...", "agent_name": "Claude",
// "key_origin": "server_generated", ... }
//
// Credential lifetime is set by the website developer via the challenge request.
// Sessions last 1 hour. Re-authenticate via challenge-response to get a fresh credential.
// USE THE SDK
The Node.js and Python SDKs include generateKeyPair() and signChallenge() helpers for the headless flow. See the SDK section below.
Official SDKs for Node.js and Python. For AI agents: use generateKeyPair() and signChallenge() for BYOK headless authentication. For website backends: verify agent credentials in a single call with verify().
The headless examples show the complete BYOK agent-side authentication flow (recommended). The verification examples show how websites verify agent credentials.
# Complete BYOK headless flow — recommended for AI agents.
# Generate your own keypair. Your private key never leaves your environment.
from auth_agents import AuthAgents
auth = AuthAgents()
# Step 1: Generate your own Ed25519 keypair (BYOK)
key_pair = AuthAgents.generate_key_pair()
# key_pair["public_key_jwk"] — send this to Agent Auth during registration
# key_pair["private_key_jwk"] — keep this secret, never share it
# Step 2: Register your identity with your public key
identity = auth.register(
agent_name="Claude",
agent_model="claude-opus-4-6",
agent_provider="Anthropic",
agent_purpose="Research assistant",
public_key_jwk=key_pair["public_key_jwk"],
)
# identity["did"] — "did:key:z6Mk..."
# identity["credential"] — "eyJhbGciOiJFZERTQSJ9..."
# identity["key_origin"] — "client_provided" (BYOK)
# Step 3: Request a challenge (repeat when credential expires)
challenge = auth.challenge(identity["did"])
# Step 4: Sign the challenge nonce and authenticate
# CRITICAL: The nonce is signed as UTF-8 text, NOT hex-decoded bytes
signature = AuthAgents.sign_challenge(key_pair["private_key_jwk"], challenge["nonce"])
session = auth.authenticate(
challenge_id=challenge["challenge_id"],
did=identity["did"],
signature=signature,
)
# session["credential"] — fresh VC-JWT (valid 24 hours)
# session["session_token"] — "sess_..."
# Step 5: Present the credential to websites
import requests
requests.get(
"https://example.com/api/task",
headers={"Authorization": f"Bearer {session['credential']}"},
)
Website Backend — FastAPI route
app/auth.py
# FastAPI example — app/auth.py
from fastapi import FastAPI, HTTPException
from pydantic import BaseModel
from auth_agents import AuthAgents
app = FastAPI()
auth_agents = AuthAgents()
class CallbackRequest(BaseModel):
credential: str
@app.post("/auth/agent-login")
async def verify_agent(body: CallbackRequest):
result = auth_agents.verify(body.credential)
if not result["valid"]:
raise HTTPException(status_code=401, detail=result["message"])
# result["did"] — "did:key:z6Mk..."
# result["agent_name"] — "Claude"
# result["agent_model"] — "claude-opus-4-6"
# result["agent_provider"] — "Anthropic"
# result["agent_purpose"] — "Research assistant"
# result["key_fingerprint"] — "SHA256:4fee8cb539c1..."
# result["key_origin"] — "server_generated" | "client_provided"
# result["issued_at"] — "2026-02-26T01:58:15.000Z"
# result["expires_at"] — "2026-02-27T01:58:15.000Z"
# Create a session in your database
session = create_session(
did=result["did"],
agent_name=result["agent_name"],
agent_model=result["agent_model"],
expires_at=result["expires_at"],
)
return {"authenticated": True, "session_id": session.id}
Flask example →
app.py
# Flask example — app.py
from flask import Flask, request, jsonify, session
from auth_agents import AuthAgents
app = Flask(__name__)
auth_agents = AuthAgents()
@app.post("/auth/agent-login")
def verify_agent():
credential = request.json.get("credential")
result = auth_agents.verify(credential)
if not result["valid"]:
return jsonify(error=result["message"]), 401
# Agent verified — create session and grant access
session["agent"] = {
"did": result["did"],
"name": result["agent_name"],
"model": result["agent_model"],
"provider": result["agent_provider"],
}
return jsonify(authenticated=True, agent_name=result["agent_name"])
// CLAUDE CODE PLUGIN
Let Claude Code integrate for you.
Install our Claude Code plugin and let Claude handle the integration automatically — SDK setup, callback pages, backend routes, and database schemas.
Once installed, just describe what you need (e.g. “Add agent sign-in to my Next.js app”) and Claude will write the integration code using our SDK and best practices.
Install via Plugin Marketplace
Claude Code
/plugin marketplace add Elydora/agentauth-skills
/plugin install elydora-integration@agentauth
Or install via Skills CLI
Terminal
npx skills add Elydora/agentauth-skills -a claude-code