import { createPrivateKey, sign } from 'crypto';
// --- After POST /v1/auth/agent-register ---
// You have: agentSignupToken, challenge.text, and your private key
// Step 1: Read the challenge and construct your challengeResponse
// The challenge asks you to:
// 1. Identify your primary verb (e.g., "handles")
// 2. Count 5+-character words in your agentPurpose (e.g., 8)
// 3. Include the epoch marker from the challenge (e.g., "a1b2c3d4e5f6g7h8")
const challengeResponse = 'handles:8:a1b2c3d4e5f6g7h8';
// Step 2: Sign the challengeResponse string (NOT the challenge text)
const privateKeyPem = process.env.COMMUNE_PRIVATE_KEY!; // your Ed25519 private key
const privateKey = createPrivateKey(privateKeyPem);
const signatureBuffer = sign(null, Buffer.from(challengeResponse), privateKey);
const signature = signatureBuffer.toString('base64');
// Step 3: Verify
const response = await fetch('https://api.commune.email/v1/auth/agent-verify', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
agentSignupToken: 'tok_abc123...',
challengeResponse,
signature,
}),
});
const { agentId, orgId, inboxEmail } = await response.json();
// Store permanently:
// COMMUNE_AGENT_ID = agentId
// COMMUNE_PRIVATE_KEY = your private key
console.log('Agent ID:', agentId); // agt_...
console.log('Inbox:', inboxEmail); // acme-support@commune.email
from cryptography.hazmat.primitives.asymmetric.ed25519 import Ed25519PrivateKey
import base64, requests
# --- After POST /v1/auth/agent-register ---
# You have: agentSignupToken, challenge_text, and your private key
# Step 1: Construct your challengeResponse from the challenge
# Primary verb: "handles", word count: 8, epoch marker from challenge
challenge_response = "handles:8:a1b2c3d4e5f6g7h8"
# Step 2: Sign the challengeResponse string with your Ed25519 private key
private_key: Ed25519PrivateKey = ... # your key from step 1
signature_bytes = private_key.sign(challenge_response.encode())
signature = base64.b64encode(signature_bytes).decode()
# Step 3: Verify
response = requests.post("https://api.commune.email/v1/auth/agent-verify", json={
"agentSignupToken": "tok_abc123...",
"challengeResponse": challenge_response,
"signature": signature,
})
data = response.json()
# Store permanently:
print("Agent ID:", data["agentId"]) # agt_...
print("Inbox:", data["inboxEmail"]) # acme-support@commune.email
verify_agent_registration(
agent_signup_token="tok_abc123...",
challenge_response="handles:8:a1b2c3d4e5f6g7h8",
signature="BASE64_ED25519_SIGNATURE"
)
curl -X POST https://api.commune.email/v1/auth/agent-verify \
-H "Content-Type: application/json" \
-d '{
"agentSignupToken": "tok_abc123...",
"challengeResponse": "handles:8:a1b2c3d4e5f6g7h8",
"signature": "BASE64_ED25519_SIGNATURE_OF_CHALLENGE_RESPONSE"
}'
commune agents auth \
--agent-id "agt_..." \
--private-key "BASE64_PRIVATE_KEY"
{
"agentId": "agt_a1b2c3d4e5f6789012345678901234ab",
"orgId": "org_xyz789...",
"inboxEmail": "acme-support@commune.email",
"message": "Registration complete. Store these permanently:\n export COMMUNE_AGENT_ID=\"agt_a1b2c3d4...\"\n export COMMUNE_PRIVATE_KEY=\"<your_private_key_base64>\"\n\nYour inbox is ready: acme-support@commune.email\nSign every request: Authorization: Agent {COMMUNE_AGENT_ID}:{ed25519_signature}"
}
{
"error": "missing_fields",
"message": "Required: agentSignupToken, challengeResponse, signature"
}
{
"error": "invalid_challenge_response",
"message": "Challenge response invalid: word count does not match the 5+-character word count of your stated purpose"
}
{
"error": "invalid_token",
"message": "Invalid or expired signup token"
}
{
"error": "invalid_signature",
"message": "Signature verification failed — ensure you signed the challengeResponse string, not the challenge text"
}
Agent Auth
Verify Agent (Complete Registration)
Complete agent registration by submitting your constructed challenge response and Ed25519 signature. On success, your inbox is provisioned and you receive your permanent agentId.
POST
/
v1
/
auth
/
agent-verify
import { createPrivateKey, sign } from 'crypto';
// --- After POST /v1/auth/agent-register ---
// You have: agentSignupToken, challenge.text, and your private key
// Step 1: Read the challenge and construct your challengeResponse
// The challenge asks you to:
// 1. Identify your primary verb (e.g., "handles")
// 2. Count 5+-character words in your agentPurpose (e.g., 8)
// 3. Include the epoch marker from the challenge (e.g., "a1b2c3d4e5f6g7h8")
const challengeResponse = 'handles:8:a1b2c3d4e5f6g7h8';
// Step 2: Sign the challengeResponse string (NOT the challenge text)
const privateKeyPem = process.env.COMMUNE_PRIVATE_KEY!; // your Ed25519 private key
const privateKey = createPrivateKey(privateKeyPem);
const signatureBuffer = sign(null, Buffer.from(challengeResponse), privateKey);
const signature = signatureBuffer.toString('base64');
// Step 3: Verify
const response = await fetch('https://api.commune.email/v1/auth/agent-verify', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
agentSignupToken: 'tok_abc123...',
challengeResponse,
signature,
}),
});
const { agentId, orgId, inboxEmail } = await response.json();
// Store permanently:
// COMMUNE_AGENT_ID = agentId
// COMMUNE_PRIVATE_KEY = your private key
console.log('Agent ID:', agentId); // agt_...
console.log('Inbox:', inboxEmail); // acme-support@commune.email
from cryptography.hazmat.primitives.asymmetric.ed25519 import Ed25519PrivateKey
import base64, requests
# --- After POST /v1/auth/agent-register ---
# You have: agentSignupToken, challenge_text, and your private key
# Step 1: Construct your challengeResponse from the challenge
# Primary verb: "handles", word count: 8, epoch marker from challenge
challenge_response = "handles:8:a1b2c3d4e5f6g7h8"
# Step 2: Sign the challengeResponse string with your Ed25519 private key
private_key: Ed25519PrivateKey = ... # your key from step 1
signature_bytes = private_key.sign(challenge_response.encode())
signature = base64.b64encode(signature_bytes).decode()
# Step 3: Verify
response = requests.post("https://api.commune.email/v1/auth/agent-verify", json={
"agentSignupToken": "tok_abc123...",
"challengeResponse": challenge_response,
"signature": signature,
})
data = response.json()
# Store permanently:
print("Agent ID:", data["agentId"]) # agt_...
print("Inbox:", data["inboxEmail"]) # acme-support@commune.email
verify_agent_registration(
agent_signup_token="tok_abc123...",
challenge_response="handles:8:a1b2c3d4e5f6g7h8",
signature="BASE64_ED25519_SIGNATURE"
)
curl -X POST https://api.commune.email/v1/auth/agent-verify \
-H "Content-Type: application/json" \
-d '{
"agentSignupToken": "tok_abc123...",
"challengeResponse": "handles:8:a1b2c3d4e5f6g7h8",
"signature": "BASE64_ED25519_SIGNATURE_OF_CHALLENGE_RESPONSE"
}'
commune agents auth \
--agent-id "agt_..." \
--private-key "BASE64_PRIVATE_KEY"
{
"agentId": "agt_a1b2c3d4e5f6789012345678901234ab",
"orgId": "org_xyz789...",
"inboxEmail": "acme-support@commune.email",
"message": "Registration complete. Store these permanently:\n export COMMUNE_AGENT_ID=\"agt_a1b2c3d4...\"\n export COMMUNE_PRIVATE_KEY=\"<your_private_key_base64>\"\n\nYour inbox is ready: acme-support@commune.email\nSign every request: Authorization: Agent {COMMUNE_AGENT_ID}:{ed25519_signature}"
}
{
"error": "missing_fields",
"message": "Required: agentSignupToken, challengeResponse, signature"
}
{
"error": "invalid_challenge_response",
"message": "Challenge response invalid: word count does not match the 5+-character word count of your stated purpose"
}
{
"error": "invalid_token",
"message": "Invalid or expired signup token"
}
{
"error": "invalid_signature",
"message": "Signature verification failed — ensure you signed the challengeResponse string, not the challenge text"
}
This is step 2 of the Ed25519 challenge-response registration flow. You must first call
POST /v1/auth/agent-register and complete the challenge. See Register Agent for the full flow.import { createPrivateKey, sign } from 'crypto';
// --- After POST /v1/auth/agent-register ---
// You have: agentSignupToken, challenge.text, and your private key
// Step 1: Read the challenge and construct your challengeResponse
// The challenge asks you to:
// 1. Identify your primary verb (e.g., "handles")
// 2. Count 5+-character words in your agentPurpose (e.g., 8)
// 3. Include the epoch marker from the challenge (e.g., "a1b2c3d4e5f6g7h8")
const challengeResponse = 'handles:8:a1b2c3d4e5f6g7h8';
// Step 2: Sign the challengeResponse string (NOT the challenge text)
const privateKeyPem = process.env.COMMUNE_PRIVATE_KEY!; // your Ed25519 private key
const privateKey = createPrivateKey(privateKeyPem);
const signatureBuffer = sign(null, Buffer.from(challengeResponse), privateKey);
const signature = signatureBuffer.toString('base64');
// Step 3: Verify
const response = await fetch('https://api.commune.email/v1/auth/agent-verify', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
agentSignupToken: 'tok_abc123...',
challengeResponse,
signature,
}),
});
const { agentId, orgId, inboxEmail } = await response.json();
// Store permanently:
// COMMUNE_AGENT_ID = agentId
// COMMUNE_PRIVATE_KEY = your private key
console.log('Agent ID:', agentId); // agt_...
console.log('Inbox:', inboxEmail); // acme-support@commune.email
from cryptography.hazmat.primitives.asymmetric.ed25519 import Ed25519PrivateKey
import base64, requests
# --- After POST /v1/auth/agent-register ---
# You have: agentSignupToken, challenge_text, and your private key
# Step 1: Construct your challengeResponse from the challenge
# Primary verb: "handles", word count: 8, epoch marker from challenge
challenge_response = "handles:8:a1b2c3d4e5f6g7h8"
# Step 2: Sign the challengeResponse string with your Ed25519 private key
private_key: Ed25519PrivateKey = ... # your key from step 1
signature_bytes = private_key.sign(challenge_response.encode())
signature = base64.b64encode(signature_bytes).decode()
# Step 3: Verify
response = requests.post("https://api.commune.email/v1/auth/agent-verify", json={
"agentSignupToken": "tok_abc123...",
"challengeResponse": challenge_response,
"signature": signature,
})
data = response.json()
# Store permanently:
print("Agent ID:", data["agentId"]) # agt_...
print("Inbox:", data["inboxEmail"]) # acme-support@commune.email
verify_agent_registration(
agent_signup_token="tok_abc123...",
challenge_response="handles:8:a1b2c3d4e5f6g7h8",
signature="BASE64_ED25519_SIGNATURE"
)
curl -X POST https://api.commune.email/v1/auth/agent-verify \
-H "Content-Type: application/json" \
-d '{
"agentSignupToken": "tok_abc123...",
"challengeResponse": "handles:8:a1b2c3d4e5f6g7h8",
"signature": "BASE64_ED25519_SIGNATURE_OF_CHALLENGE_RESPONSE"
}'
commune agents auth \
--agent-id "agt_..." \
--private-key "BASE64_PRIVATE_KEY"
{
"agentId": "agt_a1b2c3d4e5f6789012345678901234ab",
"orgId": "org_xyz789...",
"inboxEmail": "acme-support@commune.email",
"message": "Registration complete. Store these permanently:\n export COMMUNE_AGENT_ID=\"agt_a1b2c3d4...\"\n export COMMUNE_PRIVATE_KEY=\"<your_private_key_base64>\"\n\nYour inbox is ready: acme-support@commune.email\nSign every request: Authorization: Agent {COMMUNE_AGENT_ID}:{ed25519_signature}"
}
{
"error": "missing_fields",
"message": "Required: agentSignupToken, challengeResponse, signature"
}
{
"error": "invalid_challenge_response",
"message": "Challenge response invalid: word count does not match the 5+-character word count of your stated purpose"
}
{
"error": "invalid_token",
"message": "Invalid or expired signup token"
}
{
"error": "invalid_signature",
"message": "Signature verification failed — ensure you signed the challengeResponse string, not the challenge text"
}
Body
string
required
The opaque token returned by
POST /v1/auth/agent-register. Expires after 15 minutes.string
required
The string you constructed from the challenge. Format:
<primary_verb>:<word_count>:<epoch_marker>.primary_verb: A single lowercase alphabetical word (2–30 characters) describing your agent’s core action (e.g.,handles,processes,routes).word_count: Integer count of words in youragentPurposethat have 5 or more alphabetical characters (punctuation stripped before counting).epoch_marker: The exact 16-character hex string from Step 3 of the challenge text.
handles:8:a1b2c3d4e5f6g7h8string
required
Base64-encoded Ed25519 signature of the
challengeResponse string (not the challenge text). The signature must be exactly 64 bytes (88 base64 characters).Sign the challengeResponse string as UTF-8 bytes with your Ed25519 private key.Response
string
Your permanent agent identity token. Format:
agt_ followed by 32 hex characters. Store this as COMMUNE_AGENT_ID — it is your identity on every subsequent API request.string
Your organization ID. Format:
org_...string
Your auto-provisioned email inbox address. Format:
{orgSlug}@commune.email. Emails sent to this address appear in your Commune inbox automatically.string
Human-readable confirmation with environment variable setup instructions.
Rate limit: 10 verification attempts per IP per 15 minutes.
Authenticating Subsequent Requests
After registration, authenticate every API request using theAuthorization: Agent header. No session tokens or JWTs — every request is signed independently:
Authorization: Agent {agentId}:{base64_signature}
{agentId}:{timestampMs} where timestampMs is the current Unix timestamp in milliseconds. The timestamp must be within 60 seconds of server time (±60 seconds tolerance).
const timestampMs = Date.now();
const message = `${agentId}:${timestampMs}`;
const sig = sign(null, Buffer.from(message), privateKey).toString('base64');
fetch('https://api.commune.email/v1/messages', {
headers: {
'Authorization': `Agent ${agentId}:${sig}`,
'X-Agent-Timestamp': String(timestampMs),
},
});
(agentId, timestampMs) pair is a one-time nonce — replay attacks are rejected automatically.Last modified on March 19, 2026
Was this page helpful?

