Truvora API reference
Two APIs on one account: a Stripe-compatible billing API for customers, catalog, subscriptions, invoices, discounts and checkout, and a gateway API for direct card authorization, capture, void, refund and unreferenced credit — with tokenized storage, idempotent retries and signed webhooks. Everything below is available in sandbox the moment you create an account.
Overview
All endpoints live under a versioned base path and accept and return application/json.
https://api.truvora.com/gateway/v1
- Environments. Sandbox and live share the same URLs — the environment is determined by the credential you authenticate with. Sandbox routes to a deterministic simulator; live routing is enabled once underwriting approves the merchant.
- Amounts. Send
amountin major units oramountMinorin cents. Responses always report minor units. - Idempotency. Send an
Idempotency-Keyheader on transaction creation. Replaying the same key returns the original transaction instead of charging twice. - Card data. PANs are accepted only at this boundary, encrypted immediately, and never returned. Only brand, expiry and last four are readable afterwards.
- Billing API. The Stripe-compatible surface — customers, products, subscriptions, invoices, payment intents — sits on the same host at
https://api.truvora.com/v1and authenticates with aBearersecret key.
Authentication
Every request authenticates with an API login ID and a transaction key. Generate them in the portal under Developers; the transaction key is shown once at creation and stored only as a hash. Revoked credentials and merchants that are not approved are rejected with 401.
Authorization: Basic base64(apiLoginId:transactionKey)
x-api-login-id: 4mTq9Xc2LpVb x-transaction-key: 7f1c...9ba2
Never expose a transaction key in browser or mobile code — call the gateway from your server.
Billing API
Catalog, customers, saved cards, subscriptions, invoices, discounts, one-off payments, hosted checkout and the event log. Request and response shapes follow Stripe's conventions.
https://api.truvora.com/v1
Authentication
Create a secret key in the portal under Developers → API keys. It is shown once and stored only as a hash. sk_test_ keys act on sandbox data, sk_live_ keys on live data once underwriting approves the account.
Authorization: Bearer sk_test_4eC39HqLyjWDarjtT1zdp7dc # basic auth works too — the key is the username, the password is empty curl -u "sk_test_4eC39HqLyjWDarjtT1zdp7dc:" https://api.truvora.com/v1/customers
Pagination, metadata and idempotency
Every list endpoint accepts limit (1–100, default 10) and the cursors starting_after and ending_before, and returns has_more. Every object accepts a metadata map of your own keys. Send an Idempotency-Key header on charges so a retried request never bills twice.
curl -G https://api.truvora.com/v1/subscriptions \
-u "$TRUVORA_SECRET_KEY:" \
-d limit=25 -d starting_after=sub_1Nc8kL9x
{
"object": "list",
"url": "/v1/subscriptions",
"has_more": true,
"data": [ { "id": "sub_1Nc8kM2p", "object": "subscription", "...": "..." } ]
}Customers
People or companies you bill. A customer holds saved cards, subscriptions and invoices.
/v1/customersCreate a customer — email, name, phone, description, currency, metadata.
/v1/customersList customers, newest first, with limit and starting_after / ending_before cursors.
/v1/customers/{id}Retrieve a customer, including its default payment method.
/v1/customers/{id}Update a customer, including default_payment_method.
/v1/customers/{id}Delete a customer.
Payment methods & setup intents
Cards are tokenized at the API boundary and stored in the encrypted vault. A setup intent saves a card for later without charging it.
/v1/payment_methodsTokenize a card (type=card, card[number], card[exp_month], card[exp_year], card[cvc]).
/v1/payment_methodsList a customer's saved cards.
/v1/payment_methods/{id}Retrieve a saved card — brand, last4, expiry only.
/v1/payment_methods/{id}/attachAttach a saved card to a customer.
/v1/payment_methods/{id}/detachDetach a saved card from its customer.
/v1/setup_intentsSave a card for future off-session charges; returns status succeeded with the payment method.
/v1/setup_intents/{id}Retrieve a setup intent.
Products & prices
Your catalog. A product is what you sell; a price is what it costs, one-time or recurring.
/v1/productsCreate a product — name, description, images, unit_label, statement_descriptor.
/v1/productsList products; filter with active=true.
/v1/products/{id}Retrieve a product.
/v1/products/{id}Update or archive a product (active=false).
/v1/pricesCreate a price — product, unit_amount, currency, recurring[interval], recurring[interval_count], recurring[trial_period_days].
/v1/pricesList prices; filter by product, active or lookup_key.
/v1/prices/{id}Retrieve a price.
/v1/prices/{id}Update a price's nickname, lookup key, metadata or active flag.
Subscriptions & items
Recurring billing. The scheduler invoices due subscriptions hourly, charges the saved card, retries failures and advances the period.
/v1/subscriptionsCreate a subscription — customer, items[0][price], items[0][quantity], trial_period_days, coupon, default_payment_method.
/v1/subscriptionsList subscriptions; filter by customer, price or status.
/v1/subscriptions/{id}Retrieve a subscription with its items and latest invoice.
/v1/subscriptions/{id}Update items, quantity, coupon, cancel_at_period_end, pause_collection or default_payment_method.
/v1/subscriptions/{id}/cancelCancel now, or at period end with cancel_at_period_end=true.
/v1/subscriptions/{id}Cancel a subscription (same as the cancel action).
/v1/subscription_itemsAdd an item — subscription, price, quantity.
/v1/subscription_itemsList the items of a subscription (subscription is required).
/v1/subscription_items/{id}Retrieve a subscription item.
/v1/subscription_items/{id}Change an item's price, quantity or metadata.
/v1/subscription_items/{id}Remove an item from a subscription.
Invoices & line items
Invoices are created by the scheduler for subscriptions, or by you for one-off billing. Drafts are editable; finalizing opens them for collection.
/v1/invoicesCreate a draft invoice — customer, currency, description, due_date.
/v1/invoicesList invoices; filter by customer, subscription or status.
/v1/invoices/{id}Retrieve an invoice with its lines and totals.
/v1/invoices/{id}Update a draft invoice's description, due date or metadata.
/v1/invoices/{id}/linesList the line items on an invoice.
/v1/invoices/{id}/finalizeFinalize a draft: recompute totals, assign a number, move it to open.
/v1/invoices/{id}/payCharge the customer's card for an open invoice.
/v1/invoices/{id}/voidVoid a draft or open invoice.
/v1/invoices/{id}/mark_uncollectibleWrite an invoice off as uncollectible.
/v1/invoices/{id}Delete a draft invoice and its line items.
/v1/invoices/upcomingPreview the next invoice for a subscription or customer. Computed, never stored.
/v1/invoiceitemsAdd a one-off charge — customer, amount or price, quantity, description. Opens a draft invoice when none is given.
/v1/invoiceitemsList invoice items; filter by invoice or customer.
/v1/invoiceitems/{id}Retrieve an invoice item.
/v1/invoiceitems/{id}Change an item on a draft invoice.
/v1/invoiceitems/{id}Remove an item from a draft invoice.
Coupons & promotion codes
Percent or fixed discounts, applied to a subscription. A promotion code is the customer-facing code for a coupon.
/v1/couponsCreate a coupon — percent_off or amount_off, duration, duration_in_months, max_redemptions, redeem_by.
/v1/couponsList coupons.
/v1/coupons/{id}Retrieve a coupon by ID or code.
/v1/promotion_codesCreate a code for a coupon — coupon, code, max_redemptions, expires_at.
/v1/promotion_codesList promotion codes; filter by code or active.
/v1/promotion_codes/{id}Retrieve a promotion code.
/v1/promotion_codes/{id}Activate or deactivate a promotion code.
Payments, charges & refunds
One-off payments routed through the same gateway engine as the transaction API, with batching and settlement applied.
/v1/payment_intentsCharge a card or saved payment method — amount, currency, customer, payment_method, confirm.
/v1/payment_intentsList payment intents.
/v1/payment_intents/{id}Retrieve a payment intent.
/v1/payment_intents/{id}/captureCapture a previously authorized payment intent.
/v1/payment_intents/{id}/cancelVoid an uncaptured payment intent.
/v1/chargesList charges.
/v1/charges/{id}Retrieve a charge.
/v1/refundsRefund a charge or payment intent, fully or partially.
/v1/refundsList refunds.
/v1/refunds/{id}Retrieve a refund.
/v1/balanceAvailable and pending balance from settled and captured volume.
Checkout sessions, payment links & events
Hosted payment pages and the append-only event log that feeds webhooks.
/v1/checkout/sessionsCreate a session — mode (payment/subscription/setup), line_items, success_url, cancel_url.
/v1/checkout/sessionsList checkout sessions.
/v1/checkout/sessions/{id}Retrieve a session and its status.
/v1/payment_linksCreate a reusable payment link for a price.
/v1/payment_linksList hosted checkout links. Their url opens the branded Truvora checkout.
/v1/eventsList events, newest first — the same objects delivered by webhooks.
/v1/events/{id}Retrieve an event.
Billing events
The hourly scheduler and every write emit these events. They appear on /v1/events and are delivered to your webhook endpoints.
| Field | Type | Description |
|---|---|---|
| customer.subscription.created | event | A subscription was created, in trial or active. |
| customer.subscription.updated | event | Items, quantity, coupon, pause or cancellation intent changed. |
| customer.subscription.deleted | event | A subscription ended or was canceled. |
| customer.subscription.trial_will_end | event | A trial is ending soon. |
| customer.subscription.past_due | event | Collection failed and retries are underway. |
| customer.subscription.unpaid | event | Retries were exhausted. |
| invoice.created | event | An invoice was generated for a billing period. |
| invoice.finalized | event | A draft invoice was finalized and is open for collection. |
| invoice.payment_succeeded | event | An invoice was paid in full. |
| invoice.payment_failed | event | A collection attempt was declined. |
Create a transaction
/gateway/v1/transactionsRuns a purchase, authorization, account verification or unreferenced credit.
Request body
| Field | Type | Description |
|---|---|---|
| type | "purchase" | "authorize" | "verify" | "credit" | Defaults to "purchase". "authorize" holds funds for a later capture, "verify" performs a zero-amount account check, "credit" pushes an unreferenced credit to the card. |
| amount* | string | number | Major-unit amount, e.g. "24.99". Not required for "verify". |
| amountMinor | integer | Minor units (cents). Takes precedence over amount when both are sent. |
| currency | string(3) | ISO 4217 code. Defaults to the merchant's configured currency. |
| card.number | string | Raw PAN. Send this or card.token. |
| card.expMonth | integer 1-12 | Expiry month, required with card.number. |
| card.expYear | integer | Expiry year, 2 or 4 digits. |
| card.cvv | string(3-4) | Card verification value. Recommended for card-not-present. |
| card.cardholderName | string | Name as printed on the card. |
| card.token | string | A stored instrument token returned by an earlier storeCard request. |
| billing | object | firstName, lastName, address, city, state, postal, country — used for AVS. |
| description | string(255) | Free-form description stored with the transaction. |
| orderReference | string(64) | Your order identifier. |
| invoiceNumber | string(64) | Your invoice identifier. |
| customerEmail | string | Customer email for receipts and reporting. |
| storeCard | boolean | When true and the transaction is approved, the card is tokenized into the vault for reuse. |
Example
curl -X POST https://api.truvora.com/gateway/v1/transactions \
-u "$API_LOGIN_ID:$TRANSACTION_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: order-1001" \
-d '{
"type": "purchase",
"amount": "24.99",
"currency": "USD",
"orderReference": "order-1001",
"card": {
"number": "4111111111111111",
"expMonth": 12,
"expYear": 2030,
"cvv": "123"
},
"billing": { "postal": "94107", "country": "US" }
}'HTTP/1.1 201 Created
{
"ok": true,
"transaction": {
"id": "8f2c1e34-0b1f-4a90-b3ff-1f2e6c9a77d1",
"type": "purchase",
"status": "captured",
"approved": true,
"amountMinor": 2499,
"currency": "USD",
"authCode": "5JD5FT",
"processor": "fiserv",
"cardBrand": "visa",
"lastFour": "1111",
"avsResult": "Y",
"cvvResult": "M",
"declineReasonCode": null,
"declineReason": null,
"batchId": "1d0a8e21-6f4c-4c65-9a08-2b70a5f0c9ab",
"createdAt": "2026-09-03T17:22:41.019Z"
}
}HTTP/1.1 402 Payment Required
{
"ok": false,
"transaction": {
"id": "c7b2...",
"status": "declined",
"approved": false,
"declineReasonCode": "insufficient_funds",
"declineReason": "Insufficient funds"
}
}Transaction object
| Field | Type | Description |
|---|---|---|
| id | uuid | Truvora transaction ID. Use it for capture, void and refund. |
| type | string | The requested transaction type. |
| status | string | pending, authorized, captured, batched, settled, verified, declined, voided, refunded, partially_refunded or error. |
| approved | boolean | True for authorized, captured, batched, settled and verified statuses. |
| amountMinor | integer | Processed amount in minor units. |
| currency | string(3) | Currency of the transaction. |
| authCode | string | null | Acquirer authorization code. |
| processor | string | null | Processor that handled the authorization. |
| processorTransactionId | string | null | Processor-side reference. |
| cardBrand | string | null | visa, mastercard, amex, discover, and so on. |
| lastFour | string | null | Last four digits of the card. |
| avsResult | string | null | Address verification result code. |
| cvvResult | string | null | CVV match result code. |
| declineReasonCode | string | null | Normalized decline code, e.g. insufficient_funds. |
| declineReason | string | null | Human-readable decline explanation. |
| batchId | uuid | null | Settlement batch the capture landed in. |
| createdAt | timestamp | ISO 8601 creation time. |
Status codes
201 approved · 402 declined (the transaction object explains why) · 400 malformed JSON · 401 authentication problem · 422 validation or gateway rule failure · 500 unexpected gateway error.
Follow-up actions
Act on an existing transaction by ID. Omit the amount to act on the full amount, or send a smaller one for a partial capture or refund.
/gateway/v1/transactions/{id}/captureCaptures a previously authorized transaction and adds it to the open settlement batch.
/gateway/v1/transactions/{id}/voidCancels an authorization or an unsettled capture before funding.
/gateway/v1/transactions/{id}/refundReturns funds on a captured, batched or settled transaction, fully or partially.
| Field | Type | Description |
|---|---|---|
| amount | string | number | Optional major-unit amount for a partial action. |
| amountMinor | integer | Optional minor-unit amount. Takes precedence over amount. |
curl -X POST \
https://api.truvora.com/gateway/v1/transactions/$TRANSACTION_ID/capture \
-u "$API_LOGIN_ID:$TRANSACTION_KEY" \
-H "Content-Type: application/json" \
-d '{ "amount": "18.00" }'Webhooks
Register endpoints in the portal under Developers. Each endpoint subscribes to specific event types or to * for all of them, and receives its own signing secret, shown once.
| Field | Type | Description |
|---|---|---|
| transaction.authorized | event | An authorization was approved and is awaiting capture. |
| transaction.captured | event | Funds were captured and queued into a batch. |
| transaction.verified | event | A zero-amount verification succeeded. |
| transaction.declined | event | A transaction was declined by risk rules or the processor. |
| transaction.voided | event | An authorization or unsettled capture was voided. |
| transaction.refunded | event | A full or partial refund was issued. |
| batch.submitted | event | A settlement batch was closed and submitted to the processor. |
| settlement.funded | event | Funding was reconciled with gross, fee and net amounts. |
Deliveries are POSTed as JSON with the event type and an HMAC-SHA256 signature over {timestamp}.{rawBody}. Failed attempts are retried with exponential backoff, so respond 2xx quickly and process asynchronously. Deduplicate on the transaction ID and event type.
POST /your-endpoint HTTP/1.1
X-Gateway-Event: transaction.captured
X-Gateway-Signature: t=1772822561,v1=9c1f...e07
{
"eventType": "transaction.captured",
"transaction": { "id": "8f2c1e34-...", "status": "captured", "amountMinor": 2499 }
}import crypto from "node:crypto";
export function verifyTruvoraSignature(rawBody, header, signingSecret) {
const parts = Object.fromEntries(
header.split(",").map((piece) => piece.split("=")),
);
const expected = crypto
.createHmac("sha256", signingSecret)
.update(`${parts.t}.${rawBody}`)
.digest("hex");
return crypto.timingSafeEqual(
Buffer.from(expected),
Buffer.from(parts.v1),
);
}Coming from Stripe
The billing API mirrors Stripe's request and response shapes, so server code written against Stripe usually works after changing two things: the base URL and the secret key.
| Area | How it behaves |
|---|---|
| Base URL | Swap api.stripe.com for api.truvora.com. Paths and object shapes are unchanged. |
| Keys | Use a Truvora secret key (sk_test_… in sandbox, sk_live_… once live processing is approved) as a bearer token. |
| Bodies | Form-encoded with bracket nesting (items[0][price]=…) or JSON — both are accepted. |
| IDs | The same prefixes: cus_, prod_, price_, sub_, si_, in_, ii_, pi_, ch_, re_, pm_, cs_, seti_, evt_. |
| Lists | { object: "list", data, has_more, url } with limit, starting_after and ending_before. |
| Amounts | Minor units (cents), as in Stripe. |
| Errors | { error: { type, message, code, param } } with the same HTTP status codes. |
| Webhooks | Stripe-style event names and a t=…,v1=… HMAC signature header, so existing verification code works. |
| Not supported | Stripe Connect, Tax, Radar, Billing Portal sessions, usage-based metering and Terminal have no equivalent yet. |
import Stripe from "stripe";
const truvora = new Stripe(process.env.TRUVORA_SECRET_KEY, {
host: "api.truvora.com",
protocol: "https",
});
await truvora.subscriptions.create({
customer: "cus_123",
items: [{ price: "price_123" }],
});Errors
Errors use a consistent envelope:
{
"error": {
"code": "validation_failed",
"message": "A positive amount is required."
}
}| Code | HTTP | Meaning |
|---|---|---|
| invalid_json | 400 | The request body was not valid JSON. |
| authentication_required | 401 | No API login ID or transaction key was supplied. |
| authentication_failed | 401 | Credentials are invalid, revoked, or the merchant is not approved. |
| validation_failed | 422 | A field failed validation. The message names the first problem. |
| invalid_amount | 422 | A positive amount is required for this transaction type. |
| invalid_state | 422 | The transaction cannot make that transition, e.g. capturing something already captured. |
| duplicate_transaction | 422 | An identical amount and card was submitted moments ago. |
| unknown_action | 404 | The follow-up action is not capture, void or refund. |
| gateway_error | 500 | Unexpected gateway failure. Safe to retry with the same Idempotency-Key. |
A decline is not an error: it returns 402 with a full transaction object, including declineReasonCode.
Testing
Sandbox credentials route to a deterministic simulator, so the card number decides the outcome. Any future expiry works, and no real network or money movement occurs.
| Card number | Result |
|---|---|
| 4111 1111 1111 1111 | Approved |
| 4000 0000 0000 0002 | Declined — generic_decline |
| 4000 0000 0000 9995 | Declined — insufficient_funds |
| 4000 0000 0000 0069 | Declined — expired_card |
| 4000 0000 0000 0127 | Declined — cvv_mismatch |
| 4000 0000 0000 0010 | Declined — avs_mismatch (AVS returns N) |
| 4000 0000 0000 0119 | Declined — processor_error |
| 4000 0000 0000 0101 | Declined — fraud_suspected |
| 4000 0000 0000 0259 | Declined — pickup_card |
| 4000 0000 0000 0341 | Simulated processor timeout |
You can also run one-off sales without writing code from the virtual terminal in the merchant portal.
End-to-end subscription walkthrough
Run these six calls in order with a sandbox secret key to go from nothing to a trialing subscription with a saved card, a preview of the next invoice, and a scheduled cancellation.
# 1. A customer curl -X POST https://api.truvora.com/v1/customers \ -u "$TRUVORA_SECRET_KEY:" \ -d email=ada@example.com -d name="Ada Lovelace" # 2. A product and a monthly price curl -X POST https://api.truvora.com/v1/products \ -u "$TRUVORA_SECRET_KEY:" -d name="Pro plan" curl -X POST https://api.truvora.com/v1/prices \ -u "$TRUVORA_SECRET_KEY:" \ -d product=prod_123 -d unit_amount=2900 -d currency=usd \ -d "recurring[interval]=month" # 3. Save a card for the customer curl -X POST https://api.truvora.com/v1/setup_intents \ -u "$TRUVORA_SECRET_KEY:" \ -d customer=cus_123 \ -d "payment_method_data[card][number]=4111111111111111" \ -d "payment_method_data[card][exp_month]=12" \ -d "payment_method_data[card][exp_year]=2030" \ -d "payment_method_data[card][cvc]=123" # 4. Subscribe, with a 14-day trial curl -X POST https://api.truvora.com/v1/subscriptions \ -u "$TRUVORA_SECRET_KEY:" \ -d customer=cus_123 -d "items[0][price]=price_123" \ -d trial_period_days=14 # 5. Preview the next invoice curl -G https://api.truvora.com/v1/invoices/upcoming \ -u "$TRUVORA_SECRET_KEY:" -d subscription=sub_123 # 6. Cancel at the end of the period curl -X POST https://api.truvora.com/v1/subscriptions/sub_123/cancel \ -u "$TRUVORA_SECRET_KEY:" -d cancel_at_period_end=true

