Docs Building Connects

Connects

A Connect is how Aitroop acts as you on another service. Authorize once via OAuth; any Chat or App reuses your scoped token transparently.

The privilege model in one sentence. Connects are per-user by default: your colleagues' Apps run against their own accounts, not yours. Even when you build and share an App, your token never leaves your account. Each user authorizes their own Connects.

How Connects work operationally

  1. You authorize a Connect once per provider, via that provider's OAuth flow.
  2. Aitroop receives an access token and stores it encrypted at rest.
  3. From that point on, any App or Chat that needs that provider uses your token transparently.
  4. The token is scoped to whatever permissions you granted in the OAuth flow.
  5. You can revoke any Connect any time; future calls fail until re-authorized.

Why OAuth?

Three reasons OAuth is the standard rather than API keys:

  • Permissions are scoped. You can grant read-only when you only need reads.
  • Tokens are revocable. Revoke via Aitroop or via the provider's account settings.
  • You can audit. Both Aitroop and the provider log each call.

Authorizing a Connect

Two paths, depending on what triggered the need:

Path A: Proactively, from Settings

  1. Open Settings to Connects (sidebar or top-right menu).
  2. You see two sections: Authorized (active Connects) and Available (providers you can authorize).
  3. Find the provider you want under Available: Google, GitHub, your CRM, etc.
  4. Click Authorize.
  5. A popup opens with the provider's OAuth screen. Review the scopes Aitroop is requesting.
  6. Click Approve (or whatever the provider calls it).
  7. You're returned to Aitroop. The Connect now appears under Authorized with status "Active".

Path B: Just-in-time, during a run

You run an App (or use a Chat) that needs a Connect you haven't authorized. The run pauses and shows:

🔒 This App needs Google Drive access to read your files.

  [ Authorize Google Drive ]   [ Cancel run ]
  1. Click Authorize Google Drive.
  2. OAuth popup opens. Review scopes. Approve.
  3. You're returned to the chat or run. It resumes from where it paused.

What's actually behind Connects

Aitroop doesn't run a separate OAuth integration for each provider; it brokers them through Composio. The platform owns the provider catalogue (categories and definitions seeded into the connect_category and connect_definition tables), and a local mirror of each user's authorization state lives in app_user_connect. Tokens stay with Composio; Aitroop holds the connection ID and the cached "is this user connected to provider X right now" flag.

The provider catalogue

The seeded catalogue at time of writing: 12 categories, 40+ providers. The set is editable from the database; is_enabled = false hides a definition without deleting it.

CategoryProviders
EmailGmail, Outlook, SendGrid, Smartlead, AgentMail
CalendarGoogle Calendar
Code RepositoryGitHub, GitLab, Bitbucket
MessagingSlack, Discord, WhatsApp, Telegram
File StorageGoogle Drive, Dropbox, OneDrive
CRMSalesforce, HubSpot, Pipedrive, Close, Apollo, CrustData, Shopify
Project ManagementLinear, Jira, Notion, Asana
Social MediaTwitter/X, LinkedIn, Facebook, Instagram, TikTok, YouTube
SpreadsheetsGoogle Sheets, Airtable
DocumentsGoogle Docs
AutomationApify, Firecrawl
DatabaseSupabase

Programmatically:

GET /api/connect/connections // your authorized providers
GET /api/connect/status // cached connect satisfaction state
POST /api/connect/initiate // kick off an OAuth flow for a provider
GET /api/connect/callback // OAuth redirect target (no auth)
DELETE /api/connect/:provider // disconnect

How Apps declare which Connects they need

Connect requirements live on resources.connects at the App level, or on stage.connects for per-stage scoping. Two requirement shapes:

"connects": [
  // "I need GitHub specifically"
  { "type": "specific", "id": "github", "is_required": true },

  // "I need any email provider"
  { "type": "category", "id": "email",
    "is_required": true,
    "label": "Any inbox we can send from" }
]

A specific requirement names a single connect_definition.id; the user must have authorized exactly that provider. A category requirement is satisfied if the user has authorized any provider in the connect_category. The satisfaction check runs before each App execution (and again before each scheduled tick); if it fails, the run is held until the user authorizes the missing piece.

The legacy short form "connects": ["github", "hubspot"] is still accepted: each string is upgraded to { type: "specific", id: <string>, is_required: true } on read. Skills define their own connect requirements the same way, and the App's overall satisfaction state merges the App-level requirements with every required skill's requirements.

Reading vs writing: scope selection

Most providers offer separate read and write OAuth scopes. When you authorize, the provider's screen shows what's being requested.

  • Authorize read-only if the App only reads data, for example, a "weekly inbox summary" App.
  • Add write scopes later only when you want the App to write, for example, "send the summary as a reply".

You can re-authorize an existing Connect to add scopes without revoking and re-authorizing from scratch.

Operational example: incremental scope expansion

  1. You authorize Gmail (read-only) for a triage App.
  2. Months later you decide the App should also draft replies for you.
  3. Open Settings to Connects to Gmail to Manage scopes.
  4. Toggle on gmail.compose.
  5. Go through OAuth re-prompt. Approve.
  6. Apps that previously had read-only Gmail can now compose.

Declaring Connects on an App

Each App declares the Connect provider keys it needs in resources.connects:

"resources": {
  "skills": ["web-search"],
  "connects": ["google", "github"]
}

The provider keys are short identifiers like google, github, hubspot, notion, slack. When you build an App conversationally, the App Builder adds the right keys automatically based on what the App needs.

Per-user vs shared Connects

Two operational models depending on the provider and your workspace's configuration:

Per-user Connects (default for personal data)

Each user authorizes their own account. When a teammate runs a shared App, it uses their tokens, not yours.

Example: a shared "Daily inbox triage" App reads your Gmail when you run it, your colleague's when they run it. Each user authorizes Gmail separately.

Shared workspace Connects (for shared accounts)

A workspace admin authorizes once on behalf of the team. All Apps in the workspace use the shared authorization. Useful for systems like a shared Salesforce instance, where everyone should act as the same Aitroop service account.

Configured per Connect by a workspace admin. Look for the Workspace Connect label on the Connect's settings page.

Revoking a Connect

From Aitroop

  1. Open Settings to Connects to Authorized.
  2. Find the Connect to revoke.
  3. Click Revoke.
  4. Confirm the prompt.
  5. The encrypted token is deleted. Status changes to "Revoked".
  6. Apps that need this Connect will prompt for re-authorization on next run.

From the provider's side

You can also revoke from the provider's account settings (e.g., Google's "third-party app access" page). The next time an App tries to use the token, the API call fails. Aitroop detects this and prompts for re-authorization.

Connect-aware AppInputs

The file AppInput type can be configured to pick a file from one of your authorized Connects (Drive, Notion, etc.) instead of requiring a fresh upload.

This is set in the App design: the App Builder picks it automatically when the App's task makes it natural ("summarize a Notion page I'll point at"). The user running the App sees a file picker showing their cloud storage, picks a document, and the agent reads it via the Connect.

The satisfaction model: how the platform decides "OK to run"

Before any App execution begins (and again before every scheduled tick), the platform computes a satisfaction report: for each Connect the App requires, has the user authorized something that satisfies it? Two layers of requirements collapse into one check.

Requirement sources

  1. Direct requirements: what the App itself declares in resources.connects (either App-wide or per-stage).
  2. Skill requirements: every skill the App uses brings its own connects requirements with it. A "send email" skill effectively says "any App that uses me needs an email Connect".

The merged view

The platform deduplicates by (type, id) and produces a single flat list. For each entry, it asks: "is this satisfied?"

typeSatisfied when…
specificThe user has authorized the named provider (e.g. github appears in app_user_connect with connected = true).
categoryThe user has authorized at least one provider whose connect_definition.category_id matches the requirement. Which provider satisfies it is recorded in satisfied_by.

A full satisfaction record for one requirement looks like this, same shape the API exposes via GET /api/connect/status:

{
  "type": "category",
  "id": "email",
  "is_required": true,
  "category_name": "Email",
  "is_satisfied": true,
  "satisfied_by": ["gmail"],
  "available": ["gmail", "outlook", "sendgrid", ...]
}

What happens when something is unsatisfied

The behaviour differs by trigger:

  • Manual run from the UI: the run button is disabled with a tooltip naming the missing piece. Clicking the inline Authorize chip walks through OAuth, and the run button re-enables.
  • Run via API (POST /api/apps/:appId/run): the call returns 422 with the missing requirements listed in the response body. Test runs (is_test: true) skip the check; useful for trying things in the App editor.
  • Scheduled tick: the scheduler doesn't pause indefinitely. It writes last_status = 'error' with the message "Missing required connections: ..." on the task row, leaves next_run_at at its scheduled value, and tries again on the next tick. The schedule's runs page surfaces this so you don't have to monitor every provider yourself.

The cache

Calling Composio to enumerate "what is this user connected to" on every tick would be expensive. Aitroop instead keeps a local mirror in app_user_connect:

ColumnMeaning
user_idWho.
providerWhich connect_definition.id (e.g. github).
connectedtrue when the user currently has a live Composio connection for this provider.
connection_idComposio-side ID; what we hand back when actually invoking an action.
connected_at / updated_atWhen the row last flipped; used for cache freshness.

The cache is invalidated on every successful OAuth completion, on explicit revoke, and on Composio-side webhook delivery. Worst case: the cache lags reality by the webhook delay, after which the next satisfaction check will see the new state.

Confirmation for write actions

When an App or Chat is about to write through a Connect (send mail, create a record, modify a file), the behavior depends on the context:

  • In Chat: the agent shows what it's about to do and waits for explicit approval. You can approve, modify, or cancel.
  • In an App run (manual): approved at App-creation time. Writes happen without per-call confirmation.
  • In a scheduled run: approved at Schedule-creation time. Writes happen automatically.

Write capability never widens silently. Bumping an App from "drafts emails" to "sends emails" requires explicit approval and a new App version.

Security model

  • Tokens stored encrypted at rest. Storage uses workspace-level encryption keys.
  • Tokens never leave the platform. The agent uses tokens via internal service calls; raw tokens are never passed to a language model or exposed in chat transcripts.
  • Sandboxed execution. Each run executes in an isolated E2B sandbox. Tokens are injected per-run; the sandbox is destroyed after.
  • Audit trail. Every Connect call is logged with the App, run, and user that triggered it. Available under Settings to Connects to Audit log.
  • Revocation propagation. Revoking a Connect immediately invalidates the stored token. In-flight runs may complete their current call but won't be able to make new ones.

FAQ & troubleshooting

OAuth popup got blocked / closed early.

Cause: the browser blocked the popup, or you closed it before the provider returned a code.

Fix: Aitroop falls back to redirect mode if the popup fails. Just click Authorize again. If your browser blocks popups for the Aitroop domain, allow them explicitly and retry.

Authorization succeeded but the App still fails with connect_unauthorized.

Two common causes:

  • Wrong account. You authorized with a personal account when the App expected a work account (or vice versa). Sign out of the provider's account in your browser, then Revoke the Connect in Aitroop and re-authorize.
  • Missing scopes. The App needs gmail.send but you only granted gmail.readonly. Open the Connect's settings, click Add scopes, go through OAuth re-prompt.

"Token expired" errors on scheduled runs.

Some providers (Microsoft 365 in particular) rotate refresh tokens periodically. If a Connect's status flips to "Re-auth required", scheduled runs that depend on it start failing.

Fix: open the Connect's settings page; if status is "Re-auth required", click Re-authorize and complete OAuth again. Scheduled runs resume on their next cron tick.

To get alerted earlier, enable Connect health alerts in Settings to Notifications. You'll get a notification 7 days before a known expiry.

I revoked from the provider's side. How do I clean up Aitroop's state?

Open Settings to Connects. The Connect will show as "Revoked" (the platform detects this on the next attempted call). Click Remove to delete the stored token reference entirely, or Re-authorize to restore the Connect.

Can I see exactly what scopes Aitroop has?

Yes. Open the Connect, click Scopes. You'll see a list of granted permissions with a short human-readable description per scope. Compare against what the provider's own audit page shows. They should match; if not, revoke and re-authorize.

Can a teammate use my Connect tokens?

No, unless your admin has set up a shared workspace Connect for that provider (rare, and explicit). By default every user authorizes their own. When a teammate runs your App, it uses their tokens, meaning they need to have authorized the same provider in their own account.

My App needs a Connect that's not in the catalog.

Two paths:

  • Custom MCP server. If the provider has an HTTP API, wrap it in an MCP server and add it as a custom Skill. No Connect needed.
  • Request it. Tell us which provider; the most-requested ones get added each sprint.

How do I audit what an App did with my Connect?

Open the run, click any tool call against the Connect: full request and (truncated) response are logged. The provider's own audit log captures the same calls under your account. The two sources should match; discrepancies usually mean the Connect was revoked mid-run.