Reference
API documentation
Everything you need to integrate ZeniPay — accounts, agents, cards, treasury, approvals, webhooks.
Authentication
Every ZeniPay API call is authenticated by a merchant API key. Create one in /app/settings → API Keys.
Every API call must include a Bearer token in the Authorization header. Keys live in your merchant settings at /app/settings → API Keys.
curl https://api.zenipay.ca/v1/agents \ -H "Authorization: Bearer zpk_live_<your_key>"
Test vs live keys
zpk_test_ keys route to sandbox resources. zpk_live_ keys touch real money. Build against test, promote to live when the flow is stable.
Agents
Create, list, and manage AI agents. Each agent has a wallet with a balance, a policy, and a keypair signed by your org.
/api/v1/agentsCreate a new agent.curl -X POST https://api.zenipay.ca/v1/agents \
-H "Authorization: Bearer zpk_live_..." \
-H "Content-Type: application/json" \
-d '{
"name": "Marco",
"agent_type": "sales",
"description": "Qualifies inbound leads 24/7"
}'/api/v1/agentsList all agents in your org./api/v1/agents/:idFetch one agent + its wallet + policy./api/v1/agents/:idUpdate name / description / policy.Treasury
Fund an org treasury and distribute to agents. Merchant → treasury transfers always land in the org wallet first; distribution to a specific agent is a separate action.
/api/v1/agents/treasury/distribute-from-merchantDebit a ZeniPay account, credit the org treasury./api/v1/agents/treasury/request-distributionSmart wrapper around distribute-to-agent. Creates an approval request when a rule matches, otherwise executes immediately./api/v1/agents/treasury/distribute-to-agentDebit the org treasury, credit an agent wallet./api/v1/agents/treasury/reclaim-from-agentReverse of distribute-to-agent. Debit agent wallet, credit treasury./api/v1/agents/treasury/return-to-merchantDebit the org treasury, credit a merchant ZeniPay account./api/v1/agents/treasury/eventsList funding events (card top-ups, ACH, wire, USDC).curl -X POST https://api.zenipay.ca/v1/agents/treasury/request-distribution \
-H "Authorization: Bearer zpk_live_..." \
-H "Content-Type: application/json" \
-d '{
"to_agent_id": "agt_xxx",
"amount_units": 250,
"currency": "CAD",
"idempotency_key": "ops-payroll-2026-05-01"
}'ZeniCards
Issue virtual or physical cards for agents. Every authorization runs through the tamper-evident ZeniCore ledger.
/api/v1/agents/zenicards/issueIssue a new card for an agent./api/v1/agents/zenicardsList cards in your org./api/v1/agents/zenicards/:id/statusFreeze, unfreeze, or cancel a card.Webhooks
Subscribe to events to keep your own systems in sync. Every payload is signed with your webhook secret.
Events emitted:
payment.completed— customer payment settled.payout.sent— withdrawal fired to an external destination.approval.requested— merchant-rule approval pending.card.charged— agent card authorized or settled.agent.funded— agent wallet received funds from treasury.
Signature verification
import { createHmac, timingSafeEqual } from "node:crypto";
async function verify(req: Request, secret: string): Promise<boolean> {
const sig = req.headers.get("x-zp-signature") ?? "";
const body = await req.text();
const expected = createHmac("sha256", secret).update(body).digest("hex");
return timingSafeEqual(Buffer.from(sig, "hex"), Buffer.from(expected, "hex"));
}Errors
Every error response carries a stable { error: { code, message } } shape. Here are the codes you'll see most often.
| HTTP | Code | Meaning |
|---|---|---|
400 | invalid_request | Body is malformed or missing required fields. |
401 | unauthorized | API key missing, invalid, or revoked. |
403 | forbidden | Key lacks permission for this resource. |
404 | not_found | Resource doesn’t exist or you can’t see it. |
409 | conflict | State conflict — e.g. already approved. |
422 | validation_error | Business-rule violation (insufficient funds, currency mismatch). |
429 | rate_limited | Too many requests. Back off and retry. |
500 | internal_error | We broke something. Retry and tell us if it recurs. |