Platform
ScaiWave ScaiGrid ScaiCore ScaiBot ScaiDrive ScaiKey Models Tools & Services
Solutions
Organisations Developers Internet Service Providers Managed Service Providers AI-in-a-Box
Resources
Support Documentation Blog Downloads
Company
About Research Careers Investment Opportunities Contact
Log in

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.

Multi-partner service identities#

PARTNER-scoped applications can hold tokens for multiple partners at once via the allowed_partner_ids field — useful when a platform-tier service (e.g. a CRM that manages many resellers from one deployment) needs to act on behalf of multiple partners without re-minting credentials per partner.

  • partner_id (scalar) — the app's home partner.
  • allowed_partner_ids (array) — additional partners the app can mint tokens for. Only super_admin can grant this; a partner_admin who tries to set it is refused with ALLOWED_PARTNER_IDS_REQUIRES_SUPER_ADMIN.
  • The JWT carries the deduped, sorted union of both as partner_ids. See OAuth and OIDC → Partner-scoped tokens.

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).
  • assigned_users, assigned_groups — explicit assignments for apps that gate access on user/group membership rather than open registration.

Access, assignment, and provisioning#

This is the part that trips up multi-product provisioning, so read it before scripting a tenant onboarding.

Which apps a tenant can use. Nothing is auto-assigned. A tenant's usable set is its own TENANT-scoped apps plus every GLOBAL app on the platform (PARTNER apps for tenants under that partner). Because GLOBAL apps are cross-tenant, a brand-new tenant's app list is never truly empty — the GLOBAL apps are already reachable; what's missing is any per-user access, which comes from assignment.

Assignment is by user or group. An app gates access through assigned_users (explicit user ids) and assigned_groups (a user is effectively assigned if they belong to a listed group, nested groups included). Assigning a group is the usual pattern: map a ScaiKey group to the app once, and every member — now and future — is assigned. (Whether unassigned users are blocked depends on the app's require_assignment flag; when it's off, any tenant user may authenticate, but they are still only "behind" the app once assigned or logged in.)

Assigning the first user is what propagates the tenant to a downstream app — you do not create the tenant separately in each product. When a user is assigned to an app (directly or by landing in a mapped group), ScaiKey notifies that app and hands it the user together with the user's tenant_id. The downstream product (ScaiGrid, ScaiDrive, …) provisions the tenant on its side in reaction to first seeing a user behind it. ScaiKey does not reach into the app or create anything there — it is the source of identity, and the app mirrors it. Calling a downstream product's own POST /tenants by hand generally creates a duplicate; let assignment drive it instead.

Two sync paths carry that data:

  • Push — the app's sync_webhook_url. On assignment (and on later user changes), ScaiKey POSTs a signed event to the app's configured webhook. The event envelope carries the tenant_id and the user identity; it does not carry the tenant's slug/name. Delivery is best-effort: a background worker polls every 30 seconds, so first-attempt latency is 0–30 s (≈15 s typical); failures retry at 60 s / 5 min / 15 min for up to 3 attempts, then the delivery is marked failed. The target URL must be HTTPS resolving to a public address (SSRF guard).
  • Pull — GET /api/v1/admin/applications/{id}/effective-users. An app can fetch its full effective user list on demand using its own platform token (or a directory:read token). This route returns the full tenant identity per user (tenant = id/slug/name, plus partner), which the push payload omits. This is the reconciliation path apps use to backfill after a missed webhook and to resolve tenant slugs/names.

For scripted provisioning: assign the user (or map the group), then allow up to ~30 seconds for the push to land before asserting the tenant exists downstream — or have the downstream reconcile via the effective-users pull, which is immediate. Racing the sync (e.g. calling the downstream product right after assignment) is the common scripting bug.

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 idapp_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 browserWEB + TENANT scope (or GLOBAL if cross-tenant) + authorization_code grant.
  • Backend service calling ScaiKey's admin APISERVICE + GLOBAL + client_credentials + admin:read (and/or admin:write) in allowed_scopes.
  • Browser-only SPASPA + TENANT + authorization_code + PKCE.
  • CLI tool a user runs locallyNATIVE + 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.
Updated 2026-09-09 23:18:11 View source (.md) rev 8