Appearance
Invoices
Invoices collect an outstanding balance for an order — most commonly the remaining balance on a catering pre-order after any deposit. Each invoice has an unguessable public token that acts as its capability: anyone holding the token can view the invoice and pay the balance, so it can be embedded directly in a "pay your balance" link.
Fetching an invoice is public. Creating and confirming a balance payment require the kitchenclick:payments.create scope. Payment is processed through Stripe — see Payments for the client-side confirmation flow.
The token is the secret
The publicToken is a long random string (not a hashkey). Treat it like a bearer credential — never log it or expose it beyond the intended recipient.
Get Invoice
GET /v1/ecommerce/invoices/{publicToken}Authentication: None (Public — the token is the capability)
Rate Limit: 100/min per IP
Path Parameters
| Parameter | Type | Description |
|---|---|---|
publicToken | string | The invoice's public token (≥ 20 chars) |
Response
json
{
"status": "success",
"data": {
"invoice_id": "inv_00000a1B2c3D4e5",
"invoice_number": "INV-2026-0001",
"status": "partially_paid",
"issued_at": "2026-06-10T14:30:00Z",
"due_at": "2026-06-15T14:30:00Z",
"order_number": "1247",
"order_type": "catering",
"fulfillment_method": "delivery",
"scheduled_at": "2026-06-15T18:00:00Z",
"customer": { "name": "Jane Doe", "email": "jane@example.com", "phone": "(555) 123-4567" },
"location": {
"location_id": "loc_00000k1L2m3N4o5",
"name": "Downtown Kitchen",
"address": "123 Main St, Austin, TX 78701",
"phone": "(555) 555-5555",
"timezone": "America/Chicago"
},
"line_items": [
{ "item_id": "itm_00000a1B2c3D4e5", "name": "Catering Box - Chicken", "quantity": 2, "unit_price": "50.00", "subtotal": "100.00" }
],
"subtotal": "150.00",
"tax_total": "11.03",
"fees_total": "0.00",
"delivery_fee": "0.00",
"tip_amount": "0.00",
"total": "161.03",
"amount_paid": "80.52",
"balance_due": "80.51",
"payments": [
{ "type": "deposit", "method": "online", "amount": "80.52", "paid_at": "2026-06-09T10:15:00Z" }
],
"replaced_by_token": null
}
}| Field | Type | Description |
|---|---|---|
invoice_id | string | Invoice ID (prefix inv_) |
invoice_number | string | Human-readable invoice number |
status | string | issued, partially_paid, paid, or void |
balance_due | string | Outstanding balance (decimal string; 0.00 when paid, negative if overpaid) |
amount_paid | string | Total paid across all payment legs |
total | string | Grand total = subtotal + tax + fees + delivery + tip |
line_items | array | Captured order lines (item_id, name, quantity, unit_price, subtotal) |
payments | array | Succeeded payment legs (type, method, amount, paid_at) |
replaced_by_token | string | null | If void, the public token of the replacement invoice |
Money values are strings
All monetary amounts are returned as decimal strings (e.g. "80.51") to preserve precision. Parse them with a decimal-safe type, not a float.
Errors
| Status | Condition |
|---|---|
404 | Token not found, malformed (< 20 chars), or belongs to another organization |
Create Balance Payment Intent
Create a Stripe PaymentIntent for the invoice's current outstanding balance. Returns a client_secret for confirming the payment client-side (see Payments).
POST /v1/ecommerce/invoices/{publicToken}/balance-intentAuthentication: OAuth Token
Scope: kitchenclick:payments.create
Rate Limit: 30/min per client
Request Body
None.
Response
json
{
"status": "success",
"data": {
"client_secret": "pi_3Xy..._secret_abc",
"payment_intent_id": "pi_3Xy...",
"publishable_key": "pk_live_...",
"amount": 8051
}
}| Field | Type | Description |
|---|---|---|
client_secret | string | Stripe client secret — confirm the payment with Stripe.js |
payment_intent_id | string | Stripe PaymentIntent ID — pass to Confirm Balance |
publishable_key | string | null | Stripe publishable key for this account |
amount | integer | Amount in cents (e.g. 8051 = $80.51), from the current balance_due |
Errors
| Status | Message | Condition |
|---|---|---|
404 | Invoice not found | Unknown/invalid token |
422 | This invoice has no outstanding balance. | Invoice is paid, void, or balance ≤ 0 |
500 | Failed to initialize payment. Please try again. | Payment processor error |
Confirm Balance Payment
Record the balance payment after the customer confirms it client-side. Idempotent — calling again with the same payment_intent_id returns the current invoice without double-charging.
POST /v1/ecommerce/invoices/{publicToken}/confirm-balanceAuthentication: OAuth Token
Scope: kitchenclick:payments.create
Rate Limit: 30/min per client
Request Body
json
{
"payment_intent_id": "pi_3Xy..."
}| Field | Type | Required | Description |
|---|---|---|---|
payment_intent_id | string | Yes | The PaymentIntent ID from Create Balance Payment Intent |
Response
Returns the full updated invoice (same shape as Get Invoice) plus a top-level overpayment flag:
json
{
"status": "success",
"overpayment": false,
"data": {
"invoice_id": "inv_00000a1B2c3D4e5",
"status": "paid",
"amount_paid": "161.03",
"balance_due": "0.00",
"payments": [
{ "type": "deposit", "method": "online", "amount": "80.52", "paid_at": "2026-06-09T10:15:00Z" },
{ "type": "balance", "method": "payment_link", "amount": "80.51", "paid_at": "2026-06-10T15:45:00Z" }
]
}
}| Field | Type | Description |
|---|---|---|
overpayment | boolean | true when the captured amount differs from the expected balance — e.g. the balance was settled at the POS between intent creation and confirmation. The payment is still recorded for reconciliation. |
Errors
| Status | Message | Condition |
|---|---|---|
404 | Invoice not found | Unknown/invalid token |
422 | This invoice has been voided. | Invoice is void |
422 | Payment not completed | PaymentIntent has not succeeded — payment_status reflects the Stripe status |
500 | Payment verification failed | Could not verify the payment with the processor |
Changelog
| Date | Change |
|---|---|
| 2026-06-17 | Initial publication. |