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",
- "code": "TCS",
- "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"
}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"
]
}Returns a paginated, filterable, full-text-searchable list of tenants. Filter payload is in the request body to avoid URL length limits. Requires an internal service key — not exposed to end users.
| q | string Full-text search query |
| after_id | string <uuid> Forward cursor — return records after this ID (exclusive) |
| before_id | string <uuid> Backward cursor — return records before this ID (exclusive) |
| limit | integer [ 1 .. 100 ] Default: 25 Number of records per page (1–100, default 25) |
| order_by | string Column to sort by — resource-specific allowlist enforced server-side |
| order | string Default: "desc" Enum: "asc" "desc" |
| fields | Array of strings Columns to project — resource-specific allowlist enforced server-side. Omit for all allowed fields. |
object (FilterOperand) Top-level Apollo-style filter — groups of conditions combined by AND/OR. |
{- "q": "acme",
- "limit": 25,
- "order_by": "created_at",
- "order": "desc",
- "filter": {
- "operator": "and",
- "groups": [
- {
- "operator": "and",
- "filters": [
- {
- "attribute": "status",
- "condition": "is",
- "value": "active"
}
]
}
]
}
}{- "tenants": [
- {
- "id": "7c9e6679-7425-40de-944b-e07fc1f90ae7",
- "company_name": "Acme Corp",
- "slug": "acme",
- "industry_name": "Technology",
- "status": "active",
- "created_at": "2026-01-15T10:00:00Z"
}
], - "meta": {
- "next_cursor": "7c9e6679-7425-40de-944b-e07fc1f90ae7",
- "prev_cursor": "aabbccdd-1234-5678-abcd-ef0123456789",
- "limit": 25,
- "total_count": 1
}
}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
}Returns a paginated, filterable, full-text-searchable list of workspaces within a tenant schema. Filter payload is in the request body. Requires a valid session cookie and X-Schema-Name header identifying the tenant schema.
| X-Schema-Name required | string Example: acme_1234 Stable Apartment schema name of the tenant |
| q | string Full-text search query |
| after_id | string <uuid> Forward cursor — return records after this ID (exclusive) |
| before_id | string <uuid> Backward cursor — return records before this ID (exclusive) |
| limit | integer [ 1 .. 100 ] Default: 25 Number of records per page (1–100, default 25) |
| order_by | string Column to sort by — resource-specific allowlist enforced server-side |
| order | string Default: "desc" Enum: "asc" "desc" |
| fields | Array of strings Columns to project — resource-specific allowlist enforced server-side. Omit for all allowed fields. |
object (FilterOperand) Top-level Apollo-style filter — groups of conditions combined by AND/OR. |
{- "q": "engineering",
- "limit": 25,
- "order_by": "name",
- "order": "asc",
- "filter": {
- "operator": "and",
- "groups": [
- {
- "operator": "and",
- "filters": [
- {
- "attribute": "name",
- "condition": "contains",
- "value": "engineering"
}
]
}
]
}
}{- "workspaces": [
- {
- "id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
- "name": "Engineering",
- "created_at": "2026-02-01T08:00:00Z"
}
], - "meta": {
- "next_cursor": "aabbccdd-1234-5678-abcd-ef0123456789",
- "prev_cursor": "aabbccdd-1234-5678-abcd-ef0123456789",
- "limit": 25,
- "total_count": 1
}
}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"
}Returns a paginated, filterable, full-text-searchable list of users within a tenant schema. Filter payload is in the request body. auth_linkage_id and tenant_id are never included in the response. Requires a valid session cookie and X-Schema-Name header identifying the tenant schema.
| X-Schema-Name required | string Example: acme_1234 Stable Apartment schema name of the tenant |
| q | string Full-text search query |
| after_id | string <uuid> Forward cursor — return records after this ID (exclusive) |
| before_id | string <uuid> Backward cursor — return records before this ID (exclusive) |
| limit | integer [ 1 .. 100 ] Default: 25 Number of records per page (1–100, default 25) |
| order_by | string Column to sort by — resource-specific allowlist enforced server-side |
| order | string Default: "desc" Enum: "asc" "desc" |
| fields | Array of strings Columns to project — resource-specific allowlist enforced server-side. Omit for all allowed fields. |
object (FilterOperand) Top-level Apollo-style filter — groups of conditions combined by AND/OR. |
{- "q": "jane",
- "limit": 25,
- "order_by": "created_at",
- "order": "desc",
- "filter": {
- "operator": "and",
- "groups": [
- {
- "operator": "and",
- "filters": [
- {
- "attribute": "status",
- "condition": "is",
- "value": "active"
}
]
}
]
}
}{- "users": [
- {
- "id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
- "email": "jane@acme.com",
- "name": "Jane Smith",
- "status": "active",
- "created_at": "2026-03-10T12:00:00Z"
}
], - "meta": {
- "next_cursor": "aabbccdd-1234-5678-abcd-ef0123456789",
- "prev_cursor": "aabbccdd-1234-5678-abcd-ef0123456789",
- "limit": 25,
- "total_count": 1
}
}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
}Acronym suggestion and object code engine — stateless acronym candidates for tenant/workspace naming; sequential code assignment is model-layer only (core_acronym pack)
Pure stateless suggestion — returns ranked acronym candidates for the given name and object type. No database reads or writes. The caller presents these to the user, who picks one (or types their own) before saving the entity. Once saved, the code is immutable (enforced by Acronymize::Suggestable on the model). Authenticated via X-Service-JWT only — no X-Schema-Name needed (no DB access).
required | object (SuggestAcronymInput) Name and object type to generate acronym candidates for. |
{- "acronym": {
- "name": "Deutschland Workspace",
- "object_type": "workspace"
}
}{- "acronym": {
- "candidates": [
- "DU",
- "DEU",
- "DEUT",
- "DTSC",
- "DEUTS"
], - "object_type": "workspace"
}
}