Integrations
Declare managed per-user OAuth integrations separately from auth.
The integrations block declares delegated external access for your app, separate from auth.
authanswers: who is the user?integrationsanswers: which external APIs has this user connected?
Logging in with Google does not grant Google Calendar access. If your app needs Calendar, Drive, or another provider API, declare it in integrations.
Current Scope
Shipped:
- Managed per-user OAuth2 connection lifecycle
- Platform-owned connect, callback, refresh, disconnect, reconnect-required state, and audit trail
- Browser helpers for connection status and connect/disconnect flows
- Server helpers for short-lived provider access tokens
- Typed Google Calendar server helpers
Not yet shipped:
- Browser access to provider tokens
- Workspace-owned/shared integrations
- Marketplace-installed providers
Syntax
integrations {
calendar: googleCalendar {
mode: optional,
onboarding: afterAuth,
access: read,
clientId: @secret("GOOGLE_CLIENT_ID"),
clientSecret: @secret("GOOGLE_CLIENT_SECRET"),
},
notion: customOauth2 {
mode: manual,
onboarding: settings,
access: read,
clientId: @secret("NOTION_CLIENT_ID"),
clientSecret: @secret("NOTION_CLIENT_SECRET"),
authorizeUrl: "https://api.notion.com/v1/oauth/authorize",
tokenUrl: "https://api.notion.com/v1/oauth/token",
accountUrl: "https://api.notion.com/v1/users/me",
accountIdPath: "bot.owner.user.id",
accountEmailPath: "bot.owner.user.person.email",
accountNamePath: "name",
scopes: ["read:pages"],
upgradeScopes: ["read:pages", "update:pages"],
revokeUrl: "https://api.notion.com/v1/oauth/revoke",
},
}Fields
| Field | Meaning |
|---|---|
mode | required, optional, or manual |
onboarding | afterAuth, combined, or settings |
access | read or write |
clientId / clientSecret | Provider OAuth client credentials via @secret(...) |
scopes | Requested scopes for the initial connection |
upgradeScopes | Scopes requested when the app upgrades access |
authorizeUrl / tokenUrl / accountUrl | Required for customOauth2 providers |
accountIdPath / accountEmailPath / accountNamePath | JSON paths for provider account metadata |
revokeUrl | Optional provider revoke endpoint |
Modes
| Mode | Meaning |
|---|---|
required | The integration must be connected as part of setup |
optional | The app works without it but benefits from it |
manual | Users connect from settings or a later workflow |
Onboarding Hints
| Setting | Meaning |
|---|---|
afterAuth | Prompt after login or signup |
combined | Present as one onboarding sequence (auth and integration remain separate underneath) |
settings | Only prompt from settings or a feature gate |
These are product hints. They do not merge auth and integrations into one backend record.
Provider Types
customOauth2
Use customOauth2 when Kizaki owns the OAuth lifecycle but your app owns provider-specific API calls and sync logic.
Required fields:
clientIdclientSecretauthorizeUrltokenUrlaccountUrlaccountIdPath
Security:
- Provider endpoints must use
https://, except localhost-only testing URLs - The compiler rejects insecure plaintext provider URLs
- The runtime rejects provider URLs that resolve to loopback, private, or link-local addresses
- Token and account responses use a bounded JSON client; oversized responses are rejected
- Provider error bodies are not reflected into platform logs
- In local dev, integration secrets are read from
KIZAKI_DEV_SECRET_<NAME>or the platform secret store
googleCalendar
Built-in provider declaration with:
- Google-specific OAuth defaults
- Read/write access-tier modeling
- Managed connection lifecycle on the same substrate as
customOauth2 - Typed server helpers in
@kizaki/sdk
Browser code gets lifecycle helpers only. Typed Calendar operations are server-only.
Browser SDK
import {
listIntegrations,
getIntegrationStatus,
beginIntegrationConnect,
beginIntegrationUpgrade,
disconnectIntegration,
} from "@kizaki/sdk/browser";These helpers talk to /__integrations/* endpoints. They return status and redirect the browser into the provider consent flow. They do not expose raw provider tokens.
Server SDK
import {
getIntegrationStatus,
getIntegrationAccessToken,
withIntegrationAccess,
} from "@kizaki/sdk";Server-only. Returns short-lived access tokens for the current authenticated user. Never exposes refresh tokens.
For Google Calendar, use the typed helper:
import { googleCalendar } from "@kizaki/google";
/** @expose */
export async function nextWeekBusyWindows() {
return googleCalendar("calendar").getFreeBusy({
timeMin: "2026-06-01T00:00:00Z",
timeMax: "2026-06-08T00:00:00Z",
calendarIds: ["primary"],
});
}Available methods:
getStatus()listCalendars()getFreeBusy(...)listEvents(...)createEvent(...)updateEvent(...)cancelEvent(...)
Raw DIY Integrations
The managed substrate is not the only option. You can build integrations directly with:
routes(..., auth: none)for webhooksreq.rawBodyfor signature verification@secret(...)for API keys or provider credentialsnetwork {}for outbound allowlists- Effects and schedules for retry and sync work
Use the raw path for webhook-only, API-key, or partner-specific integrations where delegated per-user OAuth is unnecessary.
Recommended Pattern
- Use
authfor identity - Use
integrationsfor delegated provider access - Use
customOauth2when Kizaki should own token custody and refresh - Use raw routes/network/secrets for simple integrations without delegated OAuth
- Keep provider API calls in server code
Related: