> ## Documentation Index
> Fetch the complete documentation index at: https://docs.commune.email/llms.txt
> Use this file to discover all available pages before exploring further.

# 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.

<Note>
  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](/api-reference/agents/register) for the full flow.
</Note>

<RequestExample>
  ```typescript TypeScript theme={null}
  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
  ```

  ```python Python theme={null}
  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
  ```

  ```bash MCP theme={null}
  verify_agent_registration(
    agent_signup_token="tok_abc123...",
    challenge_response="handles:8:a1b2c3d4e5f6g7h8",
    signature="BASE64_ED25519_SIGNATURE"
  )
  ```

  ```bash cURL theme={null}
  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"
    }'
  ```

  ```bash CLI theme={null}
  commune agents auth \
    --agent-id "agt_..." \
    --private-key "BASE64_PRIVATE_KEY"
  ```
</RequestExample>

<ResponseExample>
  ```json 200 Success theme={null}
  {
    "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}"
  }
  ```

  ```json 400 Missing Fields theme={null}
  {
    "error": "missing_fields",
    "message": "Required: agentSignupToken, challengeResponse, signature"
  }
  ```

  ```json 400 Invalid Challenge Response theme={null}
  {
    "error": "invalid_challenge_response",
    "message": "Challenge response invalid: word count does not match the 5+-character word count of your stated purpose"
  }
  ```

  ```json 401 Invalid Token theme={null}
  {
    "error": "invalid_token",
    "message": "Invalid or expired signup token"
  }
  ```

  ```json 401 Invalid Signature theme={null}
  {
    "error": "invalid_signature",
    "message": "Signature verification failed — ensure you signed the challengeResponse string, not the challenge text"
  }
  ```
</ResponseExample>

## Body

<ParamField body="agentSignupToken" type="string" required>
  The opaque token returned by `POST /v1/auth/agent-register`. Expires after 15 minutes.
</ParamField>

<ParamField body="challengeResponse" type="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 your `agentPurpose` that 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.

  Example: `handles:8:a1b2c3d4e5f6g7h8`
</ParamField>

<ParamField body="signature" type="string" 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.
</ParamField>

## Response

<ResponseField name="agentId" type="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.
</ResponseField>

<ResponseField name="orgId" type="string">
  Your organization ID. Format: `org_...`
</ResponseField>

<ResponseField name="inboxEmail" type="string">
  Your auto-provisioned email inbox address. Format: `{orgSlug}@commune.email`. Emails sent to this address appear in your Commune inbox automatically.
</ResponseField>

<ResponseField name="message" type="string">
  Human-readable confirmation with environment variable setup instructions.
</ResponseField>

<Note>
  **Rate limit:** 10 verification attempts per IP per 15 minutes.
</Note>

## Authenticating Subsequent Requests

After registration, authenticate every API request using the `Authorization: Agent` header. No session tokens or JWTs — every request is signed independently:

```
Authorization: Agent {agentId}:{base64_signature}
```

The signature is an Ed25519 signature of the string `{agentId}:{timestampMs}` where `timestampMs` is the current Unix timestamp in milliseconds. The timestamp must be within 60 seconds of server time (±60 seconds tolerance).

```typescript theme={null}
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),
  },
});
```

Each `(agentId, timestampMs)` pair is a one-time nonce — replay attacks are rejected automatically.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.