Download OpenAPI specification:
Sintj Rails 8.1 API — spec-first, contract-validated.
Stytch B2B OAuth login (start/callback/session exchange) and session identity (auth_engine pack)
Returns the account id and email for the caller identified by the _sintj_session cookie.
{- "account": {
- "id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
- "email": "alice@example.com"
}
}Redirects the browser to Stytch's hosted OAuth start URL for the given tenant (identified by slug) and provider. Not JSON — a 302 browser redirect.
| provider required | string Enum: "google" "microsoft" Example: google |
| org required | string Example: org=acme Tenant slug |
| return_to | string Example: return_to=/tenant/dashboard Path within the tenant app to land on after login completes |
{- "error_code": "auth.provider_not_allowed",
- "request_id": "550e8400-e29b-41d4-a716-446655440000"
}Reached twice in sequence for a single login: first at the fixed Stytch-registered
redirect URI (shared across every tenant and region, since OAuth providers don't allow
per-tenant redirect URIs), where the Cloudflare Worker rewrites the request into a 302
to this same path on the tenant's own host (subdomain or custom domain) — that hop is
Worker-side and not a Rails request.
Carries only token — Stytch validates redirect URLs by exact match including query
string, so no other params can ride on it. The tenant is resolved from the
organization_id in Stytch's authenticated response, and return_to (captured at
/{provider}/start) travels in a short-lived cookie, not a query param.
On the tenant's own host, Rails verifies the OAuth token via Stytch, finds-or-creates
the local account, sets the real session cookie, and redirects to return_to.
Not JSON — a 302 browser redirect.
| token required | string Example: token=rTPQqcSuKpjbUhqWahYNVLbZzUE6hHMPRB6qxvExeGYy Stytch OAuth token appended by Stytch after the provider completes |
{- "error_code": "auth.unauthorized",
- "request_id": "550e8400-e29b-41d4-a716-446655440000"
}Verifies the password against Stytch (not a locally-stored hash) for the given
tenant's Organization. On success, sets the _sintj_session cookie and returns 204.
required | object (PasswordLoginInput) |
{- "session": {
- "org": "acme",
- "email": "alice@example.com",
- "password": "s3cr3t-P@ssword"
}
}{- "error_code": "auth.unauthorized",
- "request_id": "550e8400-e29b-41d4-a716-446655440000"
}Returns a tenant's full record including per-step onboarding status. Requires an internal service key — not exposed to end users.
| id required | string <uuid> Example: 7c9e6679-7425-40de-944b-e07fc1f90ae7 |
{- "tenant": {
- "id": "7c9e6679-7425-40de-944b-e07fc1f90ae7",
- "company_name": "Acme Corp",
- "slug": "acme",
- "industry_name": "Technology",
- "default_zone": "eu",
- "zones": [
- "eu",
- "us"
], - "status": "active",
- "allowed_auth_methods": [
- "google",
- "password"
], - "allowed_email_domains": [
- "acme.com"
], - "onboarding_status": {
- "status": "completed",
- "steps": [
- {
- "step": "email_verification",
- "status": "completed"
}, - {
- "step": "profile_setup",
- "status": "completed"
}, - {
- "step": "billing_setup",
- "status": "completed"
}
]
}
}
}Soft-deletes a tenant by setting deleted_at. Only disabled tenants may be deleted; attempting to delete an active tenant returns 422. Requires an internal service key — not exposed to end users.
| id required | string <uuid> Example: 7c9e6679-7425-40de-944b-e07fc1f90ae7 |
{- "error_code": "auth.unauthorized",
- "request_id": "550e8400-e29b-41d4-a716-446655440000"
}Returns all provisioned tenants with their aggregate onboarding status. Requires an internal service key — not exposed to end users.
{- "tenants": [
- {
- "id": "7c9e6679-7425-40de-944b-e07fc1f90ae7",
- "company_name": "Acme Corp",
- "slug": "acme",
- "default_zone": "eu",
- "zones": [
- "eu",
- "us"
], - "onboarding_status": "completed"
}
]
}Creates a tenant record and seeds pending onboarding status rows. Requires an internal service key — not exposed to end users.
required | object (TenantInput) |
{- "tenant": {
- "company_name": "Acme Corp",
- "slug": "acme",
- "industry_name": "Technology",
- "zones": [
- "eu",
- "us"
]
}
}{- "success": true,
- "id": "550e8400-e29b-41d4-a716-446655440000"
}Sets a tenant's status to active. Idempotent — enabling an already-active tenant is a no-op and still returns 200. Requires an internal service key.
| id required | string <uuid> Example: 7c9e6679-7425-40de-944b-e07fc1f90ae7 |
{- "success": true,
- "id": "7c9e6679-7425-40de-944b-e07fc1f90ae7"
}Sets a tenant's status to disabled. Idempotent — disabling an already-disabled tenant is a no-op and still returns 200. Requires an internal service key.
| id required | string <uuid> Example: 7c9e6679-7425-40de-944b-e07fc1f90ae7 |
{- "success": true,
- "id": "7c9e6679-7425-40de-944b-e07fc1f90ae7"
}Returns the same {tenant_id, slug, host, region, allowed_auth_methods} record normally pushed to Cloudflare KV (Platform::SyncTenantKvService), so a local frontend/Worker (e.g. wrangler dev without --remote) can resolve tenant routing without real remote KV credentials. Public and unauthenticated, but development-environment only — returns 404 in every other environment. Never available in production.
| slug required | string Example: acme |
{- "tenant_id": "7c9e6679-7425-40de-944b-e07fc1f90ae7",
- "slug": "acme",
- "host": "acme.sintj.com",
- "region": "us",
- "allowed_auth_methods": [
- "google",
- "password"
]
}Creates a new workspace under an existing tenant. Requires a valid X-Schema-Name header identifying the tenant schema.
| X-Schema-Name required | string Example: acme_1234 Stable Apartment schema name of the tenant |
required | object (WorkspaceInput) |
{- "workspace": {
- "name": "Engineering"
}
}{- "success": true,
- "id": "3fa85f64-5717-4562-b3fc-2c963f66afa6"
}Assigns an existing user to a workspace with a given role. Duplicate assignments are rejected with a 422 validation error. Requires a valid X-Schema-Name header.
| X-Schema-Name required | string Example: acme_1234 Stable Apartment schema name of the tenant |
required | object (WorkspaceUserInput) |
{- "workspace_user": {
- "workspace_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
- "user_id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
- "role": "member"
}
}{- "success": true
}Initiates the invitation flow for a new tenant user: creates a Stytch member, seeds a Tenant::User record with status "invited", and sends an invitation email. Not yet implemented — returns 501 until SIN-1329 is complete. Full contract (200 success + 422 validation error) added when SIN-1329 ships. Requires a valid X-Schema-Name header identifying the tenant schema.
| X-Schema-Name required | string Example: acme_1234 Stable Apartment schema name of the tenant |
required | object (UserInput) |
{- "user": {
- "email": "jane@example.com",
- "name": "Jane Smith"
}
}{- "error_code": "api.not_implemented",
- "request_id": "550e8400-e29b-41d4-a716-446655440000"
}Sets a tenant user's status to "active". Idempotent — enabling an already-active user succeeds. Requires a valid X-Schema-Name header identifying the tenant schema.
| id required | string <uuid> Example: a1b2c3d4-e5f6-7890-abcd-ef1234567890 |
| X-Schema-Name required | string Example: acme_1234 Stable Apartment schema name of the tenant |
{- "success": true,
- "id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890"
}Sets a tenant user's status to "disabled", preventing login. Idempotent. Requires a valid X-Schema-Name header identifying the tenant schema.
| id required | string <uuid> Example: a1b2c3d4-e5f6-7890-abcd-ef1234567890 |
| X-Schema-Name required | string Example: acme_1234 Stable Apartment schema name of the tenant |
{- "success": true,
- "id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890"
}Creates a Platform::User record (status: invited) and a mirrored Tenant::User in the tenant's Apartment schema, then sends an invitation email with an accept link. Guards: schema_provisioning step must be completed; no Platform::User may exist yet for this tenant. Requires an internal service key.
| id required | string <uuid> Example: 7c9e6679-7425-40de-944b-e07fc1f90ae7 |
required | object (InvitationInput) |
{- "invitation": {
- "email": "owner@acme.com",
- "name": "Alice Smith"
}
}{- "success": true,
- "id": "7c9e6679-7425-40de-944b-e07fc1f90ae7"
}Validates the invitation token, sets the owner's password and name, flips both the Platform::User and Tenant::User to status active, and records invitation_accepted_at. No authentication header required — the token in the URL is the auth factor.
| token required | string Example: a3f8b2c1d4e5f6a7b8c9d0e1f2a3b4c5d6e7f8a9b0c1d2e3f4a5b6c7d8e9f0a1 |
required | object (AcceptInvitationInput) |
{- "invitation": {
- "name": "Alice Smith",
- "password": "s3cr3tP@ssw0rd!",
- "password_confirmation": "s3cr3tP@ssw0rd!"
}
}{- "success": true
}Partial update of the tenant's authentication policy settings. Only keys present in the request body are written; omitted keys are left unchanged. Unknown keys are silently ignored. The legacy mfa_required key is never written by this endpoint. Requires a valid X-Schema-Name header identifying the tenant schema.
| X-Schema-Name required | string Example: acme_1234 Stable Apartment schema name of the tenant |
required | object (OrgSettingsInput) Partial update — only keys present in the request body are written. Unknown keys are silently ignored. Allowed values are strings only (the OrgSettings table is a key-value store). The legacy mfa_required key must not be sent; use mfa_mode instead. |
{- "org_settings": {
- "allow_password_login": "true",
- "mfa_mode": "required"
}
}{- "success": true
}Internal endpoint called by the central provisioning service to create an Apartment PostgreSQL schema for a tenant in this zone's database and seed OrgSettings defaults. Idempotent — returns 200 if the schema already exists. Authenticated via X-Service-JWT — never exposed to end users.
| tenant_id required | string <uuid> Example: 7c9e6679-7425-40de-944b-e07fc1f90ae7 |
required | object (ProvisionSchemaInput) |
{- "schema": {
- "name": "acme_4321"
}
}{- "success": true
}