---
title: Quickstart
path: quickstart
status: published
---

# Quickstart

Get a local ScaiControl running end-to-end in about 15 minutes. By the end you'll have:

- A running backend on `:8000` with the OpenAPI Swagger UI.
- A running portal on `:5173`.
- A registered ScaiKey app + a super-admin user.
- One service in the catalog, one trial subscription, one finalised invoice.

For production deployment, see the operator runbook (separate from this guide).

## Prerequisites

| Tool | Version |
|---|---|
| Python | 3.12+ |
| Node.js | 20+ |
| MariaDB (or use `aiosqlite` for dev) | 10.11+ |
| Redis | 7+ |
| ScaiKey instance | Reachable URL with operator account |

You also need:

- An S3-compatible bucket (MinIO works locally) — for invoice PDFs and uploads.
- Operator access to a ScaiSend instance (or accept that emails will be logged-only).

## 1. Clone & install backend

```bash
git clone <your-fork> scaicontrol
cd scaicontrol/backend
python3.12 -m venv .venv
source .venv/bin/activate
pip install -e ".[dev]"
```

## 2. Configure environment

Create `.env` at the repo root (one level up from `backend/`):

```bash
DATABASE_URL=mysql+asyncmy://scaicontrol:secret@localhost/scaicontrol
REDIS_URL=redis://localhost:6379/0
PORTAL_URL=http://localhost:5173
API_URL=http://localhost:8000

S3_BUCKET=scaicontrol-dev
S3_REGION=us-east-1
S3_ENDPOINT_URL=http://localhost:9000   # MinIO
S3_ACCESS_KEY_ID=minio
S3_SECRET_ACCESS_KEY=minio12345

OPERATOR_LEGAL_NAME=ScaiLabs Demo Co.
```

`SCAIKEY_*`, `SCAISEND_*`, payment provider keys — leave blank for now; we'll populate them via the CLI in step 4 (or set them inline if you already have credentials).

## 3. Run migrations

```bash
cd backend
alembic upgrade head
```

Should print "Running upgrade … 008_eu_invoicing" (or later) and exit clean.

## 4. Register with ScaiKey

```bash
scaicontrol setup register \
  --scaikey-url https://scaikey.scailabs.ai \
  --url http://localhost:8000 \
  --email you@example.com
```

This submits a registration request. An operator approves it in ScaiKey. Then:

```bash
scaicontrol setup status
```

Repeat until `APPROVED`. The CLI writes `SCAIKEY_CLIENT_ID`, `SCAIKEY_CLIENT_SECRET`, `SCAIKEY_APP_ID`, and `SCAIKEY_ISSUER` to `.env`.

## 5. Sync users + assign super_admin

```bash
scaicontrol sync                # pulls users from ScaiKey
scaicontrol admin grant --email you@example.com --role super_admin
```

You now exist locally with full bypass on `require_permission()` checks.

## 6. Start backend + worker + frontend

In three terminals:

```bash
# Terminal 1 — API server
cd backend
uvicorn scaicontrol.main:app --reload --host 0.0.0.0 --port 8000
```

```bash
# Terminal 2 — background worker (cron jobs, webhook dispatch, dunning)
cd backend
arq scaicontrol.workers.main.WorkerSettings
```

```bash
# Terminal 3 — portal
cd frontend
npm install
npm run dev
```

Visit:

- `http://localhost:5173` — portal (you'll redirect through ScaiKey to log in).
- `http://localhost:8000/docs` — Swagger UI (interactive API explorer).

## 7. Make your first API call

Grab a JWT by logging in through the portal, then open browser DevTools → Application → Local Storage → copy the `access_token`. Or use `curl` with ScaiKey's `/oauth/token` directly.

```bash
TOKEN=eyJhbGc...

curl -H "Authorization: Bearer $TOKEN" \
     http://localhost:8000/api/v1/auth/me
```

Expected: a JSON `CurrentUser` payload with your roles and permissions.

## 8. Add a service to the catalog

Either via the portal (`/admin/registry`) or API:

```bash
curl -X POST http://localhost:8000/api/v1/admin/registry \
     -H "Authorization: Bearer $TOKEN" \
     -H "Content-Type: application/json" \
     -d '{
       "slug": "scaikey",
       "name": "ScaiKey",
       "base_url": "https://scaikey.scailabs.ai",
       "description": "Identity & access management"
     }'
```

Approve it (auto-approved if its slug is in `AUTO_APPROVED_APP_IDS`):

```bash
curl -X POST http://localhost:8000/api/v1/admin/registry/{id}/approve \
     -H "Authorization: Bearer $TOKEN"
```

## 9. Add a plan

```bash
curl -X POST http://localhost:8000/api/v1/admin/plans \
     -H "Authorization: Bearer $TOKEN" \
     -H "Content-Type: application/json" \
     -d '{
       "service_slug": "scaikey",
       "plan_key": "scaikey.starter",
       "name": "Starter",
       "monthly_price_cents": 1900,
       "currency": "EUR",
       "billing_period": "monthly"
     }'
```

## 10. Subscribe (as a tenant) and finalise an invoice

This part assumes you have a tenant in ScaiKey already synced via step 5. From the portal:

1. Switch to the tenant context.
2. Open `/services`, find "ScaiKey", click "Subscribe → Starter".
3. As the operator, open `/admin/billing`, find the auto-generated draft invoice for this month, click **Finalize**.
4. Click **Send** — the invoice PDF is now in S3 and the customer gets an email (or a log entry if ScaiSend isn't configured).

You now have a working end-to-end flow: catalog → subscription → invoice → PDF in S3.

## Next steps

- **Understand the model** — read [Concepts: architecture](./concepts/architecture) and [Multi-tenancy](./concepts/multi-tenancy).
- **Wire up payments** — set `STRIPE_API_KEY` etc. and a webhook endpoint; see the catalog page for your provider in [API reference](./reference/api/).
- **Subscribe to webhooks** — register a subscriber at `/admin/webhook-subscriptions`; consume [events](./reference/events/catalog).
- **Customise invoice branding** — `/admin/billing/templates` → GrapesJS designer; see [Concepts: templates](./concepts/templates).
- **Run the integration test suite** — `cd backend && pytest`.

## Common first-run issues

| Problem | Fix |
|---|---|
| `alembic upgrade head` errors with "Access denied" | Wrong `DATABASE_URL`. For dev without MariaDB: `DATABASE_URL=sqlite+aiosqlite:///./dev.db` |
| `setup status` always pending | Operator hasn't approved in ScaiKey yet, OR `SCAIKEY_ISSUER` is unreachable |
| Portal redirects to ScaiKey then 404s | Check `SCAIKEY_CLIENT_ID` is set in `.env` AND in the ScaiKey app config; the redirect URI must match exactly |
| Invoice finalize 500s | See [Troubleshooting: invoice not finalising](./troubleshooting/invoice-not-finalising) |
| Worker process exits immediately | Redis isn't reachable, or `REDIS_URL` is wrong |
