Skip to content

REST API

Everything @usestack/node does is a plain HTTPS call, so you can use Stack from any language.

Base URL: https://api.usestack.cc

Authentication: your secret key as a Bearer token.

Terminal window
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-id header. Quote it when you contact us.
  • Rate limit: 100 requests a minute from one IP address. Over it you get 429 rate_limited. Back off and retry.

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.


POST /v1/payment_intents

Terminal window
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_…"
}

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).

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.

POST /v1/refunds

Terminal window
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.