Skip to main content

Documentation Index

Fetch the complete documentation index at: https://docs.radarboard.app/llms.txt

Use this file to discover all available pages before exploring further.

Overview

Integrations connect Radarboard to external services (GitHub, Stripe, Sentry, etc.). Each integration lives in its own package under integrations/ and provides:
  • Credential configuration — how users authenticate with the service
  • Data sources — API endpoints that widgets can consume
  • MCP tools (optional) — actions the AI assistant can perform
  • Webhook handler (optional) — inbound event processing

Prerequisites

  • Radarboard dev environment set up (Setup Guide)
  • Familiarity with TypeScript and REST APIs

Step 1: Scaffold

pnpm create-integration my-service
This creates integrations/my-service/ with all boilerplate, registers it in radarboard.config.ts, and runs pnpm install.

Step 2: Define Credentials

Edit src/index.ts to configure how users authenticate:
auth: {
  id: "my-service",
  name: "My Service",
  type: "api_key",
  fields: [
    {
      key: "apiKey",
      label: "API Key",
      type: "password",
      placeholder: "sk_...",
      helpText: "Find this in your My Service dashboard under Settings → API.",
    },
  ],
  docsUrl: "https://my-service.com/docs/api-keys",
  testEndpoint: "/api/credentials/test",
},
For OAuth integrations, set type: "oauth" and add an oauth block:
auth: {
  id: "my-service",
  name: "My Service",
  type: "oauth",
  fields: [
    { key: "clientId", label: "Client ID", type: "text" },
    { key: "clientSecret", label: "Client Secret", type: "password" },
  ],
  docsUrl: "https://my-service.com/docs/oauth",
  oauth: {
    provider: "my-service",
    scopes: ["read:data"],
    setupInstructions: "Create an OAuth app at my-service.com/developers. Callback URL: {origin}/api/auth/my-service/callback",
  },
},

Step 3: Build the API Client

Edit src/api/client.ts to wrap the external API:
import type { MyServiceConfig } from "../types";

const BASE_URL = "https://api.my-service.com/v1";

export async function fetchProjects(config: MyServiceConfig) {
  const res = await fetch(`${BASE_URL}/projects`, {
    headers: { Authorization: `Bearer ${config.apiKey}` },
  });
  if (!res.ok) throw new Error(`API error: ${res.status}`);
  return res.json();
}
Tips:
  • Keep the client stateless — receive credentials as arguments, don’t store them
  • Throw errors with descriptive messages so they surface in the UI
  • Define response types in src/types.ts

Step 4: Wire Up Data Sources

Edit src/api/data-sources.ts. Each data source becomes an API route:
import type { DataSourceDescriptor } from "@radarboard/integration-sdk/types";
import { fetchProjects } from "./client";

const projectsSource: DataSourceDescriptor = {
  action: "projects",           // → GET /api/integrations/my-service/projects
  description: "List all projects from My Service.",
  cacheTtlSeconds: 300,         // Cache for 5 minutes

  async fetch(params, ctx) {
    // Resolve the user's stored credentials
    const creds = await ctx.resolveCredential("my-service");
    if (!creds?.apiKey) return { projects: [] };

    // Call the API client
    const data = await fetchProjects({ apiKey: creds.apiKey });

    // Return normalized data for widgets to consume
    return { projects: data.items };
  },
};
Key concepts:
  • params contains projectSlug, range, timeZone, forceRefresh
  • ctx.resolveCredential(key) returns the stored credential fields or null
  • cacheTtlSeconds controls how long responses are cached

Step 5: Test

Run the conformance tests (included automatically in the scaffold):
pnpm --filter @radarboard/integration-my-service test
pnpm check:extensions --filter=integration
For unit testing your data source fetch functions, use the mock context:
import { createMockDataSourceContext } from "@radarboard/integration-sdk/testing";
import { describe, expect, it } from "vitest";
import { myServiceDataSource } from "./data-sources";

describe("my-service data source", () => {
  it("returns empty when no credentials", async () => {
    const ctx = createMockDataSourceContext(); // no credentials
    const result = await myServiceDataSource.fetch(
      { projectSlug: null, range: "30d", timeZone: "UTC", forceRefresh: false },
      ctx,
    );
    expect(result).toEqual({ projects: [] });
  });

  it("resolves credentials and fetches", async () => {
    const ctx = createMockDataSourceContext({
      "my-service": { apiKey: "sk_test_123" },
    });
    const result = await myServiceDataSource.fetch(
      { projectSlug: null, range: "30d", timeZone: "UTC", forceRefresh: false },
      ctx,
    );
    expect(ctx.resolvedCredentialKeys).toContain("my-service");
  });
});

Step 6: Fill in Metadata

Back in src/index.ts, complete the descriptor:
export const myServiceDescriptor: IntegrationDescriptor = {
  id: "my-service",
  name: "My Service",
  description: "Project data and analytics from My Service.", // max 120 chars
  icon: Globe,
  category: "analytics",        // revenue | deployment | analytics | monitoring | communication
  apiDocsUrl: "https://my-service.com/docs/api",
  defaultStatusPageUrl: "https://status.my-service.com",
  capabilities: [{ id: "analytics", action: "data" }],
  auth: { /* ... */ },
  dataSources: myServiceDataSources,
};

Capability Governance

capabilities is how an integration tells Radarboard which shared widget capability it can satisfy and through which action.
  • The action must match a real DataSourceDescriptor.action.
  • If the capability already has a canonical widget, update that widget’s provider list instead of creating a duplicate widget.
  • Shared capabilities currently include revenue, bookmarks, stars, domains, errors, uptime, app-reviews, downloads, sponsorship, shipping, analytics, and seo.
This contract powers capability-aware recommendations, dependency audits, and canonical widget provider selection.

Optional: Add MCP Tools

Edit src/mcp/mcp-tools.ts to let the AI assistant interact with your service:
import { z } from "zod";

export const myServiceMcpTools = [
  {
    name: "list-projects",
    description: "List all projects from My Service",
    parameters: z.object({
      status: z.enum(["active", "archived"]).optional(),
    }),
    execute: async (params: { status?: string }) => {
      // Fetch and return data
    },
  },
];

Optional: Add Webhooks

Create src/events/webhook.ts to handle inbound events:
import type { WebhookHandler } from "@radarboard/integration-sdk/types";

export const myServiceWebhookHandler: WebhookHandler = {
  async verifySignature(request, secret) {
    // Verify HMAC signature from the service
    return true;
  },
  async parsePayload(request) {
    // Parse into IntegrationEvent[]
    return [];
  },
};

Module Boundaries

Integrations can only import from:
  • @radarboard/integration-sdk
  • @radarboard/types
  • @radarboard/utils

Reference

  • Full type reference: @radarboard/integration-sdk/types
  • Capability-backed examples: integrations/revenuecat/, integrations/stripe/, integrations/sentry/, integrations/betterstack/
  • Real examples: integrations/github/, integrations/stripe/, integrations/vercel/
  • Extension rules: CONTRIBUTING-EXTENSIONS.md