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.

base url
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 amount in major units or amountMinor in cents. Responses always report minor units.
  • Idempotency. Send an Idempotency-Key header 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/v1 and authenticates with a Bearer secret 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.

http basic (preferred)
Authorization: Basic base64(apiLoginId:transactionKey)
header alternative
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.

base url
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.

bearer token
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.

cursor pagination
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.

POST/v1/customers

Create a customer — email, name, phone, description, currency, metadata.

GET/v1/customers

List customers, newest first, with limit and starting_after / ending_before cursors.

GET/v1/customers/{id}

Retrieve a customer, including its default payment method.

POST/v1/customers/{id}

Update a customer, including default_payment_method.

DELETE/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.

POST/v1/payment_methods

Tokenize a card (type=card, card[number], card[exp_month], card[exp_year], card[cvc]).

GET/v1/payment_methods

List a customer's saved cards.

GET/v1/payment_methods/{id}

Retrieve a saved card — brand, last4, expiry only.

POST/v1/payment_methods/{id}/attach

Attach a saved card to a customer.

POST/v1/payment_methods/{id}/detach

Detach a saved card from its customer.

POST/v1/setup_intents

Save a card for future off-session charges; returns status succeeded with the payment method.

GET/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.

POST/v1/products

Create a product — name, description, images, unit_label, statement_descriptor.

GET/v1/products

List products; filter with active=true.

GET/v1/products/{id}

Retrieve a product.

POST/v1/products/{id}

Update or archive a product (active=false).

POST/v1/prices

Create a price — product, unit_amount, currency, recurring[interval], recurring[interval_count], recurring[trial_period_days].

GET/v1/prices

List prices; filter by product, active or lookup_key.

GET/v1/prices/{id}

Retrieve a price.

POST/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.

POST/v1/subscriptions

Create a subscription — customer, items[0][price], items[0][quantity], trial_period_days, coupon, default_payment_method.

GET/v1/subscriptions

List subscriptions; filter by customer, price or status.

GET/v1/subscriptions/{id}

Retrieve a subscription with its items and latest invoice.

POST/v1/subscriptions/{id}

Update items, quantity, coupon, cancel_at_period_end, pause_collection or default_payment_method.

POST/v1/subscriptions/{id}/cancel

Cancel now, or at period end with cancel_at_period_end=true.

DELETE/v1/subscriptions/{id}

Cancel a subscription (same as the cancel action).

POST/v1/subscription_items

Add an item — subscription, price, quantity.

GET/v1/subscription_items

List the items of a subscription (subscription is required).

GET/v1/subscription_items/{id}

Retrieve a subscription item.

POST/v1/subscription_items/{id}

Change an item's price, quantity or metadata.

DELETE/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.

POST/v1/invoices

Create a draft invoice — customer, currency, description, due_date.

GET/v1/invoices

List invoices; filter by customer, subscription or status.

GET/v1/invoices/{id}

Retrieve an invoice with its lines and totals.

POST/v1/invoices/{id}

Update a draft invoice's description, due date or metadata.

GET/v1/invoices/{id}/lines

List the line items on an invoice.

POST/v1/invoices/{id}/finalize

Finalize a draft: recompute totals, assign a number, move it to open.

POST/v1/invoices/{id}/pay

Charge the customer's card for an open invoice.

POST/v1/invoices/{id}/void

Void a draft or open invoice.

POST/v1/invoices/{id}/mark_uncollectible

Write an invoice off as uncollectible.

DELETE/v1/invoices/{id}

Delete a draft invoice and its line items.

GET/v1/invoices/upcoming

Preview the next invoice for a subscription or customer. Computed, never stored.

POST/v1/invoiceitems

Add a one-off charge — customer, amount or price, quantity, description. Opens a draft invoice when none is given.

GET/v1/invoiceitems

List invoice items; filter by invoice or customer.

GET/v1/invoiceitems/{id}

Retrieve an invoice item.

POST/v1/invoiceitems/{id}

Change an item on a draft invoice.

DELETE/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.

POST/v1/coupons

Create a coupon — percent_off or amount_off, duration, duration_in_months, max_redemptions, redeem_by.

GET/v1/coupons

List coupons.

GET/v1/coupons/{id}

Retrieve a coupon by ID or code.

POST/v1/promotion_codes

Create a code for a coupon — coupon, code, max_redemptions, expires_at.

GET/v1/promotion_codes

List promotion codes; filter by code or active.

GET/v1/promotion_codes/{id}

Retrieve a promotion code.

POST/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.

POST/v1/payment_intents

Charge a card or saved payment method — amount, currency, customer, payment_method, confirm.

GET/v1/payment_intents

List payment intents.

GET/v1/payment_intents/{id}

Retrieve a payment intent.

POST/v1/payment_intents/{id}/capture

Capture a previously authorized payment intent.

POST/v1/payment_intents/{id}/cancel

Void an uncaptured payment intent.

GET/v1/charges

List charges.

GET/v1/charges/{id}

Retrieve a charge.

POST/v1/refunds

Refund a charge or payment intent, fully or partially.

GET/v1/refunds

List refunds.

GET/v1/refunds/{id}

Retrieve a refund.

GET/v1/balance

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

POST/v1/checkout/sessions

Create a session — mode (payment/subscription/setup), line_items, success_url, cancel_url.

GET/v1/checkout/sessions

List checkout sessions.

GET/v1/checkout/sessions/{id}

Retrieve a session and its status.

POST/v1/payment_links

Create a reusable payment link for a price.

GET/v1/payment_links

List hosted checkout links. Their url opens the branded Truvora checkout.

GET/v1/events

List events, newest first — the same objects delivered by webhooks.

GET/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.

FieldTypeDescription
customer.subscription.createdeventA subscription was created, in trial or active.
customer.subscription.updatedeventItems, quantity, coupon, pause or cancellation intent changed.
customer.subscription.deletedeventA subscription ended or was canceled.
customer.subscription.trial_will_endeventA trial is ending soon.
customer.subscription.past_dueeventCollection failed and retries are underway.
customer.subscription.unpaideventRetries were exhausted.
invoice.createdeventAn invoice was generated for a billing period.
invoice.finalizedeventA draft invoice was finalized and is open for collection.
invoice.payment_succeededeventAn invoice was paid in full.
invoice.payment_failedeventA collection attempt was declined.

Create a transaction

POST/gateway/v1/transactions

Runs a purchase, authorization, account verification or unreferenced credit.

Request body

FieldTypeDescription
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 | numberMajor-unit amount, e.g. "24.99". Not required for "verify".
amountMinorintegerMinor units (cents). Takes precedence over amount when both are sent.
currencystring(3)ISO 4217 code. Defaults to the merchant's configured currency.
card.numberstringRaw PAN. Send this or card.token.
card.expMonthinteger 1-12Expiry month, required with card.number.
card.expYearintegerExpiry year, 2 or 4 digits.
card.cvvstring(3-4)Card verification value. Recommended for card-not-present.
card.cardholderNamestringName as printed on the card.
card.tokenstringA stored instrument token returned by an earlier storeCard request.
billingobjectfirstName, lastName, address, city, state, postal, country — used for AVS.
descriptionstring(255)Free-form description stored with the transaction.
orderReferencestring(64)Your order identifier.
invoiceNumberstring(64)Your invoice identifier.
customerEmailstringCustomer email for receipts and reporting.
storeCardbooleanWhen true and the transaction is approved, the card is tokenized into the vault for reuse.

Example

request
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" }
  }'
approved response
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"
  }
}
declined response
HTTP/1.1 402 Payment Required

{
  "ok": false,
  "transaction": {
    "id": "c7b2...",
    "status": "declined",
    "approved": false,
    "declineReasonCode": "insufficient_funds",
    "declineReason": "Insufficient funds"
  }
}

Transaction object

FieldTypeDescription
iduuidTruvora transaction ID. Use it for capture, void and refund.
typestringThe requested transaction type.
statusstringpending, authorized, captured, batched, settled, verified, declined, voided, refunded, partially_refunded or error.
approvedbooleanTrue for authorized, captured, batched, settled and verified statuses.
amountMinorintegerProcessed amount in minor units.
currencystring(3)Currency of the transaction.
authCodestring | nullAcquirer authorization code.
processorstring | nullProcessor that handled the authorization.
processorTransactionIdstring | nullProcessor-side reference.
cardBrandstring | nullvisa, mastercard, amex, discover, and so on.
lastFourstring | nullLast four digits of the card.
avsResultstring | nullAddress verification result code.
cvvResultstring | nullCVV match result code.
declineReasonCodestring | nullNormalized decline code, e.g. insufficient_funds.
declineReasonstring | nullHuman-readable decline explanation.
batchIduuid | nullSettlement batch the capture landed in.
createdAttimestampISO 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.

POST/gateway/v1/transactions/{id}/capture

Captures a previously authorized transaction and adds it to the open settlement batch.

POST/gateway/v1/transactions/{id}/void

Cancels an authorization or an unsettled capture before funding.

POST/gateway/v1/transactions/{id}/refund

Returns funds on a captured, batched or settled transaction, fully or partially.

FieldTypeDescription
amountstring | numberOptional major-unit amount for a partial action.
amountMinorintegerOptional minor-unit amount. Takes precedence over amount.
partial capture
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.

FieldTypeDescription
transaction.authorizedeventAn authorization was approved and is awaiting capture.
transaction.capturedeventFunds were captured and queued into a batch.
transaction.verifiedeventA zero-amount verification succeeded.
transaction.declinedeventA transaction was declined by risk rules or the processor.
transaction.voidedeventAn authorization or unsettled capture was voided.
transaction.refundedeventA full or partial refund was issued.
batch.submittedeventA settlement batch was closed and submitted to the processor.
settlement.fundedeventFunding 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.

delivery
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 }
}
signature verification (node)
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.

AreaHow it behaves
Base URLSwap api.stripe.com for api.truvora.com. Paths and object shapes are unchanged.
KeysUse a Truvora secret key (sk_test_… in sandbox, sk_live_… once live processing is approved) as a bearer token.
BodiesForm-encoded with bracket nesting (items[0][price]=…) or JSON — both are accepted.
IDsThe 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.
AmountsMinor units (cents), as in Stripe.
Errors{ error: { type, message, code, param } } with the same HTTP status codes.
WebhooksStripe-style event names and a t=…,v1=… HMAC signature header, so existing verification code works.
Not supportedStripe Connect, Tax, Radar, Billing Portal sessions, usage-based metering and Terminal have no equivalent yet.
node — same sdk pattern, different host
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 envelope
{
  "error": {
    "code": "validation_failed",
    "message": "A positive amount is required."
  }
}
CodeHTTPMeaning
invalid_json400The request body was not valid JSON.
authentication_required401No API login ID or transaction key was supplied.
authentication_failed401Credentials are invalid, revoked, or the merchant is not approved.
validation_failed422A field failed validation. The message names the first problem.
invalid_amount422A positive amount is required for this transaction type.
invalid_state422The transaction cannot make that transition, e.g. capturing something already captured.
duplicate_transaction422An identical amount and card was submitted moments ago.
unknown_action404The follow-up action is not capture, void or refund.
gateway_error500Unexpected 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 numberResult
4111 1111 1111 1111Approved
4000 0000 0000 0002Declined — generic_decline
4000 0000 0000 9995Declined — insufficient_funds
4000 0000 0000 0069Declined — expired_card
4000 0000 0000 0127Declined — cvv_mismatch
4000 0000 0000 0010Declined — avs_mismatch (AVS returns N)
4000 0000 0000 0119Declined — processor_error
4000 0000 0000 0101Declined — fraud_suspected
4000 0000 0000 0259Declined — pickup_card
4000 0000 0000 0341Simulated 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.

sandbox walkthrough
# 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