- Doesn’t require users to leave the dashboard to generate keys.
- Records the workspace name / team / repo owner automatically (visible in
GET /v1/cofounder/integrations). - Uses provider-issued tokens that can be revoked from the provider’s UI.
- Lets you offer the same SaaS experience your users expect from any modern integrations page.
How the flow works
:provider ∈ slack | linear | github | notion.
Frontend usage
?cofounder_oauth=success&provider=slack&account=Acme%20Workspace in the query string. Detect this in your page-load code, refresh the integrations list, and show a toast.
Failure cases also redirect back, with ?cofounder_oauth=denied (user clicked Cancel) or ?cofounder_oauth=error&error=... (token exchange failed). Always inspect both shapes.
Server configuration
For each provider you want to support, create an OAuth client in the provider’s developer console. Set the Redirect URI to:
Cross-cutting:
return_to MUST start with one of the allowlisted prefixes — otherwise it’s rejected (open-redirect guard).
What gets stored
After a successful OAuth round-trip, a row is upserted incofounder_integrations:
encrypted_credentials is never returned by GET /v1/cofounder/integrations. The dashboard only sees the metadata above plus enabled and last_used_at.
Re-connect / Switch workspace
Re-running/oauth/:provider/start and completing the flow upserts the same (owner_email, provider) row — pointing the integration at the new workspace. The previous token is overwritten and effectively forgotten by the agent (it remains valid on the provider’s side until the user revokes it from there).
Disconnect
State row lifecycle
Each/start creates a cofounder_oauth_states row that:
- Holds the random
state, the user’s email, the provider, and (if applicable) the PKCEcode_verifier. - Expires after 10 minutes.
- Is deleted as soon as the callback consumes it (single-use).
/start call.
Manual API-key path (still supported)
If you’d rather skip OAuth — for example, scripting your own integration — you canPUT /v1/cofounder/integrations/:provider with the credential JSON directly. See each provider’s page for the schema. OAuth-managed and manually-managed rows coexist; the latest one wins.
