Skip to content

Adding a New Integration

Integrations expose third-party APIs to the AI as tools, plus optional UI for end users to authenticate and configure them.

Anatomy

Each integration consists of:

  1. A definition in services/api/src/integrations/registry/<category>.ts.
  2. (Optional) An OAuth handler if the service uses OAuth 2.
  3. (Optional) Tools the AI can call (tools/*).
  4. (Optional) UI for connection / config (apps/web/components/integrations/).

The registry/ is divided into categories:

  • ai-ml.ts
  • communication.ts
  • crm-marketing-social.ts
  • developer-tools.ts
  • finance-ecommerce.ts
  • productivity.ts

Pick the one that fits, or add a new file and re-export from registry/index.ts.

1. Define the integration

// services/api/src/integrations/registry/productivity.ts

export const linearIntegration = {
  id: 'linear',
  name: 'Linear',
  category: 'productivity',
  description: 'Issue tracking for fast-moving teams.',
  icon: '/integrations/linear.svg',

  auth: {
    type: 'oauth2',
    authorizeUrl: 'https://linear.app/oauth/authorize',
    tokenUrl: 'https://api.linear.app/oauth/token',
    scopes: ['read', 'write'],
    clientIdEnv: 'LINEAR_CLIENT_ID',
    clientSecretEnv: 'LINEAR_CLIENT_SECRET',
  },

  tools: [
    {
      name: 'linear_create_issue',
      description: 'Create an issue in a Linear team.',
      parameters: {
        type: 'object',
        properties: {
          team_id: { type: 'string' },
          title:   { type: 'string' },
          body:    { type: 'string' },
        },
        required: ['team_id', 'title'],
      },
      handler: async ({ team_id, title, body }, ctx) => {
        const res = await ctx.fetchOAuth('linear', 'https://api.linear.app/graphql', {
          method: 'POST',
          body: JSON.stringify({
            query: `mutation { issueCreate(input: { teamId: "${team_id}", title: "${title}", description: "${body ?? ''}" }) { success issue { id url } } }`,
          }),
          headers: { 'content-type': 'application/json' },
        });
        return await res.json();
      },
    },
  ],
};

2. Register

In services/api/src/integrations/registry/index.ts:

import { linearIntegration } from './productivity.js';

export const integrations = [
  // ... existing ones
  linearIntegration,
];

3. OAuth setup

If your provider uses OAuth 2 (most do):

  • Add LINEAR_CLIENT_ID / LINEAR_CLIENT_SECRET to .env.example.
  • Document the redirect URI to register in the provider's console: https://your-host/integrations/linear/callback.
  • The generic OAuth flow in services/api/src/integrations/oauth.ts handles the rest as long as your auth block follows the schema above.

For non-OAuth integrations (API key, basic auth):

auth: {
  type: 'api-key',
  envVarHint: 'Set this in the workspace integrations panel.',
}

The user enters the key in the UI; it's encrypted with ENCRYPTION_KEY and stored in integration_credentials.

4. Tool semantics

Each tool gets a JSON-Schema parameters block — the AI sees this as the function signature. Be terse but precise; bad descriptions waste context.

handler receives the validated arguments and a ctx object with:

  • ctx.fetchOAuth(integrationId, url, init) — auto-attaches the user's bearer token, refreshes if expired.
  • ctx.workspaceId, ctx.userId, ctx.projectId.
  • ctx.audit(action, details) — record an audit-log entry.

Return JSON-serializable data. For long results, paginate or summarize before returning — context length matters.

5. UI surfaces (optional)

Most integrations need only the generic connect/disconnect card. For richer UX (e.g. picking a Notion database, a Linear team), add:

apps/web/components/integrations/linear/picker.tsx

and reference it from apps/web/app/(workspace)/integrations/[id]/page.tsx.

6. Tests

services/api/src/integrations/registry/__tests__/linear.test.ts
  • Mock fetchOAuth with vi.fn().
  • Validate the shape of the request (URL, headers, body) and how the handler shapes the response.

7. Docs

Add a short page under documentation/user-guide/integrations.md (or extend it) explaining what the integration does, its scopes, and any setup steps.

Naming convention for tools

<integration_id>_<verb>_<object> — e.g. linear_create_issue, gmail_send_message, stripe_list_customers. The _ separator keeps them parseable. The AI will discover them via the integration manifest at chat time; only enabled tools are exposed.

Tool-permission policy

Integrations honour the workspace's tool policy:

  • auto — runs immediately.
  • ask — surfaces a confirmation prompt in chat.
  • block — never runs.

By default, write actions (create_*, delete_*, send_*) ship as ask; read actions ship as auto. Set this in your tool definition with defaultPolicy: 'ask' if you have a specific recommendation.