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)
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)
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· addread/webhookat 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
| Plan | Slips/month | Seats | LINE groups | LINE OA | REST API |
|---|---|---|---|---|---|
| Trial | 50 | 1 | 1 | 1 | ❌ |
| Starter | 500 | 1 | 1 | 3 | ✅ |
| Pro | 1,500 | 5 | 3 | 10 | ✅ |
| Business | 5,000 | Unlimited | 10 | Unlimited | ✅ |
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:
creditsAvailableinGET /api/v1/quotaandcredits.availableinGET /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·cacheddon't - At the cap →
429code: "SPEND_CAP_EXCEEDED"+Retry-Afteruntil 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 →
403code: "IP_NOT_ALLOWED"(distinct from the scope403, which has nocode) - We use the IP Cloudflare vouches for — sending your own
X-Forwarded-Forhas 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:
{
"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:
message | Cause |
|---|---|
Missing API key — send "Authorization: Bearer sk_live_…" | No Authorization header, or it doesn't start with Bearer |
Invalid or revoked API key | The 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
