npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@invoiceleaf/integration-sdk

v1.6.0

Published

SDK for building InvoiceLeaf integrations

Readme

@invoiceleaf/integration-sdk

SDK for building InvoiceLeaf integrations.

Installation

npm install @invoiceleaf/integration-sdk

Package Structure

Every integration package must have a manifest.json file at the package root. This file is the canonical source of truth for the integration's metadata, triggers, actions, and configuration schema.

my-integration/
├── manifest.json        # REQUIRED - Integration manifest
├── package.json
├── src/
│   ├── index.ts         # Handler exports
│   └── handlers/        # Handler implementations
└── README.md

manifest.json

The manifest.json file is automatically read by:

  • The InvoiceLeaf backend when registering/updating integrations (fetched from npm)
  • The plugin runtime when executing integration handlers

This ensures the same manifest definition is used everywhere, keeping the backend database and runtime in sync.

Example manifest.json:

{
  "id": "my-integration",
  "name": "My Integration",
  "version": "1.0.0",
  "description": "A sample integration",
  "author": {
    "name": "Your Name",
    "email": "[email protected]"
  },
  "icon": "puzzle",
  "category": "productivity",
  "dataAccess": ["documents", "companies"],
  "triggers": [
    {
      "id": "on-document-processed",
      "type": "event",
      "name": "On Document Processed",
      "events": ["document.processed"],
      "handler": "onDocumentProcessed"
    }
  ],
  "actions": [
    {
      "id": "sync-documents",
      "name": "Sync Documents",
      "handler": "syncDocuments"
    }
  ],
  "configSchema": {
    "type": "object",
    "properties": {
      "apiKey": {
        "type": "string",
        "title": "API Key"
      }
    },
    "required": ["apiKey"]
  }
}

Quick Start

// src/index.ts
import { defineHandler, DocumentProcessedInput } from '@invoiceleaf/integration-sdk';

// Export handlers that match the "handler" names in manifest.json
export const onDocumentProcessed = defineHandler<DocumentProcessedInput>(
  async (input, context) => {
    context.logger.info('Document processed', { id: input.documentId });

    const doc = await context.data.getDocument(input.documentId);
    // Process the document...

    return { success: true };
  }
);

export const syncDocuments = defineHandler(async (input, context) => {
  const documents = await context.data.listDocuments({
    status: 'PROCESSED',
    size: 100,
  });

  context.logger.info(`Found ${documents.total} documents to sync`);

  // Sync documents to external service...

  return { syncedCount: documents.items.length };
});

Trigger Types

Event Triggers

Triggered by InvoiceLeaf events:

{
  id: 'on-document-created',
  type: 'event',
  events: ['document.created', 'document.updated'],
  handler: 'handleDocumentEvent',
}

Available events:

  • document.created
  • document.updated
  • document.processed
  • document.deleted
  • company.created
  • company.updated
  • company.deleted
  • export.completed
  • reminder.triggered
  • space.member.added
  • space.member.removed

Schedule Triggers

Triggered on a schedule (cron):

{
  id: 'daily-sync',
  type: 'schedule',
  schedule: '0 2 * * *', // 2 AM daily
  handler: 'dailySync',
}

Webhook Triggers

Triggered by external services:

{
  id: 'stripe-webhook',
  type: 'webhook',
  webhook: {
    methods: ['POST'],
    signature: {
      header: 'stripe-signature',
      algorithm: 'hmac-sha256',
      secretConfigKey: 'stripeWebhookSecret',
    },
  },
  handler: 'handleStripeWebhook',
}

User Action Triggers

Triggered by user clicks:

{
  id: 'export-to-datev',
  type: 'user_action',
  userAction: {
    label: 'Export to DATEV',
    icon: '📤',
    placement: ['document-list', 'toolbar'],
    confirmMessage: 'Export selected documents to DATEV?',
  },
  handler: 'exportToDATEV',
}

Invocation Mappings (Optional)

Invocation mappings let the platform route external interaction operations to internal manifest actions in a structured way.

{
  "actions": [
    {
      "id": "apply-document-action",
      "name": "Apply Document Action",
      "handler": "applyDocumentAction",
      "internal": true
    }
  ],
  "invocations": [
    {
      "id": "telegram-mark-paid",
      "source": "telegram.callback",
      "operation": "mark_paid",
      "actionId": "apply-document-action",
      "requiresLinkedUser": true
    }
  ]
}

Validation rules:

  • Every invocation needs id, source, operation, and actionId
  • actionId must reference an existing actions[].id
  • Invocation IDs must be unique

External Authentication

OAuth2

externalAuth: [
  {
    provider: 'google',
    name: 'Google Drive',
    type: 'oauth2',
    oauth: {
      authorizeUrl: 'https://accounts.google.com/o/oauth2/v2/auth',
      tokenUrl: 'https://oauth2.googleapis.com/token',
      scopes: ['https://www.googleapis.com/auth/drive.file'],
    },
  },
],

Usage in handler:

const accessToken = await context.credentials.getAccessToken('google');

API Key

externalAuth: [
  {
    provider: 'openai',
    name: 'OpenAI',
    type: 'api_key',
    apiKey: {
      headerName: 'Authorization',
      prefix: 'Bearer ',
      instructions: 'Get your API key from https://platform.openai.com',
    },
  },
],

Usage in handler:

const apiKey = await context.credentials.getApiKey('openai');

Context APIs for Sync Integrations

Use these primitives for Gmail/SMTP and accounting sync integrations:

// Durable checkpoints
await context.state.set('sync:lastSuccessfulAt', new Date().toISOString());
const checkpoint = await context.state.get<string>('sync:lastSuccessfulAt');

// Provider diagnostics (non-secret)
const connection = await context.credentials.getConnectionInfo('quickbooks');

// External ID mappings
await context.mappings.upsert({
  system: 'quickbooks',
  entity: 'invoice',
  localId: 'doc_123',
  externalId: 'qbo_987',
});

// Document sync metadata
await context.data.patchDocumentIntegrationMeta({
  documentId: 'doc_123',
  system: 'quickbooks',
  status: 'synced',
  externalId: 'qbo_987',
});

Custom UI Pages

Define custom dashboard pages:

pages: [
  {
    id: 'dashboard',
    name: 'Dashboard',
    route: '/dashboard',
    dataSource: 'getDashboardData',
    layout: {
      type: 'dashboard',
      widgets: [
        {
          type: 'metric',
          title: 'Documents Synced',
          value: '{{syncedCount}}',
          format: 'number',
          trend: { direction: 'up', value: 12 },
        },
        {
          type: 'chart',
          title: 'Sync History',
          chartType: 'line',
          dataSource: '{{syncHistory}}',
          xField: 'date',
          yField: 'count',
        },
        {
          type: 'actionButton',
          label: 'Sync Now',
          actionId: 'sync-documents',
          variant: 'primary',
        },
      ],
    },
  },
],

User Configuration

Define configuration options:

configSchema: {
  type: 'object',
  properties: {
    syncEnabled: {
      type: 'boolean',
      title: 'Enable Auto-Sync',
      default: true,
    },
    syncInterval: {
      type: 'string',
      title: 'Sync Interval',
      enum: ['hourly', 'daily', 'weekly'],
      default: 'daily',
    },
    apiEndpoint: {
      type: 'string',
      title: 'API Endpoint',
      format: 'uri',
    },
  },
  required: ['syncEnabled'],
},

Access in handler:

const syncEnabled = context.config.syncEnabled as boolean;

API Reference

IntegrationContext

| Property | Type | Description | |----------|------|-------------| | spaceId | string | Current space ID | | userId | string | User who triggered execution | | installationId | string | Installation ID | | config | Record<string, unknown> | User configuration | | data | DataClient | Data access client | | credentials | CredentialsClient | Credential client | | state | StateClient | Installation-scoped state client | | email | EmailClient | SMTP/IMAP operations | | logger | Logger | Structured logger |

DataClient

| Method | Description | |--------|-------------| | listDocuments(params?) | List documents with filters | | getDocument(id) | Get document by ID | | listCompanies(params?) | List companies | | getCompany(id) | Get company by ID | | listCategories() | List all categories | | listTags() | List all tags | | createExport(params) | Create export job | | getExport(id) | Get export status | | importDocument(input) | Import file as document |

CredentialsClient

| Method | Description | |--------|-------------| | getAccessToken(provider) | Get OAuth access token | | getApiKey(provider) | Get API key | | refreshToken(provider) | Force token refresh |

StateClient

| Method | Description | |--------|-------------| | get(key) | Read installation-scoped state | | set(key, value, opts?) | Persist installation-scoped state | | delete(key) | Remove installation-scoped state |

EmailClient

| Method | Description | |--------|-------------| | sendSmtpEmail(input) | Send outbound SMTP email | | testSmtpImapConnection(input) | Verify SMTP + IMAP connection | | crawlImapPdfAttachments(input) | Fetch PDF attachments from IMAP |

Logger

| Method | Description | |--------|-------------| | debug(message, data?) | Debug log | | info(message, data?) | Info log | | warn(message, data?) | Warning log | | error(message, data?) | Error log |

License

MIT