---
summary: API keys, OAuth, device flow, and the permission model.
title: Authentication
path: concepts/authentication
status: published
---

# Authentication

Every request to ScaiAtlas (except a handful of public auth endpoints) carries a
bearer token:

```
Authorization: Bearer <token>
```

The server disambiguates by prefix: a token starting with **`sk_`** is treated as an
**API key**; anything else is validated as a **ScaiKey JWT**.

## Credential types

### API keys

Long-lived keys, ideal for services, pipelines, and CI. Create and manage them in the
web UI (or via the admin API). The plaintext is shown once at creation.

```python
from scaiatlas import ScaiAtlas
client = ScaiAtlas(api_key="sk_live_...", base_url="https://api.scaiatlas.io")
# or from SCAIATLAS_API_KEY / SCAIATLAS_BASE_URL:
client = ScaiAtlas.from_env()
```

API keys carry a **permission mode**:

- `explicit` — the key's own permission list is authoritative.
- `inherited` — the key inherits the full permissions of a linked user.
- `restricted` — the intersection of a linked user's permissions and an explicit list.

`inherited` and `restricted` require a linked user. Keys can additionally be
restricted to specific namespaces.

### OAuth (browser, PKCE)

For interactive apps. Opens a browser to authenticate against ScaiKey:

```python
client = ScaiAtlas.with_oauth(client_id="my-app")
client.login()          # opens a browser, completes PKCE
client.namespaces.list()
```

### Device flow (CLI / headless)

For terminals and machines without a browser. Displays a URL and a code to enter
elsewhere:

```python
client = ScaiAtlas.with_device_flow()   # client_id defaults to "scaiatlas-cli"
client.login()          # prints verification URL + user code, polls until authorized
```

Under the hood this uses `POST /api/v1/auth/cli/init` to start a session, then polls
`GET /api/v1/auth/cli/status` until a browser session authorizes it via
`POST /api/v1/auth/cli/authorize`.

## Who am I?

```python
me = client.get("/api/v1/auth/me")   # via REST: id, email, tenant_id, permissions, groups
```

The REST endpoint `GET /api/v1/auth/me` returns the current principal, its tenant,
and its effective global permissions and groups.

## Permission model

Two layers combine on every request.

### 1. Global permissions

Platform-wide grants carried on the identity (JWT claims + database rows). Notable:
super-admin tokens (`scaiatlas:super_admin`, `platform:admin`, `*`) bypass namespace
checks; tiered admin roles (`scaiatlas:tenant_admin`, `scaiatlas:partner_admin`) gate
the admin API.

### 2. Namespace roles

Within a namespace, a caller has a **role**, and each role grants a set of
**permissions**:

| Role | Grants (cumulative) |
|---|---|
| **viewer** | `view`, `download` |
| **member** | viewer + `create_model`, `update_own_model`, `delete_own_model`, `upload_version` |
| **admin** | member + `update_any_model`, `delete_any_model`, `manage_members`, `update_namespace` |
| **owner** | admin + `delete_namespace`, `transfer_ownership` |

Beyond explicit roles, **visibility** grants implicit read access: `public`
namespaces grant `view`/`download` to anyone; `tenant` namespaces grant them to users
in the same tenant.

### Checking your own permissions

```bash
GET /api/v1/namespaces/{namespace}/my-permissions
```

Returns your effective `role`, the full `permissions` list it expands to, and where
each permission was inherited from (direct grant vs. group).

## Managing members

```python
client.namespaces.add_member("poolnoodle", user_id="<uuid>", role="member")
client.namespaces.list_members("poolnoodle")
client.namespaces.remove_member("poolnoodle", user_id="<uuid>")
```

Membership can be granted to a **user** or a **group** (REST accepts `user_id` or
`group_id`). The last owner cannot be demoted or removed.

## Environment variables

| Variable | Purpose |
|---|---|
| `SCAIATLAS_API_KEY` | API key for `from_env()`. |
| `SCAIATLAS_BASE_URL` | Registry base URL (default `https://api.scaiatlas.io`). |
| `SCAIATLAS_TIMEOUT` | Request timeout in seconds. |
| `SCAIATLAS_VERIFY_SSL` | `true`/`false`. |
