This pack is for API-based integration only. It tells AI coding agents how to provision A2SPA agents through the dashboard Builder Pack and how to wrap existing Python, JavaScript, and TypeScript agents so they send signed payloads through the A2SPA API instead of bypassing the platform.
POST /api/verify_payload,
or replaces server-side verification with local-only checks.
If the AI has access to a logged-in browser session, it should use the Builder Pack to create agents, rotate keys, revoke keys, and capture the private key the moment it is issued.
The starter kits expose helpers for building canonical payloads, signing them locally, calling the A2SPA API, and polling a clean inbox endpoint for received messages.
A2SPA remains the verification authority. The local code signs payloads, but the platform still enforces timestamp, nonce, signature, permission, toggle, and credit checks through the API.
If a browser session is available, open the Builder Pack to create or rotate the needed agents first.
Save the returned private key immediately; it is only shown once.
Add the A2SPA environment variables to the target project.
Install one of the starter templates and wrap the existing agent with the provided send and fetch helpers.
Use the inbox_for_agent endpoint to poll received messages for the receiving agent.
Always send signed payloads through POST /api/verify_payload.
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.
Do not verify payloads locally as a substitute for the API.
Do not read or write Firestore 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.
POST https://aimodularity.com/A2SPA/api/verify_payload
Submit signed payloads through this endpoint so A2SPA can enforce verification, permission checks, replay protection, and credit usage.
GET https://aimodularity.com/A2SPA/api/inbox_for_agent
Poll received messages for a target agent through the A2SPA API. Use this as the runtime receive path for existing agents.
GET https://aimodularity.com/A2SPA/api/logs_for_agent
Use runtime logs for audit history, debugging, and dashboard-style visibility. They are not the primary inbox for receiving agents.
These templates are designed for AI coding agents. They all use the
A2SPA API and all expose a run_and_send or
runAndSend helper so an existing agent can be wrapped with
minimal code changes.
A2SPA_API_BASE=https://aimodularity.com/A2SPA
A2SPA_API_KEY=replace-with-dashboard-api-key
A2SPA_AGENT_ID=replace-with-your-agent-id
A2SPA_PRIVATE_KEY_PATH=/absolute/path/to/your-agent.priv.pem
A2SPA_TARGET_AGENT_ID=replace-when-you-send-to-another-agent
import hashlib
import json
import os
import uuid
from datetime import datetime, timezone
import requests
from cryptography.hazmat.primitives import hashes, serialization
from cryptography.hazmat.primitives.asymmetric import padding
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://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:
with open(self.private_key_path, "rb") as handle:
self._private_key = serialization.load_pem_private_key(handle.read(), password=None)
return self._private_key
def _canonical_json(self, value):
return json.dumps(value, sort_keys=True)
def _sha256_hex(self, value):
clean = {k: v for k, v in value.items() if k not in ("hash", "signature")}
return hashlib.sha256(self._canonical_json(clean).encode()).hexdigest()
def _sign(self, payload):
clean = {k: v for k, v in payload.items() if k not in ("hash", "signature")}
message = self._canonical_json(clean).encode()
return self._load_private_key().sign(
message,
padding.PKCS1v15(),
hashes.SHA256(),
).hex()
def build_payload(self, target_agent_id, input_data, output_data, alert_threshold=10, extra=None):
payload = {
"agent_id": self.agent_id,
"target_agent_id": target_agent_id,
"timestamp": datetime.now(timezone.utc).isoformat(),
"nonce": str(uuid.uuid4()),
"input": input_data,
"output": output_data,
"alert_threshold": alert_threshold,
}
if extra:
payload.update(extra)
payload["hash"] = self._sha256_hex(payload)
return payload
def send_payload(self, target_agent_id, input_data, output_data, alert_threshold=10, extra=None):
payload = self.build_payload(target_agent_id, input_data, output_data, alert_threshold, extra)
signature = self._sign(payload)
response = requests.post(
f"{self.api_base}/api/verify_payload",
headers={
"Content-Type": "application/json",
"x-api-key": self.api_key,
},
json={"payload": payload, "signature": signature},
timeout=self.timeout,
)
response.raise_for_status()
return response.json()
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 run_and_send(self, target_agent_id, input_data, runner, alert_threshold=10):
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"]
result = client.run_and_send(
target_agent_id,
{"message": "Hello from my existing Python agent"},
existing_agent,
)
print(result)
import fs from "node:fs/promises";
import crypto from "node:crypto";
function canonicalJson(value) {
if (value === null) return "null";
if (Array.isArray(value)) {
return `[${value.map((item) => canonicalJson(item)).join(", ")}]`;
}
if (typeof value === "object") {
const keys = Object.keys(value).sort();
return `{${keys
.map((key) => `${JSON.stringify(key)}: ${canonicalJson(value[key])}`)
.join(", ")}}`;
}
return JSON.stringify(value);
}
function stripSecurityFields(payload) {
const { hash, signature, ...rest } = payload;
return rest;
}
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://aimodularity.com/A2SPA",
apiKey: process.env.A2SPA_API_KEY,
agentId: process.env.A2SPA_AGENT_ID,
privateKeyPath: process.env.A2SPA_PRIVATE_KEY_PATH,
});
}
async loadPrivateKeyPem() {
if (!this.privateKeyPem) {
this.privateKeyPem = await fs.readFile(this.privateKeyPath, "utf8");
}
return this.privateKeyPem;
}
sha256Hex(value) {
return crypto.createHash("sha256").update(canonicalJson(stripSecurityFields(value))).digest("hex");
}
async sign(payload) {
const signer = crypto.createSign("RSA-SHA256");
signer.update(canonicalJson(stripSecurityFields(payload)));
signer.end();
return signer.sign(await this.loadPrivateKeyPem(), "hex");
}
buildPayload(targetAgentId, input, output, alertThreshold = 10, extra = {}) {
const payload = {
agent_id: this.agentId,
target_agent_id: targetAgentId,
timestamp: new Date().toISOString(),
nonce: crypto.randomUUID(),
input,
output,
alert_threshold: alertThreshold,
...extra,
};
payload.hash = this.sha256Hex(payload);
return payload;
}
async sendPayload(targetAgentId, input, output, alertThreshold = 10, extra = {}) {
const payload = this.buildPayload(targetAgentId, input, output, alertThreshold, extra);
const signature = await this.sign(payload);
const response = await fetch(`${this.apiBase}/api/verify_payload`, {
method: "POST",
headers: {
"Content-Type": "application/json",
"x-api-key": this.apiKey,
},
body: JSON.stringify({ payload, signature }),
});
if (!response.ok) {
throw new Error(await response.text());
}
return response.json();
}
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 runAndSend(targetAgentId, input, runner, alertThreshold = 10) {
const output = await runner(input);
return this.sendPayload(targetAgentId, input, output, alertThreshold);
}
}
async function existingAgent(input) {
return {
status: "ready",
summary: `Processed: ${input.message ?? "no message"}`,
};
}
if (import.meta.url === `file://${process.argv[1]}`) {
const client = A2SPAClient.fromEnv();
const result = await client.runAndSend(
process.env.A2SPA_TARGET_AGENT_ID,
{ message: "Hello from my existing Node agent" },
existingAgent,
);
console.log(result);
}
import fs from "node:fs/promises";
import crypto from "node:crypto";
type JsonPrimitive = string | number | boolean | null;
type JsonValue = JsonPrimitive | JsonValue[] | { [key: string]: JsonValue };
interface SendResult {
success?: boolean;
error?: string;
}
interface LogEntry {
action?: string;
[key: string]: unknown;
}
function canonicalJson(value: JsonValue): string {
if (value === null) return "null";
if (Array.isArray(value)) {
return `[${value.map((item) => canonicalJson(item)).join(", ")}]`;
}
if (typeof value === "object") {
const record = value as { [key: string]: JsonValue };
const keys = Object.keys(record).sort();
return `{${keys
.map((key) => `${JSON.stringify(key)}: ${canonicalJson(record[key])}`)
.join(", ")}}`;
}
return JSON.stringify(value);
}
function stripSecurityFields(payload: { [key: string]: JsonValue }): { [key: string]: JsonValue } {
const { hash, signature, ...rest } = payload;
return rest;
}
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://aimodularity.com/A2SPA",
apiKey: process.env.A2SPA_API_KEY || "",
agentId: process.env.A2SPA_AGENT_ID || "",
privateKeyPath: process.env.A2SPA_PRIVATE_KEY_PATH || "",
});
}
private async loadPrivateKeyPem(): Promise<string> {
if (!this.privateKeyPem) {
this.privateKeyPem = await fs.readFile(this.privateKeyPath, "utf8");
}
return this.privateKeyPem;
}
private sha256Hex(value: { [key: string]: JsonValue }): string {
return crypto.createHash("sha256").update(canonicalJson(stripSecurityFields(value))).digest("hex");
}
private async sign(payload: { [key: string]: JsonValue }): Promise<string> {
const signer = crypto.createSign("RSA-SHA256");
signer.update(canonicalJson(stripSecurityFields(payload)));
signer.end();
return signer.sign(await this.loadPrivateKeyPem(), "hex");
}
buildPayload(targetAgentId: string, input: JsonValue, output: JsonValue, alertThreshold = 10): { [key: string]: JsonValue } {
const payload: { [key: string]: JsonValue } = {
agent_id: this.agentId,
target_agent_id: targetAgentId,
timestamp: new Date().toISOString(),
nonce: crypto.randomUUID(),
input,
output,
alert_threshold: alertThreshold,
};
payload.hash = this.sha256Hex(payload);
return payload;
}
async sendPayload(targetAgentId: string, input: JsonValue, output: JsonValue, alertThreshold = 10): Promise<SendResult> {
const payload = this.buildPayload(targetAgentId, input, output, alertThreshold);
const signature = await this.sign(payload);
const response = await fetch(`${this.apiBase}/api/verify_payload`, {
method: "POST",
headers: {
"Content-Type": "application/json",
"x-api-key": this.apiKey,
},
body: JSON.stringify({ payload, signature }),
});
if (!response.ok) {
throw new Error(await response.text());
}
return (await response.json()) 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 runAndSend(
targetAgentId: string,
input: JsonValue,
runner: (input: JsonValue) => Promise<JsonValue> | JsonValue,
alertThreshold = 10,
): Promise<SendResult> {
const output = await runner(input);
return this.sendPayload(targetAgentId, input, output, alertThreshold);
}
}
async function existingAgent(input: JsonValue): Promise<JsonValue> {
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 result = await client.runAndSend(
process.env.A2SPA_TARGET_AGENT_ID || "",
{ message: "Hello from my existing TypeScript agent" },
existingAgent,
);
console.log(result);
}
void main();
{
"ai_workflow": [
"If a browser session is available, open the Builder Pack to create or rotate the needed agents first.",
"Save the returned private key immediately; it is only shown once.",
"Add the A2SPA environment variables to the target project.",
"Install one of the starter templates and wrap the existing agent with the provided send and fetch helpers.",
"Use the inbox_for_agent endpoint to poll received messages for the receiving agent."
],
"api_base": "https://aimodularity.com/A2SPA",
"code_generation_rules": [
"Always send signed payloads through POST /api/verify_payload.",
"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.",
"Do not verify payloads locally as a substitute for the API.",
"Do not read or write Firestore 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,
"platform": "A2SPA",
"provisioning": {
"builder_pack_url": "https://aimodularity.com/A2SPA/agent-builder",
"builder_spec_url": "https://aimodularity.com/A2SPA/api/agent_builder_spec",
"requires_authenticated_browser_session": true,
"summary": "Use the Builder Pack when an AI assistant has access to the logged-in dashboard session and needs to create, rotate, revoke, or toggle agents."
},
"purpose": "Teach AI coding agents how to provision A2SPA agents through the dashboard Builder Pack and how to integrate existing agents using the public A2SPA API only.",
"runtime_api": {
"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://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://aimodularity.com/A2SPA/api/logs_for_agent"
},
"verify_payload": {
"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,
"hash": "sha256 over the canonical payload without hash/signature",
"input": {
"message": "hello"
},
"nonce": "uuid",
"output": {
"status": "ready"
},
"target_agent_id": "\u003creceiver agent id\u003e",
"timestamp": "ISO-8601 string"
},
"signature": "hex RSA PKCS1v15 SHA256 signature over the canonical payload without hash/signature"
},
"url": "https://aimodularity.com/A2SPA/api/verify_payload"
}
},
"security_model": {
"dashboard_management_source": "A2SPA dashboard Builder Pack only",
"forbidden_shortcuts": [
"Direct Firestore 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",
"starter_kits": {
"env": {
"filename": ".env.a2spa.example",
"label": "Environment",
"runtime": "Generic",
"url": "https://aimodularity.com/A2SPA/integrations/templates/env"
},
"javascript": {
"filename": "a2spa-client.mjs",
"label": "JavaScript Starter",
"runtime": "Node.js 18+",
"url": "https://aimodularity.com/A2SPA/integrations/templates/javascript"
},
"python": {
"filename": "a2spa_client.py",
"label": "Python Starter",
"runtime": "Python",
"url": "https://aimodularity.com/A2SPA/integrations/templates/python"
},
"typescript": {
"filename": "a2spa-client.ts",
"label": "TypeScript Starter",
"runtime": "Node.js 18+",
"url": "https://aimodularity.com/A2SPA/integrations/templates/typescript"
}
}
}