Fake Later API
Base URL https://fakelater.tringify.store/v1 · API version 2026-10-01 · Demo company: no real money moves.
Overview
Fake Later is a demo buy-now-pay-later company with a REST API at https://fakelater.tringify.store/v1. Nobody is lent money and nothing is charged. Every account has two environments, test and live, each with its own API keys, webhook endpoints and data. The key decides the mode: sk_test_… or sk_live_….
- Your server creates a session for the order and redirects the shopper to its redirect_url.
- The shopper picks a product, enters their email and a one-time code, and is approved or declined. Approval creates an authorization for the full amount (webhook session.authorized); the shopper returns to your success_url.
- When you ship, capture the shipped amount (with line items and tracking). Each capture starts the shopper's repayment plan and is settled to you, net of fees, in a payout.
- Void what you won't ship. Uncaptured authorizations expire after 28 days. Refund against captures.
- Test — test shopper emails drive outcomes, the code is always 123456, chaos switches and test settings (dashboard) change timings and inject faults, payouts settle every 5 minutes.
- Live — no chaos, realistic timings: the decision takes ~3 s, refunds settle in ~15 s, authorizations last 28 days, instalments follow the calendar, payouts settle daily and arrive two days later. Every shopper is approved; the code is shown on the page (no message is sent).
Requests take JSON (Content-Type: application/json) or form bodies (metadata[order_id]=42, line_items[0][name]=Shirt); bodies are capped at 64 KB. Responses are JSON. Amounts are integers in the currency's minor unit.
Authentication
Send a secret key as a bearer token (basic auth with the key as the username also works). Publishable keys (pk_…) cannot call the API.
curl https://fakelater.tringify.store/v1/account -H "Authorization: Bearer sk_test_..."
Sign up at /signup; your first test and live secret keys are shown once. Create or revoke keys in the dashboard under API keys.
Products, limits & fees
| Product | Schedule | APR |
|---|---|---|
| pay_in_4 | 4 interest-free payments every 2 weeks (first at capture) | 0.00% |
| pay_in_30 | Pay the full amount in 30 days, interest-free | 0.00% |
| monthly_3 | 3 monthly payments | 0.00% |
| monthly_6 | 6 monthly payments | 9.99% |
| monthly_12 | 12 monthly payments | 19.99% |
Order limits per currency (a session must fit at least one product; pass product to offer only one):
| Currency | pay_in_4 | pay_in_30 | monthly_3 | monthly_6 | monthly_12 | Merchant fee per capture |
|---|---|---|---|---|---|---|
| aud | 100–200000 | 100–100000 | 20000–1000000 | 20000–1000000 | 20000–1000000 | 4.00% + A$0.30 |
| usd | 100–200000 | 100–100000 | 20000–1000000 | 20000–1000000 | 20000–1000000 | 3.90% + $0.30 |
| aed | 400–750000 | 400–350000 | 70000–3500000 | 70000–3500000 | 70000–3500000 | 4.50% + AED 1.00 |
| inr | 10000–15000000 | 10000–7500000 | 1500000–75000000 | 1500000–75000000 | 1500000–75000000 | 3.00% + ₹3.00 |
Monthly plans amortise at APR/12 per month; the interest is the shopper's cost and never changes your payout. Fees are not returned on refunds.
Errors
Errors use HTTP status codes and a JSON body:
{ "error": { "type": "invalid_request_error", "code": "amount_exceeds_authorization", "message": "amount 9000 is more than the remaining authorized amount 5000.", "param": "amount" } }
| 400 | invalid_request_error — bad or missing parameter, wrong state (authorization_expired, authorization_closed, amount_exceeds_refundable, …) |
| 401 | authentication_error |
| 404 | resource_missing |
| 409 | idempotency_error — a request with the same key is still running |
| 413 | request_too_large (64 KB) |
| 429 | rate_limit_error — back off and retry |
| 5xx | api_error — safe to retry with the same Idempotency-Key |
Idempotency
Every POST accepts an Idempotency-Key header (up to 255 characters). The first response (2xx or 4xx) is stored for 24 hours and replayed for retries with the same key and body, marked Idempotent-Replayed: true. Reusing a key with a different body returns 400 idempotency_key_reused. 5xx responses are not stored, so retrying them runs the request again. Always send one on captures and refunds.
Lists & pagination
List endpoints return {"object":"list","data":[…],"has_more":true}, newest first. Use limit (1–100, default 10) and starting_after=<last id>.
Sessions
curl https://fakelater.tringify.store/v1/sessions \
-H "Authorization: Bearer sk_test_..." -H "Idempotency-Key: order-1001" \
-H "Content-Type: application/json" -d '{
"amount": 25000, "currency": "aud",
"success_url": "https://shop.example/thanks?session={SESSION_ID}",
"cancel_url": "https://shop.example/cart",
"merchant_reference": "order-1001",
"line_items": [{"name": "Rain jacket", "quantity": 1, "unit_amount": 25000}],
"shopper_email": "approve@example.com"
}'
| amount, currency | required (amount may be omitted when line_items are given; if both, they must agree) |
| success_url, cancel_url | required; https (http allowed in test mode). {SESSION_ID} is replaced. |
| product | optional; one of pay_in_4, pay_in_30, monthly_3, monthly_6, monthly_12. Default: the shopper chooses from every product the amount fits. |
| line_items | optional, up to 50 × {name, quantity, unit_amount, reference} |
| merchant_reference, description, metadata, shopper_email | optional; shopper_email pre-fills the hosted page |
| expires_in | seconds the hosted page stays open: 300–86400 (test: from 60). Default 3600. |
Status: open → processing / pending_review → authorized | declined; or expired / canceled. The session carries available_products (each with instalment amounts, APR and total), shopper.limit, decline_code and, once approved, authorization.
GET /v1/sessions/{id} · GET /v1/sessions?status= · POST /v1/sessions/{id}/cancel (open sessions).
Test shoppers
In test mode the email's local part (before any +tag, any domain) decides the outcome. The one-time code is always 123456; five wrong codes decline the session (verification_failed).
| approve@example.com | Approved for the full amount. |
| decline@example.com | Declined (session.declined, decline_code shopper_declined). |
| limit@example.com | Approved up to a lower spending limit, returned as shopper.limit; orders above it are declined with over_shopper_limit. |
| review@example.com | Pending review (session.pending_review), approved after the review delay (test setting, default 30 s). |
| missed@example.com | Approved; the shopper later misses their next instalment (plan.instalment_missed). Merchant payouts are unaffected. |
| anyone else | Approved. |
The limit@ shopper's limit is A$100.00 / $100.00 / AED 350.00 / ₹8,000.00. Test emails have no special meaning in live mode.
Refunds
Refund against a capture, partially and more than once, up to what was captured. Refunds are pending, then succeeded (test ~2 s, live ~15 s) with refund.succeeded. A refund lowers the shopper's remaining instalments, latest first.
curl https://fakelater.tringify.store/v1/refunds -H "Authorization: Bearer sk_test_..." -H "Idempotency-Key: refund-1001-1" \ -d capture=cap_... -d amount=5000 -d reason=returned_goods
reason: returned_goods, order_canceled, duplicate, requested_by_customer, other. Optional line_items, reference, metadata. GET /v1/refunds/{id} · GET /v1/refunds?capture= · GET /v1/refunds?authorization=
Repayment plans
Every capture creates the shopper's plan: instalments with due dates, amounts (principal + interest), status (scheduled, paid, missed, paid_late, refunded) and progress. Plan status: active, in_arrears, completed, refunded. shopper_url is the shopper's own view.
A missed instalment puts the plan in arrears with a late fee (A$10.00 / $8.00 / AED 30.00 / ₹500.00) charged to the shopper and is collected again later. Missed payments never affect the merchant: you were settled at capture. Events: plan.instalment_paid, plan.instalment_missed, plan.completed.
Test mode: POST /v1/plans/{id}/simulate with outcome=paid (collect the next instalment now) or outcome=missed; or set "Accelerated repayments" so instalments fall due every N minutes; the missed@ shopper misses their first scheduled instalment.
GET /v1/plans/{id} · GET /v1/plans?authorization= · GET /v1/plans?status=
Payouts & statements
Each capture adds to your balance (amount − fee); each succeeded refund subtracts its amount. One payout per currency per settlement period: test every 5 minutes (paid at once), live per UTC day (created at 00:30 UTC, in_transit, paid two days later). Events payout.created, payout.paid.
statement = {gross_captured, refunds, fees, net, capture_count, refund_count}. The statement lines: GET /v1/payouts/{id}/transactions (balance transactions: type capture | refund, amount, fee, net, source, authorization).
GET /v1/payouts/{id} · GET /v1/payouts?currency=
Webhooks
Add endpoints in the dashboard or with POST /v1/webhook_endpoints (url, enabled_events, description); the signing secret is returned once. Up to 16 endpoints per mode. Live endpoints must use https.
Events: session.authorized, session.declined, session.expired, session.pending_review, capture.succeeded, authorization.voided, authorization.expired, refund.succeeded, refund.failed, plan.instalment_paid, plan.instalment_missed, plan.completed, payout.created, payout.paid.
Each delivery is a POST of the event JSON with FakeLater-Signature: t=<unix>,v1=<hex>, where v1 = HMAC-SHA256(secret, t + "." + raw body), plus FakeLater-Event-Id and FakeLater-Delivery-Id. Verify the signature, reject timestamps more than 5 minutes old, and de-duplicate on the event id.
// Node
const [t, v1] = header.split(",").map((p) => p.split("=")[1]);
const want = crypto.createHmac("sha256", secret).update(t + "." + rawBody).digest("hex");
const ok = crypto.timingSafeEqual(Buffer.from(want), Buffer.from(v1)) && Math.abs(Date.now() / 1000 - Number(t)) < 300;
Any 2xx is success. Failures retry after 10 s, 1 min, 5 min, 30 min, 2 h, 6 h, 12 h, then the delivery is parked. At most 3 deliveries run against one endpoint at a time; after 20 consecutive failures the endpoint is disabled (re-enable it to reset). Resend any event from the dashboard or with POST /v1/events/{id}/resend (optional endpoint). Events, deliveries and attempts are kept for 30 days.
GET /v1/events?type=refund.* · GET /v1/events?object_id= · GET/POST/DELETE /v1/webhook_endpoints/{id} (POST updates url, enabled_events, description, disabled).
On-site messaging
Show instalment pricing on product and cart pages. No key, no cookies, cacheable, CORS-enabled:
<script src="https://fakelater.tringify.store/widget.js" async></script> <span data-fakelater-amount="10000" data-fakelater-currency="aud"></span>
Renders "or 4 interest-free payments of A$25.00 with Fake Later" and a "Learn more" modal with every eligible plan for that amount. Update the attribute to re-render, or call FakeLater.messaging.refresh() after adding elements. Server-side: GET /v1/messaging?amount=10000¤cy=aud (no key) returns the headline and every product quote.
Test settings & chaos
Test mode only, per account, in the dashboard: delay approval, decline everything, capture API 503 for the next N calls, duplicate webhooks, authorizations expiring early, refunds reported failed then succeeded, slow API; plus the authorization lifetime, the review delay and accelerated repayments. Affected objects carry fakelater_chaos.
Rate limits & retention
The API allows about 100 requests per 10 seconds per IP; hosted pages and sign-in are limited separately. Idempotency keys last 24 hours; events, deliveries and attempts 30 days; declined, expired and canceled sessions 30 days after they close.