API reference
Full v1 API reference — authentication, errors, rate limits, idempotency, and every endpoint with cURL, Node, and PHP samples.
API reference
Base URL: your Biz Invoice Pay domain (local dev: http://localhost:3102). All endpoints live under /api/v1. Field names below are taken from src/server/api/spec.ts, the single source of truth.
Authentication
Pass your API key as a Bearer token:
Authorization: Bearer inv_test_9f3a2b1c...Use inv_test_… keys in test mode, inv_live_… in live mode. See API keys.
Without a valid key you get:
{
"error": {
"type": "authentication_error",
"code": "invalid_api_key",
"message": "The API key provided is invalid."
}
}Errors
Every error has the same shape:
{
"error": {
"type": "invalid_request | authentication_error | permission_error | not_found | rate_limit | payment_error | server_error",
"code": "machine_readable_code",
"message": "Human-readable explanation.",
"param": "offending_field (optional)"
}
}Common codes: missing_reference, below_minimum, card_declined, insufficient_funds, payment_not_found, session_not_found, already_refunded.
Rate limits
60 requests per minute per key. Every response carries headers:
X-RateLimit-Limit: 60
X-RateLimit-Remaining: 59
X-RateLimit-Reset: 1728912000Exceeding the limit returns 429 with error.type = rate_limit. Back off and retry after the reset time.
Idempotency
POST endpoints accept an Idempotency-Key header. If you retry a request with the same key, the original response is returned instead of performing the action twice — so a dropped connection never creates two payment sessions or two refunds.
Idempotency-Key: order-4821-attempt-1Use a unique key per logical operation (e.g. per order), and keep the key stable across retries of that operation.
---
Create a payment session
POST /api/v1/payment-sessions
Creates a hosted checkout session and returns its URL. Redirect your customer (or show the QR) — the customer pays on Biz Invoice Pay's page and returns to your success_url or cancel_url.
Request body:
| Field | Type | Required | Description |
|---|---|---|---|
amount_cents | integer | Yes | Charge amount in cents. Minimum 100 ($1.00). |
currency | "usd" | Yes | Only usd is supported. |
description | string | Yes | What the customer is paying for (shown at checkout). |
customer_email | string | No | Prefills the email on the pay page; receipt goes here. |
reference | string | No | Your order ID. Use it later to look up the payment. |
success_url | string (URL) | Yes | Where the customer goes after paying. |
cancel_url | string (URL) | Yes | Where the customer goes if they cancel. |
metadata | object (string → string) | No | Custom key/value pairs stored on the session. |
Response (201): a PaymentSessionResponse:
{
"id": "chk_9f3a2b1c",
"url": "http://localhost:3102/checkout/chk_9f3a2b1c",
"status": "open",
"expires_at": "2026-10-15T03:00:00.000Z",
"amount_cents": 5000,
"description": "Haircut + beard trim"
}Session status is one of open, paid, expired, canceled.
curl -X POST http://localhost:3102/api/v1/payment-sessions \
-H "Authorization: Bearer inv_test_YOUR_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: order-4821-attempt-1" \
-d '{
"amount_cents": 5000,
"currency": "usd",
"description": "Haircut + beard trim",
"customer_email": "sam@example.com",
"reference": "order-4821",
"success_url": "https://yourshop.com/thanks",
"cancel_url": "https://yourshop.com/cart",
"metadata": { "order_id": "4821" }
}'const res = await fetch("http://localhost:3102/api/v1/payment-sessions", {
method: "POST",
headers: {
Authorization: `Bearer ${process.env.INVOICLY_API_KEY}`,
"Content-Type": "application/json",
"Idempotency-Key": "order-4821-attempt-1",
},
body: JSON.stringify({
amount_cents: 5000,
currency: "usd",
description: "Haircut + beard trim",
customer_email: "sam@example.com",
reference: "order-4821",
success_url: "https://yourshop.com/thanks",
cancel_url: "https://yourshop.com/cart",
}),
});
const session = await res.json();
console.log(session.url); // send the customer here<?php
$ch = curl_init("http://localhost:3102/api/v1/payment-sessions");
curl_setopt_array($ch, [
CURLOPT_POST => true,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => [
"Authorization: Bearer " . getenv("INVOICLY_API_KEY"),
"Content-Type: application/json",
"Idempotency-Key: order-4821-attempt-1",
],
CURLOPT_POSTFIELDS => json_encode([
"amount_cents" => 5000,
"currency" => "usd",
"description" => "Haircut + beard trim",
"customer_email" => "sam@example.com",
"reference" => "order-4821",
"success_url" => "https://yourshop.com/thanks",
"cancel_url" => "https://yourshop.com/cart",
]),
]);
$session = json_decode(curl_exec($ch), true);
header("Location: " . $session["url"]);---
Retrieve a payment session
GET /api/v1/payment-sessions/:id
Returns the session, including its current status (open, paid, expired, canceled).
{
"id": "chk_9f3a2b1c",
"url": "http://localhost:3102/checkout/chk_9f3a2b1c",
"status": "paid",
"expires_at": "2026-10-15T03:00:00.000Z",
"amount_cents": 5000,
"description": "Haircut + beard trim"
}Unknown id → 404, error.type = not_found, error.code = session_not_found.
curl http://localhost:3102/api/v1/payment-sessions/chk_9f3a2b1c \
-H "Authorization: Bearer inv_test_YOUR_KEY"const res = await fetch(
`http://localhost:3102/api/v1/payment-sessions/${sessionId}`,
{ headers: { Authorization: `Bearer ${process.env.INVOICLY_API_KEY}` } }
);
const session = await res.json();
if (session.status === "paid") { /* fulfill the order */ }<?php
$ch = curl_init("http://localhost:3102/api/v1/payment-sessions/" . $sessionId);
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => ["Authorization: Bearer " . getenv("INVOICLY_API_KEY")],
]);
$session = json_decode(curl_exec($ch), true);---
Retrieve a payment
GET /api/v1/payments/:id
Returns a PaymentResponse — the authoritative record with fee and net amounts.
Response fields:
| Field | Type | Description | |
|---|---|---|---|
id | string | Payment id (pay_…). | |
amount_cents | integer | Charged amount. | |
application_fee_cents | integer | Biz Invoice Pay fee (4.9% + 30¢). | |
net_to_seller_cents | integer | What you received: amount minus fee. | |
status | string | processing, succeeded, failed, refunded, partially_refunded. | |
failure_code | string \ | null | e.g. card_declined, insufficient_funds. |
refunded_cents | integer | Total refunded so far. | |
invoice_id | string \ | null | Linked invoice, if any. |
session_id | string \ | null | Linked payment session, if any. |
customer_email | string | Customer email. | |
created_at | string (ISO 8601) | When the payment was created. |
{
"id": "pay_7d2e9a41",
"amount_cents": 5000,
"application_fee_cents": 275,
"net_to_seller_cents": 4725,
"status": "succeeded",
"failure_code": null,
"refunded_cents": 0,
"invoice_id": null,
"session_id": "chk_9f3a2b1c",
"customer_email": "sam@example.com",
"created_at": "2026-10-14T21:05:00.000Z"
}curl http://localhost:3102/api/v1/payments/pay_7d2e9a41 \
-H "Authorization: Bearer inv_test_YOUR_KEY"const res = await fetch(`http://localhost:3102/api/v1/payments/${paymentId}`, {
headers: { Authorization: `Bearer ${process.env.INVOICLY_API_KEY}` },
});
const payment = await res.json();
console.log(payment.net_to_seller_cents); // 4725<?php
$ch = curl_init("http://localhost:3102/api/v1/payments/" . $paymentId);
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => ["Authorization: Bearer " . getenv("INVOICLY_API_KEY")],
]);
$payment = json_decode(curl_exec($ch), true);---
Find payments by reference
GET /api/v1/payments?reference=order-4821
Looks up payments created from sessions that carried your reference (e.g. your order id). Returns { "data": PaymentResponse[] }.
{
"data": [
{
"id": "pay_7d2e9a41",
"amount_cents": 5000,
"application_fee_cents": 275,
"net_to_seller_cents": 4725,
"status": "succeeded",
"failure_code": null,
"refunded_cents": 0,
"invoice_id": null,
"session_id": "chk_9f3a2b1c",
"customer_email": "sam@example.com",
"created_at": "2026-10-14T21:05:00.000Z"
}
]
}Missing ?reference= → 400, error.code = missing_reference.
curl "http://localhost:3102/api/v1/payments?reference=order-4821" \
-H "Authorization: Bearer inv_test_YOUR_KEY"const res = await fetch(
"http://localhost:3102/api/v1/payments?reference=order-4821",
{ headers: { Authorization: `Bearer ${process.env.INVOICLY_API_KEY}` } }
);
const { data } = await res.json();<?php
$url = "http://localhost:3102/api/v1/payments?reference=" . urlencode("order-4821");
$ch = curl_init($url);
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => ["Authorization: Bearer " . getenv("INVOICLY_API_KEY")],
]);
$result = json_decode(curl_exec($ch), true);
$payments = $result["data"];---
Create a refund
POST /api/v1/payments/:id/refunds
Refunds a payment in full (omit amount_cents) or partially. See fee-on-refund policy for how the Biz Invoice Pay fee is treated.
Request body:
| Field | Type | Required | Description |
|---|---|---|---|
amount_cents | integer | No | Partial refund amount. Omit for a full refund. |
reason | string | No | Free-text reason recorded on the refund. |
Response (a RefundResponse):
{
"id": "ref_3b8c1f20",
"status": "succeeded",
"amount_cents": 2000
}Refund status is succeeded, pending, or failed. After the refund, the payment's status becomes refunded (full) or partially_refunded (partial), and refunded_cents increases.
curl -X POST http://localhost:3102/api/v1/payments/pay_7d2e9a41/refunds \
-H "Authorization: Bearer inv_test_YOUR_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: refund-pay_7d2e9a41-1" \
-d '{ "amount_cents": 2000, "reason": "customer returned item" }'const res = await fetch(
`http://localhost:3102/api/v1/payments/${paymentId}/refunds`,
{
method: "POST",
headers: {
Authorization: `Bearer ${process.env.INVOICLY_API_KEY}`,
"Content-Type": "application/json",
"Idempotency-Key": `refund-${paymentId}-1`,
},
body: JSON.stringify({ amount_cents: 2000, reason: "customer returned item" }),
}
);
const refund = await res.json();<?php
$ch = curl_init("http://localhost:3102/api/v1/payments/" . $paymentId . "/refunds");
curl_setopt_array($ch, [
CURLOPT_POST => true,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => [
"Authorization: Bearer " . getenv("INVOICLY_API_KEY"),
"Content-Type: application/json",
"Idempotency-Key: refund-" . $paymentId . "-1",
],
CURLOPT_POSTFIELDS => json_encode([
"amount_cents" => 2000,
"reason" => "customer returned item",
]),
]);
$refund = json_decode(curl_exec($ch), true);Error examples
Missing required field (create session without success_url):
{
"error": {
"type": "invalid_request",
"code": "missing_field",
"message": "success_url is required.",
"param": "success_url"
}
}Amount below the $1.00 minimum:
{
"error": {
"type": "invalid_request",
"code": "below_minimum",
"message": "Amount 50¢ is below the minimum charge of 100¢.",
"param": "amount_cents"
}
}Declined test card on a hosted checkout attempt surfaces as a payment record with status: "failed" and failure_code: "card_declined" — not an API error.
Last updated: October 14, 2026
Was this page helpful?