REST API
Everything @usestack/node does is a plain HTTPS call, so you can use Stack from any language.
Basics
Section titled “Basics”Base URL: https://api.usestack.cc
Authentication: your secret key as a Bearer token.
curl https://api.usestack.cc/v1/payment_intents/pi_... \ -H "Authorization: Bearer sk_test_..."- Bodies are JSON (
Content-Type: application/json). - Amounts are integers in kobo when you send them, and strings when Stack returns them
(
"570000"), so large amounts never lose precision. - Errors share one shape. See Errors.
- Every response has an
x-request-idheader. Quote it when you contact us. - Rate limit: 100 requests a minute from one IP address. Over it you get
429rate_limited. Back off and retry.
Idempotency
Section titled “Idempotency”POST requests that create something take an Idempotency-Key header. Sending the same key
again returns what the first request created, instead of creating a second one. Use
something unique to what’s being paid for, such as order_123.
Reusing a key for a payment with a different amount or currency is refused with
400 invalid_request.
Create a payment intent
Section titled “Create a payment intent”POST /v1/payment_intents
curl https://api.usestack.cc/v1/payment_intents \ -H "Authorization: Bearer sk_test_..." \ -H "Content-Type: application/json" \ -H "Idempotency-Key: order_123" \ -d '{ "amount": 570000, "currency": "NGN", "description": "Stack Food order", "metadata": { "orderId": "ord_123" } }'| Field | Type | |
|---|---|---|
amount |
integer, required | Kobo, at least 1. |
currency |
string, required | "NGN" |
description |
string | Up to 500 characters. Shown in the sheet. |
metadata |
object | Any JSON. Returned on the intent and in webhooks. |
customer |
object | { "email": "…" }, who is paying. See Tell Stack who is paying. phone (E.164) is also accepted. |
Returns 201: the payment intent, plus
client_secret.
{ "id": "pi_Lb_-_wULEDdFYYJT", "livemode": false, "merchant_id": "3f0c…", "merchant_name": "Stack Food", "merchant_logo_url": null, "amount": "570000", "currency": "NGN", "description": "Stack Food order", "status": "requires_authentication", "method": null, "metadata": { "orderId": "ord_123" }, "expires_at": "2026-09-27T10:30:00.000Z", "succeeded_at": null, "customer": null, "client_secret": "pi_Lb_-_wULEDdFYYJT_secret_…"}Retrieve a payment intent
Section titled “Retrieve a payment intent”GET /v1/payment_intents/{id}
Returns 200: the payment intent, without client_secret. 404 if it isn’t yours, or
if it was made with your other mode’s key (a test key can’t see live payments, and the
other way round).
Cancel a payment intent
Section titled “Cancel a payment intent”POST /v1/payment_intents/{id}/cancel
Cancels a payment the customer hasn’t paid. Cancelling one that’s already canceled
returns it unchanged.
Returns 200: the payment intent with status: "canceled". 400 if it’s
processing or succeeded: refund it instead.
Create a refund
Section titled “Create a refund”POST /v1/refunds
curl https://api.usestack.cc/v1/refunds \ -H "Authorization: Bearer sk_test_..." \ -H "Content-Type: application/json" \ -H "Idempotency-Key: refund_ord_123" \ -d '{ "payment_intent": "pi_Lb_-_wULEDdFYYJT", "amount": 100000 }'| Field | Type | |
|---|---|---|
payment_intent |
string, required | A succeeded payment. |
amount |
integer | Kobo. Leave it out to refund whatever hasn’t been refunded yet. |
The refund goes to the customer’s Stack balance, from yours, straight away. See Refunds.
Returns 201:
{ "id": "5b2e…", "livemode": false, "payment_intent": "pi_Lb_-_wULEDdFYYJT", "amount": "100000", "status": "succeeded", "created_at": "2026-09-27T10:05:00.000Z" }409 insufficient_balance: your balance can’t cover it. Nothing moved.400 invalid_request: the payment hasn’t succeeded, or the amount is more than what’s left to refund.