Skip to main content
This page is for a product whose users each connect their own Stripe, Linear, Notion or Google account — the way they would connect one to any SaaS app. Connecting your own account to a script needs none of it: your own account covers that.
Every pack serves many users the same way. Configure it once, with a SubjectProvider that builds one user’s credential from what you stored when they connected. Then name the user around each run:
many_users.py
The tools are built once, at import, and shared by every user. The credential is looked up below every adapter, so this works the same through MCP, LangChain, the OpenAI tools interface and the Claude Agent SDK. No per-call header has to get through the agent framework. A call with no user set raises instead of using someone else’s credential. Serving users from more than one process? Where the provider rotates refresh tokens (Stripe, Linear, Slack with rotation on), pass the client a function that reads the stored grant rather than the refresh token itself, as the Linear and Stripe pages do. A copy goes stale the moment another process refreshes, and Stripe’s refresh also revokes the other processes’ access tokens: More than one process.

Every pack

The connect column is what your user does. Store is what you keep afterwards. Provider is what for_user returns. Every row takes the same OAuth2Flow, with a different server constant. Getting the grant has the full shape: the two routes, state, PKCE, and what to store. A pasted key needs no flow. Show a field, check the key with one call, and store it the way you store any other secret. Every OAuth row needs an app registered with the provider first: a redirect URL, the scopes, and the client ID and secret the flow is built from. Each of these has a screenshot for every screen: Google, Slack, GitHub, Linear, Notion, Stripe, Shopify and Microsoft.

Each provider

Every OAuth provider has a page with its server constant, the flow and the per-user client, what a refresh does, and how a user is disconnected.

Linear

Linear: refresh tokens that rotate with a 30-minute grace, and a pasted personal key beside OAuth in one configuration.

Stripe

Stripe: a Stripe App, its test link before you publish, and a pasted restricted key as the other path.

Notion

Notion: a link that asks for no scopes, and what a refresh does that Notion’s docs leave out.

Shopify

Shopify: a server per store, shop= per call, and the expiring offline token Shopify now requires.

Microsoft

Microsoft: a tenant segment that decides who can connect, the offline_access a refresh token needs, and no revocation endpoint. The original three are Google, Slack and GitHub.

Disconnecting a user

A “Disconnect” button revokes the grant at the provider, then deletes your row. Deleting the row alone leaves the grant live, with every scope it was given, and your app still listed on the user’s account. The SubjectProvider the pack is configured with does both lookups for you:
disconnect.py
revoke(user_id) returns False when the grant was already gone (the user removed the app on the provider’s side first) and raises only when the provider refused for some other reason. Delete the row after it, not before: the client reads the grant from your store to know what to revoke, and under refresh_lock it is the grant the store holds, not one another process has already rotated. What each server is sent is on its declaration, so the call is the same for every pack. What each one does with it, measured live, is on its page:
  • Google: the refresh token, which ends every grant your app holds for that account
  • Slack: apps.uninstall, which removes the installation
  • GitHub: DELETE /applications/{client_id}/grant, with an access token
  • Linear: the refresh token, with the token_type_hint Linear needs
  • Notion: the access token; Notion answers 200 to any token
  • Shopify: uninstalls the app, and with it every grant on that store
  • Stripe: uninstalls the Stripe App, through the App Installs API
A pasted key (Linear’s personal key, a Stripe restricted key, Granola) was made by the user, and only they can delete it. Delete the row.

What stays yours

The same line as everywhere in Charter’s auth. The consent routes, the session that holds state and the PKCE verifier, the database the grants live in, and any encryption at rest are your application’s. Charter builds the link, exchanges the code, refreshes the grant, revokes it when the user disconnects, and puts the right user’s credential on each call. Security invariants lists the rules the callback has to keep.