Platform
ScaiWave ScaiGrid ScaiCore ScaiBot ScaiDrive ScaiKey Modellen Tools & Services
Oplossingen
Organisaties Ontwikkelaars Internet Service Providers Managed Service Providers AI-in-a-Box
Kenniscentrum
Ondersteuning Documentation Blog Downloads
Bedrijf
Over ons Onderzoek Vacatures Investeren Contact
Inloggen

Auth + quotas

ScaiScribe authenticates every request via ScaiKey, the ScaiLabs identity service. Tenants get limits per the quota model. Operators get aggregated rollups so they can see usage at the partner and tenant level.

Authentication#

There are two token types you'll meet:

1. Service tokens (the default for SDK / programmatic use)#

OAuth client_credentials against ScaiKey's /oauth/token endpoint. The SDKs fetch and refresh these transparently:

python
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
from scaiscribe import Client, ScaiKeyAuth

auth = ScaiKeyAuth(
    client_id=os.environ["SCAIKEY_CLIENT_ID"],
    client_secret=os.environ["SCAIKEY_CLIENT_SECRET"],
)
client = Client(
    "https://scaiscribe.scailabs.ai",
    auth=auth,
    default_tenant_id=os.environ["SCAISCRIBE_TENANT_ID"],   # required
)

Service tokens are issued globally to the application registered in ScaiKey — they don't carry a tenant_id claim by themselves. You tell ScaiScribe which tenant the request is acting on via the X-ScaiScribe-Tenant-Id header, which the SDK sends automatically when you pass default_tenant_id (Python) / defaultTenantId (TS/.NET) at construction time.

2. User tokens (browser / OIDC flows)#

When a user authenticates through ScaiKey's web flow, the resulting JWT does carry a tenant_id claim — the tenant the user belongs to. ScaiScribe trusts the JWT claim; the X-ScaiScribe-Tenant-Id header is ignored for these tokens (defence-in-depth — a user token can't be re-targeted to a different tenant).

The admin SPA at /admin/ uses this flow: backend-mediated Authorization Code with HttpOnly session cookies.

Defence-in-depth boundary#

The X-ScaiScribe-Tenant-Id header only takes effect when the underlying token has no tenant claim of its own. If the token already declares a tenant, that wins — full stop.

Quotas#

Five axes are tracked per tenant. v1.0 defaults are:

Axis Default Type HTTP code on violation
documents_count 10,000 Absolute (live count from documents) 403 QUOTA_EXCEEDED
assets_bytes 5 GB Absolute (SUM over assets + fonts) 403 QUOTA_EXCEEDED
templates_count 20 Absolute (live count from templates) 403 QUOTA_EXCEEDED
renders_per_day 1,000 Windowed (per UTC day, rolls over at midnight) 429 QUOTA_EXCEEDED
max_doc_size_mb 100 Per-document size cap 413

Absolute quotas use a 403 because the situation only resolves with cleanup (delete documents, remove assets). Windowed quotas use a 429 because retrying later might succeed — the bucket rolls over naturally.

Quota overrides per tenant land via a forthcoming tenant_quotas table; until then the defaults apply uniformly. Operator override requires a code change.

Inspecting usage#

Three admin endpoints help operators see what's happening. Access varies by role:

Per-partner rollup#

scdoc
1
GET /v1/admin/quotas/partner/{partner_id}?axis=renders_per_day

Returns the aggregate across every tenant of a partner. Useful for provisioning reviews and capacity planning. Access: superadmin OR partner_admin on the queried partner.

json
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
{
  "partner_scaikey_id": "prt_acme",
  "axis": "renders_per_day",
  "tenant_count": 12,
  "total_used": 4823,
  "total_limit": 12000,
  "per_tenant": [
    { "tenant_id": "tnt_alpha", "used": 1450, "limit": 1000 },
    { "tenant_id": "tnt_beta",  "used":  220, "limit": 1000 },
    
  ]
}

Per-user rollup#

scdoc
1
GET /v1/admin/quotas/tenant/{tenant_id}/users?axis=renders_per_day

Breaks a tenant's usage down by acting user. Access: superadmin OR partner_admin on the tenant's partner OR tenant_admin on the tenant.

json
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
{
  "tenant_id": "tnt_acme",
  "axis": "renders_per_day",
  "total_used": 217,
  "coverage": "full",
  "per_user": [
    { "user_scaikey_id": "usr_alice", "used": 142, "limit": 1000 },
    { "user_scaikey_id": "usr_bob",   "used":  75, "limit": 1000 }
  ]
}

The coverage field reports whether per-user attribution is available for the axis:

  • full — for renders_per_day and documents_count. Solid attribution.
  • none — for assets_bytes and templates_count. The Asset/Font/Template tables don't carry created_by yet (model gap; v1.x). per_user is empty in this case and a note field explains.

Admin UI#

The admin SPA exposes these inline on the Partners and Tenants pages (click "Quota" / "Per-user" on a row to expand), and as a dedicated /admin/quotas page with axis switching.

Roles#

Role Scope Powers
member One tenant Standard tenant user — can use the document APIs as their tenant.
tenant_admin One tenant Above + manage that tenant's users, see its quota rollups.
partner_admin One partner Above + cross every tenant of that partner.
superadmin Global Above + manage every partner, rotate secrets, see global health.

Roles are assigned via POST /v1/admin/users/{user_id}/roles (audit-logged with granted_by_scaikey_id). Revocation stamps revoked_at; rows stay for audit.

Where to next#

Updated 2026-07-03 13:00:09 View source (.md) rev 10