Bbizbasics/ developers

Runtime contract

Everything your container must do to run on the platform. This page is the difference between a one-click launch and a day of debugging — each rule below maps to a real mistake that has cost a team hours. Follow it exactly.

1. Listen on 0.0.0.0:$PORT

The platform injects PORT and probes your container on it. Bind 0.0.0.0 (all interfaces) on $PORT — never 127.0.0.1/localhost and never a hardcoded port. Serve in every mode (don't skip the server in a "stub" path).

If you bind localhost or the wrong port, the health probes get connection-refused and your pod CrashLoops — even though your process is "running."

2. Health & readiness

EndpointReturnsMeaning
GET /health200 as soon as the process is upLiveness. Don't gate this on the DB — if it 503s while warming, the pod is killed.
GET /ready200 only when DB + deps are reachable; 503 while warmingReadiness. 503 during startup is correct, not a failure — traffic is held until you're ready.

3. Image names (exact)

Push to these repositories — hyphenated, never nested with a slash:

registry.sage7.ai/<slug>-api:latest        # always — your backend / SSO + API
registry.sage7.ai/<slug>-frontend:latest   # only if you run a separate web tier
Pushing <slug>/api (a nested path) instead of <slug>-api is the #1 cause of ImagePullBackOff: the platform pulls <slug>-api and never finds your image.

4. Topology — one image or two

The platform supports both. Pick one and tell the platform team:

TopologyWhat you shipRouting
Single (default)one <slug>-api image that serves the SSO contract, /api, and your UIall of <slug>.bizbasics.ai → your image
Two-tier<slug>-api (contract + /api) and <slug>-frontend (your UI on port 3000)/api, /auth/*, /health, /ready, /bootstrap → api; everything else → frontend

Serve all backend paths under /api — in both topologies

It's a single host split by path, never a separate API origin (this is the Vercel/Netlify-style split). The only bare paths are the four SSO-contract endpoints — /auth/sso, /health, /ready, /bootstrap. Everything else your backend serves must live under /api/…:

  • your app's business API — /api/envelopes, not /envelopes
  • any public / customer-facing API — /api/v1/…, not /v1/…
  • your inbound webhook receiver — register it at /api/webhooks/…, not /webhooks/…
In two-tier, / routes to your frontend. A business endpoint at a bare path (/envelopes) therefore hits the frontend and 404s — not your API. In single-image mode the API receives every path, so bare paths happen to work there — which hides the bug until you switch topology. Keep business under /apiin both and the same image is topology-agnostic: you can flip single ↔ two-tier without re-routing anything.

API-only product (no UI — like a signing API your customers call)? Ship a single image; there's no frontend to serve. Keep your endpoints under /api anyway — it costs nothing and keeps the door open to a UI later.

5. Detect platform mode from the injected env

Run in platform mode when the platform variables are present — key off BIZBASICS_JWKS_URL. Do not require your own custom flag (e.g. MYAPP_MODE=platform): the platform doesn't set it, so you'll silently fall into standalone mode and never enable SSO.

6. Environment the platform injects

Pre-filled into your <slug>-secrets:

DATABASE_URL, REDIS_URL, MINIO_ACCESS_KEY, MINIO_SECRET_KEY, MINIO_BUCKET   # scoped to you
PORT                 = 8088
BIZBASICS_API_URL    = https://api.bizbasics.ai
BIZBASICS_AUTH_URL   = https://auth.bizbasics.ai
BIZBASICS_JWKS_URL   = https://auth.bizbasics.ai/.well-known/jwks.json
BIZBASICS_PRODUCT_ID = <slug>
APP_URL              = https://<slug>.bizbasics.ai    # YOUR public base URL
BIZBASICS_APP_URL    = https://<slug>.bizbasics.ai

You add three of your own (see Onboarding): your bbas_ as BIZBASICS_INTERNAL_KEY, your bbk_ as BIZBASICS_API_KEY, and an APP_SESSION_SECRET (≥32 random bytes) to sign your own session.

7. Sessions are httpOnly cookies — never localStorage

After the SSO handoff, set your own session as an httpOnly cookie and redirect into your app. Your frontend must establish auth by calling your API (e.g. GET /api/me or /bootstrap) with credentials: "include" — the browser sends the cookie, your API validates it. Never read or store the session token in localStorage.

The classic SSO loop: your API sets an httpOnly cookie, but your frontend checks localStorage for a token, finds nothing (JS can't read httpOnly cookies), and bounces back to login — forever. Derive auth from the cookie via an API call, not from JS-readable storage. This is also a hard platform security rule.

8. Build redirects & links from APP_URL

Your post-SSO landing, email links, and absolute URLs must come from APP_URL (injected). Never hardcode localhost — a left-over http://localhost:3000 default will redirect real users to their own machine after a successful login.

9. Credentials — three different things

CredentialDirectionUse
bbas_BIZBASICS_INTERNAL_KEYyou → platform (internal)X-Internal-Key on verify-app-token, quota, webhooks. Scoped to your product.
bbk_BIZBASICS_API_KEYyou → platform (data)Authorization: Bearer on the workspace-records / quota API.
Your own API keysyour customers → youKeys you mint for your customers. The platform is not involved — never hand out bbk_/bbas_.
If you regenerate a bbas_/bbk_, the old one stops working immediately — you must update your secret with the exact new value (shown once). A stale bbas_ is the usual cause of an "invalid platform token" at SSO.

10. Surface the real error

When a platform call fails, pass through the upstream status and detail — don't collapse everything into a single generic message. A 403 "Invalid internal credential" tells you the credential is wrong; a 401 "Invalid or expired app token" tells you the launcher token expired. Hiding that turns a one-line fix into a long dig.

Troubleshooting

SymptomCauseFix
Pod ImagePullBackOffWrong image repo name (nested slug/api)Push to <slug>-api:latest (hyphen). §3
Pod CrashLoopBackOff, probes "connection refused"Not listening on 0.0.0.0:$PORTBind 0.0.0.0 on $PORT, serve /health+/ready. §1–2
/ready stays 503Can't reach DB/deps, or readiness never flipsUse the injected DATABASE_URL/REDIS_URL; flip /ready to 200 once connected. §2
SSO succeeds then redirects to localhostHardcoded localhost redirectBuild the redirect from APP_URL. §8
SSO succeeds then loops back to the platform loginFrontend reads token from localStorage, not the httpOnly cookieValidate the session via an API call with credentials. §7
invalid or missing platform tokenStale/mismatched bbas_, or launcher token expiredRe-issue the credential + update your secret; surface the upstream error. §9–10
Business API 404s in two-tier (but works in single)Endpoints at bare paths route to the frontend, not your APIMove business endpoints under /api/…; keep only the SSO contract bare. §4
/ returns API JSON, not your UISingle-image but the image only serves the APIServe your UI from the same image, or switch to two-tier. §4
App card is greyed / "Upgrade to unlock"Your org isn't entitled to the productExternal apps need an entitlement (the platform team grants it). Not a code issue.
© bizbasics — developer platform All systems operational