← toolkit.bot

PDF to EPUB with Node.js and TypeScript — API Integration Guide

June 12, 2026  ·  7 min read

Automate PDF-to-EPUB conversion in your Node.js or TypeScript project using the toolkit.bot API. Here's a complete integration guide with async/await patterns, TypeScript types, and production patterns.

Install and Setup

npm install node-fetch form-data
# Or with the official SDK
npm install @toolkit-bot/sdk
// Set your API key
export TOOLKIT_API_KEY="tk_live_your_key_here"

Basic Conversion — TypeScript

import fs from 'fs';
import FormData from 'form-data';
import fetch from 'node-fetch';

const API_BASE = 'https://toolkit.bot/api/v1';
const API_KEY = process.env.TOOLKIT_API_KEY!;

interface JobResponse {
  job_id: string;
  status: 'queued' | 'processing' | 'complete' | 'failed';
  download_url?: string;
  progress?: number;
}

async function convertPdfToEpub(pdfPath: string): Promise<Buffer> {
  // 1. Submit job
  const form = new FormData();
  form.append('file', fs.createReadStream(pdfPath));
  form.append('output_format', 'epub3');

  const submitRes = await fetch(`${API_BASE}/convert`, {
    method: 'POST',
    headers: { Authorization: `Bearer ${API_KEY}`, ...form.getHeaders() },
    body: form,
  });
  if (!submitRes.ok) throw new Error(`Submit failed: ${submitRes.status}`);
  const { job_id } = await submitRes.json() as JobResponse;

  // 2. Poll until complete
  let job: JobResponse;
  do {
    await new Promise(r => setTimeout(r, 2000));
    const pollRes = await fetch(`${API_BASE}/jobs/${job_id}`, {
      headers: { Authorization: `Bearer ${API_KEY}` },
    });
    job = await pollRes.json() as JobResponse;
  } while (job.status === 'queued' || job.status === 'processing');

  if (job.status === 'failed') throw new Error('Conversion failed');

  // 3. Download result
  const dlRes = await fetch(job.download_url!, {
    headers: { Authorization: `Bearer ${API_KEY}` },
  });
  return Buffer.from(await dlRes.arrayBuffer());
}

// Usage
const epub = await convertPdfToEpub('./document.pdf');
fs.writeFileSync('./document.epub', epub);
console.log('Done:', epub.length, 'bytes');

Batch Conversion with Concurrency Limit

import { promisePool } from './utils'; // or use p-limit

async function batchConvert(
  pdfPaths: string[],
  concurrency = 3
): Promise<void> {
  const limit = pLimit(concurrency); // npm install p-limit

  const tasks = pdfPaths.map(pdf =>
    limit(async () => {
      const epub = await convertPdfToEpub(pdf);
      const out = pdf.replace(/[.]pdf$/i, '.epub');
      fs.writeFileSync(out, epub);
      console.log('Converted:', out);
    })
  );

  await Promise.all(tasks);
}

await batchConvert(
  fs.readdirSync('./pdfs').map(f => `./pdfs/${f}`)
);

Webhook Handler (Express)

import express from 'express';
const app = express();
app.use(express.json());

app.post('/hooks/epub-done', async (req, res) => {
  const { event, job_id, download_url } = req.body;

  if (event !== 'job.complete') return res.sendStatus(200);

  // Download and save
  const dlRes = await fetch(download_url, {
    headers: { Authorization: `Bearer ${API_KEY}` },
  });
  const buffer = Buffer.from(await dlRes.arrayBuffer());
  fs.writeFileSync(`./output/${job_id}.epub`, buffer);

  console.log('Saved:', job_id);
  res.sendStatus(200);
});

app.listen(3000);

Error Handling and Retry

async function convertWithRetry(
  pdfPath: string,
  maxRetries = 3
): Promise<Buffer> {
  for (let attempt = 1; attempt <= maxRetries; attempt++) {
    try {
      return await convertPdfToEpub(pdfPath);
    } catch (err) {
      if (attempt === maxRetries) throw err;
      const delay = Math.pow(2, attempt) * 1000; // exponential backoff
      console.warn(`Attempt ${attempt} failed, retry in ${delay}ms`);
      await new Promise(r => setTimeout(r, delay));
    }
  }
  throw new Error('unreachable');
}

Streaming Upload for Large Files

// For files near the size limit, stream the upload
const form = new FormData();
form.append('file', fs.createReadStream(largePdfPath), {
  filename: path.basename(largePdfPath),
  contentType: 'application/pdf',
  knownLength: fs.statSync(largePdfPath).size,
});
// knownLength lets the server know Content-Length upfront
// avoiding chunked transfer encoding issues
Get your API key at toolkit.bot →