meta

Changelog

SlipBolt API changelog — new features, fixes and breaking changes developers should know before updating

⏱ Updates

New features and changes that affect integrators · newest first

We try hard to avoid breaking changes · if one is needed we announce it in advance and version it

October 2026

More reliable integrations — additive only, everything that worked keeps working

  • New: the Idempotency-Key header on POST /verify — retrying with the same key returns the original answer (Idempotent-Replayed: true), no second verify, no second charge · 409 IDEMPOTENCY_IN_PROGRESS · 422 IDEMPOTENCY_KEY_REUSED · MCP verify_slip accepts idempotencyKey
  • Webhooks: 6 attempts over ~7 h (was 5 over ~30 s) · new headers X-Slipbolt-Event-Id + X-Slipbolt-Delivery-Attempt · an Events table under Settings → Webhooks with Resend · email/LINE alerts to the shop owner when deliveries fail · endpoints auto-disable only after more than 3 days of continuous failure (was 25 failed attempts)
  • New: rotate a webhook secret with the old one still signing for 24 h — the header carries two v1 values meanwhile · the signature samples now accept any v1
  • New: per-key charge caps + per-key IP allowlist — 429 SPEND_CAP_EXCEEDED · 403 IP_NOT_ALLOWED · GET /me now has apiKey.spendCap + apiKey.ipAllowlist
  • New: GET /api/v1/health · GET /api/v1/ready — no key needed, for uptime monitors
  • A wrong method on an existing path (e.g. GET /verify) now answers 405 with an Allow header instead of 404 · the root of api.slip-bolt.com and unknown paths answer in JSON
  • MCP: a JSON-RPC batch (array) now gets 400 with error -32600 instead of an empty 202
  • New: POST /verify/async — answers 202 immediately; the result arrives via the verification.completed webhook or by polling GET /verifications/{id} · identical results to POST /verify · new MCP tool get_verification + verify_slip accepts async: true
  • Rate-limit 429 now carries code: "RATE_LIMITED" · body over 5 MB → 413 JSON PAYLOAD_TOO_LARGE · URL too long → 414 JSON · api.slip-bolt.com/openapi.json serves the spec directly (200) instead of redirecting · MCP {} now gets -32600 (was -32700)
  • Webhooks: the first attempt to far-away endpoints (Europe/US) failed about half the time and waited for a retry — fixed · slip.duplicate always carries data.status: "DUPLICATE" · verification.completed latencyMs matches REST · no more sentry-trace/baggage headers
  • Webhooks: data.transRef is null for results with no readable reference (an internal err:… value used to leak) — matches GET /transactions

Clearer slip results (from an MCP UAT) — additive fields only, existing code is unaffected

  • warnings — warnings on a passing slip (STALE_SLIP), separate from failure reasons · errorCode is unchanged
  • receiverProxyAccount — the PromptPay id shown on the slip image, next to receiverAccount (the linked bank account)
  • quotaCharged (POST /verify only) — whether this request used quota/credits, no need to infer it from quota.used
  • Fixed: a slip sent through the API that happened to pay SlipBolt's own billing account returned SUCCESS but was not recorded (missing from GET /transactions · no quota charge · no receipt) — it is now checked against the shop's accounts as usual
  • Creating an API key with the same name as an active key is now rejected (409) — keys stay distinguishable by name

New: MCP server for AI tools — https://docs.slip-bolt.com/mcp

  • Connect Claude Code · Cursor · Claude Desktop and let the AI search/read these docs (no key needed) · check quota · look up past slips · fetch receipts · verify slips
  • Uses your existing shop key in the Authorization header — exactly the REST API's permissions; a read-only key is recommended unless the AI must verify new slips
  • Copy-paste setup: MCP for AI tools

New: verification receipts — one design across the LINE bot · LIFF · API

  • GET /api/v1/transactions/:id/receipt.png · receipt.pdf — the receipt image/PDF for a passed slip (?maskSender=true masks the sender's name)
  • Results gain a receipt field (code · checkUrl) — additive only, existing code is unaffected
  • The QR on the receipt opens a check page on slip-bolt.com so anyone can confirm SlipBolt really issued it (guards against edited images)

Fuller API docs + a more detailed OpenAPI spec (from an integrator's review)

  • API Reference adds: a complete errorCode / code table · a Stale slip section · a Bank codes table (3-digit code ↔ slug in /me) · the status table now says whether quota is charged and which webhook event fires · example DUPLICATE / RECEIVER_MISMATCH / INVALID / FRAUD results · the X-RateLimit-* headers
  • Clarified: errorCode is not always null on SUCCESS — a genuine slip transferred more than 24 hours ago gets SUCCESS + errorCode: "STALE_SLIP" (behaviour unchanged since September — the docs used to say INVALID)
  • The OpenAPI spec now has components.schemas (VerifyResult · Transaction · Quota · Account · ErrorEnvelope …) and declares response headers and 5xx
  • New: a Postman collection at /api/v1/postman.json — generated from the same OpenAPI spec · https://api.slip-bolt.com/openapi.json at the domain root now redirects to the spec
  • New: an X-Request-Id header on every response — the same value as meta.requestId / requestId in an error
  • Changed: { "qr": … } holding a payment QR (PromptPay — text starting 000201) now gets 400 instead of being sent for verification and coming back as FRAUD · real slip QRs are unaffected
  • Changed: errorMessage on PROVIDER_ERROR is now a fixed neutral message — it used to be internal text never meant to be read · keep branching on status / errorCode

Fixed: resending a slip that had answered DUPLICATE could return SUCCESS

  • A slip already used through LINE / the Dashboard, sent to POST /api/v1/verify twice in a row, used to get verified: true · duplicate: false from the image cache on the second call — it is now DUPLICATE every time (duplicate: true · cached: true)
  • A retry of a request the API itself verified first still returns the original result · see Idempotency

POST /api/v1/verify accepts the slip in 5 ways — it used to take raw bytes only

  • Adds JSON { "url": "https://…" } (we download the image) and { "qr": "<QR text>" } (no image needed)
  • multipart/form-data (a file in the file field) and JSON { "image": "<base64>" } now work · raw bytes may also be sent with Content-Type: image/jpeg / image/png
  • The result, the quota and the 24-hour cache are identical for all of them · existing code that sends application/octet-stream needs no change
  • Examples in every language: Recipes · the OpenAPI spec declares every form

New page: Recipes — copy-paste code for Node.js / Python / PHP / Go / Java / C#

  • A client with a timeout + retries (429 / 5xx / network failure) · taking a slip from a customer and closing the order end to end · sending the image as multipart / base64 / URL · a script that signs a webhook so you can test on localhost
  • API Reference adds a field table for the verify result and a "Retry?" column in the error table
  • New guidance: 4 checks before releasing an order (was 3) — adds "transRef UNIQUE on your side", because resending the same image within 24 hours returns the original result with duplicate: false · API behaviour is unchanged

September 2026

Documentation in English

  • The developer docs are now fully translated — every page on docs.slip-bolt.com/en/docs

Slip credits (pay as you go)

  • Order in which a passed slip is charged: monthly plan quota → credits → per-slip overage · credits are valid for 12 months
  • GET /api/v1/quota adds creditsAvailable · GET /api/v1/me adds a credits block (available · usedThisMonth · nextExpiry) — used is still the count against the plan quota, as before
  • The 402 QUOTA_EXCEEDED message now mentions credits — it only fires when both quota and credits are used up · branch on code, not on the message text

Error bodies now match the docs

  • Every error has success: false · path · requestId · timestamp per the Error envelope (previously some production errors only had message / error / statusCode) · the existing code field is unchanged · 429 carries a Retry-After header

Webhooks: alerts straight into Discord / Telegram — see Webhooks

  • Destinations must be public https:// · redirects aren't followed · fake slips arrive as a separate slip.fraud event

The 7-day trial = Trial-plan entitlements only

  • A shop's first unpaid period is always on the TRIAL plan (50 slips · 1 seat · 1 group · 1 OA · no REST API)
  • Picking the Pro/Business card at signup does not give 7 free days of paid-plan limits
  • GET /api/v1/me and GET /api/v1/quota (scope read) read the plan + remaining quota without using a slip — use them to check at integration boot

System status — endpoints monitors can actually call

  • GET /health · /health/liveness · /health/uptime now answer at these exact paths (previously only /v1/health existed — an uptime monitor pointed at /health as documented got 404)
  • Added GET /health/ready (readiness — the same DB + Redis check as GET /health) for monitors that use the liveness/readiness naming
  • Use /health* only — /v1/health* is not public on api.slip-bolt.com (answers 404) · if your monitor points at /v1/health, change it to /health
  • The status page now reads real values from these endpoints

Anti-fraud — recycled old slips: on by default

  • SLIP_STALE_MODE default changed from off → alert · the slip is still SUCCESS (with errorCode: "STALE_SLIP" on the result), but the shop owner is alerted when the bank time on the slip is older than SLIP_STALE_MAX_AGE_HOURS (default 24 hours)
  • alert mode never rejects a payment · to actually reject, set SLIP_STALE_MODE=reject

August 2026

Public API

  • The plan's apiAccess is now enforced — API keys of shops on a plan without API access are rejected (previously the column was written but never read) · if your integration used to pass on a small plan, check the plan first
  • Responses no longer leak the name of the external slip-verification provider (body · errors · headers)
  • Finer API key management — scopes · usage · rotate/revoke

Webhooks & Realtime

  • The Webhooks page shows the delivery history for every attempt, with status and retry round
  • "Send test" gives a clear message when the job queue isn't ready (previously a bare HTTP 500)
  • The dashboard shows new slips the moment they're verified via SSE — no refresh needed

Billing & quota

  • Overage is charged for real, with a free buffer · a monthly cap · and a hard ceiling that blocks when usage runs many times over
  • Invoices that include overage show where that amount came from on its own line
  • Plan changes show a quote before charging (proration is visible before you confirm)
  • LINE alerts when a card is declined · when renewal fails · and when payment succeeds
  • Choose how to pay an outstanding bill yourself (card / PromptPay)

Team · security

  • Team seats per plan + email invitations
  • 2FA (TOTP) with backup codes · enable at Settings → Security
  • Password policy enforced for real · CSP and a full set of security headers added
  • Audit log viewable from the settings page

LINE & LIFF

  • LIFF can post the verification result back into the group the customer sent from
  • Quick replies on shop-owner commands
  • Slips are no longer swallowed when Redis is full — the customer is asked to resend instead of silence

App

  • Switch shops without reloading + command palette (⌘K)
  • Every list is paginated · exports include the whole set, not just the visible page
  • Thai/English across the whole app and docs chrome
  • Product tour for the dashboard and linking a LINE channel
  • Cookie consent + analytics that wait for consent

July 2026

  • LIFF — manage groups · verification history · receipts · notification settings · link/unlink accounts, all inside the mini-app
  • Shop status shown clearly in the sidebar (trial · active · past due · suspended)
  • Close any modal with Esc · copy buttons report real success

June 2026

Public API — Quota UX

  • Every POST /api/v1/verify response carries X-Slipbolt-Quota-{Limit,Remaining,Used,Reset-Days} headers + a quota field in the body
  • Quota used up (live key) → HTTP 402 QUOTA_EXCEEDED instead of a silent 200 — check the HTTP status directly
  • Added GET /api/v1/quota (scope read) to read the balance without spending a verify
  • Confirmed: API calls use the same quota pool as the shop's LINE slips

Anti-fraud — recycled slip detection

  • Detects genuine but "old/reused" slips from the bank time (transDate) · alert/reject modes configurable · reject mode answers INVALID + errorCode: STALE_SLIP · alert mode answers SUCCESS with the same errorCode — off by default at the time (see September 2026)

LINE

  • Rich Menu set up automatically when a channel is linked (Today's total → LIFF)
  • Marketing broadcast to opted-in shop owners (platform side)

Billing

  • Hardened Stripe webhook handling (retries only money-related events · idempotent)

May 2026

  • Public Verify API — POST /api/v1/verify takes the slip image as raw bytes, authenticated with an API key (sk_live_/sk_test_)
  • Outgoing Webhooks — slip.verified / duplicate / failed events · HMAC-signed · 5 retries
  • API Keys — create/rotate/revoke · scopes (verify/read/webhook) · usage metrics
  • Prepaid terms — pay 1/3/6/9/12 months ahead (card + PromptPay) with tiered discounts
  • Dashboard — totals · reports · transactions · manage LINE channels/groups