@rynko/sdk
v1.4.0
Published
Official Node.js SDK for Rynko - Document generation and Flow AI validation gateway
Downloads
60
Maintainers
Readme
@rynko/sdk
Official Node.js SDK for Rynko - the document generation and AI output validation platform with unified template design for PDF and Excel documents.
Table of Contents
- Installation
- Quick Start
- Features
- Authentication
- Document Generation
- Document Jobs
- Templates
- Webhooks
- Rynko Extract
- Rynko Flow
- Configuration
- Error Handling
- TypeScript Support
- API Reference
- Requirements
- Support
Installation
npm install @rynko/sdk
# or
yarn add @rynko/sdk
# or
pnpm add @rynko/sdkQuick 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
- Rynko Extract - Extract structured data from documents with AI
- Rynko Flow - Submit runs for validation, manage gates, approvals, and deliveries
- Webhook management - Full CRUD for webhook subscriptions with delivery tracking
Authentication
Get an API Key
- Log in to your Rynko Dashboard
- Navigate to Settings → API Keys
- Click Create API Key
- 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,
});Delete Job
await rynko.documents.delete('job_abc123');Retry Job
Retry a failed document job:
const job = await rynko.documents.retry('job_abc123');
console.log('Retried job status:', job.status);Cancel Job
Cancel a queued or processing job:
const job = await rynko.documents.cancel('job_abc123');
console.log('Cancelled job status:', job.status);Download Document
Download a completed document as an ArrayBuffer:
import { writeFileSync } from 'fs';
const job = await rynko.documents.waitForCompletion('job_abc123');
if (job.downloadUrl) {
const buffer = await rynko.documents.download(job.downloadUrl);
writeFileSync('output.pdf', Buffer.from(buffer));
}Batch Status
Check the status of a batch generation:
const batch = await rynko.documents.getBatch('batch_abc123');
console.log('Status:', batch.status);
console.log('Progress:', `${batch.completedJobs}/${batch.totalJobs}`);Wait for Batch
Wait for a batch to reach a terminal state (completed, partial, or failed):
const batch = await rynko.documents.generateBatch({
templateId: 'tmpl_invoice',
format: 'pdf',
documents: [
{ variables: { invoiceNumber: 'INV-001' } },
{ variables: { invoiceNumber: 'INV-002' } },
],
});
// Wait for batch completion (default: poll every 2s, timeout 5 minutes)
const result = await rynko.documents.waitForBatchCompletion(batch.batchId, {
pollInterval: 3000, // Check every 3 seconds
timeout: 600000, // Wait up to 10 minutes
});
console.log(`${result.completedJobs}/${result.totalJobs} completed`);
console.log(`${result.failedJobs} failed`);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
The SDK provides full CRUD access to webhook subscriptions, delivery tracking, and signature verification utilities.
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);Create Webhook
const webhook = await rynko.webhooks.create({
url: 'https://your-app.com/webhooks/rynko',
events: ['document.generated', 'document.failed', 'batch.completed'],
description: 'Production webhook',
maxRetries: 5,
timeoutMs: 10000,
});
console.log('Webhook ID:', webhook.id);
console.log('Secret:', webhook.secret); // Save this - shown only onceUpdate Webhook
const webhook = await rynko.webhooks.update('wh_abc123', {
events: ['document.generated', 'batch.completed'],
isActive: true,
description: 'Updated production webhook',
});Delete Webhook
await rynko.webhooks.delete('wh_abc123');Rotate Secret
Rotate the signing secret for a webhook. The old secret will no longer verify signatures:
const webhook = await rynko.webhooks.rotateSecret('wh_abc123');
console.log('New secret:', webhook.secret); // Save this - shown only onceTest Webhook
Send a test event to verify your endpoint is working:
const delivery = await rynko.webhooks.test('wh_abc123');
console.log('Test delivery status:', delivery.status);Webhook Deliveries
Track and retry webhook deliveries:
// List recent deliveries
const { data: deliveries } = await rynko.webhooks.listDeliveries('wh_abc123', {
limit: 50,
});
for (const delivery of deliveries) {
console.log(`${delivery.id}: ${delivery.status} (HTTP ${delivery.httpStatus})`);
}
// Retry a failed delivery
const retried = await rynko.webhooks.retryDelivery('wh_abc123', 'del_xyz789');
console.log('Retry status:', retried.status);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 |
| flow.run.completed | Flow run completed successfully | runId, gateId, status, output |
| flow.run.approved | Flow run approved by reviewer | runId, gateId, status, approvalId |
| flow.run.rejected | Flow run rejected by reviewer | runId, gateId, status, reason |
| flow.run.review_required | Flow run requires human review | runId, gateId, status |
| flow.delivery.failed | Flow delivery failed | deliveryId, runId, error, attempts |
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;Rynko Extract
Rynko Extract uses AI to extract structured data from documents (PDFs, images, etc.).
Create Extraction Job
import { readFileSync } from 'fs';
// Extract data using an inline schema
const job = await rynko.extract.createJob(
[{ data: readFileSync('invoice.pdf'), filename: 'invoice.pdf' }],
{
schema: {
invoiceNumber: { type: 'string' },
total: { type: 'number' },
lineItems: { type: 'array', items: { type: 'object' } },
},
instructions: 'Extract invoice header and line items',
},
);
console.log('Job ID:', job.id);
console.log('Status:', job.status);
// Poll for result
const completed = await rynko.extract.getJob(job.id);
if (completed.status === 'completed') {
console.log('Extracted data:', completed.result);
}
// List jobs
const { data: jobs } = await rynko.extract.listJobs({ status: 'completed' });
// Cancel a pending job
await rynko.extract.cancelJob('job_abc123');
// Check usage
const usage = await rynko.extract.getUsage();
console.log(`${usage.remainingJobs} extraction jobs remaining`);Manage Configs
Extraction configs are reusable extraction templates with versioning:
// Create a config
const config = await rynko.extract.createConfig({
name: 'Invoice Extractor',
schema: { invoiceNumber: { type: 'string' }, total: { type: 'number' } },
instructions: 'Extract invoice header fields',
});
// Update it
await rynko.extract.updateConfig(config.id, {
instructions: 'Extract all invoice fields including line items',
});
// Publish the config
const published = await rynko.extract.publishConfig(config.id);
console.log('Published version:', published.version);
// Run extraction using a config
const result = await rynko.extract.runConfig(config.id, [
{ data: readFileSync('invoice.pdf'), filename: 'invoice.pdf' },
]);
// List configs
const { data: configs } = await rynko.extract.listConfigs({ status: 'published' });
// Version management
const versions = await rynko.extract.getConfigVersions(config.id);
await rynko.extract.restoreConfigVersion(config.id, 'ver_xyz789');
// Delete a config
await rynko.extract.deleteConfig(config.id);Discovery
Automatically discover document structure and generate a schema:
const schema = await rynko.extract.discover(
[{ data: readFileSync('sample.pdf'), filename: 'sample.pdf' }],
{ instructions: 'Focus on financial data' },
);
console.log('Suggested schema:', schema);Flow Integration
Extract data and run it through a Flow gate's validation pipeline:
// Extract using a gate's schema
const job = await rynko.extract.extractWithGate(
'gate_abc123',
[{ data: readFileSync('document.pdf'), filename: 'document.pdf' }],
{ instructions: 'Extract all fields' },
);
// Submit files for extraction + validation in one step
const run = await rynko.extract.submitFileRun(
'gate_abc123',
[{ data: readFileSync('invoice.pdf'), filename: 'invoice.pdf' }],
{ metadata: { source: 'email_attachment' } },
);Rynko Flow
Rynko Flow is an AI output validation gateway. Define gates with schemas and business rules, submit data for validation, handle human-in-the-loop approvals, and track webhook deliveries.
Submit and Wait for Run
// Submit data to a gate for validation
const run = await rynko.flow.submitRun('gate_abc123', {
input: {
customerName: 'John Doe',
email: '[email protected]',
amount: 150.00,
},
metadata: { source: 'checkout' },
webhookUrl: 'https://your-app.com/webhooks/flow',
});
console.log('Run ID:', run.id);
console.log('Status:', run.status); // 'validated' or 'validation_failed'
console.log('Success:', run.success); // true or false
// Check immediate validation result
if (!run.success) {
console.log('Validation failed:', run.error);
}
// For gates with rendering/approval steps, wait for terminal state
const result = await rynko.flow.waitForRun(run.id, {
pollInterval: 2000, // Check every 2 seconds (default: 1000)
timeout: 120000, // Wait up to 2 minutes (default: 60000)
});
if (result.status === 'validated' || result.status === 'approved') {
console.log('Validation passed!', result.output);
} else if (result.status === 'validation_failed') {
console.log('Validation failed:', result.errors);
} else if (result.status === 'rejected') {
console.log('Rejected by reviewer:', result.errors);
}List Gates
// List all gates
const { data: gates, meta } = await rynko.flow.listGates();
for (const gate of gates) {
console.log(`${gate.id}: ${gate.name} (${gate.status})`);
}
// Get a specific gate
const gate = await rynko.flow.getGate('gate_abc123');
console.log('Gate:', gate.name);
console.log('Schema:', gate.schema);Gate Management
Create, update, publish, and manage gates programmatically:
// Create a gate
const gate = await rynko.flow.createGate({
name: 'Order Validator',
description: 'Validates incoming order data',
schema: {
type: 'object',
properties: {
orderId: { type: 'string' },
amount: { type: 'number', minimum: 0 },
},
required: ['orderId', 'amount'],
},
});
// Update a gate
await rynko.flow.updateGate(gate.id, {
description: 'Updated order validation rules',
});
// Update gate schema only
await rynko.flow.updateGateSchema(gate.id, {
schema: { type: 'object', properties: { orderId: { type: 'string' } } },
});
// Publish the gate
const published = await rynko.flow.publishGate(gate.id);
// Rollback to a previous version
await rynko.flow.rollbackGate(gate.id);
await rynko.flow.rollbackGate(gate.id, 'ver_xyz789'); // specific version
// Export/import gates
const exported = await rynko.flow.exportGate(gate.id);
const imported = await rynko.flow.importGate(exported);
// Delete a gate
await rynko.flow.deleteGate(gate.id);Test and Validate
Test payloads against a gate without creating a run, or validate and create a run:
// Test without creating a run
const testResult = await rynko.flow.testGate('gate_abc123', {
orderId: 'ORD-001',
amount: 150.00,
});
console.log('Test passed:', testResult.success);
// Validate and create a run (returns a validationId)
const validation = await rynko.flow.validateGate('gate_abc123', {
orderId: 'ORD-001',
amount: 150.00,
});
console.log('Validation ID:', validation.validationId);
// Verify a previous validation
const verified = await rynko.flow.verifyValidation({
validationId: validation.validationId,
payload: { orderId: 'ORD-001', amount: 150.00 },
});Run Details
Access run payloads, correlation chains, and transactions:
// Get run payload (full or specific field)
const payload = await rynko.flow.getRunPayload('run_abc123');
const inputOnly = await rynko.flow.getRunPayload('run_abc123', 'input');
const sanitized = await rynko.flow.getRunPayload('run_abc123', 'sanitized');
// Get all runs in a correlation chain
const chain = await rynko.flow.getRunChain('corr_abc123');
console.log(`${chain.length} runs in chain`);
// Get a transaction
const txn = await rynko.flow.getTransaction('txn_abc123');List and Filter Runs
// List all runs
const { data: runs } = await rynko.flow.listRuns();
// Filter by status
const { data: approved } = await rynko.flow.listRuns({ status: 'approved' });
// List runs for a specific gate
const { data: gateRuns } = await rynko.flow.listRunsByGate('gate_abc123');
// List active (in-progress) runs
const { data: active } = await rynko.flow.listActiveRuns();
console.log(`${active.length} runs in progress`);
// Get a specific run
const run = await rynko.flow.getRun('run_abc123');
console.log('Status:', run.status);Manage Approvals
When a gate has approval rules, runs may enter a pending_approval state:
// List pending approvals
const { data: approvals } = await rynko.flow.listApprovals({ status: 'pending' });
for (const approval of approvals) {
console.log(`Approval ${approval.id} for run ${approval.runId}`);
// Approve with a note
await rynko.flow.approve(approval.id, { note: 'Looks good, approved.' });
// Or reject with a reason
// await rynko.flow.reject(approval.id, { reason: 'Amount exceeds limit.' });
}Monitor Deliveries
Track webhook deliveries for completed runs:
// List deliveries for a run
const { data: deliveries } = await rynko.flow.listDeliveries('run_abc123');
for (const delivery of deliveries) {
console.log(`${delivery.id}: ${delivery.status} → ${delivery.url}`);
}
// Retry a failed delivery
const retried = await rynko.flow.retryDelivery('delivery_abc123');
console.log('Retry status:', retried.status);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_hereimport '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,
// Flow types
FlowGate,
FlowRun,
FlowRunStatus,
FlowApproval,
FlowDelivery,
SubmitRunOptions,
ListGatesOptions,
ListRunsOptions,
ListApprovalsOptions,
WaitForRunOptions,
// 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 |
| delete(jobId) | Promise<void> | Delete a document job |
| retry(jobId) | Promise<DocumentJob> | Retry a failed document job |
| cancel(jobId) | Promise<DocumentJob> | Cancel a queued or processing job |
| download(downloadUrl) | Promise<ArrayBuffer> | Download a document from a signed URL |
| getBatch(batchId) | Promise<BatchStatusResult> | Get batch status by ID |
| waitForCompletion(jobId, options?) | Promise<DocumentJob> | Poll until job completes or fails |
| waitForBatchCompletion(batchId, options?) | Promise<BatchStatusResult> | Poll until batch reaches terminal state |
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 |
| create(options) | Promise<WebhookSubscription> | Create a new webhook subscription |
| update(id, options) | Promise<WebhookSubscription> | Update a webhook subscription |
| delete(id) | Promise<void> | Delete a webhook subscription |
| rotateSecret(id) | Promise<WebhookSubscription> | Rotate the signing secret |
| test(id) | Promise<WebhookDelivery> | Send a test event |
| listDeliveries(id, options?) | Promise<{ data: WebhookDelivery[]; meta }> | List webhook deliveries |
| retryDelivery(webhookId, deliveryId) | Promise<WebhookDelivery> | Retry a failed delivery |
Extract Resource
| Method | Returns | Description |
|--------|---------|-------------|
| createJob(files, options?) | Promise<ExtractJob> | Create an extraction job |
| getJob(jobId) | Promise<ExtractJob> | Get extraction job by ID |
| listJobs(options?) | Promise<{ data: ExtractJob[]; meta }> | List extraction jobs |
| cancelJob(jobId) | Promise<void> | Cancel an extraction job |
| getUsage() | Promise<ExtractUsage> | Get extraction usage |
| discover(files, options?) | Promise<Record> | Discover document structure |
| createConfig(options) | Promise<ExtractConfig> | Create an extraction config |
| getConfig(configId) | Promise<ExtractConfig> | Get config by ID |
| listConfigs(options?) | Promise<{ data: ExtractConfig[]; meta }> | List configs |
| updateConfig(configId, options) | Promise<ExtractConfig> | Update a config |
| deleteConfig(configId) | Promise<void> | Delete a config |
| publishConfig(configId) | Promise<ExtractConfig> | Publish a config |
| getConfigVersions(configId) | Promise<ExtractConfig[]> | Get config version history |
| restoreConfigVersion(configId, versionId) | Promise<ExtractConfig> | Restore a config version |
| runConfig(configId, files) | Promise<ExtractJob> | Run extraction using a config |
| extractWithGate(gateId, files, options?) | Promise<ExtractJob> | Extract using a Flow gate's schema |
| submitFileRun(gateId, files, options?) | Promise<Record> | Submit files for extraction + validation |
Flow Resource
| Method | Returns | Description |
|--------|---------|-------------|
| listGates(options?) | Promise<{ data: FlowGate[]; meta }> | List all gates |
| getGate(gateId) | Promise<FlowGate> | Get gate by ID |
| createGate(options) | Promise<FlowGate> | Create a new gate |
| updateGate(gateId, options) | Promise<FlowGate> | Update a gate |
| deleteGate(gateId) | Promise<void> | Delete a gate |
| updateGateSchema(gateId, options) | Promise<FlowGate> | Update gate schema |
| publishGate(gateId) | Promise<FlowGate> | Publish a gate |
| rollbackGate(gateId, versionId?) | Promise<FlowGate> | Rollback to a previous version |
| exportGate(gateId) | Promise<Record> | Export gate configuration |
| importGate(data) | Promise<FlowGate> | Import gate configuration |
| testGate(gateId, payload) | Promise<TestGateResult> | Test without creating a run |
| validateGate(gateId, payload, options?) | Promise<ValidateGateResult> | Validate and create a run |
| verifyValidation(options) | Promise<Record> | Verify a previous validation |
| getRunPayload(runId, field?) | Promise<Record> | Get run payload |
| getRunChain(correlationId) | Promise<FlowRun[]> | Get correlation chain |
| getTransaction(transactionId) | Promise<Record> | Get a transaction |
| submitRun(gateId, options) | Promise<SubmitRunResponse> | Submit a run for validation |
| getRun(runId) | Promise<FlowRun> | Get run by ID |
| listRuns(options?) | Promise<{ data: FlowRun[]; meta }> | List all runs |
| listRunsByGate(gateId, options?) | Promise<{ data: FlowRun[]; meta }> | List runs for a gate |
| listActiveRuns(options?) | Promise<{ data: FlowRun[]; meta }> | List active runs |
| waitForRun(runId, options?) | Promise<FlowRun> | Poll until run reaches terminal state |
| listApprovals(options?) | Promise<{ data: FlowApproval[]; meta }> | List approvals |
| approve(approvalId, options?) | Promise<FlowApproval> | Approve a pending approval |
| reject(approvalId, options?) | Promise<FlowApproval> | Reject a pending approval |
| listDeliveries(runId, options?) | Promise<{ data: FlowDelivery[]; meta }> | List deliveries for a run |
| retryDelivery(deliveryId) | Promise<FlowDelivery> | Retry a failed delivery |
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:
- basic-generate.ts - Generate a PDF and wait for completion
- batch-generate.ts - Generate multiple documents
- webhook-handler.ts - Express webhook endpoint
- error-handling.ts - Handle API errors
- flow-submit-and-wait.ts - Submit a run and wait for validation
- flow-approval-workflow.ts - Programmatic approval automation
- flow-webhook-handler.ts - Express webhook handler for Flow events
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]
