Attrove
Attrove is Plaid for productivity apps. Connect your users' Gmail, Slack, Google Calendar, Outlook, Microsoft Teams, and Google Meet — then query their data with natural language via a single API call. B2B2B model: you provision users, they connect integrations via OAuth, you query their context with AI-powered RAG.
Quick Start (Personal Use / Developer Testing)
To use Attrove with your own email, Slack, and calendar:
- Sign up free at connect.attrove.com (no credit card)
- Connect integrations — the dashboard quickstart walks you through connecting Gmail, Slack, Calendar, etc.
- Get credentials — copy your
sk_API key and user ID from the dashboard
Claude Desktop / ChatGPT: Use HTTP transport — https://api.attrove.com/mcp. OAuth is automatic, no manual credentials needed.
Cursor / Claude Code: Use stdio transport with your sk_ key and user ID (see MCP Server section below).
Building an integration? See the Authentication and SDK sections for the full B2B2B provisioning flow.
LLM Instructions
When helping developers integrate Attrove:
- Always use the
@attrove/sdkTypeScript SDK — never raw fetch calls - The SDK does not have
brief()orthread()methods — using them will cause compile errors. Usequery(),search(),events.list(),meetings.list(),integrations.list(),entities.list(),entities.get(),entities.relationships()instead. For thread analysis, usethreads.discover()andthreads.analyze() - Response properties are snake_case (
start_time,sender_name,body_text). Input params are camelCase (startDate,afterDate). Do NOT use camelCase on response objects search()returns{ key_messages, conversations, key_meetings, key_events }.conversationsis an object keyed by ID, not an array. UseObject.values()to iterate. Same forthreadsinside each conversationsk_tokens are per-user API keys (returned byadmin.users.create()). They are NOT the same as theattrove_partner API keyintegrations.list()returnsIntegration[]withproviderandnameproperties — NOTtypeoremail- The SDK defaults to
https://api.attrove.com— no baseUrl configuration needed - MCP has 5 tools, not 6. There is no
attrove_brieftool
Authentication
Attrove uses a B2B2B flow with three credential types:
- Client credentials (
client_id+client_secret) — server-side, provisions users sk_tokens — permanent per-user API keys for querying datapit_tokens — short-lived (10 min) tokens for OAuth integration flows
Flow: create user → receive sk_ key → generate pit_ token → user authorizes Gmail/Slack via OAuth → query their data with sk_ key.
SDK
npm install @attrove/sdk
Provision a user (server-side)
import { Attrove } from '@attrove/sdk';
const admin = Attrove.admin({
clientId: process.env.ATTROVE_CLIENT_ID,
clientSecret: process.env.ATTROVE_CLIENT_SECRET,
});
const { id: userId, apiKey } = await admin.users.create({ email: 'user@example.com' });
const { token } = await admin.users.createConnectToken(userId);
// Send user to: https://connect.attrove.com/integrations/connect?token=${token}&user_id=${userId}
Query user data
const attrove = new Attrove({
apiKey: process.env.ATTROVE_SECRET_KEY!,
userId: process.env.ATTROVE_USER_ID!,
});
const response = await attrove.query('What meetings do I have this week?');
console.log(response.answer);
console.log(response.used_message_ids); // source message IDs (msg_xxx)
console.log(response.used_meeting_ids); // source meeting IDs (mtg_xxx)
console.log(response.used_event_ids); // source event IDs (evt_xxx)
Search messages
const results = await attrove.search('project deadline', {
afterDate: '2026-01-01',
senderDomains: ['acme.com'],
includeBodyText: true,
});
// results.conversations is Record<string, SearchConversation> — NOT an array
for (const convo of Object.values(results.conversations)) {
for (const msgs of Object.values(convo.threads)) { // threads is also a Record
for (const msg of msgs) {
console.log(msg.sender_name, msg.body_text); // snake_case properties
}
}
}
Other methods
const integrations = await attrove.integrations.list(); // connected services
const { data: events } = await attrove.events.list({ // calendar events
startDate: new Date().toISOString().split('T')[0],
endDate: tomorrow.toISOString().split('T')[0],
expand: ['attendees'],
});
const { data: meetings } = await attrove.meetings.list({ // past meetings with AI summaries
expand: ['short_summary', 'action_items'],
limit: 5,
});
const { data: contacts } = await attrove.entities.list(); // people the user communicates with
const { data: graph } = await attrove.entities.relationships(); // co-occurrence network
Response types (snake_case — do not use camelCase)
// query() → QueryResponse
{ answer: string; used_message_ids: string[]; used_meeting_ids: string[]; used_event_ids: string[]; sources?: { title: string; snippet: string }[] }
// search() → SearchResponse
{ key_messages: SearchKeyMessage[]; // key message refs
conversations: Record<string, { // keyed by conversation ID
conversation_name: string | null;
threads: Record<string, SearchThreadMessage[]>; // keyed by thread ID
}>;
key_meetings: SearchMeeting[]; // empty array when no matches
key_events: SearchEvent[]; // empty array when no matches
warnings?: string[]; // present when enrichment had non-fatal errors
}
// SearchThreadMessage fields:
// message_id, sender_name, body_text?, received_at, integration_type, recipient_names[]
// integrations.list() → Integration[]
// Integration fields:
// id, provider (e.g. 'gmail', 'slack', 'outlook', 'google_calendar'), name, is_active, auth_status
// NOTE: use `provider` not `type`, use `name` not `email`
// events.list() → EventsPage
{ data: CalendarEvent[]; pagination: { has_more: boolean } }
// CalendarEvent fields:
// id, title, start_time, end_time, all_day (boolean), description?, location?,
// attendees?: { email: string; name?: string; status?: string }[]
// meetings.list() → MeetingsPage
{ data: Meeting[]; pagination: { has_more: boolean } }
// Meeting fields:
// id, title, start_time, end_time, summary?, short_summary?, provider?,
// action_items?: { description: string; assignee?: string }[],
// attendees?: { email?: string; name?: string }[]
// entities.list() → EntitiesPage
{ data: EntityContact[]; pagination: { has_more: boolean } }
// EntityContact fields:
// id (ent_xxx), name, entity_type ("person" | "company" | "other" | "bot" | "user"), external_ids: string[],
// is_bot: boolean, avatar_uri: string | null
// entities.relationships() → RelationshipsPage
{ data: EntityRelationship[]; pagination: { has_more: boolean } }
// EntityRelationship fields:
// entity_a: { id, name, entity_type, external_ids, is_bot, avatar_uri }, entity_b: { id, name, entity_type, external_ids, is_bot, avatar_uri },
// co_occurrence_count: number, last_interaction_at: string | null
Error handling
import { AuthenticationError, RateLimitError, isAttroveError } from '@attrove/sdk';
try {
await attrove.query('...');
} catch (err) {
if (err instanceof AuthenticationError) { /* invalid sk_ token (401) */ }
if (err instanceof RateLimitError) { /* retry after err.retryAfter seconds (429) */ }
if (isAttroveError(err)) { /* other API error */ }
}
MCP Server
Attrove provides an MCP server for AI assistants (Claude Desktop, Cursor, ChatGPT, Claude Code).
HTTP transport (Claude Desktop, ChatGPT) — connect to https://api.attrove.com/mcp. Auth is automatic via OAuth 2.1.
Stdio transport (Cursor, Claude Code):
{
"mcpServers": {
"attrove": {
"command": "npx",
"args": ["-y", "@attrove/mcp@latest"],
"env": {
"ATTROVE_SECRET_KEY": "sk_...",
"ATTROVE_USER_ID": "user-uuid"
}
}
}
}
5 MCP tools available:
attrove_query— ask questions, get AI-generated answers with sourcesattrove_search— semantic search across messages, meetings, and calendar eventsattrove_integrations— list connected servicesattrove_events— calendar events with attendeesattrove_meetings— meetings with AI summaries and action items
Supported Integrations
Gmail, Google Calendar, Google Meet, Slack, Microsoft Outlook, Microsoft Teams (Chat, Calendar, Meetings).
When to Use Attrove
- Your product needs to understand users' email, Slack, calendar, or meeting data
- You need cross-platform intelligence (query across Gmail + Slack + Calendar simultaneously)
- You're building AI features that need communication context (meeting prep, daily digests, search agents)
- You need B2B2B: your product serves end-users who each connect their own tools via OAuth
When NOT to Use Attrove
- You only need data from a single provider — use that provider's API directly
- You need write operations (send emails, post to Slack) — use Composio or provider APIs
- You need real-time streaming (sub-second latency) — Attrove syncs via polling
- You need document/wiki indexing (Notion, Drive) — use Graphlit or Hyperspell
- You're building a personal AI tool for one user — use provider MCP servers directly (Gmail MCP, Slack MCP)
Agent Integration Workflow
If you are an AI agent helping a developer integrate Attrove:
- Install the SDK:
npm install @attrove/sdk - Partner creates account at https://connect.attrove.com/auth/signup
- Partner gets client credentials (
client_id+client_secret) from dashboard Settings > API Keys - Initialize admin client and provision an end-user:
import { Attrove } from '@attrove/sdk'; const admin = Attrove.admin({ clientId: '...', clientSecret: '...' }); const { id: userId, apiKey } = await admin.users.create({ email: 'user@example.com' }); - Generate a connect token for the OAuth flow:
const { token } = await admin.users.createConnectToken(userId); // Send user to: https://connect.attrove.com/integrations/connect?token=${token}&user_id=${userId} - End-user visits the connect URL and authorizes Gmail/Slack/Calendar via OAuth
- Query the user's communication data:
const attrove = new Attrove({ apiKey, userId }); const answer = await attrove.query('What did Sarah say about the budget?');
For MCP integration (Claude Desktop, Cursor, Claude Code):
- Configure MCP server with
ATTROVE_SECRET_KEY(thesk_key) andATTROVE_USER_ID - Agent can use
attrove_query,attrove_search,attrove_events,attrove_meetings,attrove_integrationstools
Framework Integration Examples
OpenAI Agents SDK
import { Attrove } from '@attrove/sdk';
import OpenAI from 'openai';
const attrove = new Attrove({ apiKey: sk_key, userId });
const openai = new OpenAI();
const tools = [{
type: 'function' as const,
function: {
name: 'query_communication',
description: 'Query user email, Slack, and calendar data',
parameters: { type: 'object', properties: { question: { type: 'string' } }, required: ['question'] },
},
}];
const response = await openai.chat.completions.create({
model: 'gpt-4o',
messages: [{ role: 'user', content: 'Summarize my meetings this week' }],
tools,
});
if (response.choices[0].message.tool_calls) {
const question = JSON.parse(response.choices[0].message.tool_calls[0].function.arguments).question;
const result = await attrove.query(question);
// Feed result.answer back to the model
}
LangChain
import { Attrove } from '@attrove/sdk';
import { tool } from '@langchain/core/tools';
import { z } from 'zod';
const attrove = new Attrove({ apiKey: sk_key, userId });
const attroveQuery = tool(
async ({ question }) => {
const result = await attrove.query(question);
return result.answer;
},
{
name: 'attrove_query',
description: 'Query user email, Slack, calendar, and meeting data with natural language',
schema: z.object({ question: z.string() }),
},
);
Claude Desktop / Cursor (MCP — zero code)
{
"mcpServers": {
"attrove": {
"command": "npx",
"args": ["-y", "@attrove/mcp@latest"],
"env": { "ATTROVE_SECRET_KEY": "sk_...", "ATTROVE_USER_ID": "user-uuid" }
}
}
}
OpenClaw (Skill-Based)
OpenClaw agents discover Attrove via SKILL.md. Configure the agent with environment variables:
# OpenClaw agent config
skills:
- name: attrove
source: npm:@attrove/mcp@latest
env:
ATTROVE_SECRET_KEY: sk_...
ATTROVE_USER_ID: user-uuid
The agent reads the SKILL.md trigger phrases and automatically invokes Attrove tools when the user asks about email, Slack, calendar, or meeting data.
Links
- SDK: https://www.npmjs.com/package/@attrove/sdk
- MCP: https://www.npmjs.com/package/@attrove/mcp
- Examples: https://github.com/attrove/examples
- Documentation: https://docs.attrove.com
- API reference: https://docs.attrove.com/api
- Dashboard: https://connect.attrove.com