Integration Pack

Integration Pack for developers and AI coding assistants.

Use this page to wrap existing Python, JavaScript, and TypeScript agents with the published a2spa package and public A2SPA APIs.

Runtime integrations must call A2SPA APIs. Do not generate code that writes directly to backend storage, skips POST /api/verify_payload, or treats local helper checks as enforcement.

1. Create Agents

Use the authenticated dashboard to create agents and save issued private keys immediately.

2. Wrap Runtime Code

Install and use the published a2spa package to build canonical payloads, sign locally, call A2SPA, and poll inbox endpoints.

3. Stay API-Only

A2SPA remains the verification authority for signatures, policy, replay protection, status, and credits.

AI Workflow

Step 1

Create or rotate the needed agents in the authenticated dashboard first.

Step 2

Save the returned private key immediately; it is only shown once.

Step 3

Add the A2SPA environment variables to the target project.

Step 4

Install the published a2spa package and adapt the minimal language example for the target project.

Step 5

Use the inbox_for_agent endpoint to poll received messages for the receiving agent.

Required Rules

Required

Always send signed payloads through POST /api/verify_payload.

Required

Put policy-relevant semantics in signed input fields such as intent, action, workflow_scope, amount_usd, currency, and on_behalf_of.

Required

When using A2SPA 2.0 state_continuity, register the authoritative state through POST /api/continuity_state first.

Required

Always fetch received runtime messages through GET /api/inbox_for_agent.

Required

Use GET /api/logs_for_agent for audit and troubleshooting, not as the main runtime inbox.

Required

Use the published a2spa package for payload construction and signing where available.

Required

Do not verify payloads locally as a substitute for the API.

Required

Do not read or write A2SPA backend storage directly from customer agent code.

Required

Do not bypass nonce, timestamp, signature, permission, or toggle checks.

Required

Store the agent private key outside source control and outside public directories.

A2SPA API Endpoints
Send + Verify

Verify Payload

POSThttps://api.aimodularity.com/A2SPA/api/verify_payload

Submit signed payloads for runtime authorization.

Inbox

Agent Inbox

GEThttps://api.aimodularity.com/A2SPA/api/inbox_for_agent

Poll received messages for a target agent through A2SPA.

Logs

Agent Logs

GEThttps://api.aimodularity.com/A2SPA/api/logs_for_agent

Use logs for audit and troubleshooting, not as the primary inbox.

curl -X POST https://api.aimodularity.com/A2SPA/api/verify_payload \
  -H "Content-Type: application/json" \
  -H "x-api-key: $A2SPA_API_KEY" \
  -d @signed-request.json
Package Examples

Use the published a2spa package.

Generic

Environment

.env.a2spa.example

Open
Python

Python Package Example

a2spa_client.py

Open
Node.js 20+

JavaScript Package Example

a2spa-client.mjs

Open
Node.js 20+

TypeScript Package Example

a2spa-client.ts

Open
.env.a2spa.example
A2SPA_API_BASE=https://api.aimodularity.com/A2SPA
A2SPA_API_KEY=YOUR_API_KEY_HERE
A2SPA_AGENT_ID=your_user_id_my_agent_send
A2SPA_PRIVATE_KEY_PATH=your_sender_agent.priv.pem
A2SPA_TARGET_AGENT_ID=your_user_id_my_agent_receive
a2spa_client.py
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,
    compute_payload_hash,
    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}/api/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}/api/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}/api/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}")
a2spa-client.mjs
import fs from "node:fs/promises";
import { createRequire } from "node:module";
import { fileURLToPath } from "node:url";

const require = createRequire(import.meta.url);
const a2spa = require("a2spa");
const { buildPayloadFields, buildSignedRequest, policyInput, requireAuthorizedResult } = a2spa;

export const canonicalizeA2spaPayload = a2spa.canonicalizeA2spaPayload;
export const computePayloadHash = a2spa.computePayloadHash;
export const prepareSignedBytes = a2spa.prepareSignedBytes;
export const finalizePayload = a2spa.finalizePayload;
export const buildRequestBody = a2spa.buildRequestBody;
export const signPayload = a2spa.signPayload;
export { buildPayloadFields, buildSignedRequest, policyInput, requireAuthorizedResult };

// Install with: npm install a2spa


function requireEnv(name) {
  const value = process.env[name];
  if (!value) {
    throw new Error(`Missing required environment variable: ${name}`);
  }
  return value;
}


export class A2SPAClient {
  constructor({ apiBase, apiKey, agentId, privateKeyPath, timeoutMs = 20000 }) {
    this.apiBase = apiBase.replace(/\/$/, "");
    this.apiKey = apiKey;
    this.agentId = agentId;
    this.privateKeyPath = privateKeyPath;
    this.timeoutMs = timeoutMs;
    this.privateKeyPem = null;
  }

  static fromEnv() {
    return new A2SPAClient({
      apiBase: process.env.A2SPA_API_BASE || "https://api.aimodularity.com/A2SPA",
      apiKey: requireEnv("A2SPA_API_KEY"),
      agentId: requireEnv("A2SPA_AGENT_ID"),
      privateKeyPath: requireEnv("A2SPA_PRIVATE_KEY_PATH"),
    });
  }

  async loadPrivateKeyPem() {
    if (!this.privateKeyPem) {
      this.privateKeyPem = await fs.readFile(this.privateKeyPath, "utf8");
    }
    return this.privateKeyPem;
  }

  async buildSignedRequest(targetAgentId, input, output = { status: "ready" }, options = {}) {
    const payload = buildPayloadFields({
      agentId: this.agentId,
      targetAgentId,
      input,
      output,
      alertThreshold: options.alertThreshold ?? 10,
      stateContinuity: options.stateContinuity ?? null,
    });
    return buildSignedRequest(payload, await this.loadPrivateKeyPem());
  }

  async postRequestBody(requestBody) {
    return fetch(`${this.apiBase}/api/verify_payload`, {
      method: "POST",
      headers: {
        "Content-Type": "application/json",
        "x-api-key": this.apiKey,
      },
      body: JSON.stringify(requestBody),
    });
  }

  async sendPayload(targetAgentId, input, output = { status: "ready" }, options = {}, debug = false) {
    const requestBody = await this.buildSignedRequest(targetAgentId, input, output, options);
    const response = await this.postRequestBody(requestBody);
    const result = await response.json().catch(() => ({}));
    if (!response.ok) {
      if (debug) {
        console.error(JSON.stringify({ status: response.status, error: result.error, reason_code: result.reason_code }, null, 2));
      }
      throw new Error(result.reason_code || result.error || `A2SPA did not authorize delivery (${response.status})`);
    }
    return requireAuthorizedResult(result);
  }

  async fetchLogs(agentId = this.agentId) {
    const params = new URLSearchParams({ agent_id: agentId });
    const response = await fetch(`${this.apiBase}/api/logs_for_agent?${params.toString()}`, {
      headers: { "x-api-key": this.apiKey },
    });
    if (!response.ok) {
      throw new Error(await response.text());
    }
    const body = await response.json();
    return body.logs || [];
  }

  async fetchInbox(agentId = this.agentId, after = "", limit = 50) {
    const params = new URLSearchParams({ agent_id: agentId, limit: String(limit) });
    if (after) params.set("after", after);
    const response = await fetch(`${this.apiBase}/api/inbox_for_agent?${params.toString()}`, {
      headers: { "x-api-key": this.apiKey },
    });
    if (!response.ok) {
      throw new Error(await response.text());
    }
    const body = await response.json();
    return body.messages || [];
  }

  async prepareAndSend(targetAgentId, input, runner, options = {}) {
    // The runner must only prepare output. Do not perform side effects before A2SPA authorization.
    const output = await runner(input);
    return this.sendPayload(targetAgentId, input, output, options);
  }
}


async function existingAgent(input) {
  return {
    status: "ready",
    summary: `Processed: ${input.message ?? "no message"}`,
  };
}


async function main() {
  const client = A2SPAClient.fromEnv();
  const input = policyInput({ action: "send_message", workflowScope: "messages:send", message: "Hello from my existing Node agent" });
  const output = await existingAgent(input);
  const requestBody = await client.buildSignedRequest(process.env.A2SPA_TARGET_AGENT_ID, input, output);
  const response = await client.postRequestBody(requestBody);
  console.log(`A2SPA verify response: ${response.status} ${await response.text()}`);
}


if (process.argv[1] && fileURLToPath(import.meta.url) === process.argv[1]) {
  main().catch((error) => {
    console.error(error);
    process.exit(1);
  });
}
a2spa-client.ts
import fs from "node:fs/promises";
import { createRequire } from "node:module";
import { fileURLToPath } from "node:url";
import type { A2spaPayloadContent, JsonValue } from "a2spa";

const require = createRequire(import.meta.url);
const a2spa = require("a2spa") as typeof import("a2spa");

export const buildPayloadFields = a2spa.buildPayloadFields;
export const buildSignedRequest = a2spa.buildSignedRequest;
export const policyInput = a2spa.policyInput;
export const canonicalizeA2spaPayload = a2spa.canonicalizeA2spaPayload;
export const computePayloadHash = a2spa.computePayloadHash;
export const prepareSignedBytes = a2spa.prepareSignedBytes;
export const finalizePayload = a2spa.finalizePayload;
export const buildRequestBody = a2spa.buildRequestBody;
export const signPayload = a2spa.signPayload;


interface SendResult {
  success?: boolean;
  decision?: string;
  reason_code?: string;
  delivery?: { status?: string };
  error?: string;
  receipt?: unknown;
  [key: string]: unknown;
}

interface LogEntry {
  action?: string;
  [key: string]: unknown;
}

// Install with: npm install a2spa


function requireEnv(name: string): string {
  const value = process.env[name];
  if (!value) {
    throw new Error(`Missing required environment variable: ${name}`);
  }
  return value;
}


export class A2SPAClient {
  private apiBase: string;
  private apiKey: string;
  private agentId: string;
  private privateKeyPath: string;
  private privateKeyPem?: string;

  constructor(options: { apiBase: string; apiKey: string; agentId: string; privateKeyPath: string }) {
    this.apiBase = options.apiBase.replace(/\/$/, "");
    this.apiKey = options.apiKey;
    this.agentId = options.agentId;
    this.privateKeyPath = options.privateKeyPath;
  }

  static fromEnv(): A2SPAClient {
    return new A2SPAClient({
      apiBase: process.env.A2SPA_API_BASE || "https://api.aimodularity.com/A2SPA",
      apiKey: requireEnv("A2SPA_API_KEY"),
      agentId: requireEnv("A2SPA_AGENT_ID"),
      privateKeyPath: requireEnv("A2SPA_PRIVATE_KEY_PATH"),
    });
  }

  private async loadPrivateKeyPem(): Promise<string> {
    if (!this.privateKeyPem) {
      this.privateKeyPem = await fs.readFile(this.privateKeyPath, "utf8");
    }
    return this.privateKeyPem;
  }

  async buildSignedRequest(
    targetAgentId: string,
    input: A2spaPayloadContent,
    output: A2spaPayloadContent = { status: "ready" },
    options: { alertThreshold?: number; stateContinuity?: JsonValue | null } = {},
  ): Promise<ReturnType<typeof buildSignedRequest>> {
    const payload = buildPayloadFields({
      agentId: this.agentId,
      targetAgentId,
      input,
      output,
      alertThreshold: options.alertThreshold ?? 10,
      stateContinuity: options.stateContinuity ?? null,
    });
    return buildSignedRequest(payload, await this.loadPrivateKeyPem());
  }

  async postRequestBody(requestBody: ReturnType<typeof buildSignedRequest>): Promise<Response> {
    return fetch(`${this.apiBase}/api/verify_payload`, {
      method: "POST",
      headers: {
        "Content-Type": "application/json",
        "x-api-key": this.apiKey,
      },
      body: JSON.stringify(requestBody),
    });
  }

  async sendPayload(
    targetAgentId: string,
    input: A2spaPayloadContent,
    output: A2spaPayloadContent = { status: "ready" },
    options: { alertThreshold?: number; stateContinuity?: JsonValue | null } = {},
    debug = false,
  ): Promise<SendResult> {
    const requestBody = await this.buildSignedRequest(targetAgentId, input, output, options);
    const response = await this.postRequestBody(requestBody);
    const body = (await response.json().catch(() => ({}))) as SendResult;
    if (!response.ok) {
      if (debug) {
        console.error(JSON.stringify({ status: response.status, error: body.error, reason_code: body.reason_code }, null, 2));
      }
      throw new Error(body.reason_code || body.error || `A2SPA did not authorize delivery (${response.status})`);
    }
    return requireAuthorizedResult(body) as SendResult;
  }

  async fetchLogs(agentId = this.agentId): Promise<LogEntry[]> {
    const params = new URLSearchParams({ agent_id: agentId });
    const response = await fetch(`${this.apiBase}/api/logs_for_agent?${params.toString()}`, {
      headers: { "x-api-key": this.apiKey },
    });
    if (!response.ok) {
      throw new Error(await response.text());
    }
    const body = (await response.json()) as { logs?: LogEntry[] };
    return body.logs || [];
  }

  async fetchInbox(agentId = this.agentId, after = "", limit = 50): Promise<LogEntry[]> {
    const params = new URLSearchParams({ agent_id: agentId, limit: String(limit) });
    if (after) params.set("after", after);
    const response = await fetch(`${this.apiBase}/api/inbox_for_agent?${params.toString()}`, {
      headers: { "x-api-key": this.apiKey },
    });
    if (!response.ok) {
      throw new Error(await response.text());
    }
    const body = (await response.json()) as { messages?: LogEntry[] };
    return body.messages || [];
  }

  async prepareAndSend(
    targetAgentId: string,
    input: A2spaPayloadContent,
    runner: (input: A2spaPayloadContent) => Promise<A2spaPayloadContent> | A2spaPayloadContent,
    options: { alertThreshold?: number; stateContinuity?: JsonValue | null } = {},
  ): Promise<SendResult> {
    // The runner must only prepare output. Do not perform side effects before A2SPA authorization.
    const output = await runner(input);
    return this.sendPayload(targetAgentId, input, output, options);
  }
}


async function existingAgent(input: A2spaPayloadContent): Promise<A2spaPayloadContent> {
  const message = typeof input === "object" && input && !Array.isArray(input) ? (input as { message?: string }).message : undefined;
  return {
    status: "ready",
    summary: `Processed: ${message ?? "no message"}`,
  };
}


async function main(): Promise<void> {
  const client = A2SPAClient.fromEnv();
  const input = policyInput({ action: "send_message", workflowScope: "messages:send", message: "Hello from my existing TypeScript agent" });
  const output = await existingAgent(input);
  const requestBody = await client.buildSignedRequest(process.env.A2SPA_TARGET_AGENT_ID || "", input, output);
  const response = await client.postRequestBody(requestBody);
  console.log(`A2SPA verify response: ${response.status} ${await response.text()}`);
}


if (process.argv[1] && fileURLToPath(import.meta.url) === process.argv[1]) {
  main().catch((error) => {
    console.error(error);
    process.exit(1);
  });
}
a2spa-integration.json
{
  "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.",
    "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 /api/verify_payload.",
    "Put policy-relevant semantics in signed input fields such as intent, action, workflow_scope, amount_usd, currency, and on_behalf_of.",
    "When using A2SPA 2.0 state_continuity, register the authoritative state through POST /api/continuity_state first.",
    "Always fetch received runtime messages through GET /api/inbox_for_agent.",
    "Use GET /api/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."
  ],
  "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,
  "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",
  "purpose": "Teach developers and AI coding assistants how to integrate existing agents using the published A2SPA payload helpers and public A2SPA API. 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 A2SPA 2.0 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/api/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/api/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/api/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/api/verify_payload"
    }
  },
  "security_model": {
    "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",
      "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": "2026-03-23"
}