orgstate / README.md
Legal-i's picture
Initial OrgState deploy via Stage 150 free-tier stack
d2d1903 verified
---
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.