Concepts

Connections & integrations

Your tool calls `ellivate.connection('google-calendar')` and gets back a typed client whose `.fetch()` already has a valid, auto-refreshing OAuth token attached. The user connects their Google account once in Ellivate; from then on, every tool of yours that touches Calendar / Drive / Gmail just works.

The one pattern

# Python (Flask / FastAPI / Streamlit)
import ellivate

cal = ellivate.connection("google-calendar")

try:
    event = cal.fetch_json(
        cal.api_base + "/calendars/primary/events",
        method="POST",
        json_body={
            "summary": "Lunch with Alice",
            "start": {"dateTime": "2026-06-01T12:00:00-07:00"},
            "end":   {"dateTime": "2026-06-01T13:00:00-07:00"},
        },
    )
except ellivate.EllivateConnectionNeeded as e:
    # Render a "Connect your Google account" prompt
    return render_connect_prompt(e.provider_display_name)
// JS / TS (Next.js, Vite, vanilla HTML, Node)
import { ellivate, EllivateConnectionNeeded } from "../ellivate-client";

const cal = ellivate.connection("google-calendar");

try {
  const event = await cal.fetchJson(
    cal.apiBase + "/calendars/primary/events",
    {
      method: "POST",
      body: JSON.stringify({
        summary: "Lunch with Alice",
        start: { dateTime: "2026-06-01T12:00:00-07:00" },
        end:   { dateTime: "2026-06-01T13:00:00-07:00" },
      }),
    }
  );
} catch (err) {
  if (err instanceof EllivateConnectionNeeded) {
    // Surface a "Connect your Google account" UI
  }
}

That's the whole thing. The first time the user's tool hits a connection-bearing call, they get prompted to connect once. After that, Ellivate manages everything — token storage, refresh on expiry, rotation, revocation.

What this replaces

Without managed connections, an app that wants to talk to Google Calendar needs to:

  • Register an OAuth app at Google Cloud Console
  • Get a client ID + client secret per app
  • Build the OAuth flow: consent screen, redirect handler, token exchange
  • Store access + refresh tokens somewhere persistent
  • Track expiry, refresh transparently, handle refresh failure
  • Surface a "connect / disconnect" UI to the user

That's an afternoon of work even for someone who's done it before — and it's table-stakes infra, not the thing you actually want to build. Ellivate handles all of it. You write ellivate.connection("google-calendar") and get a working client.

PERSONAL vs SHARED

Connections have a scope — same axis as data scopes, but smaller (no PUBLIC):

  • PERSONAL (the default) — each viewer connects their own account. Right for "sync MY tasks to MY calendar", "remind ME", anything where the data is per-user. Doesn't block publish; viewers are prompted to connect on first use.
  • SHARED — the builder's account, used by all viewers. Right for "family meal planner writes to the family calendar", "team standup tracker posts to one Slack channel". Blocks publish until the builder has connected — the connection is part of the app's identity.
// PERSONAL (default)
ellivate.connection("google-calendar");

// SHARED — builder's account
ellivate.connection("google-calendar", { scope: "SHARED" });

When uncertain, default PERSONAL. The publisher can flip it on the publish page if the classifier guessed wrong.

EllivateConnectionNeeded — surface a prompt, don't swallow

The most common runtime exception. Fires on:

  • Viewer has never connected this provider (PERSONAL)
  • Builder has never connected (SHARED)
  • The connection was revoked
  • Connection exists but doesn't grant required scopes

The exception carries the fields needed for a good prompt: provider, providerDisplayName, scope, oauthScopes, optionally missingScopes. Show a "Connect [Provider]" button — the Ellivate shell (web or mobile) handles the actual OAuth flow when the user taps it.

Supported integrations

The list is intentionally curated. Each integration is an Ellivate-side configuration that registers the OAuth app, stores client credentials, and defines the API base + scope catalog.

Integration keyWhat it grantsProvider scope
google-calendarRead + write events on the viewer's calendarscalendar.events
google-driveCreate + read + edit Drive files the tool itself made on the viewer's behalf. Cannot see or touch anything the tool didn't create.drive.file
google-sheetsRead + write rows on any spreadsheet whose ID the tool already knows (typically one it created via google-drive).spreadsheets
slackPost messages as the viewer — DMs to themselves, posts in channels they belong to. Cannot read messages or list channels.chat:write
stravaRead the viewer's basic profile + non-private activities. Cannot see private activities or upload new ones.read, activity:read

More integrations land as Ellivate ships them — each new key is a config-only change on the Ellivate side, so adding one is fast once we've decided to support a service. If your tool needs something not on this list, file a request — we prioritize integrations by user demand, not by corpus order.

How it actually works

The mechanics, in case you're debugging or just curious:

  1. ellivate.connection(name) is synchronous and cheap — just returns a handle.
  2. First call to .fetch() / .fetchJson() triggers a token-fetch against Ellivate's API. Ellivate looks up the user's connection row, refreshes the access token if it's near expiry, and hands the SDK the plaintext token.
  3. The SDK caches the token in-memory until ~30s before expiry, so back-to-back calls don't round-trip.
  4. The SDK calls the provider's API directly with Authorization: Bearer <token>. Ellivate is NOT in the data path — we don't see the actual request bodies.
  5. On a 401 from the provider, the SDK force-refreshes the token once and retries. If that still fails, the connection is marked for reconnect and the next call throws EllivateConnectionNeeded.

What we're not

For sites with no public API — or sites the user prefers to authenticate with a stored browser session — use the older Stored sessions path instead (Playwright storage_state JSON, attached per-app). That's a different primitive with a different shape; see the Connections tab in your dashboard for the existing functionality.

For services with a raw API key (not OAuth — e.g., OpenWeather, Stripe), use process.env.MY_API_KEY + add the value via your app's Connections page. Managed OAuth handles the "here's a credential the user has to interactively authorize" case; raw API keys are a different surface.

What's next