Skip to content
In this section

Sandbox & testing

Test hosts, keys, test cards and time travel through a subscription's life.

Everything here works without touching live money. The goal: your first successful call within minutes, and a full trial-to-renewal subscription exercised without waiting a month of wall-clock time.

Hosts

Environment Host
Production https://api.fynex.ai. Whichever host serves you, the OpenAPI document's servers block lists that deployment's own origin first.

The environment that matters is your account's, not the host's: a Fynex account operates in demo mode or live mode, and test behaviour follows the account. A demo account is a sandbox account on any host.

The OpenAPI document's servers block declares the origin of the deployment serving it. Each entry is the bare origin; the /billing-api/v1 prefix is already part of every path in the document, so join a server with a path exactly as written and do not add the prefix twice.

Keys

Issue a secret key for your account in the Fynex dashboard: Integration in the left-hand menu, then the API keys card. While your account is in demo mode the key is sk_test_…; after go-live only sk_live_… keys authenticate and the test key answers 401 from then on. A key is shown once, at creation — store it server-side and never in a browser.

The same key authenticates every surface in these docs: the Billing API and usage ingestion under /billing-api/v1, and the Payments API.

Test cards

On a demo account, hosted payment pages (including the links POST /invoices/{invoiceId}/send creates) accept these Visa sandbox numbers:

Card number Notes
4111 1111 1111 1111 Universal Visa test card
4530 9100 0001 2345 Visa
4037 1122 3300 0001 Visa

For all of them: any future expiry, any 3-digit CVV, Latin cardholder name. Paying an invoice's link with one of these drives the document through sent → paid exactly as a live card would, so it is the way to test the settlement-polling loop end to end.

Time travel

Subscription flows — trial ending, first charge, renewal — run on billing dates, and a sandbox that makes you wait 30 real days for a renewal is not a sandbox. Each contract can be given a test clock of its own and moved forward, with a sk_test_ key and no help from us:

Call What it does
GET /contracts/{contractId}/test-clock Reads what the billing engines treat as now for that one contract.
POST /contracts/{contractId}/test-clock/advance Moves it forward to to, then runs that contract's subscription lifecycle, recurring invoicing and usage period close as of it.

Test-mode (demo) accounts only. A live account answers 403 on both: a live subscription's renewal is real revenue and a real document to a real customer.

One contract at a time. The clock belongs to the contract in the path. Advancing it never moves a sibling contract — yours or another account's — and no scheduled pass sees a changed clock. That is why this is safe to publish.

Forward only, capped at 366 days. to must be after the contract's current clock. Re-sending the instant the clock already sits at is a no-op: 200 with advanced: false, nothing renewed and nothing issued twice, so the call is safe to retry. A target in the past is 400 — the invoices an advance mints are numbered and immutable, so a clock cannot be wound back.

Worked example: a monthly subscription renewing in one call

Create the subscription today (42 is a contract that already carries at least one invoice — see the note below):

curl -s -X POST "$FYNEX_API_BASE/contracts/42/subscriptions" \
  -H "Authorization: Bearer $FYNEX_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: $(uuidgen)" \
  -d '{"frequency":"monthly","priceMinor":4999,"currency":"EUR","startDate":"2026-01-01"}'

Ask where the contract's clock is:

curl -s "$FYNEX_API_BASE/contracts/42/test-clock" \
  -H "Authorization: Bearer $FYNEX_API_KEY"
# {"contractId":42,"now":"2026-01-01T09:15:00Z","simulated":false}

Advance it past the end of the first term — 31 days:

curl -s -X POST "$FYNEX_API_BASE/contracts/42/test-clock/advance" \
  -H "Authorization: Bearer $FYNEX_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"to":"2026-02-01T00:00:00Z"}'
{
  "clock": { "contractId": 42, "now": "2026-02-01T00:00:00Z", "simulated": true },
  "advanced": true,
  "lifecycle": { "renewed": 1, "trialsActivated": 0, "cronDisabled": false },
  "invoices": { "issued": 1, "sent": 1, "skipped": 0, "failed": 0, "passDisabled": false }
}

The renewal is now visible on the two reads that matter — the subscription's new term, and the document it earned:

curl -s "$FYNEX_API_BASE/subscriptions?contractId=42" \
  -H "Authorization: Bearer $FYNEX_API_KEY"
# currentPeriodStart moves to 2026-02-01, currentPeriodEnd to 2026-03-01

curl -s "$FYNEX_API_BASE/invoices?contractId=42" \
  -H "Authorization: Bearer $FYNEX_API_KEY"
# one more invoice, origin "recurring", for the new term

GET /contracts/{contractId}/invoices/upcoming before the advance, and the issued document after it, should agree — which is the cheapest end-to-end check that your contract is configured the way you think it is.

What it does not advance

The clock moves the subscription state machine, the recurring invoice and the close of the contract's ended usage periods (reported in the usage block: each period that ended by to becomes a pending usage line). It does not:

  • close per-seat periods or mint usage invoices,
  • accept usage events dated after real time,
  • send dunning e-mail for a subscription that went past_due,
  • run off-session auto-charge on an issued invoice,
  • move the issue date: an invoice raised by an advance is dated at REAL time (so its number stays in the real month's gapless sequence) while its period is the simulated term — read the period, not the date, to check the renewal.

Those stay on real time in their own scheduled passes, and every advance repeats the list in its notAdvanced field. Do not write a test that waits on one of them after an advance.

Usage periods closed ahead of real time

An advance closes the contract's usage periods that have ended by to — at most those; a period ending after to waits for a later advance (or for real time). Because the close happens at the simulated date, it can close a period whose real events have not all arrived yet. A real event you send afterwards for that period is still accepted, but it cannot join the closed period's usage line: it is recorded as a late event flagged requires_correction and goes to the correction backlog, where reopening the period is an explicit backoffice act. Send a period's events before you advance past its end.

A period whose close failed (usage.failed), or a close that failed as a whole (usage.error — the lifecycle and invoicing still ran and the answer is still 200), is retried by an advance to a later instant. Re-sending the same instant is a no-op and retries nothing.

When an advance renews but issues nothing

Three operator switches decide whether the parts above run at all, and the response says which one stopped:

Response field Switch Symptom
lifecycle.cronDisabled billing.subscription.lifecycle_cron Nothing renews, no matter how far the clock moves.
invoices.passDisabled billing.recurring.invoice_pass The term renews, and no document appears on GET /invoices.
usage.passDisabled billing.usage.close_pass An ended usage period stays open; usage.periodsClosed stays 0.

All three default to off — see the table below. There is also one data precondition the clock cannot supply: the recurring lane continues a contract's existing invoicing pattern, so a contract with no prior invoice is reported in invoices.skipped rather than issued. Raise the first document once with POST /invoices (or let a contract you already invoiced carry the subscription), and every later advance issues by itself.

Recurring invoicing is also switched on per account and currency. A demo account has it from the start; on a live account Fynex enables it. A contract whose account is not enabled is reported in invoices.skipped as well.

Metered usage is priced per contract: register the metric once (POST /usage/metrics) and price it on the contract (PUT /usage/contracts/{contractId}/metrics/{metricName}/price) before you send its events. The MCP endpoint offers both as create_usage_metric and set_usage_price.

The staff-operated clock is a different thing and is not this. It shifts the lifecycle dates of test-mode payment-link subscriptions (the recurring hosted-checkout product), estate-wide, and never touches the billing subscriptions this reference describes.

What a fresh sandbox has switched off

Several billing capabilities sit behind operator feature flags that default to off, because each one moves money or issues documents on a schedule. A sandbox where they are off looks broken — subscriptions never charge, usage periods never close into invoices, no invoice is ever raised, credits report 501 — when it is merely unconfigured. If a flow below does nothing, ask Fynex to confirm the flag before debugging your integration:

Flag What it gates
billing.subscription.lifecycle_cron The pass that ends trials, charges renewals and applies scheduled plan changes.
billing.usage.close_pass The pass that closes usage periods and mints usage invoice lines.
billing.recurring.invoice_pass The pass that turns due subscription terms into issued invoices.
billing.usage.invoice_pass The pass that composes closed usage lines into issued invoices. Separate from the recurring one on purpose — a seller may be ready to auto-bill terms and not metered usage, or the reverse.
billing.proration.invoice_binding Whether a subscription's unbilled mid-term proration is put on the invoice its term is billed on — a charge as an extra line, a credit as a discount on the recurring line, any remainder as a non-expiring proration credit lot. While off, a mid-term change is still recorded against the contract but never reaches a document.
billing.invoice.auto_charge Off-session collection: the loop that charges a customer's saved card for a sent or overdue invoice. Without it a document is only ever paid by someone visiting its link or making a transfer.
payment_links.recurring_billing Recurring charging through hosted payment links.
billing.credits Stored-value credits; while off, the credit endpoints answer 501 and invoices draw no credit down.
billing.promotions.subsidy_payout Credits each seller the platform-funded part of a promotional order (a platform coupon's discount, platform credit the buyer spent) to their main wallet, the way the order's cash is credited. While off, the subsidy is only recorded as owed on the order's funding record.
billing.promotions.checkout Buyer-facing coupon activity at checkout: quoting and applying a code or held coupon, the saved-coupon picker, and a payment link's preset code. While off, those requests answer 409 with reason: "promotions_disabled", a link is charged its plain price, and claiming into the wallet still works; holds made before the flag was switched off are still honoured.
billing.promotions.subscriptions Attaching new coupons to subscriptions: at a subscription link's checkout, from a link's preset code, through the seller API and by Fynex staff. While off, those requests answer 409 with reason: "promotions_disabled"; coupons attached before it was switched off keep discounting their renewals on the terms they were attached with.
billing.credits.wallet Promotional credit in the consumer coupon wallet; while off, the wallet answers 200 with state: "credits_disabled" instead of a balance, credit coupons cannot be claimed, and checkout refuses a credit opt-in. Coupon discounts work either way.
billing.usage.alert_webhook Delivery of UsageAlertFired when a contract crosses a usage-limit threshold.

Closing a period is not the same as billing it. The close pass and the invoice passes are different switches: with only billing.usage.close_pass on, GET /contracts/{contractId}/usage moves and periods close, and no invoice ever appears on GET /invoices. That pairing is the single most common reason a sandbox looks like it has stopped halfway.

A sandbox session that proves the loop

The target this page is written against is the one in our own onboarding PRD: a first transaction under 30 minutes, self-serve — from a fresh sandbox key to an invoice on GET /invoices without talking to anyone at Fynex. The steps below are that path; if one of them cannot be done inside that budget, the gap is ours, not yours.

  1. GET /contracts — key, host and network path all work.
  2. Register a metric and price it (see Usage ingestion), send a few events, and watch GET /contracts/{contractId}/usage move.
  3. Create a subscription in the dashboard with a trial that has already ended, and let the lifecycle pass run — the renewal invoice appears on GET /invoices.
  4. POST /invoices/{invoiceId}/send, pay the link with a test card, and poll the invoice to paid.

That is the whole billing loop — metering, rating, issuance, collection, settlement — without a real card or a real month.