Peppered key hashes, shown once
Keys are ss_ plus 48 hex chars. Only an HMAC-SHA256 hash and a 12-char prefix touch the database; the raw token is shown once at creation and never again. A leaked DB hands an attacker nothing usable.
~/simple-stack $ ./launch
# Bearer-token keys, rate limits, and a per-call credit cost, managed from a left-sidebar member app — billing, auth and a dedicated admin app already wired, on plain PHP and SQLite. $ composer install && php scripts/install.php ✓ database created · migrations applied $ php -S 127.0.0.1:8000 -t public router.php ✓ live — start charging
Bearer-key auth, per-call credits and rate limits that just work. Watch a request go out and a response come back — no SDK required.
Users mint API keys, hit your endpoints with a token, and you meter every call. A working notes JSON API ships as the example you clone into your own routes.
Keys are ss_ plus 48 hex chars. Only an HMAC-SHA256 hash and a 12-char prefix touch the database; the raw token is shown once at creation and never again. A leaked DB hands an attacker nothing usable.
Send Authorization: Bearer ss_... or an X-API-Key header. require_api_user() resolves the key to its owner and plan; a bad token gets a clean 401. The same handler serves browser sessions and API keys, so you write each endpoint once.
A fixed-window limiter in SQLite caps each key at 300 requests per 60s out of the box (one env var to change). Over the line returns 429 with a Retry-After header and a retry_after field in the body.
Plans grant credits per billing period (25 free, up to 2,000 on Scale) and each action spends from the same ledger. Set a different cost per endpoint in app/subscriptions.php.
Users create, name, list and revoke their own keys from the member sidebar app, up to ten active each. last_used_at is stamped on every call, and a superadmin can revoke any key from the separate admin app at /admin.
A versioned REST API mounted at /v1: GET /v1/me and GET /v1/usage return the key's account, plan and live credit balance, and GET/POST/PATCH/DELETE /v1/notes is a complete, scoped-per-user CRUD resource. Data comes back as { "data": ... } with real status codes (200/201/204/401/404/422/429) and paginated lists. Delete the notes logic, keep the shape, and drop in your own resource.
Send a Bearer key to a notes endpoint and the same short path runs every time — resolve the key, count the window, charge a credit, return your JSON.
# POST a note as one of your API users. # The ss_ key is shown once at creation. curl -X POST https://api.yoursite.com/api.php \ -H "Authorization: Bearer ss_a1f3...8e2d" \ -d action=create_note \ -d title="Shipped v1" -d status=open # 200 OK · X-RateLimit: 41/300 in this 60s window { "ok": true, "cost": 1, "balance_after": 1974, "note": { "id": 5012, "status": "open" } }
Every figure here is how the metering actually works, not a sales chart.
You're in one now. Hop straight to any other site type — same tab, still a demo: no card needed, no payment is real, and nothing you do is saved.
Each site-type demo is one of the seven ready-to-run profiles in the download — copy the folder, add your keys, ship.
Keep the infrastructure small. Make the product yours.
| web_build_step | none |
| source_code | included |
| hosting | your own server |
| database_mode | SQLite WAL |
Every install ships with this. Search users, replay webhooks, inspect billing and revoke keys — server-rendered, access-gated, no second app.
| User | Plan | Status | Joined |
|---|---|---|---|
| ada@example.com | Pro | active | 2d ago |
| grace@example.com | Scale | active | 5d ago |
| linus@example.com | Starter | past due | 3w ago |
| margaret@example.com | Pro | active | 1mo ago |
| blocked@example.com | Free | blocked | 1mo ago |
invoice.paidevt_1Q8x…a3customer.subscription.updatedevt_1Q8w…f1checkout.session.completedevt_1Q8w…7cinvoice.payment_failedevt_1Q8v…02charge.refundedevt_1Q8u…9dcurl -H "Authorization: Bearer ss_live_••••" \ https://app.yoursaas.com/api.php?resource=notes { "ok": true, "data": [ "note_18f2", "note_18f9" ], "rate_limit": "58/60", "credits_left": 1840 }
copy a template folder
composer install · Stripe + SES
rebrand · plans · run doctor
NGINX + PHP-FPM · take cards
Try the local app before configuring billing or email. Provider approval and deployment take additional time.
Define plans in app/subscriptions.php, attach Stripe price IDs, and the checkout flow stays generic across every project.
Default access for new users and trial accounts.
For a small paid SaaS tier.
For heavier usage and premium features.
For power users and high-credit products.
With bearer API keys they generate from their dashboard. Keys are hashed and peppered at rest, can be revoked or rotated, and are capped per user.
Per-key rate limits are built in, and each call can cost credits — so you bill by usage and protect your endpoints from one config.
Yes — plans set access levels and credit grants, so higher tiers get higher limits and more included calls.
The starter includes prepared queries, signed Stripe webhooks, CSRF checks, rate limiting, session hardening, setup diagnostics and regression tests. Configure the enabled providers, verify backups and test your own product before launch.
You pay your hosting and service providers directly. Budget for a PHP server, domain, transactional email and payment processing. Image generation/storage and native app store accounts add costs if you enable them. Check provider pricing for your region and expected usage; the template itself is a one-time purchase.
This is a live, fully interactive demo — explore every screen. Nothing you do is saved and no payments are real.