---
title: Applications
path: concepts/applications
status: published
---

# Applications

An *application* in ScaiKey is an OAuth/OIDC client — anything that obtains tokens. Every external system that integrates with ScaiKey is registered as an application.

You pick two things at registration time: the **type** (which determines which grants the app can use) and the **scope** (which determines which tenants it can act on).

## Application types

| Type | Confidential? | Has a secret? | Use case |
|---|---|---|---|
| `WEB` | Yes | Yes | Web app with a backend (PHP, Rails, Django, Express) |
| `SERVICE` | Yes | Yes | Backend service that calls APIs but never serves a browser |
| `SPA` | No (public) | No | Single-page app running in the browser (React, Solid, Vue) |
| `NATIVE` | No (public) | No | Mobile or desktop app (iOS, Android, Electron) |

**Confidential vs public** matters because public clients can't safely hold a `client_secret` — anyone can decompile the app and read it. Public clients are required to use PKCE on the `authorization_code` flow and cannot use `client_credentials` at all.

Confidential clients (`WEB`, `SERVICE`) can use every grant including `client_credentials` and Token Exchange.

## Application scope

Determines the breadth of what an app's tokens can address:

| Scope | Lives at | Tokens can act on | Who can register |
|---|---|---|---|
| `GLOBAL` | Platform-level (no tenant) | Any tenant in the platform | `super_admin` only |
| `PARTNER` | Owned by a partner | Tenants under that partner | `super_admin` or that `partner_admin` |
| `TENANT` | Owned by a tenant | Only that tenant | `tenant_admin` of that tenant (or any higher admin) |

`GLOBAL` apps use the platform OAuth endpoints (`/api/v1/platform/oauth/...`); `TENANT` apps use tenant-scoped endpoints (`/api/v1/auth/tenants/{slug}/oauth/...`); `PARTNER` apps currently use tenant-scoped endpoints with an explicit tenant in the URL.

## Per-application configuration

When you register an app, you set:

- **`redirect_uris`** — the exact URIs the OAuth flow may redirect to after login. Exact match (no wildcards, no path globs). Mismatches reject the request.
- **`logout_uris`** — accepted `post_logout_redirect_uri` values for RP-initiated logout.
- **`allowed_origins`** — CORS origins for browser-based clients. ScaiKey's CORS middleware checks these dynamically (registered origins are allowed even if not in the static settings list).
- **`allowed_scopes`** — the scope superset this app may ever request. A token request can only get scopes that are in this list. To add a new scope to an existing app, update this list via `PATCH /api/v1/admin/applications/{id}`.
- **`token_lifetime`** — access token TTL in seconds (default 3600). Configurable per app.
- **`refresh_token_lifetime`** — refresh token TTL (default 2592000, 30 days).
- **`token_exchange_allowed`** — boolean opt-in to be a *target* of Token Exchange. False by default; a `super_admin` flips this on for trusted downstream services.
- **`sync_webhook_url`** + **`sync_webhook_secret`** — optional per-app webhook endpoint that receives events relevant to users/groups assigned to this app (see [Reference → Webhooks](/docs/scaikey/reference/webhooks)).
- **`assigned_users`**, **`assigned_groups`** — explicit assignments for apps that gate access on user/group membership rather than open registration.

## Identifiers

- **`client_id`** — the OAuth identifier. A randomly generated string (not prefixed; e.g. `cp5pk59e0qnx4hwmvtw37ly6jnbx52uv`). Show it to humans, log it, embed it in tokens — it's not a secret.
- **`client_secret`** — shown **once** at registration. Hash-stored in the database after that; if lost, generate a new one (which invalidates the old).
- **Internal `id`** — `app_xxxxx`. Used in the admin API path (`/api/v1/admin/applications/app_xxxxx`). Not used in OAuth flows.

## Common patterns

- **Web app with backend, users log in via browser** → `WEB` + `TENANT` scope (or `GLOBAL` if cross-tenant) + `authorization_code` grant.
- **Backend service calling ScaiKey's admin API** → `SERVICE` + `GLOBAL` + `client_credentials` + `admin:read` (and/or `admin:write`) in `allowed_scopes`.
- **Browser-only SPA** → `SPA` + `TENANT` + `authorization_code` + PKCE.
- **CLI tool a user runs locally** → `NATIVE` + `TENANT` + `device_code`.
- **Long-running async job that calls a downstream service hours after user request** → register the caller (`SERVICE` confidential) and the target (`SERVICE`, with `token_exchange_allowed = true`); use Token Exchange at request time, cache the exchanged token, retrieve in the worker.
