---
title: Authentication
path: getting-started/authentication
status: published
---

# Authentication

Every ScaiGrid request (except `/health` and `/metrics`) must authenticate. Two methods are supported.

## API keys

API keys are the right choice for **server-to-server** integration — your backend calls ScaiGrid.

**Format:** `sgk_` followed by 32+ random characters (e.g., `sgk_a8f3e2...`).
**Lifetime:** Never expire by default. Can be revoked individually.
**Scope:** Belong to a single user within a single tenant. Inherit that user's permissions.

### Creating an API key

From the admin UI: **Access → API Keys → Create**. You see the full key exactly once — copy it immediately. The database stores only a SHA-256 hash.

Via the API:

```bash
curl -X POST https://scaigrid.scailabs.ai/v1/api-keys \
  -H "Authorization: Bearer $EXISTING_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"name": "production-integration"}'
```

### Using an API key

Include it in the `Authorization` header:

```
Authorization: Bearer sgk_your_key_here
```

```bash
curl -H "Authorization: Bearer sgk_your_key_here" \
     https://scaigrid.scailabs.ai/v1/models
```

```python
import httpx

client = httpx.Client(headers={"Authorization": f"Bearer {API_KEY}"})
resp = client.get("https://scaigrid.scailabs.ai/v1/models")
```

```typescript
const client = new Headers({ Authorization: `Bearer ${API_KEY}` });
const resp = await fetch("https://scaigrid.scailabs.ai/v1/models", { headers: client });
```

### Revoking an API key

```bash
curl -X DELETE https://scaigrid.scailabs.ai/v1/api-keys/{key_id} \
  -H "Authorization: Bearer $EXISTING_TOKEN"
```

Once revoked, the key is permanently invalid — including any in-flight requests using it.

### Rate limits

API keys have independent rate limits (configurable per tenant). Hitting the limit returns `429 RATE_LIMITED` with a `Retry-After` header. See [Rate Limiting](../07-advanced/05-rate-limiting.md).

## ScaiKey JWTs

JWTs are the right choice for **human users** interacting with ScaiGrid — the admin UI, user-facing dashboards, tools where individual users log in via SSO.

**Issuer:** ScaiKey (your identity provider).
**Format:** Standard JWT, signed RS256.
**Lifetime:** Short (typically 1 hour). Refreshed via `/v1/auth/refresh`.
**Scope:** Identifies the user and their tenant. Permissions resolved from the user's roles at request time.

### Obtaining a JWT

ScaiKey issues JWTs through an OAuth 2.0 authorization code flow with PKCE. The admin UI handles this automatically. For your own user-facing app, the flow is:

1. `POST /v1/auth/identify` with the user's email → returns available tenants
2. `POST /v1/auth/authorize` with tenant + PKCE code challenge → returns an OAuth URL
3. User completes SSO at ScaiKey → redirected back with authorization code
4. `POST /v1/auth/token` with the code → returns access + refresh tokens

See [Authentication Reference](../06-reference/01-authentication.md) for the complete flow.

### Using a JWT

Same `Authorization` header as API keys:

```
Authorization: Bearer eyJhbGc...
```

### Refreshing a JWT

```bash
curl -X POST https://scaigrid.scailabs.ai/v1/auth/refresh \
  -H "Content-Type: application/json" \
  -d '{"refresh_token": "..."}'
```

Response contains a new access token. Your client should refresh proactively before expiration, or reactively on `401 AUTH_TOKEN_EXPIRED`.

## Which should I use?

| Situation | Use |
|-----------|-----|
| Backend service calling ScaiGrid | **API key** |
| CI/CD pipeline, cron job, batch processor | **API key** |
| Personal scripts, quick experiments | **API key** |
| Admin UI or user-facing dashboard | **JWT** (via ScaiKey login) |
| MCP integration for a user's own tools | **JWT** |
| Shared key across a team | Avoid — create one API key per service, per environment |

**Rule of thumb:** if a human is in the loop, JWT. If it's automation, API key.

## What if I need both?

Common case: a backend service calls ScaiGrid on behalf of end users, and you want the requests accounted per end user, not per service.

Two patterns work:

1. **Service-to-service JWT.** The backend uses its own JWT (obtained via client credentials through ScaiKey) and passes the user identity in a custom header that your tenant's usage pipeline aggregates.
2. **Per-user API keys.** Each end user gets their own API key, and the backend proxies requests using the user's key. Simpler but requires storing user keys.

Pattern 1 is cleaner for SaaS platforms. Pattern 2 is simpler for internal tooling.

## Security notes

- **Never put API keys in client-side code.** A JavaScript bundle in a browser leaks the key to anyone who opens DevTools. Always proxy through your backend.
- **Rotate keys regularly.** At minimum on personnel changes. Consider quarterly rotation for production integrations.
- **Use one key per service, per environment.** Easier to revoke surgically without disrupting unrelated systems.
- **Enable audit logging.** See [Audit Log](../06-reference/09-audit-log.md). Every authenticated mutation is recorded.
- **Monitor for unusual usage.** Spikes in a single API key's activity are the earliest signal of a leak.

## What's next

- [Your First Integration](./03-your-first-integration.md) — complete walk-through including error handling.
- [Roles and Permissions](../03-core-concepts/02-roles-and-permissions.md) — what permissions do, how they're resolved.
- [Authentication Reference](../06-reference/01-authentication.md) — full OAuth flow and endpoint details.
