API Reference
The complete REST API for accepting crypto payments — checkouts, wallets, transactions, payments, projects, and signed webhooks. Every endpoint, parameter, and response, documented.
The ZyraPay API is organized around predictable, resource-oriented URLs, accepts and returns JSON, and uses standard HTTP verbs and status codes. This page is the exhaustive reference; for a guided walkthrough start with the Quickstart.
Base URL
All requests go to a single versioned base URL over HTTPS:
https://api.zyrapay.net/v1Versioning
The API is versioned in the path. Every endpoint below is mounted under /v1. We introduce breaking changes only behind a new version prefix, so pinning to /v1 keeps your integration stable.
Requests and responses
Send request bodies as JSON with a Content-Type: application/json header. Responses are JSON and, by convention, wrap their payload in a success flag with the data under data (or top-level fields for creation endpoints such as checkout). Errors set success: false — see Errors.
Authentication at a glance
Every endpoint authenticates with your project API key in the x-api-key header — that's all your server needs. Creation and money-moving requests also accept an Idempotency-Key header for safe retries. Full details are in Authentication.
Keep your keys secret
API keys carry full access to your project. Send them only from your server, never from browser or mobile client code, and rotate immediately if one is exposed.
Authentication
The ZyraPay API authenticates with a project API key sent in the x-api-key header. It is the only credential your server needs — there are no tokens to mint and no OAuth flow.
API key (gateway, checkout, wallets)
Server-to-server endpoints authenticate with a project API key sent in the x-api-key header (the legacy apikey header is also accepted). The key is a 64-character hex string. Each key resolves to exactly one project, so the project context is implied by the key — you never pass a project id on these requests.
x-api-key: 4f3c9a...e9Keys are hashed at rest — store them securely
ZyraPay never stores your key in plaintext. We persist only its SHA-256 hash (the indexed lookup) plus a short non-secret 8-character prefix shown in the dashboard. The full key is revealed exactly once, at project creation (or rotation). Keep it server-side; if you lose it, rotate the key from the dashboard — there is no way to recover the original.
If the key is missing or invalid you receive:
{ "success": false, "message": "API key is required" }
{ "success": false, "message": "Invalid API key get your api key from the dashboard" }Wallets behave slightly differently
POST /v1/wallets reads the same x-api-key header but returns 401 when the key is missing and 403 when the key is invalid (rather than 401 for both).
One credential for everything
Every endpoint in this reference — checkout, wallets, transactions, payments, and key rotation — uses the same x-api-key. There is no separate token or login to manage. You can also create projects and view history visually in the dashboard.
The Idempotency-Key header
Money-moving and creation requests support an optional Idempotency-Key request header. Send a unique value (for example a UUID) and a replay returns the original response verbatim instead of performing the action twice — making retries safe over flaky networks.
| Field | Type | Description |
|---|---|---|
Idempotency-Key | string | A unique, client-generated key. Replays within the TTL (24h default) return the cached 2xx response. |
The key is honored on the following endpoints, each scoped to a different collision namespace:
| Endpoint | Scope |
|---|---|
| POST /v1/checkout | Project |
| POST /v1/wallets | API key |
A request that is still in flight under the same key returns 409:
{ "success": false, "message": "A request with this Idempotency-Key is still being processed." }Checkout
A checkout is a hosted payment page for a one-off purchase. You create it with your API key, redirect the customer to checkoutUrl, and receive signed webhooks as the payment lands. Checkouts default to a 30-minute expiry and accept overpayment and partial payment.
Create a checkout
/v1/checkoutAPI keyCreates a checkout session, mints a deposit wallet for it, and (if a webhookUrl is supplied) fires a best-effort signed checkout.created webhook. Honors the Idempotency-Key header, scoped per project.
Body parameters
| Field | Type | Description |
|---|---|---|
itemsrequired | object[] | Line items. Must be a non-empty array. See item fields below. |
currency | string | One of PI, USD, USDT, DOLLAR. USD-family currencies are converted to PI at the live price. Defaults to PI. |
expiresInMinutes | number | Minutes until the session expires. Must be positive. Defaults to 30. |
redirectUrl | string | URL the hosted page returns the customer to after payment. |
successUrl | string | URL to redirect to on a successful payment. |
cancelUrl | string | URL to redirect to if the customer cancels. |
webhookUrl | string | Per-checkout webhook URL. Overrides the project default for this session. |
metadata | object | Arbitrary key/value data echoed back on the checkout and its webhooks. |
Item fields
| Field | Type | Description |
|---|---|---|
namerequired | string | Display name of the line item. |
amountrequired | number | Unit price. Must be a positive number. |
quantityrequired | number | Quantity. Must be a positive number. |
description | string | Optional line-item description. |
image | string | Optional image URL shown on the hosted page. |
metadata | object | Optional per-item metadata. |
curl https://api.zyrapay.net/v1/checkout \
-H "x-api-key: $ZYRA_API_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: $(uuidgen)" \
-d '{
"items": [
{ "name": "Pro plan", "amount": 5, "quantity": 1 },
{ "name": "Add-on seat", "amount": 2, "quantity": 4 }
],
"currency": "PI",
"expiresInMinutes": 30,
"successUrl": "https://yourapp.com/success",
"cancelUrl": "https://yourapp.com/cancel",
"webhookUrl": "https://yourapp.com/webhooks/zyra"
}'Validation errors return 400
If items is empty, or any item lacks a string name / positive amount / positive quantity, you receive { "success": false, "message": "Invalid request data", "errors": [...] }.
List checkouts
/v1/checkoutAPI keyLists checkouts for the project tied to your API key, with filtering, sorting, and pagination. The project scope is fixed by the key.
Query parameters
| Field | Type | Description |
|---|---|---|
page | integer | 1-based page number. Defaults to 1. |
limit | integer | Page size, capped at 100. Defaults to 20. |
sort | string | Sort field: createdAt | totalAmount | status. Defaults to createdAt. |
order | string | asc | desc. Defaults to desc. |
status | string | pending | expired | cancelled | completed | partial | overpaid. Use all (or omit) for no filter. |
dateFrom | ISO date | Include checkouts created on or after this timestamp. |
dateTo | ISO date | Include checkouts created on or before this timestamp (inclusive). |
minAmount | number | Filter on totalAmount ≥ this value. |
maxAmount | number | Filter on totalAmount ≤ this value. |
q | string | Case-insensitive search over checkoutId and embedded items.name. |
curl "https://api.zyrapay.net/v1/checkout?status=completed&sort=totalAmount&order=desc&page=1&limit=20" \
-H "x-api-key: $ZYRA_API_KEY"Retrieve a checkout
/v1/checkout/{checkoutId}PublicReturns a single checkout populated with its project.name and the checkout's deposit address. This route is public (no auth) so the hosted checkout page can read it directly.
curl https://api.zyrapay.net/v1/checkout/chk_8f2a1c9b4d6e7fCancel a checkout
/v1/checkout/{checkoutId}API keyCancels a checkout owned by your project. Only a pending checkout can be cancelled.
curl -X DELETE https://api.zyrapay.net/v1/checkout/chk_8f2a1c9b4d6e7f \
-H "x-api-key: $ZYRA_API_KEY"Wallets
A wallet is a per-customer deposit address. Generate one for each user or invoice, hand them the address, and receive a signed deposit webhook whenever Pi arrives on it.
Create a wallet
/v1/walletsAPI keyGenerates a new deposit address for the project tied to your API key. Takes no body. Emits a wallet.created webhook to the project's configured webhookUrl if one is set. Honors the Idempotency-Key header, scoped per API key.
Rate limit
Wallet creation is limited to 200 requests per 15 minutes per API key (or IP), with a one-hour block when the limit is breached.
curl -X POST https://api.zyrapay.net/v1/wallets \
-H "x-api-key: $ZYRA_API_KEY" \
-H "Idempotency-Key: $(uuidgen)"Auth errors
A missing x-api-key header returns 401; an invalid key returns 403.
Transactions
Transactions are the ledger of every money movement on your project — deposits, withdrawals, payments, refunds, and fees. These endpoints authenticate with your x-api-key and return your project's transactions.
List transactions
/v1/transactionsx-api-keyReturns a paginated list of the caller's transactions, each with its referenced project populated (name, type).
Query parameters
| Field | Type | Description |
|---|---|---|
page | integer | 1-based page number. Defaults to 1. |
limit | integer | Page size, capped at 100. Defaults to 20. |
sort | string | Sort field: createdAt | amount | status. Defaults to createdAt. |
order | string | asc | desc. Defaults to desc. |
status | string | pending | completed | failed | cancelled. Use all (or omit) for no filter. |
type | string | deposit | withdrawal | transfer | fee | payment | refund. Use all (or omit) for no filter. |
projectId | string | Filter by a specific project id. Use all (or omit) for no filter. |
dateFrom | ISO date | Include transactions created on or after this timestamp. |
dateTo | ISO date | Include transactions created on or before this timestamp (inclusive). |
minAmount | number | Filter on amount ≥ this value. |
maxAmount | number | Filter on amount ≤ this value. |
q | string | Case-insensitive search over trx_id and title. |
curl "https://api.zyrapay.net/v1/transactions?type=deposit&status=completed&sort=amount&order=desc&page=1&limit=20" \
-H "x-api-key: $ZYRA_API_KEY"Retrieve a transaction
/v1/transactions/{id}x-api-keyReturns a single transaction owned by the caller.
curl https://api.zyrapay.net/v1/transactions/trx_a1b2c3d4 \
-H "x-api-key: $ZYRA_API_KEY"Payments
Payment endpoints move Pi off your balance — sending funds to a recipient (withdraw) or returning a previously received payment (refund). Authenticate with your x-api-key like every other endpoint. Both honor the Idempotency-Key header, and on-chain transfers are serialized behind a lock so concurrent calls cannot double-spend.
Withdraw
/v1/payments/withdrawx-api-keyDebits your balance atomically (a conditional update guarantees two concurrent withdrawals can't both pass a stale balance check), submits a Pi payment to recipient, and records a completed withdrawal transaction. If the on-chain transfer fails, the reserved balance is refunded automatically.
Body parameters
| Field | Type | Description |
|---|---|---|
recipientrequired | string | The destination Pi address to send funds to. |
amountrequired | number | Amount of Pi to withdraw. Must not exceed your available balance. |
curl -X POST https://api.zyrapay.net/v1/payments/withdraw \
-H "x-api-key: $ZYRA_API_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: $(uuidgen)" \
-d '{
"recipient": "GBVYHIK3X4VQ2...",
"amount": 10
}'Errors
400 — missing/invalid input or insufficient balance. 401 — no authenticated user. 500 — on-chain failure (the debited balance is restored).
Refund
/v1/payments/refundx-api-keyRefunds a previously received payment. The original transaction must belong to you, be a payment or deposit, and be completed. Double refunds are blocked. Because the on-chain payer address is not stored on the transaction, you supply the recipient to refund to. Omit amount for a full refund, or pass a smaller value for a partial refund.
Body parameters
| Field | Type | Description |
|---|---|---|
trxIdrequired | string | The trx_id of the original completed transaction to refund. |
recipientrequired | string | The address to send the refund to (the on-chain payer address). |
amount | number | Optional partial-refund amount. Defaults to the full original amount. Cannot exceed it. |
curl -X POST https://api.zyrapay.net/v1/payments/refund \
-H "x-api-key: $ZYRA_API_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: $(uuidgen)" \
-d '{
"trxId": "trx_a1b2c3d4",
"recipient": "GBVYHIK3X4VQ2...",
"amount": 5
}'Errors
400 — missing/invalid input, not refundable, or amount too large. 401 — no user. 404 — original transaction not found. 409 — already refunded. 500 — on-chain failure.
Projects
A project owns an API key and groups its checkouts, wallets, and transactions. You can create projects in the dashboard; the endpoint below rotates a project's key and authenticates with your x-api-key.
Rotate an API key
/v1/projects/{projectId}/rotate-keyx-api-keyGenerates a fresh API key for the project, stores only its new SHA-256 hash and 8-character prefix, and returns the new plaintext key exactly once. The previous key stops working immediately — rotation is also how you revoke a leaked key.
The key is shown only once
Copy the returned apiKey and store it securely. It is never displayed again; if you lose it you must rotate again.
curl -X POST https://api.zyrapay.net/v1/projects/650f1c.../rotate-key \
-H "x-api-key: $ZYRA_API_KEY"Webhook Events
ZyraPay delivers events to the webhookUrl configured on your project (or, for checkouts, the per-session webhookUrl). Every delivery is an HTTP POST with a JSON body in the SimpleWebhookPayload envelope and an HMAC-SHA256 signature you should verify before trusting it.
Event types
| Type | Description |
|---|---|
deposit | Pi arrived on a deposit wallet. |
wallet.created | A new deposit wallet was minted via POST /v1/wallets. |
checkout.created | A checkout session was created via POST /v1/checkout. |
checkout.paid | A checkout was paid in full. |
checkout.underpaid | A checkout received a partial payment (less than the total). |
checkout.overpaid | A checkout received more than its total amount. |
checkout.expired | A pending checkout reached its expiry without full payment. |
checkout.cancelled | A pending checkout was cancelled. |
Payload shape
Every event uses the same envelope. type is one of the values above, timestamp is a Unix epoch (milliseconds), projectId identifies your project, and signature is the HMAC-SHA256 hex digest of the payload (also sent in the header). The data object varies by event type.
{
"type": "checkout.paid",
"data": {
"projectId": "650f1c...",
"checkoutId": "chk_8f2a1c9b4d6e7f",
"transactionId": "trx_a1b2c3d4",
"from": "GBVYHIK3X4VQ2...",
"amount": 13,
"metadata": {}
},
"timestamp": 1749902700000,
"projectId": "650f1c...",
"signature": "3a7f...hmac-sha256-hex"
}The data fields differ per family: deposit carries address, transactionId, and amount; the checkout.paid / underpaid / overpaid events carry checkoutId, from, transactionId, and amount; and the lifecycle events (checkout.expired, checkout.cancelled) carry checkoutId and amount.
Verifying the signature
Each request carries an X-Zyra-Signature header of the form sha256=<hex>. Compute the HMAC-SHA256 of the raw request body using your webhook secret and compare it to the header. ZyraPay also sends X-Project-ID, X-Event-Type, and X-Timestamp headers for convenience.
import crypto from "crypto";
function verify(rawBody, signatureHeader, secret) {
const expected =
"sha256=" +
crypto.createHmac("sha256", secret).update(rawBody).digest("hex");
return crypto.timingSafeEqual(
Buffer.from(expected),
Buffer.from(signatureHeader)
);
}
// Express: capture the raw body, then
app.post("/webhooks/zyra", (req, res) => {
const ok = verify(req.rawBody, req.headers["x-zyra-signature"], process.env.WEBHOOK_SECRET);
if (!ok) return res.status(400).send("bad signature");
// handle req.body.type ...
res.sendStatus(200);
});Retries and delivery
ZyraPay retries failed deliveries up to 3 times with increasing back-off (1s, 5s, 15s). Respond with a 2xx status as quickly as possible and process asynchronously; treat events as at-least-once and deduplicate on data.transactionId.
Errors
ZyraPay uses conventional HTTP status codes: 2xx for success, 4xx for problems with the request (these are safe to surface to the caller), and 5xx for errors on ZyraPay's side.
Status codes
| Status | Meaning |
|---|---|
| 200 OK | The request succeeded. |
| 201 Created | A resource was created (checkout, wallet, refund). |
| 400 Bad Request | Invalid or missing parameters, or a non-refundable / insufficient-balance condition. |
| 401 Unauthorized | Missing or invalid API key,. |
| 403 Forbidden | Invalid API key on the wallets endpoint. |
| 404 Not Found | The resource does not exist or is not owned by the caller. |
| 409 Conflict | A duplicate idempotent request is in flight, or a payment has already been refunded. |
| 429 Too Many Requests | Rate limit exceeded (for example, wallet creation). |
| 500 Server Error | An unexpected or on-chain failure. Withdrawals restore the reserved balance. |
Error response shape
Error responses carry a success: false flag and a human-readable message. Some errors also include a machine-readable error code, the statusCode, and a details object with field-level information.
{
"success": false,
"error": "VALIDATION_ERROR",
"message": "Invalid request data",
"statusCode": 400,
"details": {
"errors": [
{ "field": "items", "message": "items must be a non-empty array" }
]
}
}