core

Authentication

Bearer JWT (dashboard) vs API key (server-to-server) — which one to use, and when

SlipBolt has two kinds of credential — each for its own role

Bearer JWT (Dashboard / LIFF)

Users log in at app.slip-bolt.com and receive a JWT access token (15-minute TTL) + a refresh cookie (sb_rt · 7 days · rotated on every use)

HTTP
Authorization: Bearer eyJhbGciOiJIUzI1NiIs...
  • Refreshed automatically when the access token expires → the useApi composable handles all of it
  • Suited to browsers / mobile apps

API Key (Server-to-server)

Create one at Settings → API Keys — format sk_live_<32 chars>

Requires the Starter plan or higher — the free Trial cannot issue API keys, and a key issued while on a paid plan is rejected with 403 the moment the plan expires or the shop is suspended (checked on every request, not only when the key is issued)

HTTP
Authorization: Bearer sk_live_a1b2c3d4...
  • Never expires until revoked
  • At most 5 non-revoked keys per shop — revoke one you no longer use before issuing a new one
  • Keys carry limited scopes (default: verify · add read/webhook at Settings → API Keys) — see API Reference
  • Store it in a server environment variable only — never put it in frontend code

Entitlements per plan (quota · seats · LINE · API)

A shop's first 7-day trial gets the Trial row only — not the plan picked during onboarding

PlanSlips/monthSeatsLINE groupsLINE OAREST API
Trial50111❌
Starter500113✅
Pro1,5005310✅
Business5,000Unlimited10Unlimited✅

The slip quota is one pool shared with slips that arrive through LINE — whichever channel is used draws from the same pool. Read the balance from GET /api/v1/quota · GET /api/v1/me or the X-Slipbolt-Quota-* headers (see API Reference)

Slip credits (pay as you go)

For shops that bought slip credits — once the plan's quota for the month runs out, credits are used first, and only then is overage charged · credits apply to both LINE slips and POST /api/v1/verify (only verified slips count, same as the quota) · 402 QUOTA_EXCEEDED only comes back when both quota and credits are used up

  • Credit balance: creditsAvailable in GET /api/v1/quota and credits.available in GET /api/v1/me
  • Total slips you can still verify ≈ remaining + creditsAvailable
  • A shop with only credits (no monthly plan) gets the Trial row — no REST API, keys get 403

sk_test_ — test lane

Keys starting with sk_test_ do not consume the shop's monthly slip quota — build your integration without spending what you paid for · responses carry "environment": "test"

The test lane is capped at 200 calls/shop/month — because every call still verifies against the real bank (it is not a mock). Past the cap you get 402 with code: "TEST_QUOTA_EXCEEDED" (the same status as when the shop's quota runs out — the fix is to move to an sk_live_ key) · every request counts, not only successful ones, because the cost is incurred when the call is made

Rate limit (same on every plan)

120 req/min per API key — counted in fixed 1-minute windows and not tied to the plan. POST /api/v1/verify has an additional combined ceiling of 240 req/min

Over the limit → 429 Too Many Requests + header Retry-After: <seconds>

Every response also carries X-RateLimit-Limit / -Remaining / -Reset — but those count per IP for that endpoint, not what is left on the key (details: API Reference)

Need a higher ceiling? Contact the team — it is adjusted server-side (API_KEY_RATE_LIMIT_PER_MIN), not by upgrading your plan

Per-key caps and allowed IPs

Set them under Settings → API Keys → the shield icon of each key · not set = everything works as before · every change is recorded in the Audit log

Charge caps — stop a leaked key or a runaway loop from burning the shop's quota/credits (240/min is still hundreds of thousands of calls a day)

  • Set a max per hour and/or per day (a day = 00:00–24:00 Thailand time) · applies to sk_live_ keys only
  • Only requests that are actually charged (quotaCharged: true) count — failed slips · DUPLICATE · cached don't
  • At the cap → 429 code: "SPEND_CAP_EXCEEDED" + Retry-After until the next window + spendCap: { window, limit } — unlike the rate limit, don't retry in a loop: wait for the next window or have the shop owner raise the cap
  • Counted by reserving before verifying ⇒ no amount of concurrency overshoots the cap (in-flight requests hold a slot, so close to the cap a call may be refused slightly early)
  • The shop owner gets an email/LINE at 80% and 100% (once per window) · read the caps and current usage from GET /api/v1/me → apiKey.spendCap

Allowed IPs — a leaked key can't be used from another machine

  • Enter IPv4 · IPv6 · CIDR (e.g. 203.0.113.10 · 198.51.100.0/24 · 2001:db8::/32), up to 20 · empty = any IP
  • Any other IP → 403 code: "IP_NOT_ALLOWED" (distinct from the scope 403, which has no code)
  • We use the IP Cloudflare vouches for — sending your own X-Forwarded-For has no effect · the settings show the last IP that used the key so you can add it
  • It applies to the MCP tools too (the IP of the machine running the AI) · don't set it if your server has no static IP (serverless, etc.)

Error responses

Every error envelope has the same shape:

JSON
{
  "success": false,
  "error": "Unauthorized",
  "message": "Invalid or revoked API key",
  "statusCode": 401,
  "path": "/api/v1/verify",
  "requestId": "3b1f0c9e-6a2d-4f5e-8b7c-1d2e3f4a5b6c",
  "timestamp": "2026-05-25T05:30:45.120Z"
}

A 401 carries one of two messages — they tell you which part went wrong:

messageCause
Missing API key — send "Authorization: Bearer sk_live_…"No Authorization header, or it doesn't start with Bearer
Invalid or revoked API keyThe key is mistyped, truncated, or has been revoked (effective immediately — no cache window)

Authorization: Bearer … is the only way to send the key — the X-API-Key header and Basic auth are not accepted (you get the first 401 message)

The code field (e.g. QUOTA_EXCEEDED) is present only on some errors — the full error table is in the API Reference