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

@rynko/sdk

v1.2.2

Published

Official Node.js SDK for Rynko - Generate PDFs and Excel files from templates

Readme

@rynko/sdk

Official Node.js SDK for Rynko - the document generation platform with unified template design for PDF and Excel documents.

npm version License: MIT

Table of Contents

Installation

npm install @rynko/sdk
# or
yarn add @rynko/sdk
# or
pnpm add @rynko/sdk

Quick Start

import { Rynko } from '@rynko/sdk';

const rynko = new Rynko({
  apiKey: process.env.RYNKO_API_KEY!,
});

// Generate a PDF document (async - returns job info immediately)
const job = await rynko.documents.generatePdf({
  templateId: 'tmpl_invoice',
  variables: {
    customerName: 'John Doe',
    invoiceNumber: 'INV-001',
    total: 150.00,
  },
});

console.log('Job ID:', job.jobId);
console.log('Status:', job.status);  // 'queued'

// Wait for completion to get the download URL
const completed = await rynko.documents.waitForCompletion(job.jobId);
console.log('Download URL:', completed.downloadUrl);

Features

  • Full TypeScript support with comprehensive type definitions
  • Promise-based API for modern async/await usage
  • PDF generation - Generate PDF documents from templates
  • Excel generation - Generate Excel spreadsheets from templates
  • Batch generation - Generate multiple documents in a single request
  • Environment support - Generate documents in specific environments
  • Webhook verification - Secure HMAC signature verification for incoming webhooks
  • Polling utility - Built-in waitForCompletion() method with configurable timeout
  • Automatic retries - Configurable retry logic for transient failures

Authentication

Get an API Key

  1. Log in to your Rynko Dashboard
  2. Navigate to SettingsAPI Keys
  3. Click Create API Key
  4. Copy the key and store it securely (it won't be shown again)

Initialize the Client

import { Rynko } from '@rynko/sdk';

// Using environment variable (recommended)
const rynko = new Rynko({
  apiKey: process.env.RYNKO_API_KEY!,
});

// Verify authentication
const user = await rynko.me();
console.log('Authenticated as:', user.email);
console.log('Project:', user.teamName);

Verify API Key

// Check if API key is valid
const isValid = await rynko.verifyApiKey();
console.log('API Key valid:', isValid);

Document Generation

Document generation in Rynko is asynchronous. When you call a generate method, the job is queued for processing and you receive a job ID immediately. Use waitForCompletion() to poll until the document is ready.

Generate PDF

// Queue PDF generation
const job = await rynko.documents.generatePdf({
  templateId: 'tmpl_invoice',
  variables: {
    invoiceNumber: 'INV-001',
    customerName: 'John Doe',
    customerEmail: '[email protected]',
    items: [
      { description: 'Product A', quantity: 2, price: 50.00 },
      { description: 'Product B', quantity: 1, price: 50.00 },
    ],
    subtotal: 150.00,
    tax: 15.00,
    total: 165.00,
  },
});

console.log('Job queued:', job.jobId);
console.log('Status:', job.status);  // 'queued'

// Wait for completion
const completed = await rynko.documents.waitForCompletion(job.jobId);
console.log('Download URL:', completed.downloadUrl);

Generate Excel

const job = await rynko.documents.generateExcel({
  templateId: 'tmpl_sales_report',
  variables: {
    reportTitle: 'Q1 2026 Sales Report',
    reportDate: '2026-03-31',
    salesData: [
      { region: 'North', q1: 125000, q2: 0, q3: 0, q4: 0 },
      { region: 'South', q1: 98000, q2: 0, q3: 0, q4: 0 },
      { region: 'East', q1: 145000, q2: 0, q3: 0, q4: 0 },
      { region: 'West', q1: 112000, q2: 0, q3: 0, q4: 0 },
    ],
    totalSales: 480000,
  },
});

const completed = await rynko.documents.waitForCompletion(job.jobId);
console.log('Excel file ready:', completed.downloadUrl);

Generate with Options

The generate() method supports all document formats and advanced options:

const job = await rynko.documents.generate({
  // Required
  templateId: 'tmpl_contract',
  format: 'pdf',  // 'pdf' | 'excel' | 'csv'

  // Template variables
  variables: {
    contractNumber: 'CTR-2026-001',
    clientName: 'Acme Corporation',
    startDate: '2026-02-01',
    endDate: '2027-01-31',
  },

  // Optional settings
  filename: 'contract-acme-2026',  // Custom filename (without extension)
  webhookUrl: 'https://your-app.com/webhooks/document-ready',  // Webhook notification
  metadata: {                       // Custom metadata (passed to webhook)
    orderId: 'ORD-12345',
    userId: 'user_abc',
  },
  useDraft: false,                  // Use draft template version (for testing)
  useCredit: false,                 // Force use of purchased credits
});

Batch Generation

Generate multiple documents from a single template:

// Each object in the documents array requires a `variables` property
const batch = await rynko.documents.generateBatch({
  templateId: 'tmpl_invoice',
  format: 'pdf',
  documents: [
    {
      variables: {
        invoiceNumber: 'INV-001',
        customerName: 'John Doe',
        total: 150.00,
      },
    },
    {
      variables: {
        invoiceNumber: 'INV-002',
        customerName: 'Jane Smith',
        total: 275.50,
      },
    },
    {
      variables: {
        invoiceNumber: 'INV-003',
        customerName: 'Bob Wilson',
        total: 89.99,
      },
    },
  ],
  webhookUrl: 'https://your-app.com/webhooks/batch-complete',
});

console.log('Batch ID:', batch.batchId);
console.log('Total jobs:', batch.totalJobs);  // 3
console.log('Status:', batch.status);  // 'queued'
console.log('Estimated wait:', batch.estimatedWaitSeconds, 'seconds');

Wait for Completion

The waitForCompletion() method polls the job status until it completes or fails:

// Default settings (1 second interval, 30 second timeout)
const completed = await rynko.documents.waitForCompletion(job.jobId);

// Custom polling settings
const completed = await rynko.documents.waitForCompletion(job.jobId, {
  pollInterval: 2000,   // Check every 2 seconds
  timeout: 60000,       // Wait up to 60 seconds
});

// Check result
if (completed.status === 'completed') {
  console.log('Download URL:', completed.downloadUrl);
  console.log('File size:', completed.fileSize, 'bytes');
  console.log('Expires at:', completed.downloadUrlExpiresAt);
} else if (completed.status === 'failed') {
  console.error('Generation failed:', completed.errorMessage);
  console.error('Error code:', completed.errorCode);
}

Document Jobs

Get Job Status

const job = await rynko.documents.getJob('job_abc123');

console.log('Status:', job.status);
// Possible values: 'queued' | 'processing' | 'completed' | 'failed'

console.log('Template:', job.templateName);
console.log('Format:', job.format);
console.log('Created:', job.createdAt);

if (job.status === 'completed') {
  console.log('Download URL:', job.downloadUrl);
  console.log('File size:', job.fileSize);
  console.log('URL expires:', job.downloadUrlExpiresAt);
}

if (job.status === 'failed') {
  console.log('Error:', job.errorMessage);
  console.log('Error code:', job.errorCode);
}

List Jobs

// List recent jobs with pagination
const { data: jobs, meta } = await rynko.documents.listJobs({
  limit: 20,
  page: 1,
});

console.log('Total jobs:', meta.total);
console.log('Pages:', meta.totalPages);

for (const job of jobs) {
  console.log(`${job.jobId}: ${job.status} - ${job.templateName}`);
}

// Filter by status
const { data: completedJobs } = await rynko.documents.listJobs({
  status: 'completed',
});

// Filter by format
const { data: pdfJobs } = await rynko.documents.listJobs({
  format: 'pdf',
});

// Filter by template
const { data: invoiceJobs } = await rynko.documents.listJobs({
  templateId: 'tmpl_invoice',
});

// Filter by environment
const { data: workspaceJobs } = await rynko.documents.listJobs({
  workspaceId: 'ws_abc123',
});

// Filter by date range
const { data: recentJobs } = await rynko.documents.listJobs({
  dateFrom: new Date('2026-01-01'),
  dateTo: new Date('2026-01-31'),
});

// Combine filters
const { data: filteredJobs } = await rynko.documents.listJobs({
  status: 'completed',
  format: 'pdf',
  templateId: 'tmpl_invoice',
  limit: 50,
});

Templates

List Templates

// List all templates
const { data: templates, meta } = await rynko.templates.list();

console.log('Total templates:', meta.total);

for (const template of templates) {
  console.log(`${template.id}: ${template.name} (${template.type})`);
}

// Paginated list
const { data: page2 } = await rynko.templates.list({
  page: 2,
  limit: 10,
});

// Search by name
const { data: invoiceTemplates } = await rynko.templates.list({
  search: 'invoice',
});

// List PDF templates only
const { data: pdfTemplates } = await rynko.templates.listPdf();

// List Excel templates only
const { data: excelTemplates } = await rynko.templates.listExcel();

Get Template Details

// Get template by ID (supports UUID, shortId, or slug)
const template = await rynko.templates.get('tmpl_invoice');

console.log('Template:', template.name);
console.log('Type:', template.type);  // 'pdf' | 'excel'
console.log('Description:', template.description);
console.log('Created:', template.createdAt);
console.log('Updated:', template.updatedAt);

// View template variables
if (template.variables) {
  console.log('\nVariables:');
  for (const variable of template.variables) {
    console.log(`  ${variable.name} (${variable.type})`);
    console.log(`    Required: ${variable.required ?? false}`);
    if (variable.defaultValue !== undefined) {
      console.log(`    Default: ${JSON.stringify(variable.defaultValue)}`);
    }
  }
}

Webhooks

Webhook subscriptions are managed through the Rynko Dashboard. The SDK provides read-only access to view webhooks and utilities for signature verification.

List Webhooks

const { data: webhooks, meta } = await rynko.webhooks.list();

for (const webhook of webhooks) {
  console.log(`${webhook.id}: ${webhook.url}`);
  console.log(`  Events: ${webhook.events.join(', ')}`);
  console.log(`  Active: ${webhook.isActive}`);
  console.log(`  Created: ${webhook.createdAt}`);
}

Get Webhook Details

const webhook = await rynko.webhooks.get('wh_abc123');

console.log('URL:', webhook.url);
console.log('Events:', webhook.events);
console.log('Active:', webhook.isActive);
console.log('Description:', webhook.description);

Verify Webhook Signatures

When receiving webhooks, always verify the signature to ensure the request came from Rynko:

import { verifyWebhookSignature, WebhookSignatureError } from '@rynko/sdk';

// Express.js example
app.post('/webhooks/rynko', express.raw({ type: 'application/json' }), (req, res) => {
  const signature = req.headers['x-rynko-signature'] as string;

  try {
    // The signature header contains both timestamp and signature: t=<ts>,v1=<hex>
    // Timestamp is validated automatically (default tolerance: 5 minutes)
    const event = verifyWebhookSignature({
      payload: req.body.toString(),
      signature,
      secret: process.env.WEBHOOK_SECRET!,
      tolerance: 300,  // Optional: tolerance in seconds (default: 300)
    });

    // Process the verified event
    console.log('Event type:', event.type);
    console.log('Event ID:', event.id);
    console.log('Timestamp:', event.timestamp);

    switch (event.type) {
      case 'document.generated':
        const { jobId, downloadUrl, templateId, metadata } = event.data;
        console.log(`Document ${jobId} ready: ${downloadUrl}`);
        // Access metadata you passed during generation
        if (metadata) {
          console.log(`Order ID: ${metadata.orderId}`);
        }
        // Download or process the document
        break;

      case 'document.failed':
        const { jobId: failedJobId, errorMessage, errorCode, metadata: failedMeta } = event.data;
        console.error(`Document ${failedJobId} failed: ${errorMessage}`);
        // Access metadata for correlation
        if (failedMeta) {
          console.log(`Failed order: ${failedMeta.orderId}`);
        }
        // Handle failure (retry, notify user, etc.)
        break;

      case 'batch.completed':
        const { batchId, totalJobs, completedJobs, failedJobs } = event.data;
        console.log(`Batch ${batchId} done: ${completedJobs}/${totalJobs} succeeded, ${failedJobs} failed`);
        break;

      case 'document.downloaded':
        const { jobId: downloadedJobId, downloadedAt } = event.data;
        console.log(`Document ${downloadedJobId} downloaded at ${downloadedAt}`);
        break;

      default:
        console.log('Unhandled event type:', event.type);
    }

    res.status(200).send('OK');
  } catch (error) {
    if (error instanceof WebhookSignatureError) {
      console.error('Invalid webhook signature:', error.message);
      res.status(401).send('Invalid signature');
    } else {
      console.error('Webhook processing error:', error);
      res.status(500).send('Internal error');
    }
  }
});

Webhook Event Types

| Event | Description | Payload | |-------|-------------|---------| | document.generated | Document successfully generated | jobId, templateId, format, downloadUrl, fileSize, metadata | | document.failed | Document generation failed | jobId, templateId, errorMessage, errorCode, metadata | | document.downloaded | Document was downloaded | jobId, downloadedAt | | batch.completed | Batch generation finished | batchId, templateId, format, totalJobs, completedJobs, failedJobs, metadata |

Webhook Headers

Rynko sends these headers with each webhook request:

| Header | Description | |--------|-------------| | X-Rynko-Signature | HMAC-SHA256 signature (format: t=<timestamp>,v1=<hex>) | | X-Rynko-Timestamp | Unix timestamp when the webhook was sent | | X-Rynko-Event-Id | Unique event identifier | | X-Rynko-Event-Type | Event type (e.g., document.generated) |

Low-Level Signature Utilities

For advanced use cases, you can use the low-level signature utilities:

import { parseSignatureHeader, computeSignature } from '@rynko/sdk';

// Parse the signature header (format: t=<timestamp>,v1=<hex>)
const { timestamp, signature } = parseSignatureHeader(signatureHeader);

// Compute expected signature
const expectedSignature = computeSignature(timestamp, payload, secret);

// Compare signatures
const isValid = signature === expectedSignature;

Configuration

const rynko = new Rynko({
  // Required: Your API key
  apiKey: process.env.RYNKO_API_KEY!,

  // Optional: Custom base URL (default: https://api.rynko.dev)
  baseUrl: 'https://api.rynko.dev',

  // Optional: Request timeout in milliseconds (default: 30000)
  timeout: 30000,

  // Optional: Custom headers for all requests
  headers: {
    'X-Custom-Header': 'value',
  },

  // Optional: Retry configuration (enabled by default)
  retry: {
    maxAttempts: 5,           // Maximum retry attempts (default: 5)
    initialDelayMs: 1000,     // Initial delay between retries (default: 1000)
    maxDelayMs: 30000,        // Maximum delay between retries (default: 30000)
    maxJitterMs: 1000,        // Maximum jitter to add (default: 1000)
    retryableStatuses: [429, 503, 504],  // HTTP status codes to retry (default)
  },
  // Or disable retry entirely:
  // retry: false,
});

Environment Variables

We recommend using environment variables for configuration:

# .env
RYNKO_API_KEY=your_api_key_here
WEBHOOK_SECRET=your_webhook_secret_here
import 'dotenv/config';
import { Rynko } from '@rynko/sdk';

const rynko = new Rynko({
  apiKey: process.env.RYNKO_API_KEY!,
});

Error Handling

import { Rynko, RynkoError } from '@rynko/sdk';

const rynko = new Rynko({ apiKey: 'your_api_key' });

try {
  const job = await rynko.documents.generatePdf({
    templateId: 'invalid_template',
    variables: {},
  });
} catch (error) {
  if (error instanceof RynkoError) {
    console.error('API Error:', error.message);
    console.error('Error Code:', error.code);
    console.error('Status Code:', error.statusCode);

    // Handle specific error codes
    switch (error.code) {
      case 'ERR_TMPL_001':
        console.error('Template not found');
        break;
      case 'ERR_TMPL_003':
        console.error('Template validation failed');
        break;
      case 'ERR_QUOTA_001':
        console.error('Document quota exceeded - upgrade your plan');
        break;
      case 'ERR_AUTH_001':
        console.error('Invalid API key');
        break;
      case 'ERR_AUTH_004':
        console.error('API key expired or revoked');
        break;
      default:
        console.error('Unknown error');
    }
  } else {
    // Network error or other non-API error
    throw error;
  }
}

Common Error Codes

| Code | Description | |------|-------------| | ERR_AUTH_001 | Invalid credentials / API key | | ERR_AUTH_004 | Token expired or revoked | | ERR_TMPL_001 | Template not found | | ERR_TMPL_003 | Template validation failed | | ERR_DOC_001 | Document job not found | | ERR_DOC_004 | Document generation failed | | ERR_QUOTA_001 | Document quota exceeded | | ERR_QUOTA_002 | Rate limit exceeded |

TypeScript Support

This SDK is written in TypeScript and includes comprehensive type definitions:

import {
  Rynko,
  RynkoError,
  verifyWebhookSignature,
  WebhookSignatureError,
} from '@rynko/sdk';

import type {
  // Configuration
  RynkoConfig,

  // Document types
  GenerateDocumentOptions,
  GenerateBatchOptions,
  GenerateDocumentResponse,
  GenerateBatchResponse,
  DocumentJob,
  DocumentJobStatus,
  ListDocumentJobsOptions,

  // Template types
  Template,
  TemplateVariable,
  ListTemplatesOptions,

  // Webhook types
  WebhookSubscription,
  WebhookEventType,
  WebhookEvent,

  // Response types
  ApiResponse,
  PaginationMeta,
  ApiError,

  // User types
  User,
} from '@rynko/sdk';

// Type-safe document generation
const options: GenerateDocumentOptions = {
  templateId: 'tmpl_invoice',
  format: 'pdf',
  variables: {
    invoiceNumber: 'INV-001',
    items: [{ name: 'Widget', price: 29.99 }],
  },
  metadata: { orderId: 'ORD-12345' },  // Optional: custom metadata
};

const result: GenerateDocumentResponse = await rynko.documents.generate(options);

API Reference

Client Methods

| Method | Returns | Description | |--------|---------|-------------| | me() | Promise<User> | Get current authenticated user | | verifyApiKey() | Promise<boolean> | Verify API key is valid |

Documents Resource

| Method | Returns | Description | |--------|---------|-------------| | generate(options) | Promise<GenerateDocumentResponse> | Generate a document (PDF, Excel, or CSV) | | generatePdf(options) | Promise<GenerateDocumentResponse> | Generate a PDF document | | generateExcel(options) | Promise<GenerateDocumentResponse> | Generate an Excel document | | generateBatch(options) | Promise<GenerateBatchResponse> | Generate multiple documents | | getJob(jobId) | Promise<DocumentJob> | Get document job by ID | | listJobs(options?) | Promise<{ data: DocumentJob[]; meta: PaginationMeta }> | List/search document jobs | | waitForCompletion(jobId, options?) | Promise<DocumentJob> | Poll until job completes or fails |

Templates Resource

| Method | Returns | Description | |--------|---------|-------------| | get(templateId) | Promise<Template> | Get template by ID (UUID, shortId, or slug) | | list(options?) | Promise<{ data: Template[]; meta: PaginationMeta }> | List all templates | | listPdf(options?) | Promise<{ data: Template[]; meta: PaginationMeta }> | List PDF templates only | | listExcel(options?) | Promise<{ data: Template[]; meta: PaginationMeta }> | List Excel templates only |

Webhooks Resource

| Method | Returns | Description | |--------|---------|-------------| | get(webhookId) | Promise<WebhookSubscription> | Get webhook subscription by ID | | list() | Promise<{ data: WebhookSubscription[]; meta: PaginationMeta }> | List all webhook subscriptions |

Utilities

| Function | Returns | Description | |----------|---------|-------------| | verifyWebhookSignature(options) | WebhookEvent | Verify signature and parse webhook event | | parseSignatureHeader(header) | { timestamp: number; signature: string } | Parse signature header | | computeSignature(timestamp, payload, secret) | string | Compute expected signature |

Examples

See the examples/ directory for runnable code samples:

For complete project templates with full setup, see the developer-resources repository.

Requirements

  • Node.js 18.0.0 or higher
  • npm, yarn, or pnpm

License

MIT

Support

  • Documentation: https://docs.rynko.dev/sdk/node
  • API Reference: https://docs.rynko.dev/api
  • Examples: https://github.com/rynko-dev/developer-resources
  • GitHub Issues: https://github.com/rynko-dev/sdk-node/issues
  • Email: [email protected]