Integration guide

Add A2SPA to your agents in four steps.

For developers and AI coding assistants. Wrap the Python, JavaScript or TypeScript agents you already run with the published a2spa package.

Four steps

  1. 1

    Create a wrapper per agent

    In the dashboard, create a wrapper for each agent you already run. Save its private key right away. It's shown only once.

    Open Wrappers →
  2. 2

    Copy your API key

    Your account key authenticates calls to A2SPA. Keep it and the private key server-side, in a secret manager.

    Open API key & setup →
  3. 3

    Install the SDK

    pip install a2spa        # Python 3.11–3.13
    npm install a2spa        # Node.js 22+
  4. 4

    Check each action before it runs

    Sign the action, send it to A2SPA, and run it only when the answer is approved. The starter code below does exactly this.

Always go through the API. Your code must call POST /v1/verify_payload and act on its answer. Local helper checks never replace the A2SPA decision.

API endpoints

UseMethodURL
Check an action
Send a signed action and get a decision.
POSThttps://api.aimodularity.com/A2SPA/v1/verify_payload
Inbox
Approved actions waiting for a target wrapper.
GEThttps://api.aimodularity.com/A2SPA/v1/inbox_for_agent
Logs
For audit and troubleshooting, not as the inbox.
GEThttps://api.aimodularity.com/A2SPA/v1/logs_for_agent
curl -X POST https://api.aimodularity.com/A2SPA/v1/verify_payload \
  -H "Content-Type: application/json" \
  -H "x-api-key: $A2SPA_API_KEY" \
  -d @signed-request.json

Starter code

Copy a file into your project, fill in the environment variables, and call it before each action.

import json
import os
from pathlib import Path

import requests
from a2spa import (
    build_payload_fields as package_build_payload_fields,
    build_request_body,
    build_signed_request as build_a2spa_signed_request,
    build_target_acknowledgement,
    compute_payload_hash,
    enforce_target_authorization,
    finalize_payload,
    policy_input,
    require_authorized_result,
)


# Install with: pip install a2spa requests


class A2SPAClient:
    def __init__(self, api_base, api_key, agent_id, private_key_path, timeout=20):
        self.api_base = api_base.rstrip("/")
        self.api_key = api_key
        self.agent_id = agent_id
        self.private_key_path = private_key_path
        self.timeout = timeout
        self._private_key = None

    @classmethod
    def from_env(cls):
        return cls(
            api_base=os.environ.get("A2SPA_API_BASE", "https://api.aimodularity.com/A2SPA"),
            api_key=os.environ["A2SPA_API_KEY"],
            agent_id=os.environ["A2SPA_AGENT_ID"],
            private_key_path=os.environ["A2SPA_PRIVATE_KEY_PATH"],
        )

    def _load_private_key(self):
        if self._private_key is None:
            self._private_key = Path(self.private_key_path).read_text(encoding="utf-8")
        return self._private_key

    def build_payload_fields(self, target_agent_id, input_data, output_data=None, alert_threshold=10, state_continuity=None):
        return package_build_payload_fields(
            agent_id=self.agent_id,
            target_agent_id=target_agent_id,
            input_data=input_data,
            output_data=output_data if output_data is not None else {"status": "ready"},
            alert_threshold=alert_threshold,
            state_continuity=state_continuity,
        )

    def build_signed_request(self, target_agent_id, input_data, output_data=None, alert_threshold=10, state_continuity=None):
        payload = self.build_payload_fields(target_agent_id, input_data, output_data, alert_threshold, state_continuity)
        return build_a2spa_signed_request(self._load_private_key(), payload)

    def post_request_body(self, request_body):
        return requests.post(
            f"{self.api_base}/v1/verify_payload",
            headers={
                "Content-Type": "application/json",
                "x-api-key": self.api_key,
            },
            json=request_body,
            timeout=self.timeout,
        )

    def send_payload(self, target_agent_id, input_data, output_data=None, alert_threshold=10, state_continuity=None, debug=False):
        request_body = self.build_signed_request(target_agent_id, input_data, output_data, alert_threshold, state_continuity)
        response = self.post_request_body(request_body)
        if debug and response.status_code >= 400:
            print(json.dumps({"status_code": response.status_code, "error": response.text}, indent=2))
        response.raise_for_status()
        result = response.json()
        return require_authorized_result(result)

    def fetch_logs(self, agent_id=None):
        response = requests.get(
            f"{self.api_base}/v1/logs_for_agent",
            headers={"x-api-key": self.api_key},
            params={"agent_id": agent_id or self.agent_id},
            timeout=self.timeout,
        )
        response.raise_for_status()
        return response.json().get("logs", [])

    def fetch_inbox(self, agent_id=None, after=None, limit=50):
        params = {"agent_id": agent_id or self.agent_id, "limit": limit}
        if after:
            params["after"] = after
        response = requests.get(
            f"{self.api_base}/v1/inbox_for_agent",
            headers={"x-api-key": self.api_key},
            params=params,
            timeout=self.timeout,
        )
        response.raise_for_status()
        return response.json().get("messages", [])

    def prepare_and_send(self, target_agent_id, input_data, runner, alert_threshold=10):
        # The runner must only prepare output_data. Do not perform side effects before A2SPA authorization.
        output_data = runner(input_data)
        return self.send_payload(target_agent_id, input_data, output_data, alert_threshold)


def existing_agent(input_data):
    return {
        "status": "ready",
        "summary": f"Processed: {input_data.get('message', 'no message')}",
    }


if __name__ == "__main__":
    client = A2SPAClient.from_env()
    target_agent_id = os.environ["A2SPA_TARGET_AGENT_ID"]
    input_data = policy_input(action="send_message", workflow_scope="messages:send", message="Hello from my existing Python agent")
    output_data = existing_agent(input_data)
    signed_request = client.build_signed_request(target_agent_id, input_data, output_data)
    response = client.post_request_body(signed_request)
    print(f"A2SPA verify response: {response.status_code} {response.text}")

Workflow for AI coding assistants

Point Copilot, Cursor, Claude Code or ChatGPT at this page or the JSON spec. These are the steps they should follow.

  1. Create or rotate the needed agents in the authenticated dashboard first.
  2. Save the returned private key immediately; it is only shown once.
  3. Add the A2SPA environment variables to the target project.
  4. Install the published a2spa package and adapt the minimal language example for the target project.
  5. Put the final target operation behind an A2SPA 2.1 enforcement guard before enabling production consequences.
  6. Use the inbox_for_agent endpoint to poll received messages for the receiving agent.

Required rules

  • Always send signed payloads through POST /v1/verify_payload.
  • For protected consequences, place A2SPA 2.1 target-side enforcement at the target, trusted gateway, service, or executor.
  • Do not let agent credentials directly commit the protected consequence without the A2SPA gate.
  • Inventory admin endpoints, legacy APIs, service accounts, internal RPC, queues, workers, direct database writes, and maintenance paths that could create the same consequence.
  • Put policy-relevant semantics in signed input fields such as intent, action, workflow_scope, amount_usd, currency, and on_behalf_of.
  • When using state_continuity, register the authoritative state through POST /v1/continuity_state first.
  • Always fetch received runtime messages through GET /v1/inbox_for_agent.
  • Use GET /v1/logs_for_agent for audit and troubleshooting, not as the main runtime inbox.
  • Use the published a2spa package for payload construction and signing where available.
  • Do not verify payloads locally as a substitute for the API.
  • Do not read or write A2SPA backend storage directly from customer agent code.
  • Do not bypass nonce, timestamp, signature, permission, or toggle checks.
  • Store the agent private key outside source control and outside public directories.

Machine-readable spec

The same guide as JSON. It's also published at the well-known URL.

a2spa-integration.json
{
  "a2spa_2_2_foundation": {
    "delegation_chains": "Introduced in A2SPA 2.3 and included in the current 2.4 Python/runtime capability set.",
    "ephemeral_agents": "Task-scoped identities with TTL/completion metadata; completed or expired agents fail closed.",
    "evidence_package_schema": "a2spa.evidence_package.v1",
    "policy_constraints_schema": "a2spa.policy.v1",
    "status": "implemented_policy_evidence_ephemeral_capabilities"
  },
  "a2spa_2_3_2_4_foundation": {
    "delegation_chain_schema": "a2spa.delegation_chain.v1",
    "delegation_features": [
      "signed grants",
      "root-to-leaf chain validation",
      "deterministic attenuation",
      "target supplied trust anchors",
      "revocation status fail-closed option"
    ],
    "delegation_grant_schema": "a2spa.delegation.v1",
    "state_integrity_features": [
      "HMAC protected consumption records",
      "rollback detection",
      "sequence floor checks",
      "authorization receipt hash binding"
    ],
    "state_integrity_schema": "a2spa.state_integrity.v1",
    "status": "implemented_capabilities_require_deployment_specific_trust_and_enforcement"
  },
  "ai_workflow": [
    "Create or rotate the needed agents in the authenticated dashboard first.",
    "Save the returned private key immediately; it is only shown once.",
    "Add the A2SPA environment variables to the target project.",
    "Install the published a2spa package and adapt the minimal language example for the target project.",
    "Put the final target operation behind an A2SPA 2.1 enforcement guard before enabling production consequences.",
    "Use the inbox_for_agent endpoint to poll received messages for the receiving agent."
  ],
  "api_base": "https://api.aimodularity.com/A2SPA",
  "code_generation_rules": [
    "Always send signed payloads through POST /v1/verify_payload.",
    "For protected consequences, place A2SPA 2.1 target-side enforcement at the target, trusted gateway, service, or executor.",
    "Do not let agent credentials directly commit the protected consequence without the A2SPA gate.",
    "Inventory admin endpoints, legacy APIs, service accounts, internal RPC, queues, workers, direct database writes, and maintenance paths that could create the same consequence.",
    "Put policy-relevant semantics in signed input fields such as intent, action, workflow_scope, amount_usd, currency, and on_behalf_of.",
    "When using state_continuity, register the authoritative state through POST /v1/continuity_state first.",
    "Always fetch received runtime messages through GET /v1/inbox_for_agent.",
    "Use GET /v1/logs_for_agent for audit and troubleshooting, not as the main runtime inbox.",
    "Use the published a2spa package for payload construction and signing where available.",
    "Do not verify payloads locally as a substitute for the API.",
    "Do not read or write A2SPA backend storage directly from customer agent code.",
    "Do not bypass nonce, timestamp, signature, permission, or toggle checks.",
    "Store the agent private key outside source control and outside public directories."
  ],
  "deployment_profile": "A2SPA 2.1 target-side enforcement",
  "discovery_urls": {
    "docs_url": "https://aimodularity.com/A2SPA/docs",
    "integration_page_url": "https://aimodularity.com/A2SPA/integrations",
    "integration_spec_url": "https://aimodularity.com/A2SPA/integrations/spec.json",
    "well_known_url": "https://aimodularity.com/A2SPA/.well-known/a2spa-integration.json"
  },
  "environment_template_url": "https://aimodularity.com/A2SPA/integrations/templates/env",
  "must_use_a2spa_api": true,
  "openapi_url": "https://aimodularity.com/A2SPA/openapi.json",
  "package_examples": {
    "env": {
      "filename": ".env.a2spa.example",
      "label": "Environment",
      "runtime": "Generic",
      "url": "https://aimodularity.com/A2SPA/integrations/templates/env"
    },
    "javascript": {
      "filename": "a2spa-client.mjs",
      "label": "JavaScript Package Example",
      "runtime": "Node.js 20+",
      "url": "https://aimodularity.com/A2SPA/integrations/templates/javascript"
    },
    "python": {
      "filename": "a2spa_client.py",
      "label": "Python Package Example",
      "runtime": "Python",
      "url": "https://aimodularity.com/A2SPA/integrations/templates/python"
    },
    "typescript": {
      "filename": "a2spa-client.ts",
      "label": "TypeScript Package Example",
      "runtime": "Node.js 20+",
      "url": "https://aimodularity.com/A2SPA/integrations/templates/typescript"
    }
  },
  "platform": "A2SPA",
  "protocol_version": "v1.0",
  "purpose": "Teach developers and AI coding assistants how to integrate existing agents using the published A2SPA payload helpers, public A2SPA API, and A2SPA 2.1 target-side enforcement profile. Agent setup happens in the authenticated dashboard.",
  "runtime_api": {
    "continuity_state": {
      "headers": {
        "Content-Type": "application/json",
        "x-api-key": "\u003cdashboard API key\u003e"
      },
      "method": "POST",
      "notes": [
        "Use this before verify_payload when a workflow step requires state-continuity enforcement.",
        "The response includes the state_hash to bind into payload.state_continuity.",
        "If you enable system-of-record checks, configure the allowed source host/base URL before registering the state."
      ],
      "request_json": {
        "binding": {
          "action": "reserve",
          "agent_id": "\u003csender agent id\u003e",
          "amount_usd": "100.00",
          "currency": "USD",
          "target_agent_id": "\u003creceiver agent id\u003e",
          "workflow_scope": "payments:reserve"
        },
        "expires_at": "2026-08-10T20:00:00Z",
        "sequence": 1,
        "state": {
          "currency": "USD",
          "reserved_amount": "100.00",
          "status": "reserved"
        },
        "state_id": "workflow-123-step-1",
        "status": "active"
      },
      "url": "https://api.aimodularity.com/A2SPA/v1/continuity_state"
    },
    "inbox_for_agent": {
      "headers": {
        "x-api-key": "\u003cdashboard API key\u003e"
      },
      "method": "GET",
      "notes": [
        "Use this to poll received messages for an agent through the A2SPA API.",
        "This is the preferred runtime retrieval path for receiving agents."
      ],
      "query": {
        "after": "optional ISO timestamp for polling",
        "agent_id": "\u003cagent id to inspect\u003e",
        "limit": 50
      },
      "url": "https://api.aimodularity.com/A2SPA/v1/inbox_for_agent"
    },
    "logs_for_agent": {
      "headers": {
        "x-api-key": "\u003cdashboard API key\u003e"
      },
      "method": "GET",
      "notes": [
        "Use this for audit logs, troubleshooting, and dashboard-style history.",
        "Do not use this as the primary runtime inbox when inbox_for_agent is available."
      ],
      "query": {
        "agent_id": "\u003cagent id to inspect\u003e"
      },
      "url": "https://api.aimodularity.com/A2SPA/v1/logs_for_agent"
    },
    "verify_payload": {
      "canonicalization": {
        "array_order_preserved": true,
        "compute_hash_before_setting_hash_field": true,
        "excluded_fields": [
          "hash",
          "signature",
          "extra metadata not in the current verifier schema"
        ],
        "hash_algorithm": "sha256",
        "included_fields": [
          "agent_id",
          "target_agent_id",
          "timestamp",
          "nonce",
          "input",
          "output",
          "alert_threshold",
          "crypto_profile required for current non-legacy profiles",
          "state_continuity when present"
        ],
        "number_handling": "Use finite JSON numbers; send exact decimal spellings as strings when cross-language lexical stability matters",
        "recursive_key_sorting": true,
        "signature_algorithm": "RSA PKCS1v15 SHA256 or ML-DSA-65 when crypto_profile is a2spa.pq.mldsa.v1",
        "signing_preimage": "Current v1 signs the UTF-8 bytes of canonical JSON only; no domain-separation prefix is present in this compatibility profile.",
        "string_escaping": "Python json.dumps(..., sort_keys=True) with ensure_ascii=True semantics",
        "timestamp_format": "ISO-8601 UTC string",
        "utf8_bytes": true,
        "whitespace": "Python default separators: comma+space and colon+space"
      },
      "headers": {
        "Content-Type": "application/json",
        "x-api-key": "\u003cdashboard API key\u003e"
      },
      "method": "POST",
      "request_json": {
        "payload": {
          "agent_id": "\u003csender agent id\u003e",
          "alert_threshold": 10,
          "crypto_profile": "a2spa.classical.rsa_pkcs1v15_sha256.v1",
          "hash": "sha256 over the UTF-8 bytes of Python-compatible canonical JSON for the signable payload",
          "input": {
            "action": "reserve",
            "message": "hello",
            "workflow_scope": "payments:reserve"
          },
          "nonce": "uuid",
          "output": {
            "status": "ready"
          },
          "state_continuity": {
            "sequence": 1,
            "state_hash": "optional registered state sha256 hex",
            "state_id": "workflow-123-step-1"
          },
          "target_agent_id": "\u003creceiver agent id\u003e",
          "timestamp": "ISO-8601 string"
        },
        "signature": "hex signature over the same canonical UTF-8 bytes using the algorithm selected by crypto_profile"
      },
      "url": "https://api.aimodularity.com/A2SPA/v1/verify_payload"
    }
  },
  "security_model": {
    "a2spa_2_1_target_enforcement": {
      "invariant": "Every path to consequence must cross the authorization boundary.",
      "pass_condition": "The protected consequence cannot execute unless valid A2SPA authorization reaches the execution boundary.",
      "target_acknowledgement": "Bind target commit/result evidence to the A2SPA authorization receipt id and canonical hash when commit evidence is needed."
    },
    "dashboard_management_source": "A2SPA authenticated dashboard only",
    "delegation_boundary": [
      "on_behalf_of is rejected unless the sender agent has an explicit delegation policy allowing that principal.",
      "Cryptographic binding proves integrity. Policy determines semantic permission.",
      "Agent policies can enforce target, sender, intent, action, workflow scope, currency, and spend limits before nonce consumption."
    ],
    "forbidden_shortcuts": [
      "Direct backend storage writes",
      "Skipping the verify_payload API",
      "Protected target routes that commit without checking an authorized A2SPA receipt",
      "Embedding private keys in git",
      "Replacing server-side verification with local-only checks"
    ],
    "runtime_verification_source": "A2SPA API only"
  },
  "spec_kind": "api_integration_pack",
  "spec_version": "2.4.7"
}
Ask your AI

Ask ChatGPT or Claude about A2SPA.

Get a second opinion. Each button opens the assistant with a question about A2SPA already filled in.