Mixedbread
Bridge

Bridge

Bridge runs Gmail, Google Drive, Slack, and Granola connections for your users under your own OAuth apps. Your backend calls the API with one organization API key; users consent on Google or Slack and return to your platform.

OAuth setup

Register your apps once per organization under Bridge → Apps in the dashboard. Each card's dialog shows the redirect URI, the scopes, and the Slack manifest to copy.

AppSecrets you enterWhere they come from
GmailClient ID, Client secret, Return URLA Google Cloud OAuth client ID (Web application) with the Gmail API enabled and the dialog's redirect URI authorized. Scope gmail.readonly.
Google DriveClient ID, Client secret, Return URLSame, with the Google Drive API enabled and the Drive redirect URI authorized. Scope drive.readonly.
SlackClient ID, Client secret, Signing secret, Return URLA Slack app created From a manifest with the manifest in the dialog. Credentials are under Basic Information → App Credentials.
GranolaNoneEach user pastes a personal Granola API key into your platform.

Gmail and Google Drive are separate apps: enter one Google client on both cards or a different client on each. A shared client needs both redirect URIs authorized. Publish the Google consent screen; an app left in Testing gets refresh tokens that expire after 7 days.

Return URL

The page on your platform where users land after consent, for example https://platform.example.com/connected (http://localhost is allowed for development). The callback appends these query parameters:

ParameterValue
providergmail, google_drive, or slack
statusconnected or error
installation_idThe new installation, when status=connected. Store it against your user.
errorWhy consent failed, when status=error
errorMeaning
access_deniedThe user declined consent
missing_codeThe provider returned no authorization code
account_already_connectedGmail and Drive: the Google account is connected in another Mixedbread organization. Disconnect it there first.
workspace_already_connectedSlack: the workspace is connected in another Mixedbread organization
reconnect_account_mismatchGmail: a reconnect used a different Google account
invalid_stateThe consent link expired or was reused. Start again.
token_exchange_failedGoogle rejected the code. Check the client secret and the redirect URI.
callback_failedAnything else

API reference

OAuth clients

EndpointMethodPath
List OAuth ClientsGET/v1/integrations/oauth-clients
Register OAuth ClientPUT/v1/integrations/oauth-clients/{provider}
Delete OAuth ClientDELETE/v1/integrations/oauth-clients/{provider}
List OAuth Client ConnectionsGET/v1/integrations/oauth-clients/{provider}/connections

Connect

EndpointMethodPath
Authorize GmailGET/v1/integrations/gmail/oauth/authorize
Authorize Google DriveGET/v1/integrations/google_drive/oauth/authorize
Authorize SlackGET/v1/integrations/slack/oauth/authorize
Connect GranolaPOST/v1/integrations/granola/connect

Installations

EndpointMethodPath
List InstallationsGET/v1/integrations/installations
Get InstallationGET/v1/integrations/installations/{installation_id}
Disconnect InstallationDELETE/v1/integrations/installations/{installation_id}
Sync InstallationPOST/v1/integrations/installations/{installation_id}/sync
Update Granola SchedulePUT/v1/integrations/granola/schedule
Replace ResourcesPUT/v1/integrations/installations/{installation_id}/resources
List Failed ResourcesGET/v1/integrations/installations/{installation_id}/resources/failed
Refresh ResourcesPOST/v1/integrations/installations/{installation_id}/resources/refresh

Stores

EndpointMethodPath
Get StoreGET/v1/stores/{store_identifier}
List Store FilesPOST/v1/stores/{store_identifier}/files/list
Search ChunksPOST/v1/stores/search

Gmail

curl -s -o /dev/null -w '%{redirect_url}' \
  'https://api.mixedbread.com/v1/integrations/gmail/oauth/authorize?store_name=mail-42' \
  -H 'Authorization: Bearer YOUR_API_KEY'

Redirect the browser to the printed URL. store_name names the user's store; omit it for gmail-<email>.

The browser arrives at https://platform.example.com/connected?provider=gmail&status=connected&installation_id=…. Store installation_id against your user, then read the store id:

curl https://api.mixedbread.com/v1/integrations/installations/INSTALLATION_ID \
  -H 'Authorization: Bearer YOUR_API_KEY'
{
  "data": {
    "installation_id": "2f1c9a8e-6b4d-4e3a-9c1f-7d8e5b6a4c21",
    "provider": "gmail",
    "status": "active",
    "account": { "id": "olivia@example.com", "name": "olivia@example.com", "email": "olivia@example.com", "workspace": null },
    "store_id": "5d3e7f9a-1b2c-4d6e-8f0a-9c8b7a6d5e4f",
    "store_name": "mail-42",
    "sync": { "failed_resource_count": 0, "last_error": null, "last_error_operation": null, "last_error_at": null }
  }
}

Keep installation_id and store_id per user. Mixedbread keeps no link between your users and their installations.

curl -X POST https://api.mixedbread.com/v1/integrations/installations/INSTALLATION_ID/sync \
  -H 'Content-Type: application/json' \
  -H 'Authorization: Bearer YOUR_API_KEY' \
  -d '{"sync_all": true}'

Every message outside spam, trash, and drafts is imported, and new mail is picked up every ten minutes. To sync selected labels instead, list them and replace the selection:

curl https://api.mixedbread.com/v1/integrations/installations/INSTALLATION_ID/resources \
  -H 'Authorization: Bearer YOUR_API_KEY'

curl -X PUT https://api.mixedbread.com/v1/integrations/installations/INSTALLATION_ID/resources \
  -H 'Content-Type: application/json' \
  -H 'Authorization: Bearer YOUR_API_KEY' \
  -d '{"resource_ids": ["INBOX", "Label_12"]}'
# installation status and account-level errors
curl https://api.mixedbread.com/v1/integrations/installations/INSTALLATION_ID \
  -H 'Authorization: Bearer YOUR_API_KEY'

# per-resource outcome in resources[].sync.last_run
curl https://api.mixedbread.com/v1/integrations/installations/INSTALLATION_ID/resources \
  -H 'Authorization: Bearer YOUR_API_KEY'

# resources whose last job failed
curl https://api.mixedbread.com/v1/integrations/installations/INSTALLATION_ID/resources/failed \
  -H 'Authorization: Bearer YOUR_API_KEY'

# files ingested so far, in file_counts
curl https://api.mixedbread.com/v1/stores/STORE_ID \
  -H 'Authorization: Bearer YOUR_API_KEY'

status: "needs_reauth" on the installation means the user must consent again: repeat step 1 with installation_id=INSTALLATION_ID so the same installation is reused. last_run.status is in_progress, completed, failed, or stale; a large mailbox stays in_progress across slices until the label is done. To list individual files, use List Store Files.

curl -X POST https://api.mixedbread.com/v1/stores/search \
  -H 'Content-Type: application/json' \
  -H 'Authorization: Bearer YOUR_API_KEY' \
  -d '{"query": "renewal terms", "store_identifiers": ["STORE_ID"], "top_k": 10}'

Pass only the store ids that belong to the signed-in user. The organization key can search every store.

Google Drive

Same flow as Gmail with /v1/integrations/google_drive/oauth/authorize. Sync the whole Drive:

curl -X POST https://api.mixedbread.com/v1/integrations/installations/INSTALLATION_ID/sync \
  -H 'Content-Type: application/json' \
  -H 'Authorization: Bearer YOUR_API_KEY' \
  -d '{"sync_all": true}'

Differences:

  • sync_all runs one job over the whole Drive, Shared with me included, and registers push notifications for changes. queued_resource_ids is empty and the root folders keep last_run: null; watch file_counts on the store instead.
  • Resources are hierarchical: list a folder's children with ?parent_id=FOLDER_ID. Selecting a folder syncs its subtree.
  • Files Drive cannot export (unsupported types, empty files) are skipped, not failed.
  • Reconnect after needs_reauth by repeating step 1 with the same Google account.

Slack

Same flow with /v1/integrations/slack/oauth/authorize. Sync every conversation the user is in:

curl -X POST https://api.mixedbread.com/v1/integrations/installations/INSTALLATION_ID/sync \
  -H 'Content-Type: application/json' \
  -H 'Authorization: Bearer YOUR_API_KEY' \
  -d '{"sync_all": true}'

Differences:

  • Users connect as themselves (access_mode=user, the default), each with their own installation and store. account.workspace holds the workspace name.
  • sync_all covers every conversation the user is a member of: channels, private channels, group messages, and direct messages. Public channels they have not joined are skipped with not_a_member.
  • Right after connecting, the conversation catalog is still being built: the listing reports listing_status: "loading" and sync_all is applied when the walk completes. Refresh Resources rebuilds the catalog later, for example after the user joins channels.
  • New messages arrive through Slack events verified with the signing secret you registered.

Granola

No OAuth. Connect with the user's API key and state who it belongs to:

curl -X POST https://api.mixedbread.com/v1/integrations/granola/connect \
  -H 'Content-Type: application/json' \
  -H 'Authorization: Bearer YOUR_API_KEY' \
  -d '{
    "api_key": "GRANOLA_API_KEY",
    "account_name": "Olivia Chen",
    "account_email": "olivia@example.com",
    "store_name": "notes-42"
  }'

data.id is the installation id and data.metadata.granola_ingestion_store_id the store id; an invalid key returns 422. Sync every note:

curl -X POST https://api.mixedbread.com/v1/integrations/installations/INSTALLATION_ID/sync \
  -H 'Content-Type: application/json' \
  -H 'Authorization: Bearer YOUR_API_KEY' \
  -d '{"sync_all": true}'

Then monitor and search as in steps 4 and 5 of the Gmail flow. Differences:

  • sync_all imports every note the key can see and polls every ten minutes. Change the cadence with Update Granola Schedule: off, ten_minutes, hourly, daily, or weekly.
  • Resources are folders, listed hierarchically like Drive.
  • Personal keys expire on Granola's side. The installation then records sync.last_error and the user connects again with a new key.

Disconnect

curl -X DELETE https://api.mixedbread.com/v1/integrations/installations/INSTALLATION_ID \
  -H 'Authorization: Bearer YOUR_API_KEY'

Stops the sync and revokes the grant at the provider. Google revokes the whole authorization for a client, so a Google grant is kept while another installation of the same account uses the same OAuth client, for example Gmail and Drive on one client. The store and its files remain; delete the store separately if the data should go too.