Connected Accounts (totalum-sdk/connections)
import … from 'totalum-sdk/connections' — or the same namespace on totalum from 'totalum-sdk'. Install, configuration and errors: Totalum SDK.
Let your app's users connect their own accounts of about 1,500 apps — Google Calendar, Gmail, Google Drive,
Outlook, Slack, HubSpot, Notion, GitHub, Stripe, Airtable and more — and act on them from your server: create a
calendar event, send an e-mail from their Gmail, read their files, or receive their events. They sign in on a hosted
page, you never handle OAuth apps or tokens, and each user only ever sees their own connections. userId is always
your signed-in user's id, taken from the session on the server. Actions are billed in credits; listing is free.
const url = await totalum.connections.connectUrl({ userId: user.id, toolkit: 'googlecalendar', returnUrl });| Method or type | What it does |
|---|---|
totalumConnections | Creates the connections namespace (the same object as totalum.connections; scope connections; server-side code only: it needs the project key). |
connections.connectUrl | The URL to send the user to so they connect toolkit (e.g. |
connections.list | The user's connected accounts (newest first), optionally of one app: show them on a settings page with a "Disconnect" button. |
connections.isConnected | Whether the user has an active connection of an app: show "Connect" or the feature that needs it. |
connections.disconnect | Disconnects one of the user's connected accounts (the provider's access is revoked). |
connections.execute | Runs action (e.g. |
connections.actions | The actions an app offers (slug, name, description), optionally filtered by words — to find the slug to pass to execute(). |
connections.action | One action with its inputSchema: the JSON Schema of what execute() takes as input. |
connections.toolkits | Searches the catalogue of apps (search matches names and descriptions, category e.g. |
connections.toolkit | One app, exactly: how it connects and, for credentials, the fields the user types on the hosted page. |
connections.triggers | The events (triggers) an app can send, such as GMAIL_NEW_GMAIL_MESSAGE — to find the slug to pass to subscribe(). |
connections.trigger | One trigger with configSchema (what subscribe's config takes) and payloadSchema (the event's shape). |
connections.subscribe | Watches trigger on the user's active connection of its toolkit: each event is POSTed to deliverTo (a path of your own app, default /api/webhooks/connections) on the preview (dev users) or the published site (live users), signed with your project's webhook secret — read it with verifyEvent. |
connections.subscriptions | The user's event subscriptions. |
connections.unsubscribe | Stops one of the user's subscriptions: no more events are delivered. |
connections.verifyEvent | Verifies one delivered event (the raw body text and the request headers of your deliverTo route) with TOTALUM_WEBHOOK_SECRET and returns it typed: data.userId is your user, data.payload the event. |
Connection | One of a user's connected accounts. |
ConnectionAction | An action an app offers. |
ConnectionEvent | One event from a user's connected account, delivered to your deliverTo route and returned by connections.verifyEvent(). |
ConnectionStatus | The state of a connected account. |
ConnectionSubscription | A subscription to a trigger on a user's connected account. |
ConnectionToolkit | An app of the catalogue. |
ConnectionToolkitDetail | One app, from connections.toolkit(slug): the catalogue fields plus the credential fields. |
ConnectionTrigger | An event an app can send. |
#totalumConnections
totalumConnections(options?: TotalumClientOptions): TotalumConnectionsCreates the connections namespace (the same object as totalum.connections; scope connections; server-side code
only: it needs the project key). Your app's users connect their own accounts of ~1,500 apps — Google Calendar, Gmail, Google Drive, Outlook,
OneDrive, Slack, Dropbox, Notion, HubSpot, GitHub, Stripe, Airtable, … — on a hosted page (they sign in at the
provider, or type their own API key there), with no OAuth app of yours; then your server runs actions on them
(GOOGLECALENDAR_CREATE_EVENT, GMAIL_SEND_EMAIL, …) and receives their events (subscribe). The tokens never
reach your app. userId is always your signed-in user's id (the one in your database), taken from the session
on the server — never from the request: each user, project and environment (dev/live) only ever sees its own
connections.
Parameters
| Name | Type | Required | Description |
|---|---|---|---|
options | TotalumClientOptions | no | The project key, API URL and environment; each falls back to its environment variable. |
options.key | string | no | Project key tlm_pk_…; default TOTALUM_PROJECT_KEY. Server-side only. |
options.apiUrl | string | no | The Totalum API's URL: default TOTALUM_SDK_API_URL, else https://sdk-api.totalum.app. |
options.env | 'dev' | 'live' | no | Sent as x-totalum-env on every call (the database it targets, and dev vs live usage); default TOTALUM_ENV, else live. |
Returns TotalumConnections — The connections namespace: the methods below.
Example
import { totalumConnections } from 'totalum-sdk/connections';
const connections = totalumConnections();
const list = await connections.list({ userId: user.id });The namespace's type is exported as TotalumConnections (import type { TotalumConnections } from 'totalum-sdk/connections').
#connections.connectUrl
connections.connectUrl(input: { userId, toolkit, returnUrl }): Promise<string>The URL to send the user to so they connect toolkit (e.g. 'googlecalendar'); valid about 10 minutes, so
make it when the user clicks "Connect" and redirect at once. They sign in at the provider (or type their own key
on the hosted page) and come back to returnUrl with ?connection=success|failed&toolkit=<toolkit> added. A new
connection through Totalum's sign-in is billed once in credits when it becomes active.
Parameters
| Name | Type | Required | Description |
|---|---|---|---|
input | { userId, toolkit, returnUrl } | yes | Your user, the app to connect and where to come back. |
input.userId | string | yes | Your signed-in user's id (from your session, never from the request). Allowed: at least 1 character, at most 200 characters. |
input.toolkit | string | yes | The app to connect, by slug (googlecalendar, gmail, slack); find slugs with toolkits({ search }). |
input.returnUrl | string | yes | Where the user comes back after signing in; https:// (or http://localhost). ?connection=success|failed&toolkit=… is added. Allowed: at most 2000 characters, a URL. |
Returns Promise<string> — The hosted sign-in URL to redirect the user to.
Errors — besides the errors every call can throw:
| Code | HTTP | When |
|---|---|---|
VALIDATION_ERROR | 400 | returnUrl is not https:// (only localhost may be http). |
RECORD_NOT_FOUND | 404 | No app with this slug: look it up with toolkits({ search }). |
TOOLKIT_NOT_AVAILABLE | 400 | This app cannot be connected. |
TOOLKIT_NEEDS_OWN_APP | 400 | This app only signs in through an OAuth app registered by the developer, which is not available. |
Example
// src/app/api/connect/google-calendar/route.ts
const user = await requireUser(); // your own session
const url = await totalum.connections.connectUrl({
userId: user.id,
toolkit: 'googlecalendar',
returnUrl: 'https://acme.totalum.app/settings/integrations',
});
return Response.redirect(url, 302);Result:
"https://connect.<sign-in host>/link/lk_Iem_dXdIv3-i"#connections.list
connections.list(input: { userId, toolkit? }): Promise<Connection[]>The user's connected accounts (newest first), optionally of one app: show them on a settings page with a
"Disconnect" button. status is active when usable. Free.
Parameters
| Name | Type | Required | Description |
|---|---|---|---|
input | { userId, toolkit? } | yes | Your user, and optionally one app. |
input.userId | string | yes | Your signed-in user's id, from the session. |
input.toolkit | string | no | Only connections of this app (googlecalendar). |
Returns Promise<Connection[]> — The connections.
Each item:
| Field | Type | Always present | Description |
|---|---|---|---|
id | string | yes | The connection id: pass it to disconnect(), or as connectionId. |
toolkit | string | yes | The app (googlecalendar). |
status | ConnectionStatus | yes | Whether it can be used: see ConnectionStatus. |
createdAt | string | yes | When the user connected it. |
Errors — besides the errors every call can throw:
| Code | HTTP | When |
|---|---|---|
VALIDATION_ERROR | 400 | userId is empty or over 200 characters. |
Example
const list = await totalum.connections.list({ userId: 'user_42' });Result:
[
{ "id": "ca_8Qw2nZ4kLp", "toolkit": "googlecalendar", "status": "active", "createdAt": "2026-10-02T09:14:00.000Z" },
{ "id": "ca_3Hy7tR1mXc", "toolkit": "gmail", "status": "expired", "createdAt": "2026-08-21T16:40:00.000Z" }
]#connections.isConnected
connections.isConnected(input: { userId, toolkit }): Promise<boolean>Whether the user has an active connection of an app: show "Connect" or the feature that needs it. Free.
Parameters
| Name | Type | Required | Description |
|---|---|---|---|
input | { userId, toolkit } | yes | Your user and the app. |
input.userId | string | yes | Your signed-in user's id, from the session. |
input.toolkit | string | yes | The app's slug (googlecalendar). |
Returns Promise<boolean> — true when the user has an active connection of toolkit.
Errors — besides the errors every call can throw:
| Code | HTTP | When |
|---|---|---|
VALIDATION_ERROR | 400 | userId is empty or over 200 characters. |
Example
const connected = await totalum.connections.isConnected({ userId: 'user_42', toolkit: 'googlecalendar' });Result:
false#connections.disconnect
connections.disconnect(input: { userId, id }): Promise<{ deleted }>Disconnects one of the user's connected accounts (the provider's access is revoked). Free.
Parameters
| Name | Type | Required | Description |
|---|---|---|---|
input | { userId, id } | yes | Your user and the connection. |
input.userId | string | yes | Your signed-in user's id, from the session. |
input.id | string | yes | The connection's id, from list(). |
Returns Promise<{ deleted }> — { deleted: true }.
| Field | Type | Always present | Description |
|---|---|---|---|
deleted | true | yes | Always true. |
Errors — besides the errors every call can throw:
| Code | HTTP | When |
|---|---|---|
RECORD_NOT_FOUND | 404 | The user has no connection with this id. |
Example
await totalum.connections.disconnect({ userId: 'user_42', id: 'ca_8Qw2nZ4kLp' });Result:
{ "deleted": true }#connections.execute
connections.execute(input: { userId, action, input?, connectionId? }): Promise<{ data, usage }>Runs action (e.g. GOOGLECALENDAR_CREATE_EVENT) with the user's active connection of its toolkit and
returns the provider's answer in data. Read the action's inputSchema with action(slug) to build input.
Billed in credits per action.
Parameters
| Name | Type | Required | Description |
|---|---|---|---|
input | { userId, action, input?, connectionId? } | yes | Your user, the action and its input. |
input.userId | string | yes | Your signed-in user's id. Allowed: at least 1 character, at most 200 characters. |
input.action | string | yes | The action's slug (GOOGLECALENDAR_CREATE_EVENT); find it with actions({ toolkit, search }). |
input.input | Record<string, unknown> | no | The action's arguments, as its inputSchema describes (read it with action(slug)). |
input.connectionId | string | no | Which of the user's connections to use, when they have several of the same app. Default: their newest active one. Allowed: at most 64 characters. |
Returns Promise<{ data, usage }> — The provider's answer and what the action cost.
| Field | Type | Always present | Description |
|---|---|---|---|
data | unknown | yes | The provider's answer, as the action's documentation describes (the created event, the sent message, …). |
usage | { credits } | yes | What the call cost. |
usage.credits | number | yes | Credits charged for this call. |
Errors — besides the errors every call can throw:
| Code | HTTP | When |
|---|---|---|
CONNECTION_REQUIRED | 409 | The user has not connected this app yet: send them to connectUrl(); details.action names the action. |
CONNECTION_ACTION_FAILED | 422 | The provider refused the action (a bad input, a missing permission); the message says why. |
RECORD_NOT_FOUND | 404 | No action with this slug. |
PROVIDER_TIMEOUT | 504 | The provider did not answer in time. |
Example
const { data } = await totalum.connections.execute({
userId: 'user_42',
action: 'GOOGLECALENDAR_CREATE_EVENT',
input: {
summary: 'Demo with Acme',
start_datetime: '2026-10-14T10:00:00',
timezone: 'Europe/Madrid',
event_duration_minutes: 30,
attendees: ['ana@example.com'],
},
});Result:
{
"data": { "id": "5r1q8m2v6k0c4n9b", "status": "confirmed", "htmlLink": "https://www.google.com/calendar/event?eid=NXIxcThtMnY2azBj…", … },
"usage": { "credits": 0.0028 }
}#connections.actions
connections.actions(input: { toolkit, search? }): Promise<ConnectionAction[]>The actions an app offers (slug, name, description), optionally filtered by words — to find the slug to pass to
execute(). Free.
Parameters
| Name | Type | Required | Description |
|---|---|---|---|
input | { toolkit, search? } | yes | The app and an optional search. |
input.toolkit | string | yes | The app's slug (googlecalendar). |
input.search | string | no | Only actions matching these words (create event). |
Returns Promise<ConnectionAction[]> — The matching actions, without their input schemas (read one with action(slug)).
Each item:
| Field | Type | Always present | Description |
|---|---|---|---|
slug | string | yes | The action's id, to pass to execute(). |
toolkit | string | yes | The app it belongs to. |
name | string | yes | A short human name. |
description | string | yes | What it does and how to call it. |
inputSchema | Record<string, unknown> | no | The JSON Schema of execute()'s input (only from action(slug)). |
Errors — besides the errors every call can throw:
| Code | HTTP | When |
|---|---|---|
RECORD_NOT_FOUND | 404 | No app with this slug. |
Example
const actions = await totalum.connections.actions({ toolkit: 'googlecalendar', search: 'create event' });Result:
[
{ "slug": "GOOGLECALENDAR_CREATE_EVENT", "toolkit": "googlecalendar", "name": "Create Event", "description": "Creates a timed event using start_datetime and either end_datetime or duration fields, or an all-day event…" },
{ "slug": "GOOGLECALENDAR_QUICK_ADD", "toolkit": "googlecalendar", "name": "Quick Add Event", "description": "Creates an event from a text string such as 'Lunch with Ana tomorrow at 1pm'…" }
]#connections.action
connections.action(slug: string): Promise<ConnectionAction>One action with its inputSchema: the JSON Schema of what execute() takes as input. Free.
Parameters
| Name | Type | Required | Description |
|---|---|---|---|
slug | string | yes | The action's slug (GOOGLECALENDAR_CREATE_EVENT). |
Returns Promise<ConnectionAction> — The action and its input schema.
| Field | Type | Always present | Description |
|---|---|---|---|
slug | string | yes | The action's id, to pass to execute(). |
toolkit | string | yes | The app it belongs to. |
name | string | yes | A short human name. |
description | string | yes | What it does and how to call it. |
inputSchema | Record<string, unknown> | no | The JSON Schema of execute()'s input (only from action(slug)). |
Errors — besides the errors every call can throw:
| Code | HTTP | When |
|---|---|---|
RECORD_NOT_FOUND | 404 | No action with this slug. |
Example
const action = await totalum.connections.action('GOOGLECALENDAR_CREATE_EVENT');Result:
{
"slug": "GOOGLECALENDAR_CREATE_EVENT",
"toolkit": "googlecalendar",
"name": "Create Event",
"description": "Creates a timed event using start_datetime and either end_datetime or duration fields…",
"inputSchema": {
"type": "object",
"properties": {
"summary": { "type": "string", "description": "Summary (title) of the event." },
"start_datetime": { "type": "string", "description": "Event start time…" },
…
}
}
}#connections.toolkits
connections.toolkits(input?: { search?, category?, cursor?, limit? }): Promise<{ toolkits, nextCursor, total }>Searches the catalogue of apps (search matches names and descriptions, category e.g. crm); connect
says how a user connects it: oauth (signs in at the provider), credentials (types their own key on the
hosted page), none (no connection: run its actions directly), unavailable (cannot be connected). Pages
with cursor = the previous nextCursor. Free.
Parameters
| Name | Type | Required | Description |
|---|---|---|---|
input | { search?, category?, cursor?, limit? } | no | The search, category and paging; all optional. |
input.search | string | no | Words to match in app names and descriptions (calendar). |
input.category | string | no | Only apps of this category (crm, email, calendar). |
input.cursor | string | no | The previous page's nextCursor. |
input.limit | number | no | Apps per page. |
Returns Promise<{ toolkits, nextCursor, total }> — One page of apps, the cursor of the next and the total.
| Field | Type | Always present | Description |
|---|---|---|---|
toolkits | ConnectionToolkit[] | yes | The apps of this page. |
nextCursor | string | null | yes | Pass it as cursor for the next page; null on the last. |
total | number | yes | How many apps match in total. |
Errors — besides the errors every call can throw:
| Code | HTTP | When |
|---|---|---|
PROVIDER_UNAVAILABLE | 503 | The catalogue is not available right now; retry later. |
Example
const page = await totalum.connections.toolkits({ search: 'calendar', limit: 2 });Result:
{
"toolkits": [
{ "slug": "googlecalendar", "name": "Google Calendar", "description": "Google Calendar is a time management tool…", "categories": ["scheduling-&-booking"], "authSchemes": ["OAUTH2"], "connect": "oauth", "actionsCount": 47, "triggersCount": 7 },
{ "slug": "outlook", "name": "Outlook", "description": "Outlook is Microsoft's email and calendaring platform…", "categories": ["email", "calendar"], "authSchemes": ["OAUTH2", "S2S_OAUTH2"], "connect": "oauth", "actionsCount": 297, "triggersCount": 5 }
],
"nextCursor": "Mi0y",
"total": 18
}#connections.toolkit
connections.toolkit(slug: string): Promise<ConnectionToolkitDetail>One app, exactly: how it connects and, for credentials, the fields the user types on the hosted page. Free.
Parameters
| Name | Type | Required | Description |
|---|---|---|---|
slug | string | yes | The app's slug (googlecalendar). |
Returns Promise<ConnectionToolkitDetail> — The app's details.
| Field | Type | Always present | Description |
|---|---|---|---|
slug | string | yes | The app's id (googlecalendar). |
name | string | yes | Its name. |
description | string | yes | What it is. |
categories | string[] | yes | Its categories (email, calendar, crm). |
authSchemes | string[] | yes | How the provider authenticates (OAUTH2, API_KEY). |
connect | 'none' | 'credentials' | 'oauth' | 'unavailable' | yes | How your user connects it: oauth (signs in at the provider), credentials (types their own key on the hosted page), none (no connection needed: run its actions directly), unavailable (cannot be connected). |
actionsCount | number | yes | How many actions it offers. |
triggersCount | number | yes | How many event triggers it offers. |
credentialFields | ({ name, label, description })[] | yes | For connect: "credentials": the fields the user types on the hosted page. |
credentialFields.name | string | yes | The field's id. |
credentialFields.label | string | yes | Its label. |
credentialFields.description | string | yes | What to type there. |
Errors — besides the errors every call can throw:
| Code | HTTP | When |
|---|---|---|
RECORD_NOT_FOUND | 404 | No app with this slug. |
Example
const app = await totalum.connections.toolkit('googlecalendar');Result:
{
"slug": "googlecalendar",
"name": "Google Calendar",
"description": "Google Calendar is a time management tool providing scheduling features…",
"categories": ["scheduling-&-booking"],
"authSchemes": ["OAUTH2"],
"connect": "oauth",
"actionsCount": 47,
"triggersCount": 7,
"credentialFields": []
}#connections.triggers
connections.triggers(input: { toolkit }): Promise<ConnectionTrigger[]>The events (triggers) an app can send, such as GMAIL_NEW_GMAIL_MESSAGE — to find the slug to pass to
subscribe(). Free.
Parameters
| Name | Type | Required | Description |
|---|---|---|---|
input | { toolkit } | yes | The app. |
input.toolkit | string | yes | The app's slug (gmail). |
Returns Promise<ConnectionTrigger[]> — The app's triggers, without their schemas (read one with trigger(slug)).
Each item:
| Field | Type | Always present | Description |
|---|---|---|---|
slug | string | yes | The trigger's id, to pass to subscribe(). |
toolkit | string | yes | The app it belongs to. |
name | string | yes | A short human name. |
description | string | yes | When it fires. |
type | string | yes | poll (checked every few minutes) or webhook (pushed by the provider at once). |
instructions | string | no | Setup notes from the provider. |
configSchema | Record<string, unknown> | no | The JSON Schema of subscribe()'s config (only from trigger(slug)). |
payloadSchema | Record<string, unknown> | no | The JSON Schema of each event's payload (only from trigger(slug)). |
Errors — besides the errors every call can throw:
| Code | HTTP | When |
|---|---|---|
RECORD_NOT_FOUND | 404 | No app with this slug. |
Example
const triggers = await totalum.connections.triggers({ toolkit: 'gmail' });Result:
[
{ "slug": "GMAIL_EMAIL_SENT_TRIGGER", "toolkit": "gmail", "name": "Email Sent", "description": "Triggers when a Gmail message is sent by the authenticated user…", "type": "poll" },
{ "slug": "GMAIL_NEW_GMAIL_MESSAGE", "toolkit": "gmail", "name": "New Gmail Message Received Trigger", "description": "Triggers when a new message is received in Gmail.", "type": "poll" }
]#connections.trigger
connections.trigger(slug: string): Promise<ConnectionTrigger>One trigger with configSchema (what subscribe's config takes) and payloadSchema (the event's shape).
Free.
Parameters
| Name | Type | Required | Description |
|---|---|---|---|
slug | string | yes | The trigger's slug (GMAIL_NEW_GMAIL_MESSAGE). |
Returns Promise<ConnectionTrigger> — The trigger and its schemas.
| Field | Type | Always present | Description |
|---|---|---|---|
slug | string | yes | The trigger's id, to pass to subscribe(). |
toolkit | string | yes | The app it belongs to. |
name | string | yes | A short human name. |
description | string | yes | When it fires. |
type | string | yes | poll (checked every few minutes) or webhook (pushed by the provider at once). |
instructions | string | no | Setup notes from the provider. |
configSchema | Record<string, unknown> | no | The JSON Schema of subscribe()'s config (only from trigger(slug)). |
payloadSchema | Record<string, unknown> | no | The JSON Schema of each event's payload (only from trigger(slug)). |
Errors — besides the errors every call can throw:
| Code | HTTP | When |
|---|---|---|
RECORD_NOT_FOUND | 404 | No trigger with this slug. |
Example
const trigger = await totalum.connections.trigger('GMAIL_NEW_GMAIL_MESSAGE');Result:
{
"slug": "GMAIL_NEW_GMAIL_MESSAGE",
"toolkit": "gmail",
"name": "New Gmail Message Received Trigger",
"description": "Triggers when a new message is received in Gmail.",
"type": "poll",
"instructions": "Provide the user ID (usually 'me')…",
"configSchema": {
"properties": {
"interval": { "type": "number", "default": 2, "description": "Periodic Interval to Check for Updates & Send a Trigger in Minutes" },
"labelIds": { "type": "string", "default": "INBOX", "description": "Filter messages by a single label ID…" }
}
},
"payloadSchema": { "properties": { "message_id": { "type": "string" }, "sender": { "type": "string" }, "subject": { "type": "string" }, … } }
}#connections.subscribe
connections.subscribe(input: { userId, trigger, config?, connectionId?, deliverTo? }): Promise<ConnectionSubscription>Watches trigger on the user's active connection of its toolkit: each event is POSTed to deliverTo (a path
of your own app, default /api/webhooks/connections) on the preview (dev users) or the published site (live
users), signed with your project's webhook secret — read it with verifyEvent. Each delivered event is billed in
credits.
Parameters
| Name | Type | Required | Description |
|---|---|---|---|
input | { userId, trigger, config?, connectionId?, deliverTo? } | yes | Your user, the trigger, its configuration and where to deliver. |
input.userId | string | yes | Your signed-in user's id. Allowed: at least 1 character, at most 200 characters. |
input.trigger | string | yes | The trigger's slug (GMAIL_NEW_GMAIL_MESSAGE); find it with triggers({ toolkit }). |
input.config | Record<string, unknown> | no | The trigger's settings, as its configSchema describes (read it with trigger(slug)). |
input.connectionId | string | no | Which of the user's connections to watch, when they have several of the same app. Allowed: at most 64 characters. |
input.deliverTo | string | no | The path of your app the events are POSTed to. Default /api/webhooks/connections. |
Returns Promise<ConnectionSubscription> — The subscription; keep its id to unsubscribe() later.
| Field | Type | Always present | Description |
|---|---|---|---|
id | string | yes | The subscription id, to pass to unsubscribe(). |
trigger | string | yes | The trigger watched. |
connectionId | string | yes | The connected account watched. |
config | Record<string, unknown> | yes | The trigger's settings. |
enabled | boolean | yes | Whether events are being delivered. |
Errors — besides the errors every call can throw:
| Code | HTTP | When |
|---|---|---|
CONNECTION_REQUIRED | 409 | The user has not connected this app yet: send them to connectUrl(). |
RECORD_NOT_FOUND | 404 | No trigger with this slug. |
VALIDATION_ERROR | 400 | config does not match the trigger's configSchema, or deliverTo is not a path. |
Example
const sub = await totalum.connections.subscribe({
userId: 'user_42',
trigger: 'GMAIL_NEW_GMAIL_MESSAGE',
config: { labelIds: 'INBOX', interval: 1 },
});Result:
{ "id": "ti_Zp4Lm8Qw2nXc", "trigger": "GMAIL_NEW_GMAIL_MESSAGE", "connectionId": "ca_3Hy7tR1mXc", "config": { "labelIds": "INBOX", "interval": 1 }, "enabled": true }#connections.subscriptions
connections.subscriptions(input: { userId }): Promise<ConnectionSubscription[]>The user's event subscriptions. Free.
Parameters
| Name | Type | Required | Description |
|---|---|---|---|
input | { userId } | yes | Your user. |
input.userId | string | yes | Your signed-in user's id, from the session. |
Returns Promise<ConnectionSubscription[]> — The subscriptions.
Each item:
| Field | Type | Always present | Description |
|---|---|---|---|
id | string | yes | The subscription id, to pass to unsubscribe(). |
trigger | string | yes | The trigger watched. |
connectionId | string | yes | The connected account watched. |
config | Record<string, unknown> | yes | The trigger's settings. |
enabled | boolean | yes | Whether events are being delivered. |
Errors — besides the errors every call can throw:
| Code | HTTP | When |
|---|---|---|
VALIDATION_ERROR | 400 | userId is empty or over 200 characters. |
Example
const subs = await totalum.connections.subscriptions({ userId: 'user_42' });Result:
[
{ "id": "ti_Zp4Lm8Qw2nXc", "trigger": "GMAIL_NEW_GMAIL_MESSAGE", "connectionId": "ca_3Hy7tR1mXc", "config": { "labelIds": "INBOX", "interval": 1 }, "enabled": true }
]#connections.unsubscribe
connections.unsubscribe(input: { userId, id }): Promise<{ deleted }>Stops one of the user's subscriptions: no more events are delivered. Free.
Parameters
| Name | Type | Required | Description |
|---|---|---|---|
input | { userId, id } | yes | Your user and the subscription. |
input.userId | string | yes | Your signed-in user's id, from the session. |
input.id | string | yes | The subscription's id, from subscribe() or subscriptions(). |
Returns Promise<{ deleted }> — { deleted: true }.
| Field | Type | Always present | Description |
|---|---|---|---|
deleted | true | yes | Always true. |
Errors — besides the errors every call can throw:
| Code | HTTP | When |
|---|---|---|
RECORD_NOT_FOUND | 404 | The user has no subscription with this id. |
Example
await totalum.connections.unsubscribe({ userId: 'user_42', id: 'ti_Zp4Lm8Qw2nXc' });Result:
{ "deleted": true }#connections.verifyEvent
connections.verifyEvent(rawBody: string, headers: Headers | Record<string, string>, secret?: string): Promise<ConnectionEvent>Verifies one delivered event (the raw body text and the request headers of your deliverTo route) with
TOTALUM_WEBHOOK_SECRET and returns it typed: data.userId is your user, data.payload the event. Answer 2xx
quickly; a failed delivery is retried.
Parameters
| Name | Type | Required | Description |
|---|---|---|---|
rawBody | string | yes | The request body as text, untouched (await request.text()). |
headers | Headers | Record<string, string> | yes | The request's headers. |
secret | string | no | The signing secret. Default: TOTALUM_WEBHOOK_SECRET. |
Returns Promise<ConnectionEvent> — The verified event.
| Field | Type | Always present | Description |
|---|---|---|---|
id | string | yes | The event id; handle each id once (a failed delivery is retried). |
createdAt | string | yes | When it was delivered. |
projectId | string | yes | Your project id. |
env | 'dev' | 'live' | yes | dev for the preview's users, live for the published site's. |
type | 'connections.event' | yes | Always connections.event. |
data | { subscriptionId, trigger, userId, connectionId, payload } | yes | The event. |
data.subscriptionId | string | yes | The subscription that produced it. |
data.trigger | string | yes | The trigger slug (GMAIL_NEW_GMAIL_MESSAGE). |
data.userId | string | yes | Your user's id, as given to subscribe(). |
data.connectionId | string | yes | The connected account it came from. |
data.payload | Record<string, unknown> | yes | The event itself, as the trigger's payloadSchema describes (a new e-mail's sender, subject, …). |
livemode | boolean | no | Not set for connection events. |
Errors — besides the errors every call can throw:
| Code | HTTP | When |
|---|---|---|
WEBHOOK_SIGNATURE_INVALID | 401 | Not signed by Totalum (or the body was changed): answer 401. |
WEBHOOK_TIMESTAMP_EXPIRED | 401 | A replayed delivery: answer 401. |
WEBHOOK_SECRET_MISSING | — | No secret is set or passed. |
Example
// src/app/api/webhooks/connections/route.ts
export async function POST(request: Request) {
const event = await totalum.connections.verifyEvent(await request.text(), request.headers);
if (event.data.trigger === 'GMAIL_NEW_GMAIL_MESSAGE') await saveInboxItem(event.data.userId, event.data.payload);
return new Response('ok');
}Result:
{
"id": "evt_01M4P2A9K3J7H5G1F8D6S4A2Q0",
"type": "connections.event",
"createdAt": "2026-10-11T18:20:31.000Z",
"projectId": "acme-crm",
"env": "live",
"data": {
"subscriptionId": "ti_Zp4Lm8Qw2nXc",
"trigger": "GMAIL_NEW_GMAIL_MESSAGE",
"userId": "user_42",
"connectionId": "ca_3Hy7tR1mXc",
"payload": { "message_id": "19a2c4e6f8b0d1e3", "sender": "Ana <ana@example.com>", "subject": "Quote for October", … }
}
}#Connection
One of a user's connected accounts.
| Field | Type | Always present | Description |
|---|---|---|---|
id | string | yes | The connection id: pass it to disconnect(), or as connectionId. |
toolkit | string | yes | The app (googlecalendar). |
status | ConnectionStatus | yes | Whether it can be used: see ConnectionStatus. |
createdAt | string | yes | When the user connected it. |
#ConnectionAction
An action an app offers.
| Field | Type | Always present | Description |
|---|---|---|---|
slug | string | yes | The action's id, to pass to execute(). |
toolkit | string | yes | The app it belongs to. |
name | string | yes | A short human name. |
description | string | yes | What it does and how to call it. |
inputSchema | Record<string, unknown> | no | The JSON Schema of execute()'s input (only from action(slug)). |
#ConnectionEvent
One event from a user's connected account, delivered to your deliverTo route and returned by connections.verifyEvent().
| Field | Type | Always present | Description |
|---|---|---|---|
id | string | yes | The event id; handle each id once (a failed delivery is retried). |
createdAt | string | yes | When it was delivered. |
projectId | string | yes | Your project id. |
env | 'dev' | 'live' | yes | dev for the preview's users, live for the published site's. |
type | 'connections.event' | yes | Always connections.event. |
data | { subscriptionId, trigger, userId, connectionId, payload } | yes | The event. |
data.subscriptionId | string | yes | The subscription that produced it. |
data.trigger | string | yes | The trigger slug (GMAIL_NEW_GMAIL_MESSAGE). |
data.userId | string | yes | Your user's id, as given to subscribe(). |
data.connectionId | string | yes | The connected account it came from. |
data.payload | Record<string, unknown> | yes | The event itself, as the trigger's payloadSchema describes (a new e-mail's sender, subject, …). |
livemode | boolean | no | Not set for connection events. |
#ConnectionStatus
The state of a connected account.
| Value | Meaning |
|---|---|
'expired' | The provider's access expired or was revoked: ask the user to connect again. |
'active' | Connected and usable. |
'failed' | The connection attempt failed: ask the user to connect again. |
'disabled' | Turned off: ask the user to connect again. |
'pending' | The user started connecting but has not finished. |
#ConnectionSubscription
A subscription to a trigger on a user's connected account.
| Field | Type | Always present | Description |
|---|---|---|---|
id | string | yes | The subscription id, to pass to unsubscribe(). |
trigger | string | yes | The trigger watched. |
connectionId | string | yes | The connected account watched. |
config | Record<string, unknown> | yes | The trigger's settings. |
enabled | boolean | yes | Whether events are being delivered. |
#ConnectionToolkit
An app of the catalogue.
| Field | Type | Always present | Description |
|---|---|---|---|
slug | string | yes | The app's id (googlecalendar). |
name | string | yes | Its name. |
description | string | yes | What it is. |
categories | string[] | yes | Its categories (email, calendar, crm). |
authSchemes | string[] | yes | How the provider authenticates (OAUTH2, API_KEY). |
connect | 'none' | 'credentials' | 'oauth' | 'unavailable' | yes | How your user connects it: oauth (signs in at the provider), credentials (types their own key on the hosted page), none (no connection needed: run its actions directly), unavailable (cannot be connected). |
actionsCount | number | yes | How many actions it offers. |
triggersCount | number | yes | How many event triggers it offers. |
#ConnectionToolkitDetail
One app, from connections.toolkit(slug): the catalogue fields plus the credential fields.
| Field | Type | Always present | Description |
|---|---|---|---|
slug | string | yes | The app's id (googlecalendar). |
name | string | yes | Its name. |
description | string | yes | What it is. |
categories | string[] | yes | Its categories (email, calendar, crm). |
authSchemes | string[] | yes | How the provider authenticates (OAUTH2, API_KEY). |
connect | 'none' | 'credentials' | 'oauth' | 'unavailable' | yes | How your user connects it: oauth (signs in at the provider), credentials (types their own key on the hosted page), none (no connection needed: run its actions directly), unavailable (cannot be connected). |
actionsCount | number | yes | How many actions it offers. |
triggersCount | number | yes | How many event triggers it offers. |
credentialFields | ({ name, label, description })[] | yes | For connect: "credentials": the fields the user types on the hosted page. |
credentialFields.name | string | yes | The field's id. |
credentialFields.label | string | yes | Its label. |
credentialFields.description | string | yes | What to type there. |
#ConnectionTrigger
An event an app can send.
| Field | Type | Always present | Description |
|---|---|---|---|
slug | string | yes | The trigger's id, to pass to subscribe(). |
toolkit | string | yes | The app it belongs to. |
name | string | yes | A short human name. |
description | string | yes | When it fires. |
type | string | yes | poll (checked every few minutes) or webhook (pushed by the provider at once). |
instructions | string | no | Setup notes from the provider. |
configSchema | Record<string, unknown> | no | The JSON Schema of subscribe()'s config (only from trigger(slug)). |
payloadSchema | Record<string, unknown> | no | The JSON Schema of each event's payload (only from trigger(slug)). |
