DEV Community

Alexandre Gertsch
Alexandre Gertsch

Posted on

Building a pay-per-use API platform on shared PHP hosting: no Node, no Docker, no queue

I'm a solo developer in Geneva and I just built Supercalcula API, a small platform of utility APIs sold by usage: QR codes and barcodes, email verification, CSV/JSON/XML/YAML conversion, SEO analysis, PII redaction, and currency/unit conversion.

The interesting constraint: it runs on ordinary shared hosting (Infomaniak). No Docker, no Redis, no queue, no Node on the server. Here is what that taught me.

The stack

  • PHP 8.4, Slim 4, Twig, PHP-DI, PDO + MariaDB
  • Stripe Checkout, Customer Portal and webhooks
  • Tailwind compiled locally and committed, so the server never needs a build step
  • PHPUnit with a real MariaDB test database, PHPStan level 6
  • Three languages (FR/DE/EN) with translated URLs

1. Quota in credits, reserved then settled

Each endpoint costs a number of credits. The gate reserves the cost before the handler runs and settles it afterwards:

  • a successful call costs the endpoint's credits
  • a client error (4xx) costs 1 credit, so validation can't be abused for free
  • a server error (5xx), a bad key (401) or a rate limit (429) costs nothing
  • monthly quota is consumed first, then purchased credit packs, oldest first

Rate limiting is a sliding window stored in the database in 10-second buckets. It's not Redis-fast, but it's boring and it works on shared hosting.

2. No cron? Make the cron call you

The host's scheduler can only call a URL, once per hour at best. So every background job (daily cleanup, Stripe reconciliation, exchange rates, status checks, encrypted backups) is a protected URL secured by a long random token, and the scheduler just hits it. Locally the same tasks run as CLI scripts.

3. Stripe webhooks must be idempotent

Every event is stored by its id before being processed, so a retry never double-credits a customer. I also added a reconcile job that compares Stripe with my database, because webhooks do get missed (I had one subscription event missing from my own endpoint configuration; the reconcile job caught it).

4. Privacy by construction

The request log stores the endpoint, status, duration, credits and an IP hash with a daily salt. It never stores the data you send. I verified it: I sent 50 calls containing a marker string, then searched the whole production database and every file for it. Zero hits.

One honest caveat: for GET endpoints the parameters live in the URL, and the hosting provider's access logs may keep full URLs. I documented that in the privacy policy rather than pretending otherwise.

5. Deploying without Node, Docker or SFTP

My host's SFTP subsystem didn't work, so the deploy script uses FTPS. It runs the tests and static analysis first and refuses to deploy if either fails. Then it syncs files, switches on a maintenance flag, runs the database migrations behind a secret token and removes the flag. A rollback script restores the previous package.

A small trap that cost me an hour: asset versioning (app.css?v=hash) must hash files from the real document root in production, otherwise browsers keep serving the old CSS for a year.

Where I'd like your help

The platform is in private beta, in Stripe test mode: nothing is charged. Free accounts get 200 calls a month. I would love feedback from people who actually use APIs:

  • Is the documentation clear? There's a "try it with your key" panel on every endpoint.
  • Do the examples work as written?
  • Anything confusing in sign-up, the dashboard or the billing flow?

👉 https://supercalcula.ch/en/ (also in French and German)

You can test the subscription flow with Stripe's test card 4242 4242 4242 4242. Please don't send real or sensitive data. Comments here or contact@supercalcula.ch are both welcome.Thanks

Disclosure: I built this with the help of AI coding tools (Claude Code), reviewing and testing the result myself.

Top comments (2)

Collapse
 
alexcodebytes profile image
Oleksandr •

Building a pay-per-use, credits-metered infrastructure on shared PHP hosting without reaching for Redis, Docker, or Node is an absolute masterclass in cutting through modern web dev bloat. Implementing database-backed sliding window rate-limiting in 10-second buckets and leveraging webhook idempotency shows that solid software design beats over-engineered infra stacks every single time.
We’ve been analyzing resource-constrained, high-efficiency API architectures for our own tooling lately. Seeing someone ship a production-grade utility suite using pure PHP 8.4 and boring, reliable MariaDB state management is incredibly refreshing. Exceptional engineering grit.

Collapse
 
alexandre_gertsch_f02622a profile image
Alexandre Gertsch •

Thanks, that means a lot! The 10-second buckets were the least glamorous part, and probably the most reliable. Since you're working on similar tooling, I'd really value your eyes on it: would you try the "try it" panel in the docs and tell me which part felt unclear or slow? supercalcula.ch/en/ (free account, no payment, test mode)