| --- |
| title: OrgState API |
| emoji: π |
| colorFrom: blue |
| colorTo: indigo |
| sdk: docker |
| app_port: 8080 |
| suggested_hardware: cpu-basic |
| pinned: false |
| --- |
| |
| # OrgState API on Hugging Face Spaces |
|
|
| This Space runs the OrgState drift-detection API. Two deployment modes: |
|
|
| * **Free production (recommended)** β point the Space at an external |
| Postgres (Neon free tier or Supabase free tier). The DB persists |
| through every Space restart. Use this for any deployment that has |
| customers, even pilots. |
| * **Demo-only** β leave the default `ORGSTATE_DB_PATH=/data/orgstate. |
| sqlite3`. Data is wiped on every Space restart since HF's free CPU |
| tier has no persistent storage. Use only for "click around the UI" |
| demos with no real users. |
|
|
| Both modes use the same Dockerfile + Space configuration. The choice is |
| one env var. |
|
|
| ## Endpoints |
|
|
| - `GET /health` β liveness probe |
| - `GET /health/ready` β readiness probe |
| - `GET /metrics` β Prometheus |
| - `GET /docs` β Swagger UI with paste-ready examples |
| - `POST /tenants/{tid}/...` β see Swagger UI |
|
|
| ## Free production setup (HF + Neon) |
|
|
| 1. **Neon Postgres** β sign in to [neon.tech](https://neon.tech), create |
| a new project ("orgstate"). Copy the connection string from the |
| project dashboard. It looks like: |
| ``` |
| postgresql://neondb_owner:xxx@ep-yyy-pooler.region.aws.neon.tech/neondb?sslmode=require |
| ``` |
| Neon's free tier (0.5GB storage shared across projects, no card) is |
| plenty for the first 10k tenant rows + 1M audit rows. Storage is |
| shared across all projects in the account β if you're tight, switch |
| to Supabase ([supabase.com](https://supabase.com)) which has a |
| separate quota (500MB per account). |
|
|
| 2. **HF Space variables** (Settings β Variables and secrets): |
| * `ORGSTATE_DB_PATH` = the Neon connection string above |
| * `ORGSTATE_ADMIN_KEY` = `openssl rand -hex 16` output |
| * `ORGSTATE_CORS_ORIGINS` = your dashboard origin |
| (e.g. `https://orgstate.1bigfam.com`) |
| * `ORGSTATE_HSTS_ENABLED` = `false` initially (HF's TLS terminator |
| is fine, but enable only AFTER you've verified your dashboard |
| loads cleanly β HSTS pins for `max-age=1 year` by default and |
| a misconfig will brick browsers for the duration) |
| |
| 3. **Push the repo to your Space**: |
| ```bash |
| git remote add hf https://huggingface.co/spaces/<username>/orgstate-api |
| git push hf master |
| ``` |
| First build takes ~5 min (pip install heavy line is psycopg's |
| binary wheel + cryptography). Subsequent rebuilds are fast. |
|
|
| 4. **Verify** at `https://<username>-orgstate-api.hf.space/docs`. You |
| should see the Swagger UI listing every `/v1/...` and `/scim/v2/...` |
| endpoint. |
|
|
| 5. **Bootstrap the first tenant + API key**: |
| ```bash |
| # against the live URL, using the admin key you set above |
| curl -X POST https://<...>.hf.space/v1/tenants \ |
| -H "Authorization: Bearer $ORGSTATE_ADMIN_KEY" \ |
| -H "Content-Type: application/json" \ |
| -d '{"tenant_id":"acme","name":"ACME Inc"}' |
| |
| # mint a per-tenant API key (shown ONCE): |
| curl -X POST https://<...>.hf.space/v1/tenants/acme/keys \ |
| -H "Authorization: Bearer $ORGSTATE_ADMIN_KEY" \ |
| -H "Content-Type: application/json" \ |
| -d '{"name":"admin"}' |
| ``` |
| Paste the returned `raw` value into the dashboard's "Sign in" form. |
|
|
| 6. **Drop the bootstrap admin key** from HF env once a DB-backed admin |
| key exists (the platform falls back to DB-backed admins; the env |
| key is only needed for the first POST `/tenants`). |
|
|
| ## Demo-only setup (HF + SQLite ephemeral) |
|
|
| Skip the Neon step. Set only `ORGSTATE_ADMIN_KEY` and (optionally) |
| `ORGSTATE_CORS_ORIGINS`. The DB lives at `/data/orgstate.sqlite3` and |
| gets wiped on every restart. Demo data must be re-seeded; see the |
| `gtm/` directory for sample fixtures. |
|
|
| ## Limits (both modes) |
|
|
| - **HF auto-sleeps after 48h of no traffic.** The first request after |
| sleep takes 30-60s to wake the container. Subsequent requests are |
| normal. The dashboard handles this gracefully (loading state on 502) |
| but cron jobs hitting the API will see occasional failures during |
| cold start. |
| - **No scheduler process.** HF Spaces runs a single container; the |
| background ingestion scheduler isn't started. Manually trigger runs |
| via the dashboard or `POST /tenants/{tid}/observations/run`. |
| - **Rate limits apply.** Defaults are 600 rpm per credential, 60 rpm |
| per IP (see Stage 79). Tighten via `ORGSTATE_RATE_LIMIT_PER_KEY`. |
| - **No HSTS by default** β flip `ORGSTATE_HSTS_ENABLED=true` only |
| AFTER the dashboard works end-to-end on HTTPS. HSTS pins for a year |
| and a misconfig is hard to roll back. |
|
|
| ## When to graduate off HF |
|
|
| HF free is fine through your first 10-20 customers OR a few thousand |
| SCIM users. The signals to graduate: |
|
|
| * Cold-start latency hurts (real customers complain about the 30s |
| wake delay). |
| * You exceed Neon's free 0.5GB quota β bump to Neon's $19/mo tier |
| OR migrate to Supabase pro / self-hosted Postgres. |
| * You need a scheduler process β pick Fly (`deploy/fly.toml`) or |
| Render (`deploy/render.yaml`) which support multi-process apps. |
|
|
| ## Replacing this README |
|
|
| In your Space repo root, copy this file as `README.md` and adjust the |
| frontmatter (title/emoji/colors). The frontmatter drives Spaces' UI; |
| the body is what users see when they visit the Space page. |
|
|