← toolkit.bot

PDF to EPUB Conversion in C# / .NET — toolkit.bot API Integration

June 12, 2026  ·  8 min read

This guide shows how to call the toolkit.bot REST API from C# to convert PDF files to EPUB. The implementation uses HttpClient with MultipartFormDataContent — no third-party packages required.

HttpClient example (.NET 6+)

using System;
using System.IO;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Text.Json;
using System.Threading;
using System.Threading.Tasks;

class Pdf2EpubClient
{
    private static readonly HttpClient _http = new();
    private const string ApiBase = "https://toolkit.bot/api/v1";

    static async Task Main(string[] args)
    {
        var apiKey   = Environment.GetEnvironmentVariable("TOOLKIT_API_KEY")
                       ?? throw new Exception("TOOLKIT_API_KEY not set");
        var pdfPath  = args.Length > 0 ? args[0] : "document.pdf";
        var epubPath = Path.ChangeExtension(pdfPath, ".epub");

        await ConvertAsync(apiKey, pdfPath, epubPath);
        Console.WriteLine($"Saved: {epubPath}");
    }

    static async Task ConvertAsync(string apiKey, string pdfPath, string epubPath)
    {
        _http.DefaultRequestHeaders.Authorization =
            new AuthenticationHeaderValue("Bearer", apiKey);

        // --- Upload ---
        await using var fs   = File.OpenRead(pdfPath);
        using var form        = new MultipartFormDataContent();
        var fileContent       = new StreamContent(fs);
        fileContent.Headers.ContentType =
            new MediaTypeHeaderValue("application/pdf");
        form.Add(fileContent, "file", Path.GetFileName(pdfPath));

        var uploadResp = await _http.PostAsync($"{ApiBase}/jobs", form);
        uploadResp.EnsureSuccessStatusCode();

        using var uploadDoc = JsonDocument.Parse(
            await uploadResp.Content.ReadAsStringAsync());
        var jobId = uploadDoc.RootElement.GetProperty("job_id").GetString()!;
        Console.WriteLine($"Job ID: {jobId}");

        // --- Poll ---
        string? downloadUrl = null;
        for (int i = 0; i < 60; i++)
        {
            await Task.Delay(4000);
            var statusResp = await _http.GetAsync($"{ApiBase}/jobs/{jobId}");
            using var statusDoc = JsonDocument.Parse(
                await statusResp.Content.ReadAsStringAsync());
            var status = statusDoc.RootElement.GetProperty("status").GetString();
            Console.WriteLine($"[{i+1}] {status}");

            if (status == "done")
            {
                downloadUrl = statusDoc.RootElement
                    .GetProperty("download_url").GetString();
                break;
            }
            if (status == "failed")
                throw new Exception("Conversion failed on server");
        }
        if (downloadUrl is null)
            throw new TimeoutException("Job did not complete in time");

        // --- Download ---
        var epubBytes = await _http.GetByteArrayAsync(downloadUrl);
        await File.WriteAllBytesAsync(epubPath, epubBytes);
    }
}

Run from CLI:

dotnet run -- document.pdf

Or build a standalone executable:

dotnet publish -c Release -r win-x64 --self-contained true

ASP.NET Core controller

For web applications, inject IHttpClientFactory and wrap the conversion in a background service or controller action:

// Program.cs
builder.Services.AddHttpClient("toolkit", c =>
{
    c.BaseAddress = new Uri("https://toolkit.bot/api/v1/");
    c.DefaultRequestHeaders.Authorization =
        new AuthenticationHeaderValue(
            "Bearer", builder.Configuration["Toolkit:ApiKey"]);
});
builder.Services.AddScoped<IEpubConversionService, EpubConversionService>();
// EpubConversionService.cs
public class EpubConversionService : IEpubConversionService
{
    private readonly HttpClient _http;

    public EpubConversionService(IHttpClientFactory factory)
        => _http = factory.CreateClient("toolkit");

    public async Task<byte[]> ConvertAsync(
        Stream pdfStream, string filename,
        CancellationToken ct = default)
    {
        using var form    = new MultipartFormDataContent();
        var content       = new StreamContent(pdfStream);
        content.Headers.ContentType = new MediaTypeHeaderValue("application/pdf");
        form.Add(content, "file", filename);

        var upload = await _http.PostAsync("jobs", form, ct);
        upload.EnsureSuccessStatusCode();

        var uploadJson = await upload.Content.ReadFromJsonAsync<JsonElement>(
            cancellationToken: ct);
        var jobId = uploadJson.GetProperty("job_id").GetString()!;

        for (int i = 0; i < 60; i++)
        {
            await Task.Delay(4000, ct);
            var status = await _http.GetFromJsonAsync<JsonElement>(
                $"jobs/{jobId}", ct);
            var s = status.GetProperty("status").GetString();
            if (s == "done")
            {
                var url = status.GetProperty("download_url").GetString()!;
                return await _http.GetByteArrayAsync(url, ct);
            }
            if (s == "failed")
                throw new InvalidOperationException("Conversion failed");
        }
        throw new TimeoutException("Job timed out");
    }
}

// Controller
[HttpPost("convert")]
public async Task<IActionResult> Convert(IFormFile pdf)
{
    await using var stream = pdf.OpenReadStream();
    var epubBytes = await _epubService.ConvertAsync(stream, pdf.FileName);
    return File(epubBytes, "application/epub+zip",
        Path.ChangeExtension(pdf.FileName, ".epub"));
}

NuGet packages

The examples above use only BCL types. If you prefer a higher-level HTTP library, Refit generates a typed client from an interface:

// Install: dotnet add package Refit

public interface IToolkitApi
{
    [Multipart]
    [Post("/jobs")]
    Task<JobResponse> UploadAsync([AliasAs("file")] StreamPart file);

    [Get("/jobs/{jobId}")]
    Task<JobResponse> GetStatusAsync(string jobId);
}

public record JobResponse(
    [property: JsonPropertyName("job_id")]       string JobId,
    [property: JsonPropertyName("status")]        string Status,
    [property: JsonPropertyName("download_url")]  string? DownloadUrl);

FAQ

Does this work with .NET Framework?

Yes. The HttpClient and MultipartFormDataContent APIs exist in .NET Framework 4.5+. Replace async/await Task.Delay with Thread.Sleep if targeting older frameworks without async support.

How do I handle cancellation tokens properly?

Pass a CancellationToken through to every await call. Use CancellationTokenSource.CancelAfter(TimeSpan.FromMinutes(5)) for an overall timeout on the conversion.

Is there a NuGet package for toolkit.bot?

Not yet. The HttpClient approach above is production-ready and requires no external dependencies.

Get your free API key
Sign up at toolkit.bot/api — free tier, no credit card required.