One API
Connectors & connected accounts
A connector is a third-party integration a user connects once — Gmail, Microsoft 365, GitHub, Adobe, Slack, Stripe, Notion, … A connected account is that user's live credential row. Tool agents then act as that user: google-gmail reads the mailbox of whoever owns the ozk_ key that made the call. Nothing about the credential ever appears in the API.
Three things are easy to confuse:
| Thing | What it is | Where |
|---|---|---|
Tool agent (google-gmail) | code that calls a provider's API on behalf of a user | tools / agents planes |
Connector (google, github, …) | the OAuth app or API-key slot a user connects | Studio → Integrations |
Connector proxy provider (brave-search, firecrawl, …) | a metered pass-through to a provider's REST API with your own or the platform's key | api.plungeai.com/v1/connect/… (7.5) |
7.1 Two credential tiers
| Tier | How the user connects | Examples | Stored as |
|---|---|---|---|
| OAuth | clicks Continue with <Provider> in Studio, approves at the provider, lands back connected | Google Workspace, Microsoft 365, Adobe, GitHub, GitLab, Slack, Salesforce, HubSpot, Bitbucket, Shopify | encrypted access + refresh token; refreshed server-side when tokensExpire |
| API key / PAT | pastes a key in the connector dialog | Anthropic, OpenAI, Stripe, Notion (internal token), GitLab PAT, Linear, Jira, Datadog, … | encrypted key; verified live before storage (core/core-secrets/api-key-verify.ts probes the provider — a rejected key is never saved) |
Both land in user_credentials on the compliance database, AES-256-GCM encrypted with per-scope keys; every read is audited (core/core-secrets/CLAUDE.md). Agents never receive raw tokens from callers — the platform injects them at run time, which is why params names such as api_key or token are stripped by contract validation.
7.2 How a connected account flows into an API call
your app ──ozk_ key──► One API ──user_id──► engine ──► google-gmail agent
│ resolveApiKey(google, user)
▼
user_credentials row? yes → call Gmail as the user
no → outcome: needs_connectionWithout a connection the agent answers with a structured refusal (success: false, outcome: needs_connection) and the tools plane returns 424 connection_required:
{ "error": { "code": "connection_required", "message": "Connect your Google account in Studio → Integrations to use google-gmail", "outcome": "needs_connection", "connect": { "provider": "google", "method": "oauth", "link": "POST /v1/connections/google/link" } } }connect is the way in: the connector (provider), how it connects (method: oauth, api_key or wallet) and the route that starts it (7.3). Every 424 from the tools, agents, workflows and Agent Call routes carries it when the agent is connection-tier; the Markdown and text wires print it as connect: POST /v1/connections/<provider>/link under the execution footer. credential_required (needs_api_key) carries it the same way.
The same run with a connection returns 200 { ok: true, content } — live example from 2026-09-18 with a Microsoft 365 connection on the calling account (microsoft-todo, operation list_lists, request id 00000000-0000-4000-8000-000000000003).
Which agents need what: the credential field on every card (GET /v1/tools/<id>, the lists, GET /v1/discovery/search) — { "tier": "platform" } or { "tier": "connection", "provider", "method" }; the Credential column in the tool catalog is that field. The rule of thumb: google-* needs the google connector (except the key-based tool APIs google-books, google-translate, google-places-agent, google-cloud-iot-agent), microsoft-* needs Microsoft 365 (connector id azure), adobe-* Adobe (not adobe-pdf-services), gitlab-agent GitLab, stripe-agent a Stripe key; search/scrape/document tools run on platform keys.
7.3 How your testers connect an account
Connecting is an API call. The ozk_ key that makes the call owns the connection, and no Studio login is needed:
| Call | Answer | |
|---|---|---|
| Bring a key | POST /v1/connections/:provider · { "api_key", "api_secret"?, "on_behalf_of"? } | 201 { provider, on_behalf_of, status: "connected" }; 400 credential_rejected when the provider refuses the key (providers with a live probe — GitLab today — are checked before anything is stored); 404 unknown_provider |
| Send a hosted link | POST /v1/connections/:provider/link · { "on_behalf_of"?, "return_url"? } | 201 { url, expires_at } |
| List | GET /v1/connections?on_behalf_of= | 200 { connections: [ { provider, on_behalf_of, method, status, connected_at } ] } — status only; no token, key or hint is ever returned |
| Disconnect | DELETE /v1/connections/:provider?on_behalf_of= | 204; 404 connection_not_found |
The hosted link is https://studio.plungeai.com/connect/<token>: signed, single-use, valid 15 minutes, and it works without a Studio session — send it to the person who owns the account. The page opens with who is asking — your Studio name, else your email — and says that your agents will act on the account on their behalf until they disconnect it. For an OAuth provider the page is Continue with <Provider> → approve at the provider → back to your return_url (an https URL you gave, with ?connected=<provider> added) or, without one, a confirmation page. Where Studio has no OAuth app for the provider the page is a key form: the pasted key is checked and stored the same way as POST /v1/connections/:provider. A tampered or expired link is 404, a used one 410.
curl -s -X POST "https://api.plungeai.com/v1/connections/google/link" \
-H "Authorization: Bearer $PLUNGE_API_KEY" -H "Content-Type: application/json" \
-d '{"return_url":"https://example.com/done"}'Your customers (on_behalf_of). Name a customer with any id you own — 1 to 128 characters of A-Z a-z 0-9 _ . - (no .., no :) — and the connection is that customer's, not yours: it is stored under your key and your customer id, so two developers who use the same id never collide, GET /v1/connections shows only the account you ask for (yours, or the one on_behalf_of names) and nobody can reach a customer of another key. An empty on_behalf_of is refused, never read as your own account.
To run an agent as a customer, send the same id on the call: POST /v1/execute with on_behalf_of: cust_42 next to agent:. A connection-tier agent then uses the account connected under that id (and answers 424 with connect when that customer has none) — never yours; the run, its billing and a saved agent stay yours, and the result is returned to you as usual. It is a wire key, like format: it never reaches the agent as a field. It is accepted on POST /v1/execute only: the per-id doors (/v1/tools/{id}/execute, /v1/agents/{id}/execute, /v1/workflows/{id}/execute and their stream variants) answer 400 invalid_body rather than quietly running as you.
Provider ids are the connector ids of 7.4 — the credential.provider of a card or the connect.provider of a 424 names the one to use (google, azure for Microsoft 365, github, slack, stripe, …).
The same connection can be made in Ocean Studio (https://studio.plungeai.com), where a signed-in user connects their own account in a browser:
- Studio → Integrations → Browse connectors → pick the provider.
- OAuth connector: Continue with <Provider> → approve → back in Studio as Connected. API-key connector: paste the key → it is probed live → Connected (or an inline "rejected" error).
- Verify with the API: run the agent with the same user's
ozk_key — theneeds_connectionoutcome disappears.
The Studio endpoints behind those buttons (session cookie, not API key — listed so you know what happens, not for you to call): GET /api/connections (yours), GET /api/connections/providers (public: every OAuth provider and its scopes), GET /api/auth/oauth/:provider/start, POST /api/connections/api-key (400 when the live probe rejects the key), DELETE /api/connections/:provider. Special flows exist for the Circle agent wallet (/api/connections/circle-agent-wallet/init|verify), retailer logins such as Amazon (/api/connections/retailer/connect) and QR/browser logins such as WhatsApp (/api/connections/browser-session/*) — those connect in Studio only.
curl -s "https://studio.plungeai.com/api/connections/providers"Redirect URIs and OAuth app registration are platform-side — testers never configure anything at the provider.
7.4 The connector catalog
The registry lists 802 connector cards (GET /v1/discovery/search?kind=connectors&limit=100, page with offset until total; live 2026-09-29). Every card carries connector.auth — never null:
connector.auth | Meaning | Count |
|---|---|---|
oauth_static | a platform-registered OAuth app; user clicks Continue | 46 |
api_key | user pastes a key/PAT | 83 |
oauth_dcr | an MCP server the user connects with Dynamic Client Registration (see MCP plane) | 519 |
none | platform-key or public provider — nothing to connect. Also what a card reads when its source declared no auth (57 of the 154 today: 35 community mirrors and 22 legacy tool cards) | 154 |
curl -s "https://api.plungeai.com/v1/discovery/search?kind=connectors&limit=3" \
-H "Authorization: Bearer $PLUNGE_API_KEY"curl -s "https://api.plungeai.com/v1/discovery/cards/connector/slack" \
-H "Authorization: Bearer $PLUNGE_API_KEY"OAuth apps registered on the platform today (client id wired in the Studio worker, wrangler-templates/template.wrangler.ocean.jsonc ↔ core/core-secrets/oauth-providers.ts): google (Workspace: Drive, Gmail, Calendar, Contacts, Tasks, Docs, Sheets, Slides, Analytics, Search Console, Merchant Center, Business Profile, YouTube; google-youtube also connects YouTube on its own), azure (Microsoft 365: OneDrive, Outlook, Calendar, Teams, SharePoint, To Do, Planner, OneNote, Office files), adobe (Creative Cloud, Express, Photoshop, Lightroom, Stock, Experience Platform, Journey Optimizer), github, gitlab, bitbucket, slack, salesforce, hubspot, shopify. Provider entries also exist for sonos, spotify, tesla, facebook, linkedin, twitter, reddit, tiktok, pinterest, snapchat, intuit, square, zoom, sentry, notion — their cards show in Studio, but the OAuth app is not registered yet, so Continue with … bounces with oauth_not_configured; connect those with an API key where the card offers one.
By category (ids as the registry spells them):
| Group | Connectors |
|---|---|
| AI & ML | openai, anthropic, huggingface, ibm |
google (Workspace umbrella), google-drive, google-gmail, google-calendar, google-contacts, google-tasks, google-youtube | |
| Microsoft | azure (Microsoft 365 umbrella), microsoft-onedrive, microsoft-outlook-mail, microsoft-outlook-calendar, microsoft-teams, microsoft-sharepoint, microsoft-365 |
| Adobe | adobe, adobe-express, adobe-photoshop, adobe-lightroom, adobe-stock, adobe-cc-files, adobe-aep, adobe-ajo, adobe-workfront |
| Developer & cloud | github, gitlab, bitbucket, postman, aws, cloudflare, vercel, netlify, supabase, databricks, snowflake |
| Observability | sentry, datadog, grafana-mcp-server, honeycomb, incident-io, logrocket-mcp, jam |
| Communication | slack, discord, telegram, twilio, sendgrid, mailchimp, resend, whatsapp, whatsapp-personal |
| Productivity & PM | notion, airtable, asana, jira, linear, monday, clickup, trello, basecamp, teamwork, craft, mem, roam-research, ticktick, … (28 productivity + 19 project-management cards) |
| CRM, sales & marketing | salesforce, hubspot, pipedrive, zoho, close, helpscout, intercom, attio, apollo-io, klaviyo, lemlist, outreach, salesloft, zoominfo, … (49 marketing-and-sales cards) |
| Payments & finance | stripe, paypal, square, plaid, xero, circle, circle-agent-wallet, x402-wallet, intuit, intuit-quickbooks, intuit-payments, intuit-payroll, intuit-time-tracking, airwallex |
| Data & analytics | amplitude, mixpanel, segment, looker, alpha-vantage |
| Search & scraping (platform-key providers) | brave-search, exa, firecrawl, crawl4ai, browserbase, scrapingbee, serpapi, serper, perplexity-search, news-api, nyt, arxiv, wikipedia, product-hunt, weather, wolfram-alpha |
| Social | facebook, instagram, threads, linkedin, twitter, reddit, pinterest, snapchat, tiktok, youtube, bereal, clubhouse |
| Commerce & travel | shopify, amazon, target-app, shop, instacart, expedia, booking, tripadvisor, uber, viator, kiwi-com, amadeus, … |
| Legal, security, research, lifestyle | 13 legal, 15 security, 8 research and 11 lifestyle MCP-server connectors (oauth_dcr / none) |
7.5 The connector proxy — /v1/connect/:provider/proxy/*
For providers you would otherwise call directly, the inference gateway offers a metered pass-through (inference/gateway/src/routes/connector-proxy.ts): one ozk_ key, one bill, transparent bytes in both directions (streaming included, no body transformation).
| Key | your ozk_… platform key (Dashboard → One API → Keys, https://dashboard.plungeai.com); a legacy sk-conn-… key is still accepted until it is revoked and replaced |
| Route | ANY https://api.plungeai.com/v1/connect/<provider_id>/proxy/<upstream path> — the path after /proxy/ is appended to the provider's base_url |
Mode (from the connector_providers rate card) | byok — your own upstream token connected for the org is used, you pay byok_routing_fee; platform — the platform's key is used, you pay price_per_call; both — your token if connected, else the platform's |
| No connection, provider is BYOK-only | 402 "connection required" |
| Unknown or inactive provider | 404 model_not_found / 400 provider_unavailable |
| Limits | per-provider rate_limit_rpm; org spend caps and guardrails apply exactly as on the models plane |
| Auth failures | 401 invalid_api_key; a suspended or closed account is 401 Account suspended |
Example (one metered provider call, so it carries no # verify marker):
curl "https://api.plungeai.com/v1/connect/brave-search/proxy/res/v1/web/search?q=cloudflare+workers" \
-H "Authorization: Bearer $PLUNGE_API_KEY"Each call is metered as a connector_requests row and appears in the Dashboard next to your inference spend. Prefer the tools plane when you want the agent's judgement (result formatting, pagination, outcome vocabulary); use the proxy when you want the provider's raw API with one key and one bill.