Reference
API reference
The complete list of integration points for building your own integration on
the Pay-Pi Connect API — no POS required. The /v1 surface is frozen:
breaking changes ship under a new version prefix. Every route below is relative to the base URL.
https://connect.pay-pi.com openapi.json) — both generated from the live API, always in sync with this page.
Integration points at a glance
| Area | How you connect | What it does |
|---|---|---|
| Tokenization | Finix.js in the browser | Collect the card/bank, get a single-use token. |
| Charge | POST /v1/payments | Create a payment from a token or stored instrument. |
| Refund | POST /v1/payments/{id}/refund | Full or partial reversal. |
| Lookup | GET /v1/payments/{id} | Current ledger state of a payment. |
| Reconcile | GET /v1/payments · /v1/payouts · /v1/disputes | List payments, payouts, and disputes via the API. |
| Onboarding | POST /v1/onboarding + GET /v1/onboarding/status | Hosted merchant onboarding + readiness. |
| Card-present | /v1/terminal/* + POST /v1/payments/terminal | In-person sales on PAX readers. |
| Config | GET /v1/meta | Public app id + environment (no auth). |
| Events | Your callback URL | Signed callbacks for async settlement, disputes, onboarding. |
Authentication
Every tenant request carries your secret API key (lc_live_…), as either the
X-Api-Key header or a Bearer token. Keys are created and revoked in your
dashboard and are shown once at issue time — Pay-Pi
stores only a hash, so keep the raw value as a backend secret.
X-Api-Key: lc_live_XXXXXXXXXXXXXXXXXXXX
# or, equivalently:
Authorization: Bearer lc_live_XXXXXXXXXXXXXXXXXXXX GET /v1/meta and the /health* checks are the only anonymous routes.
Idempotency
POST /v1/payments and POST /v1/payments/terminal require an Idempotency-Key header — derive it from your order key.
A replay with the same key returns the original result; the same key with a
different body returns 409, as does a key whose original request is still
in flight. On POST /v1/payments/{id}/refund the key is
optional (supply one to make a retried refund safe to replay).
Endpoints
Payments
Auth: Tenant keyPOST /v1/payments POST /v1/payments/{id}/refund GET /v1/payments/{id} Reconciliation
Auth: Tenant keyGET /v1/payments GET /v1/payouts GET /v1/disputes POST /v1/disputes/{id}/evidence POST /v1/disputes/{id}/submit Terminal / card-present
Auth: Tenant keyPOST /v1/terminal/readers GET /v1/terminal/readers GET /v1/terminal/readers/{id} POST /v1/terminal/readers/{id}/activate POST /v1/terminal/readers/{id}/deactivate POST /v1/payments/terminal Onboarding
Auth: Tenant keyPOST /v1/onboarding GET /v1/onboarding/form/{formId} GET /v1/onboarding/status Meta / health
Auth: AnonymousGET /v1/meta GET /health GET /health/live GET /health/ready Create a payment
Provide either finixToken (from Finix.js)
or a reusable finixInstrumentId (PI…) — exactly one. When
using a token, buyer is required. amountCents is in the smallest currency
unit.
curl -X POST https://connect.pay-pi.com/v1/payments \
-H "X-Api-Key: lc_live_..." \
-H "Idempotency-Key: SO-1001" \
-H "Content-Type: application/json" \
-d '{
"amountCents": 5000,
"currency": "USD",
"finixToken": "TKxxxx",
"buyer": { "firstName": "Ada", "lastName": "Lovelace", "email": "ada@example.com" },
"orderRef": "SO-1001"
}' Card charges settle synchronously; ACH returns processing and settles via a callback.
{
"lcPaymentId": "lcpay_9f2c4b1e…",
"status": "succeeded",
"amountCents": 5000,
"applicationFeeCents": 155,
"finixTransferId": "TR…",
"finixState": "SUCCEEDED"
} lcPaymentIdPay-Pi's stable id (lcpay_…) — store it for lookups and refunds.statussucceeded, processing, failed, canceled, partially_refunded, refunded, or disputed.applicationFeeCentsThe Pay-Pi fee applied to this payment.finixTransferIdThe underlying Finix transfer id.finixStateThe raw Finix transfer state.Refund a payment
Omit amountCents for a full refund. Refunds are guarded: the payment
must be refundable (succeeded or partially_refunded) and the amount ≤ the
remaining balance, else 409. The response is a PaymentResponse — the same
shape returned by GET /v1/payments/{id}.
curl -X POST https://connect.pay-pi.com/v1/payments/lcpay_9f2c…/refund \
-H "X-Api-Key: lc_live_..." \
-H "Content-Type: application/json" \
-d '{
"amountCents": 2500,
"reason": "requested_by_customer",
"refundApplicationFee": true
}' {
"lcPaymentId": "lcpay_9f2c4b1e…",
"status": "partially_refunded",
"amountCents": 5000,
"applicationFeeCents": 155,
"currency": "USD",
"orderRef": "SO-1001",
"processorTransferId": "TR…"
} List & reconcile
For a headless integration (no dashboard), read and act on your own data with your API key.
GET /v1/payments lists/searches payments — filter with from, to
(UTC ISO), and status; limit caps the page (≤200). When hasMore is
true, pass the returned nextCursor back as ?cursor= for the next page.
GET /v1/payouts and GET /v1/disputes return your settlements and chargebacks, and
you can contest a dispute with POST /v1/disputes/{id}/evidence (a multipart
file) followed by POST /v1/disputes/{id}/submit.
curl "https://connect.pay-pi.com/v1/payments?status=succeeded&limit=50" \
-H "X-Api-Key: lc_live_..." {
"data": [
{
"lcPaymentId": "lcpay_9f2c…",
"status": "succeeded",
"amountCents": 5000,
"currency": "USD",
"applicationFeeCents": 155,
"amountRefundedCents": 0,
"method": "card",
"orderRef": "SO-1001",
"processorTransferId": "TR…",
"createdAtUtc": "2026-08-03T14:05:11Z",
"updatedAtUtc": "2026-08-03T14:05:12Z"
}
],
"hasMore": false
} Onboarding & readiness
Onboarding is a Finix hosted form. Create one with
POST /v1/onboarding, send the merchant to the returned onboardingUrl, and
finalize by reading GET /v1/onboarding/form/{formId} on return. Charges are
gated until the merchant is approved — check GET /v1/onboarding/status:
{
"processorMerchantId": "MU…",
"onboardingStatus": "enabled",
"chargesEnabled": true,
"payoutsEnabled": true,
"reasons": [],
"requirements": [],
"pci": { "status": "valid", "validUntil": "2027-07-17", "actionUrl": null },
"onboardingUrl": null
} onboardingStatus is one of not_started, pending,
enabled, or restricted. Charging while chargesEnabled is
false returns a 403 merchant-not-ready.
See Onboarding & readiness.
Meta (public config)
GET /v1/meta is the one source of truth for the public values your frontend needs —
no auth. Read it at runtime so a sandbox→live move needs no code change.
{
"service": "connect.pay-pi.com",
"processor": "DUMMY_V1",
"finixEnvironment": "sandbox",
"finixApplicationId": "APxxxx",
"finixApiVersion": "…",
"currency": "USD"
} Callbacks (Pay-Pi → your app)
Some outcomes arrive after an API call returns — ACH settlement, disputes, and onboarding changes.
Pay-Pi POSTs these as signed events to the callback URL you register in your dashboard,
with the raw JSON body and these headers:
LC-Signature: t=1718900000,v1=3f9a… # HMAC-SHA256, verify before trusting
LC-Event-Type: transfer.updated # {entity}.{type}
LC-Event-Id: event_… # dedupe on this LC-Event-Type carries the underlying Finix {entity}.{type}
(e.g. transfer.updated, merchant.updated, dispute.created).
Verify the signature before trusting the body: v1 = HMAC_SHA256(signingSecret, "{t}.{rawBody}") as lowercase hex.
Reject if t is older than your tolerance (e.g. 5 min), dedupe on
LC-Event-Id, and ack with 2xx (non-2xx is retried with backoff).
const crypto = require("crypto");
function verify(secret, header, rawBody, toleranceSec = 300) {
const parts = Object.fromEntries(header.split(",").map((kv) => kv.split("=")));
const t = Number(parts.t);
if (Math.abs(Date.now() / 1000 - t) > toleranceSec) return false;
const expected = crypto.createHmac("sha256", secret).update(t + "." + rawBody).digest("hex");
return crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(parts.v1));
}
// Use the RAW request body — any re-serialization changes the signature. See Webhooks for the delivery model and handler guidance.
Test your integration
Signed-in merchants can run a full tokenize → charge → refund against their own
sandbox merchant from the dashboard's Developer area — it embeds
Finix.js, uses your sandbox credentials, and shows the exact request and response (plus the
equivalent curl) so you can replicate it in your own backend. No code required to
prove the path works end to end.
Errors
All failures are application/problem+json (RFC 7807) with a type,
title, status, detail, and traceId. See the full
error reference.
Next steps
- Tokenize cards with Finix.js
- Accept an online payment
- Onboarding & readiness
- Full working sample — a runnable Node integration
- Create an account and get an API key