0.5 credits

PDF Writer Node.js API

Generate PDF documents from HTML documents using our Node.js PDF writing API. Simply use HTML and CSS to describe how the document should look, and call our API to generate your PDF.

Why Nutrient DWS API?

SOC 2 Compliant

Build the workflows you need without worrying about security. We don’t store any document data, and our API endpoints are served through encrypted connections.

Easy Integration

Get up and running in hours, not weeks. Access well-documented APIs and code samples that make integrating with your existing workflows a snap.

Robust and Flexible

With access to more than 30 tools, you can process one document in multiple ways by using API credits. Generate PDF from HTML, convert Word, Excel, PowerPoint and image files to PDF, and more.

Simple and Transparent Pricing

Select a package that suits your needs according to the number of credits you wish to spend. Each API tool and action has a specific credit cost.

Try It Out

This example will create a PDF from the supplied index.html file and render it on an A4-sized page. For detailed information on how to structure your HTML files, along with all the available customization options, see here.

1

Use Your Free API Calls

Sign up and receive 100 credits for free, or log in to automatically add your API key to sample code. If you are not sure how credits are consumed read more in our pricing documentation , or check out this guide on calculating credit usage.

Add a File

Add an HTML file named index.html to your project folder. You can also use our sample file. The file name is case sensitive. Make sure the file name matches the file name in the sample code.

Run the Code

Copy the code and run it from the same folder you added the files to. For more information, see our language-specific getting started guides.

View the Results

Open result.pdf in your project folder to view the results.

curl -X POST https://api.nutrient.io/build \
  -H "Authorization: Bearer your_api_key_here" \
  -o result.pdf \
  --fail \
  -F [email protected] \
  -F instructions='{
      "parts": [
        {
          "html": "index.html"
        }
      ]
    }'
curl -X POST https://api.nutrient.io/build ^
  -H "Authorization: Bearer your_api_key_here" ^
  -o result.pdf ^
  --fail ^
  -F [email protected] ^
  -F instructions="{\"parts\": [{\"html\": \"index.html\"}]}"
package com.example.pspdfkit;

import java.io.File;
import java.io.IOException;
import java.nio.file.FileSystems;
import java.nio.file.Files;
import java.nio.file.StandardCopyOption;

import org.json.JSONArray;
import org.json.JSONObject;

import okhttp3.MediaType;
import okhttp3.MultipartBody;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.RequestBody;
import okhttp3.Response;

public final class PspdfkitApiExample {
  public static void main(final String[] args) throws IOException {
    final RequestBody body = new MultipartBody.Builder()
      .setType(MultipartBody.FORM)
      .addFormDataPart(
        "index.html",
        "index.html",
        RequestBody.create(
          MediaType.parse("text/html"),
          new File("index.html")
        )
      )
      .addFormDataPart(
        "instructions",
        new JSONObject()
          .put("parts", new JSONArray()
            .put(new JSONObject()
              .put("html", "index.html")
            )
          ).toString()
      )
      .build();

    final Request request = new Request.Builder()
      .url("https://api.nutrient.io/build")
      .method("POST", body)
      .addHeader("Authorization", "Bearer your_api_key_here")
      .build();

    final OkHttpClient client = new OkHttpClient()
      .newBuilder()
      .build();

    final Response response = client.newCall(request).execute();

    if (response.isSuccessful()) {
      Files.copy(
        response.body().byteStream(),
        FileSystems.getDefault().getPath("result.pdf"),
        StandardCopyOption.REPLACE_EXISTING
      );
    } else {
      // Handle the error
      throw new IOException(response.body().string());
    }
  }
}
using System;
using System.IO;
using System.Net;
using RestSharp;

namespace PspdfkitApiDemo
{
  class Program
  {
    static void Main(string[] args)
    {
      var client = new RestClient("https://api.nutrient.io/build");

      var request = new RestRequest(Method.POST)
        .AddHeader("Authorization", "Bearer your_api_key_here")
        .AddFile("index.html", "index.html")
        .AddParameter("instructions", new JsonObject
        {
          ["parts"] = new JsonArray
          {
            new JsonObject
            {
              ["html"] = "index.html"
            }
          }
        }.ToString());

      request.AdvancedResponseWriter = (responseStream, response) =>
      {
        if (response.StatusCode == HttpStatusCode.OK)
        {
          using (responseStream)
          {
            using var outputFileWriter = File.OpenWrite("result.pdf");
            responseStream.CopyTo(outputFileWriter);
          }
        }
        else
        {
          var responseStreamReader = new StreamReader(responseStream);
          Console.Write(responseStreamReader.ReadToEnd());
        }
      };

      client.Execute(request);
    }
  }
}
// This code requires Node.js. Do not run this code directly in a web browser.

const axios = require('axios')
const FormData = require('form-data')
const fs = require('fs')

const formData = new FormData()
formData.append('instructions', JSON.stringify({
  parts: [
    {
      html: "index.html"
    }
  ]
}))
formData.append('index.html', fs.createReadStream('index.html'))

;(async () => {
  try {
    const response = await axios.post('https://api.nutrient.io/build', formData, {
      headers: formData.getHeaders({
        'Authorization': 'Bearer your_api_key_here'
      }),
      responseType: "stream"
    })

    response.data.pipe(fs.createWriteStream("result.pdf"))
  } catch (e) {
    const errorString = await streamToString(e.response.data)
    console.log(errorString)
  }
})()

function streamToString(stream) {
  const chunks = []
  return new Promise((resolve, reject) => {
    stream.on("data", (chunk) => chunks.push(Buffer.from(chunk)))
    stream.on("error", (err) => reject(err))
    stream.on("end", () => resolve(Buffer.concat(chunks).toString("utf8")))
  })
}
import requests
import json

response = requests.request(
  'POST',
  'https://api.nutrient.io/build',
  headers = {
    'Authorization': 'Bearer your_api_key_here'
  },
  files = {
    'index.html': open('index.html', 'rb')
  },
  data = {
    'instructions': json.dumps({
      'parts': [
        {
          'html': 'index.html'
        }
      ]
    })
  },
  stream = True
)

if response.ok:
  with open('result.pdf', 'wb') as fd:
    for chunk in response.iter_content(chunk_size=8096):
      fd.write(chunk)
else:
  print(response.text)
  exit()
<?php

$FileHandle = fopen('result.pdf', 'w+');

$curl = curl_init();

curl_setopt_array($curl, array(
  CURLOPT_URL => 'https://api.nutrient.io/build',
  CURLOPT_CUSTOMREQUEST => 'POST',
  CURLOPT_RETURNTRANSFER => true,
  CURLOPT_ENCODING => '',
  CURLOPT_POSTFIELDS => array(
    'instructions' => '{
      "parts": [
        {
          "html": "index.html"
        }
      ]
    }',
    'index.html' => new CURLFILE('index.html')
  ),
  CURLOPT_HTTPHEADER => array(
    'Authorization: Bearer your_api_key_here'
  ),
  CURLOPT_FILE => $FileHandle,
));

$response = curl_exec($curl);

curl_close($curl);

fclose($FileHandle);

POST https://api.nutrient.io/build HTTP/1.1
Content-Type: multipart/form-data; boundary=--customboundary
Authorization: Bearer your_api_key_here

--customboundary
Content-Disposition: form-data; name="instructions"
Content-Type: application/json

{
  "parts": [
    {
      "html": "index.html"
    }
  ]
}
--customboundary
Content-Disposition: form-data; name="index.html"; filename="index.html"
Content-Type: text/html

(index.html data)
--customboundary--
Using Postman? Download our official collection and start using the API with a single click. Read more 

Your API Key

Getting Started

The following section will walk you through how to go from a simple HTML file to a fully featured invoice, making use of all the functionality the PDF Generator API provides.

The Basics of PDF Generation

Let's start with the basics. PDF Generation allows you to create a completely new PDF from a simple HTML document. You can make use of all the tools you'd usually use like:

  • CSS for styling
  • Images and fonts
  • HTML forms

Our PDF Generation API will convert these to a PDF. Let's look at the most basic example. This will generate a PDF with the text "Hello World, I am red!" in large black letters. Don't worry about the color; we'll get to that later.

To run this, create an index.html file containing the HTML in the same folder as your code. Run the code, and you should get result.pdf with your newly generated PDF.

HTML template section:

HTML
<h1>Hello World, I am red!</h1>
<p>And I am green.</p>

Command to generate a PDF file:

curl -X POST https://api.nutrient.io/build \
  -H "Authorization: Bearer your_api_key_here" \
  -o result.pdf \
  --fail \
  -F [email protected] \
  -F instructions='{
      "parts": [
        {
          "html": "index.html"
        }
      ]
    }'
curl -X POST https://api.nutrient.io/build ^
  -H "Authorization: Bearer your_api_key_here" ^
  -o result.pdf ^
  --fail ^
  -F [email protected] ^
  -F instructions="{\"parts\": [{\"html\": \"index.html\"}]}"
package com.example.pspdfkit;

import java.io.File;
import java.io.IOException;
import java.nio.file.FileSystems;
import java.nio.file.Files;
import java.nio.file.StandardCopyOption;

import org.json.JSONArray;
import org.json.JSONObject;

import okhttp3.MediaType;
import okhttp3.MultipartBody;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.RequestBody;
import okhttp3.Response;

public final class PspdfkitApiExample {
  public static void main(final String[] args) throws IOException {
    final RequestBody body = new MultipartBody.Builder()
      .setType(MultipartBody.FORM)
      .addFormDataPart(
        "index.html",
        "index.html",
        RequestBody.create(
          MediaType.parse("text/html"),
          new File("index.html")
        )
      )
      .addFormDataPart(
        "instructions",
        new JSONObject()
          .put("parts", new JSONArray()
            .put(new JSONObject()
              .put("html", "index.html")
            )
          ).toString()
      )
      .build();

    final Request request = new Request.Builder()
      .url("https://api.nutrient.io/build")
      .method("POST", body)
      .addHeader("Authorization", "Bearer your_api_key_here")
      .build();

    final OkHttpClient client = new OkHttpClient()
      .newBuilder()
      .build();

    final Response response = client.newCall(request).execute();

    if (response.isSuccessful()) {
      Files.copy(
        response.body().byteStream(),
        FileSystems.getDefault().getPath("result.pdf"),
        StandardCopyOption.REPLACE_EXISTING
      );
    } else {
      // Handle the error
      throw new IOException(response.body().string());
    }
  }
}
using System;
using System.IO;
using System.Net;
using RestSharp;

namespace PspdfkitApiDemo
{
  class Program
  {
    static void Main(string[] args)
    {
      var client = new RestClient("https://api.nutrient.io/build");

      var request = new RestRequest(Method.POST)
        .AddHeader("Authorization", "Bearer your_api_key_here")
        .AddFile("index.html", "index.html")
        .AddParameter("instructions", new JsonObject
        {
          ["parts"] = new JsonArray
          {
            new JsonObject
            {
              ["html"] = "index.html"
            }
          }
        }.ToString());

      request.AdvancedResponseWriter = (responseStream, response) =>
      {
        if (response.StatusCode == HttpStatusCode.OK)
        {
          using (responseStream)
          {
            using var outputFileWriter = File.OpenWrite("result.pdf");
            responseStream.CopyTo(outputFileWriter);
          }
        }
        else
        {
          var responseStreamReader = new StreamReader(responseStream);
          Console.Write(responseStreamReader.ReadToEnd());
        }
      };

      client.Execute(request);
    }
  }
}
// This code requires Node.js. Do not run this code directly in a web browser.

const axios = require('axios')
const FormData = require('form-data')
const fs = require('fs')

const formData = new FormData()
formData.append('instructions', JSON.stringify({
  parts: [
    {
      html: "index.html"
    }
  ]
}))
formData.append('index.html', fs.createReadStream('index.html'))

;(async () => {
  try {
    const response = await axios.post('https://api.nutrient.io/build', formData, {
      headers: formData.getHeaders({
        'Authorization': 'Bearer your_api_key_here'
      }),
      responseType: "stream"
    })

    response.data.pipe(fs.createWriteStream("result.pdf"))
  } catch (e) {
    const errorString = await streamToString(e.response.data)
    console.log(errorString)
  }
})()

function streamToString(stream) {
  const chunks = []
  return new Promise((resolve, reject) => {
    stream.on("data", (chunk) => chunks.push(Buffer.from(chunk)))
    stream.on("error", (err) => reject(err))
    stream.on("end", () => resolve(Buffer.concat(chunks).toString("utf8")))
  })
}
import requests
import json

response = requests.request(
  'POST',
  'https://api.nutrient.io/build',
  headers = {
    'Authorization': 'Bearer your_api_key_here'
  },
  files = {
    'index.html': open('index.html', 'rb')
  },
  data = {
    'instructions': json.dumps({
      'parts': [
        {
          'html': 'index.html'
        }
      ]
    })
  },
  stream = True
)

if response.ok:
  with open('result.pdf', 'wb') as fd:
    for chunk in response.iter_content(chunk_size=8096):
      fd.write(chunk)
else:
  print(response.text)
  exit()
<?php

$FileHandle = fopen('result.pdf', 'w+');

$curl = curl_init();

curl_setopt_array($curl, array(
  CURLOPT_URL => 'https://api.nutrient.io/build',
  CURLOPT_CUSTOMREQUEST => 'POST',
  CURLOPT_RETURNTRANSFER => true,
  CURLOPT_ENCODING => '',
  CURLOPT_POSTFIELDS => array(
    'instructions' => '{
      "parts": [
        {
          "html": "index.html"
        }
      ]
    }',
    'index.html' => new CURLFILE('index.html')
  ),
  CURLOPT_HTTPHEADER => array(
    'Authorization: Bearer your_api_key_here'
  ),
  CURLOPT_FILE => $FileHandle,
));

$response = curl_exec($curl);

curl_close($curl);

fclose($FileHandle);

POST https://api.nutrient.io/build HTTP/1.1
Content-Type: multipart/form-data; boundary=--customboundary
Authorization: Bearer your_api_key_here

--customboundary
Content-Disposition: form-data; name="instructions"
Content-Type: application/json

{
  "parts": [
    {
      "html": "index.html"
    }
  ]
}
--customboundary
Content-Disposition: form-data; name="index.html"; filename="index.html"
Content-Type: text/html

(index.html data)
--customboundary--

Applying Basic Styling

Now you have your basic PDF, but in spite of what the text says, it's all black. Luckily, you can use inline CSS to style your text.

In this case, you'll make the heading red and the text below green. Update your index.html file, adding the inline styles, and run your code again.

HTML template section:

HTML
<h1 style="color: red;">Hello World, I am red!</h1>
<p style="color: green;">And I am green.</p>

Command to generate a PDF file:

curl -X POST https://api.nutrient.io/build \
  -H "Authorization: Bearer your_api_key_here" \
  -o result.pdf \
  --fail \
  -F [email protected] \
  -F instructions='{
      "parts": [
        {
          "html": "index.html"
        }
      ]
    }'
curl -X POST https://api.nutrient.io/build ^
  -H "Authorization: Bearer your_api_key_here" ^
  -o result.pdf ^
  --fail ^
  -F [email protected] ^
  -F instructions="{\"parts\": [{\"html\": \"index.html\"}]}"
package com.example.pspdfkit;

import java.io.File;
import java.io.IOException;
import java.nio.file.FileSystems;
import java.nio.file.Files;
import java.nio.file.StandardCopyOption;

import org.json.JSONArray;
import org.json.JSONObject;

import okhttp3.MediaType;
import okhttp3.MultipartBody;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.RequestBody;
import okhttp3.Response;

public final class PspdfkitApiExample {
  public static void main(final String[] args) throws IOException {
    final RequestBody body = new MultipartBody.Builder()
      .setType(MultipartBody.FORM)
      .addFormDataPart(
        "index.html",
        "index.html",
        RequestBody.create(
          MediaType.parse("text/html"),
          new File("index.html")
        )
      )
      .addFormDataPart(
        "instructions",
        new JSONObject()
          .put("parts", new JSONArray()
            .put(new JSONObject()
              .put("html", "index.html")
            )
          ).toString()
      )
      .build();

    final Request request = new Request.Builder()
      .url("https://api.nutrient.io/build")
      .method("POST", body)
      .addHeader("Authorization", "Bearer your_api_key_here")
      .build();

    final OkHttpClient client = new OkHttpClient()
      .newBuilder()
      .build();

    final Response response = client.newCall(request).execute();

    if (response.isSuccessful()) {
      Files.copy(
        response.body().byteStream(),
        FileSystems.getDefault().getPath("result.pdf"),
        StandardCopyOption.REPLACE_EXISTING
      );
    } else {
      // Handle the error
      throw new IOException(response.body().string());
    }
  }
}
using System;
using System.IO;
using System.Net;
using RestSharp;

namespace PspdfkitApiDemo
{
  class Program
  {
    static void Main(string[] args)
    {
      var client = new RestClient("https://api.nutrient.io/build");

      var request = new RestRequest(Method.POST)
        .AddHeader("Authorization", "Bearer your_api_key_here")
        .AddFile("index.html", "index.html")
        .AddParameter("instructions", new JsonObject
        {
          ["parts"] = new JsonArray
          {
            new JsonObject
            {
              ["html"] = "index.html"
            }
          }
        }.ToString());

      request.AdvancedResponseWriter = (responseStream, response) =>
      {
        if (response.StatusCode == HttpStatusCode.OK)
        {
          using (responseStream)
          {
            using var outputFileWriter = File.OpenWrite("result.pdf");
            responseStream.CopyTo(outputFileWriter);
          }
        }
        else
        {
          var responseStreamReader = new StreamReader(responseStream);
          Console.Write(responseStreamReader.ReadToEnd());
        }
      };

      client.Execute(request);
    }
  }
}
// This code requires Node.js. Do not run this code directly in a web browser.

const axios = require('axios')
const FormData = require('form-data')
const fs = require('fs')

const formData = new FormData()
formData.append('instructions', JSON.stringify({
  parts: [
    {
      html: "index.html"
    }
  ]
}))
formData.append('index.html', fs.createReadStream('index.html'))

;(async () => {
  try {
    const response = await axios.post('https://api.nutrient.io/build', formData, {
      headers: formData.getHeaders({
        'Authorization': 'Bearer your_api_key_here'
      }),
      responseType: "stream"
    })

    response.data.pipe(fs.createWriteStream("result.pdf"))
  } catch (e) {
    const errorString = await streamToString(e.response.data)
    console.log(errorString)
  }
})()

function streamToString(stream) {
  const chunks = []
  return new Promise((resolve, reject) => {
    stream.on("data", (chunk) => chunks.push(Buffer.from(chunk)))
    stream.on("error", (err) => reject(err))
    stream.on("end", () => resolve(Buffer.concat(chunks).toString("utf8")))
  })
}
import requests
import json

response = requests.request(
  'POST',
  'https://api.nutrient.io/build',
  headers = {
    'Authorization': 'Bearer your_api_key_here'
  },
  files = {
    'index.html': open('index.html', 'rb')
  },
  data = {
    'instructions': json.dumps({
      'parts': [
        {
          'html': 'index.html'
        }
      ]
    })
  },
  stream = True
)

if response.ok:
  with open('result.pdf', 'wb') as fd:
    for chunk in response.iter_content(chunk_size=8096):
      fd.write(chunk)
else:
  print(response.text)
  exit()
<?php

$FileHandle = fopen('result.pdf', 'w+');

$curl = curl_init();

curl_setopt_array($curl, array(
  CURLOPT_URL => 'https://api.nutrient.io/build',
  CURLOPT_CUSTOMREQUEST => 'POST',
  CURLOPT_RETURNTRANSFER => true,
  CURLOPT_ENCODING => '',
  CURLOPT_POSTFIELDS => array(
    'instructions' => '{
      "parts": [
        {
          "html": "index.html"
        }
      ]
    }',
    'index.html' => new CURLFILE('index.html')
  ),
  CURLOPT_HTTPHEADER => array(
    'Authorization: Bearer your_api_key_here'
  ),
  CURLOPT_FILE => $FileHandle,
));

$response = curl_exec($curl);

curl_close($curl);

fclose($FileHandle);

POST https://api.nutrient.io/build HTTP/1.1
Content-Type: multipart/form-data; boundary=--customboundary
Authorization: Bearer your_api_key_here

--customboundary
Content-Disposition: form-data; name="instructions"
Content-Type: application/json

{
  "parts": [
    {
      "html": "index.html"
    }
  ]
}
--customboundary
Content-Disposition: form-data; name="index.html"; filename="index.html"
Content-Type: text/html

(index.html data)
--customboundary--

Introducing Assets

While inline styling works, more commonly, you'll want to have all your styles in a single CSS file you include. As luck would have it, our PDF Generation API supports this. You can send as many additional assets with your request as you require. For now, you'll just add a CSS file to keep track of all your styles. Create style.css in the same folder as your HTML file and copy the content.

CSS file:

CSS
h1 {
  color: red;
}

p {
  color: green;
}

To make use of your new style.css, you'll have to update your index.html file. When updating your HTML file, make sure to refer to the CSS file with just its name. Nested paths aren't currently supported, and assets need to always be referred to without any path.

HTML template section:

HTML
<!DOCTYPE html>
<html lang="en">
  <head>
    <link rel="stylesheet" href="style.css" />
  </head>
  <body>
    <h1>Hello World, I am red!</h1>
    <p>And I am green.</p>
  </body>
</html>

You'll also have to include your new CSS file in your request. It needs to be added both as an additional file to be sent with the request, and in the assets key of your HTML part.

Command to generate a PDF file:

curl -X POST https://api.nutrient.io/build \
  -H "Authorization: Bearer your_api_key_here" \
  -o result.pdf \
  --fail \
  -F [email protected] \
  -F [email protected] \
  -F instructions='{
      "parts": [
        {
          "html": "index.html",
          "assets": [
            "style.css"
          ]
        }
      ]
    }'
curl -X POST https://api.nutrient.io/build ^
  -H "Authorization: Bearer your_api_key_here" ^
  -o result.pdf ^
  --fail ^
  -F [email protected] ^
  -F [email protected] ^
  -F instructions="{\"parts\": [{\"html\": \"index.html\", \"assets\": [\"style.css\"]}]}"
package com.example.pspdfkit;

import java.io.File;
import java.io.IOException;
import java.nio.file.FileSystems;
import java.nio.file.Files;
import java.nio.file.StandardCopyOption;

import org.json.JSONArray;
import org.json.JSONObject;

import okhttp3.MediaType;
import okhttp3.MultipartBody;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.RequestBody;
import okhttp3.Response;

public final class PspdfkitApiExample {
  public static void main(final String[] args) throws IOException {
    final RequestBody body = new MultipartBody.Builder()
      .setType(MultipartBody.FORM)
      .addFormDataPart(
        "index.html",
        "index.html",
        RequestBody.create(
          MediaType.parse("text/html"),
          new File("index.html")
        )
      )
      .addFormDataPart(
        "style.css",
        "style.css",
        RequestBody.create(
          MediaType.parse("text/css"),
          new File("style.css")
        )
      )
      .addFormDataPart(
        "instructions",
        new JSONObject()
          .put("parts", new JSONArray()
            .put(new JSONObject()
              .put("html", "index.html")
              .put("assets", new JSONArray()
                .put("style.css")
              )
            )
          ).toString()
      )
      .build();

    final Request request = new Request.Builder()
      .url("https://api.nutrient.io/build")
      .method("POST", body)
      .addHeader("Authorization", "Bearer your_api_key_here")
      .build();

    final OkHttpClient client = new OkHttpClient()
      .newBuilder()
      .build();

    final Response response = client.newCall(request).execute();

    if (response.isSuccessful()) {
      Files.copy(
        response.body().byteStream(),
        FileSystems.getDefault().getPath("result.pdf"),
        StandardCopyOption.REPLACE_EXISTING
      );
    } else {
      // Handle the error
      throw new IOException(response.body().string());
    }
  }
}
using System;
using System.IO;
using System.Net;
using RestSharp;

namespace PspdfkitApiDemo
{
  class Program
  {
    static void Main(string[] args)
    {
      var client = new RestClient("https://api.nutrient.io/build");

      var request = new RestRequest(Method.POST)
        .AddHeader("Authorization", "Bearer your_api_key_here")
        .AddFile("index.html", "index.html")
        .AddFile("style.css", "style.css")
        .AddParameter("instructions", new JsonObject
        {
          ["parts"] = new JsonArray
          {
            new JsonObject
            {
              ["html"] = "index.html",
              ["assets"] = new JsonArray
              {
                "style.css"
              }
            }
          }
        }.ToString());

      request.AdvancedResponseWriter = (responseStream, response) =>
      {
        if (response.StatusCode == HttpStatusCode.OK)
        {
          using (responseStream)
          {
            using var outputFileWriter = File.OpenWrite("result.pdf");
            responseStream.CopyTo(outputFileWriter);
          }
        }
        else
        {
          var responseStreamReader = new StreamReader(responseStream);
          Console.Write(responseStreamReader.ReadToEnd());
        }
      };

      client.Execute(request);
    }
  }
}
// This code requires Node.js. Do not run this code directly in a web browser.

const axios = require('axios')
const FormData = require('form-data')
const fs = require('fs')

const formData = new FormData()
formData.append('instructions', JSON.stringify({
  parts: [
    {
      html: "index.html",
      assets: [
        "style.css"
      ]
    }
  ]
}))
formData.append('index.html', fs.createReadStream('index.html'))
formData.append('style.css', fs.createReadStream('style.css'))

;(async () => {
  try {
    const response = await axios.post('https://api.nutrient.io/build', formData, {
      headers: formData.getHeaders({
        'Authorization': 'Bearer your_api_key_here'
      }),
      responseType: "stream"
    })

    response.data.pipe(fs.createWriteStream("result.pdf"))
  } catch (e) {
    const errorString = await streamToString(e.response.data)
    console.log(errorString)
  }
})()

function streamToString(stream) {
  const chunks = []
  return new Promise((resolve, reject) => {
    stream.on("data", (chunk) => chunks.push(Buffer.from(chunk)))
    stream.on("error", (err) => reject(err))
    stream.on("end", () => resolve(Buffer.concat(chunks).toString("utf8")))
  })
}
import requests
import json

response = requests.request(
  'POST',
  'https://api.nutrient.io/build',
  headers = {
    'Authorization': 'Bearer your_api_key_here'
  },
  files = {
    'index.html': open('index.html', 'rb'),
    'style.css': open('style.css', 'rb')
  },
  data = {
    'instructions': json.dumps({
      'parts': [
        {
          'html': 'index.html',
          'assets': [
            'style.css'
          ]
        }
      ]
    })
  },
  stream = True
)

if response.ok:
  with open('result.pdf', 'wb') as fd:
    for chunk in response.iter_content(chunk_size=8096):
      fd.write(chunk)
else:
  print(response.text)
  exit()
<?php

$FileHandle = fopen('result.pdf', 'w+');

$curl = curl_init();

curl_setopt_array($curl, array(
  CURLOPT_URL => 'https://api.nutrient.io/build',
  CURLOPT_CUSTOMREQUEST => 'POST',
  CURLOPT_RETURNTRANSFER => true,
  CURLOPT_ENCODING => '',
  CURLOPT_POSTFIELDS => array(
    'instructions' => '{
      "parts": [
        {
          "html": "index.html",
          "assets": [
            "style.css"
          ]
        }
      ]
    }',
    'index.html' => new CURLFILE('index.html'),
    'style.css' => new CURLFILE('style.css')
  ),
  CURLOPT_HTTPHEADER => array(
    'Authorization: Bearer your_api_key_here'
  ),
  CURLOPT_FILE => $FileHandle,
));

$response = curl_exec($curl);

curl_close($curl);

fclose($FileHandle);

POST https://api.nutrient.io/build HTTP/1.1
Content-Type: multipart/form-data; boundary=--customboundary
Authorization: Bearer your_api_key_here

--customboundary
Content-Disposition: form-data; name="instructions"
Content-Type: application/json

{
  "parts": [
    {
      "html": "index.html",
      "assets": [
        "style.css"
      ]
    }
  ]
}
--customboundary
Content-Disposition: form-data; name="index.html"; filename="index.html"
Content-Type: text/html

(index.html data)
--customboundary
Content-Disposition: form-data; name="style.css"; filename="style.css"
Content-Type: text/css

(style.css data)
--customboundary--

Creating An Invoice

With the core building blocks (the HTML and external assets) out of the way, let's look at a real-world example: creating a PDF invoice. You'll start with the basic invoice and then add some advanced features on top.

Basics

CSS

First, you'll update your CSS. All the code is taken from the "Invoice" example you can download at the bottom, but the relevant parts are also duplicated here. First, replace the contents of style.css . Define some common styles used throughout the invoice, as well as the styles only applying to the header.

CSS file:

CSS
body {
  font-size: 0.75rem;
  font-weight: 400;
  color: #000000;
  margin: 0 auto;
  position: relative;
}

h2 {
  font-size: 1.25rem;
  font-weight: 400;
}

h4 {
  font-size: 1rem;
  font-weight: 400;
}

.page {
  margin-left: 5rem;
  margin-right: 5rem;
}

.intro-table {
  display: flex;
  justify-content: space-between;
  margin: 3rem 0 3rem 0;
  border-top: 1px solid #000000;
  border-bottom: 1px solid #000000;
}

.intro-form {
  display: flex;
  flex-direction: column;
  border-right: 1px solid #000000;
  width: 50%;
}

.intro-form:last-child {
  border-right: none;
}

.intro-table-title {
  font-size: 0.625rem;
  margin: 0;
}

.intro-form-item {
  padding: 1.25rem 1.5rem 1.25rem 1.5rem;
}

.intro-form-item:first-child {
  padding-left: 0;
}

.intro-form-item:last-child {
  padding-right: 0;
}

.intro-form-item-border {
  padding: 1.25rem 0 0.75rem 1.5rem;
  border-bottom: 1px solid #000000;
}

.intro-form-item-border:last-child {
  border-bottom: none;
}

HTML

Next, update your index.html file, adding the invoice numbers, as well as an address block, and some additional data, like date and type of payment. As you can see, you're only using plain HTML here.

HTML template section:

HTML
<!DOCTYPE html>
<html lang="en">
  <head>
    <link rel="stylesheet" href="style.css" />
  </head>
  <body>
    <div class="page" style="page-break-after: always">
      <div>
        <h2>Invoice #</h2>
      </div>

      <div class="intro-table">
        <div class="intro-form intro-form-item">
          <p class="intro-table-title">Billed To:</p>
          <p>
            Company Ltd.<br />
            Address<br />
            Country<br />
            VAT ID: ATU12345678
          </p>
        </div>

        <div class="intro-form">
          <div class="intro-form-item-border">
            <p class="intro-table-title">Payment Date:</p>
            <p>November 22nd 2021</p>
          </div>

          <div class="intro-form-item-border">
            <p class="intro-table-title">Payment Method:</p>
            <p>Bank Transfer</p>
          </div>
        </div>
      </div>
    </div>
  </body>
</html>

Code

If you rerun the generation with the new HTML and CSS, you'll see a nicely formatted invoice header in your PDF.

Command to generate a PDF file:

curl -X POST https://api.nutrient.io/build \
  -H "Authorization: Bearer your_api_key_here" \
  -o result.pdf \
  --fail \
  -F [email protected] \
  -F [email protected] \
  -F instructions='{
      "parts": [
        {
          "html": "index.html",
          "assets": [
            "style.css"
          ]
        }
      ]
    }'
curl -X POST https://api.nutrient.io/build ^
  -H "Authorization: Bearer your_api_key_here" ^
  -o result.pdf ^
  --fail ^
  -F [email protected] ^
  -F [email protected] ^
  -F instructions="{\"parts\": [{\"html\": \"index.html\", \"assets\": [\"style.css\"]}]}"
package com.example.pspdfkit;

import java.io.File;
import java.io.IOException;
import java.nio.file.FileSystems;
import java.nio.file.Files;
import java.nio.file.StandardCopyOption;

import org.json.JSONArray;
import org.json.JSONObject;

import okhttp3.MediaType;
import okhttp3.MultipartBody;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.RequestBody;
import okhttp3.Response;

public final class PspdfkitApiExample {
  public static void main(final String[] args) throws IOException {
    final RequestBody body = new MultipartBody.Builder()
      .setType(MultipartBody.FORM)
      .addFormDataPart(
        "index.html",
        "index.html",
        RequestBody.create(
          MediaType.parse("text/html"),
          new File("index.html")
        )
      )
      .addFormDataPart(
        "style.css",
        "style.css",
        RequestBody.create(
          MediaType.parse("text/css"),
          new File("style.css")
        )
      )
      .addFormDataPart(
        "instructions",
        new JSONObject()
          .put("parts", new JSONArray()
            .put(new JSONObject()
              .put("html", "index.html")
              .put("assets", new JSONArray()
                .put("style.css")
              )
            )
          ).toString()
      )
      .build();

    final Request request = new Request.Builder()
      .url("https://api.nutrient.io/build")
      .method("POST", body)
      .addHeader("Authorization", "Bearer your_api_key_here")
      .build();

    final OkHttpClient client = new OkHttpClient()
      .newBuilder()
      .build();

    final Response response = client.newCall(request).execute();

    if (response.isSuccessful()) {
      Files.copy(
        response.body().byteStream(),
        FileSystems.getDefault().getPath("result.pdf"),
        StandardCopyOption.REPLACE_EXISTING
      );
    } else {
      // Handle the error
      throw new IOException(response.body().string());
    }
  }
}
using System;
using System.IO;
using System.Net;
using RestSharp;

namespace PspdfkitApiDemo
{
  class Program
  {
    static void Main(string[] args)
    {
      var client = new RestClient("https://api.nutrient.io/build");

      var request = new RestRequest(Method.POST)
        .AddHeader("Authorization", "Bearer your_api_key_here")
        .AddFile("index.html", "index.html")
        .AddFile("style.css", "style.css")
        .AddParameter("instructions", new JsonObject
        {
          ["parts"] = new JsonArray
          {
            new JsonObject
            {
              ["html"] = "index.html",
              ["assets"] = new JsonArray
              {
                "style.css"
              }
            }
          }
        }.ToString());

      request.AdvancedResponseWriter = (responseStream, response) =>
      {
        if (response.StatusCode == HttpStatusCode.OK)
        {
          using (responseStream)
          {
            using var outputFileWriter = File.OpenWrite("result.pdf");
            responseStream.CopyTo(outputFileWriter);
          }
        }
        else
        {
          var responseStreamReader = new StreamReader(responseStream);
          Console.Write(responseStreamReader.ReadToEnd());
        }
      };

      client.Execute(request);
    }
  }
}
// This code requires Node.js. Do not run this code directly in a web browser.

const axios = require('axios')
const FormData = require('form-data')
const fs = require('fs')

const formData = new FormData()
formData.append('instructions', JSON.stringify({
  parts: [
    {
      html: "index.html",
      assets: [
        "style.css"
      ]
    }
  ]
}))
formData.append('index.html', fs.createReadStream('index.html'))
formData.append('style.css', fs.createReadStream('style.css'))

;(async () => {
  try {
    const response = await axios.post('https://api.nutrient.io/build', formData, {
      headers: formData.getHeaders({
        'Authorization': 'Bearer your_api_key_here'
      }),
      responseType: "stream"
    })

    response.data.pipe(fs.createWriteStream("result.pdf"))
  } catch (e) {
    const errorString = await streamToString(e.response.data)
    console.log(errorString)
  }
})()

function streamToString(stream) {
  const chunks = []
  return new Promise((resolve, reject) => {
    stream.on("data", (chunk) => chunks.push(Buffer.from(chunk)))
    stream.on("error", (err) => reject(err))
    stream.on("end", () => resolve(Buffer.concat(chunks).toString("utf8")))
  })
}
import requests
import json

response = requests.request(
  'POST',
  'https://api.nutrient.io/build',
  headers = {
    'Authorization': 'Bearer your_api_key_here'
  },
  files = {
    'index.html': open('index.html', 'rb'),
    'style.css': open('style.css', 'rb')
  },
  data = {
    'instructions': json.dumps({
      'parts': [
        {
          'html': 'index.html',
          'assets': [
            'style.css'
          ]
        }
      ]
    })
  },
  stream = True
)

if response.ok:
  with open('result.pdf', 'wb') as fd:
    for chunk in response.iter_content(chunk_size=8096):
      fd.write(chunk)
else:
  print(response.text)
  exit()
<?php

$FileHandle = fopen('result.pdf', 'w+');

$curl = curl_init();

curl_setopt_array($curl, array(
  CURLOPT_URL => 'https://api.nutrient.io/build',
  CURLOPT_CUSTOMREQUEST => 'POST',
  CURLOPT_RETURNTRANSFER => true,
  CURLOPT_ENCODING => '',
  CURLOPT_POSTFIELDS => array(
    'instructions' => '{
      "parts": [
        {
          "html": "index.html",
          "assets": [
            "style.css"
          ]
        }
      ]
    }',
    'index.html' => new CURLFILE('index.html'),
    'style.css' => new CURLFILE('style.css')
  ),
  CURLOPT_HTTPHEADER => array(
    'Authorization: Bearer your_api_key_here'
  ),
  CURLOPT_FILE => $FileHandle,
));

$response = curl_exec($curl);

curl_close($curl);

fclose($FileHandle);

POST https://api.nutrient.io/build HTTP/1.1
Content-Type: multipart/form-data; boundary=--customboundary
Authorization: Bearer your_api_key_here

--customboundary
Content-Disposition: form-data; name="instructions"
Content-Type: application/json

{
  "parts": [
    {
      "html": "index.html",
      "assets": [
        "style.css"
      ]
    }
  ]
}
--customboundary
Content-Disposition: form-data; name="index.html"; filename="index.html"
Content-Type: text/html

(index.html data)
--customboundary
Content-Disposition: form-data; name="style.css"; filename="style.css"
Content-Type: text/css

(style.css data)
--customboundary--

Custom Fonts

CSS

Now that you've got your basic header, you'll make things a bit nicer and add some custom fonts. Much like with the CSS file, all the fonts you use need to be placed in the same folder as your HTML file and then added to your code for making requests to Nutrient DWS API. (The example is already updated to show you how.)

One thing to consider is that, as discussed before, you should always refer to assets by their name, as subfolders aren't supported.

First, you have to add the @font-family definitions to your CSS file. The example shows you the entire updated style.css file.

CSS file:

CSS
@font-face {
  font-family: "Inter";
  src: url("Inter-Regular.ttf") format("truetype");
  font-weight: 400;
  font-style: normal;
}

@font-face {
  font-family: "Inter";
  src: url("Inter-Medium.ttf") format("truetype");
  font-weight: 500;
  font-style: normal;
}

@font-face {
  font-family: "Inter";
  src: url("Inter-Bold.ttf") format("truetype");
  font-weight: 700;
  font-style: normal;
}

@font-face {
  font-family: "Space Mono";
  src: url("SpaceMono-Regular.ttf") format("truetype");
  font-weight: 400;
  font-style: normal;
}

body {
  font-size: 0.75rem;
  font-family: "Inter", sans-serif;
  font-weight: 400;
  color: #000000;
  margin: 0 auto;
  position: relative;
}

h2 {
  font-family: "Space Mono", monospace;
  font-size: 1.25rem;
  font-weight: 400;
}

h4 {
  font-family: "Space Mono", monospace;
  font-size: 1rem;
  font-weight: 400;
}

.page {
  margin-left: 5rem;
  margin-right: 5rem;
}

.intro-table {
  display: flex;
  justify-content: space-between;
  margin: 3rem 0 3rem 0;
  border-top: 1px solid #000000;
  border-bottom: 1px solid #000000;
}

.intro-form {
  display: flex;
  flex-direction: column;
  border-right: 1px solid #000000;
  width: 50%;
}

.intro-form:last-child {
  border-right: none;
}

.intro-table-title {
  font-size: 0.625rem;
  margin: 0;
}

.intro-form-item {
  padding: 1.25rem 1.5rem 1.25rem 1.5rem;
}

.intro-form-item:first-child {
  padding-left: 0;
}

.intro-form-item:last-child {
  padding-right: 0;
}

.intro-form-item-border {
  padding: 1.25rem 0 0.75rem 1.5rem;
  border-bottom: 1px solid #000000;
}

.intro-form-item-border:last-child {
  border-bottom: none;
}

Code

Then you have to add the fonts to your code for making requests. You also have to add the font files in the same folder as your HTML file and CSS file. This way, they can be sent with the request. You can find the fonts as part of our invoice example.

Add all the font files to the same folder as your index.html and style.css files.

If you rerun the new code with the new CSS file, you should now see the fonts in the PDF replaced by your newly provided fonts, giving the invoice a cleaner look.

Command to generate a PDF file:

curl -X POST https://api.nutrient.io/build \
  -H "Authorization: Bearer your_api_key_here" \
  -o result.pdf \
  --fail \
  -F [email protected] \
  -F [email protected] \
  -F [email protected] \
  -F [email protected] \
  -F [email protected] \
  -F [email protected] \
  -F instructions='{
      "parts": [
        {
          "html": "index.html",
          "assets": [
            "style.css",
            "Inter-Regular.ttf",
            "Inter-Medium.ttf",
            "Inter-Bold.ttf",
            "SpaceMono-Regular.ttf"
          ]
        }
      ]
    }'
curl -X POST https://api.nutrient.io/build ^
  -H "Authorization: Bearer your_api_key_here" ^
  -o result.pdf ^
  --fail ^
  -F [email protected] ^
  -F [email protected] ^
  -F [email protected] ^
  -F [email protected] ^
  -F [email protected] ^
  -F [email protected] ^
  -F instructions="{\"parts\": [{\"html\": \"index.html\", \"assets\": [\"style.css\", \"Inter-Regular.ttf\", \"Inter-Medium.ttf\", \"Inter-Bold.ttf\", \"SpaceMono-Regular.ttf\"]}]}"
package com.example.pspdfkit;

import java.io.File;
import java.io.IOException;
import java.nio.file.FileSystems;
import java.nio.file.Files;
import java.nio.file.StandardCopyOption;

import org.json.JSONArray;
import org.json.JSONObject;

import okhttp3.MediaType;
import okhttp3.MultipartBody;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.RequestBody;
import okhttp3.Response;

public final class PspdfkitApiExample {
  public static void main(final String[] args) throws IOException {
    final RequestBody body = new MultipartBody.Builder()
      .setType(MultipartBody.FORM)
      .addFormDataPart(
        "index.html",
        "index.html",
        RequestBody.create(
          MediaType.parse("text/html"),
          new File("index.html")
        )
      )
      .addFormDataPart(
        "style.css",
        "style.css",
        RequestBody.create(
          MediaType.parse("text/css"),
          new File("style.css")
        )
      )
      .addFormDataPart(
        "Inter-Regular.ttf",
        "Inter-Regular.ttf",
        RequestBody.create(
          MediaType.parse("font/ttf"),
          new File("Inter-Regular.ttf")
        )
      )
      .addFormDataPart(
        "Inter-Medium.ttf",
        "Inter-Medium.ttf",
        RequestBody.create(
          MediaType.parse("font/ttf"),
          new File("Inter-Medium.ttf")
        )
      )
      .addFormDataPart(
        "Inter-Bold.ttf",
        "Inter-Bold.ttf",
        RequestBody.create(
          MediaType.parse("font/ttf"),
          new File("Inter-Bold.ttf")
        )
      )
      .addFormDataPart(
        "SpaceMono-Regular.ttf",
        "SpaceMono-Regular.ttf",
        RequestBody.create(
          MediaType.parse("font/ttf"),
          new File("SpaceMono-Regular.ttf")
        )
      )
      .addFormDataPart(
        "instructions",
        new JSONObject()
          .put("parts", new JSONArray()
            .put(new JSONObject()
              .put("html", "index.html")
              .put("assets", new JSONArray()
                .put("style.css")
                .put("Inter-Regular.ttf")
                .put("Inter-Medium.ttf")
                .put("Inter-Bold.ttf")
                .put("SpaceMono-Regular.ttf")
              )
            )
          ).toString()
      )
      .build();

    final Request request = new Request.Builder()
      .url("https://api.nutrient.io/build")
      .method("POST", body)
      .addHeader("Authorization", "Bearer your_api_key_here")
      .build();

    final OkHttpClient client = new OkHttpClient()
      .newBuilder()
      .build();

    final Response response = client.newCall(request).execute();

    if (response.isSuccessful()) {
      Files.copy(
        response.body().byteStream(),
        FileSystems.getDefault().getPath("result.pdf"),
        StandardCopyOption.REPLACE_EXISTING
      );
    } else {
      // Handle the error
      throw new IOException(response.body().string());
    }
  }
}
using System;
using System.IO;
using System.Net;
using RestSharp;

namespace PspdfkitApiDemo
{
  class Program
  {
    static void Main(string[] args)
    {
      var client = new RestClient("https://api.nutrient.io/build");

      var request = new RestRequest(Method.POST)
        .AddHeader("Authorization", "Bearer your_api_key_here")
        .AddFile("index.html", "index.html")
        .AddFile("style.css", "style.css")
        .AddFile("Inter-Regular.ttf", "Inter-Regular.ttf")
        .AddFile("Inter-Medium.ttf", "Inter-Medium.ttf")
        .AddFile("Inter-Bold.ttf", "Inter-Bold.ttf")
        .AddFile("SpaceMono-Regular.ttf", "SpaceMono-Regular.ttf")
        .AddParameter("instructions", new JsonObject
        {
          ["parts"] = new JsonArray
          {
            new JsonObject
            {
              ["html"] = "index.html",
              ["assets"] = new JsonArray
              {
                "style.css",
                "Inter-Regular.ttf",
                "Inter-Medium.ttf",
                "Inter-Bold.ttf",
                "SpaceMono-Regular.ttf"
              }
            }
          }
        }.ToString());

      request.AdvancedResponseWriter = (responseStream, response) =>
      {
        if (response.StatusCode == HttpStatusCode.OK)
        {
          using (responseStream)
          {
            using var outputFileWriter = File.OpenWrite("result.pdf");
            responseStream.CopyTo(outputFileWriter);
          }
        }
        else
        {
          var responseStreamReader = new StreamReader(responseStream);
          Console.Write(responseStreamReader.ReadToEnd());
        }
      };

      client.Execute(request);
    }
  }
}
// This code requires Node.js. Do not run this code directly in a web browser.

const axios = require('axios')
const FormData = require('form-data')
const fs = require('fs')

const formData = new FormData()
formData.append('instructions', JSON.stringify({
  parts: [
    {
      html: "index.html",
      assets: [
        "style.css",
        "Inter-Regular.ttf",
        "Inter-Medium.ttf",
        "Inter-Bold.ttf",
        "SpaceMono-Regular.ttf"
      ]
    }
  ]
}))
formData.append('index.html', fs.createReadStream('index.html'))
formData.append('style.css', fs.createReadStream('style.css'))
formData.append('Inter-Regular.ttf', fs.createReadStream('Inter-Regular.ttf'))
formData.append('Inter-Medium.ttf', fs.createReadStream('Inter-Medium.ttf'))
formData.append('Inter-Bold.ttf', fs.createReadStream('Inter-Bold.ttf'))
formData.append('SpaceMono-Regular.ttf', fs.createReadStream('SpaceMono-Regular.ttf'))

;(async () => {
  try {
    const response = await axios.post('https://api.nutrient.io/build', formData, {
      headers: formData.getHeaders({
        'Authorization': 'Bearer your_api_key_here'
      }),
      responseType: "stream"
    })

    response.data.pipe(fs.createWriteStream("result.pdf"))
  } catch (e) {
    const errorString = await streamToString(e.response.data)
    console.log(errorString)
  }
})()

function streamToString(stream) {
  const chunks = []
  return new Promise((resolve, reject) => {
    stream.on("data", (chunk) => chunks.push(Buffer.from(chunk)))
    stream.on("error", (err) => reject(err))
    stream.on("end", () => resolve(Buffer.concat(chunks).toString("utf8")))
  })
}
import requests
import json

response = requests.request(
  'POST',
  'https://api.nutrient.io/build',
  headers = {
    'Authorization': 'Bearer your_api_key_here'
  },
  files = {
    'index.html': open('index.html', 'rb'),
    'style.css': open('style.css', 'rb'),
    'Inter-Regular.ttf': open('Inter-Regular.ttf', 'rb'),
    'Inter-Medium.ttf': open('Inter-Medium.ttf', 'rb'),
    'Inter-Bold.ttf': open('Inter-Bold.ttf', 'rb'),
    'SpaceMono-Regular.ttf': open('SpaceMono-Regular.ttf', 'rb')
  },
  data = {
    'instructions': json.dumps({
      'parts': [
        {
          'html': 'index.html',
          'assets': [
            'style.css',
            'Inter-Regular.ttf',
            'Inter-Medium.ttf',
            'Inter-Bold.ttf',
            'SpaceMono-Regular.ttf'
          ]
        }
      ]
    })
  },
  stream = True
)

if response.ok:
  with open('result.pdf', 'wb') as fd:
    for chunk in response.iter_content(chunk_size=8096):
      fd.write(chunk)
else:
  print(response.text)
  exit()
<?php

$FileHandle = fopen('result.pdf', 'w+');

$curl = curl_init();

curl_setopt_array($curl, array(
  CURLOPT_URL => 'https://api.nutrient.io/build',
  CURLOPT_CUSTOMREQUEST => 'POST',
  CURLOPT_RETURNTRANSFER => true,
  CURLOPT_ENCODING => '',
  CURLOPT_POSTFIELDS => array(
    'instructions' => '{
      "parts": [
        {
          "html": "index.html",
          "assets": [
            "style.css",
            "Inter-Regular.ttf",
            "Inter-Medium.ttf",
            "Inter-Bold.ttf",
            "SpaceMono-Regular.ttf"
          ]
        }
      ]
    }',
    'index.html' => new CURLFILE('index.html'),
    'style.css' => new CURLFILE('style.css'),
    'Inter-Regular.ttf' => new CURLFILE('Inter-Regular.ttf'),
    'Inter-Medium.ttf' => new CURLFILE('Inter-Medium.ttf'),
    'Inter-Bold.ttf' => new CURLFILE('Inter-Bold.ttf'),
    'SpaceMono-Regular.ttf' => new CURLFILE('SpaceMono-Regular.ttf')
  ),
  CURLOPT_HTTPHEADER => array(
    'Authorization: Bearer your_api_key_here'
  ),
  CURLOPT_FILE => $FileHandle,
));

$response = curl_exec($curl);

curl_close($curl);

fclose($FileHandle);

POST https://api.nutrient.io/build HTTP/1.1
Content-Type: multipart/form-data; boundary=--customboundary
Authorization: Bearer your_api_key_here

--customboundary
Content-Disposition: form-data; name="instructions"
Content-Type: application/json

{
  "parts": [
    {
      "html": "index.html",
      "assets": [
        "style.css",
        "Inter-Regular.ttf",
        "Inter-Medium.ttf",
        "Inter-Bold.ttf",
        "SpaceMono-Regular.ttf"
      ]
    }
  ]
}
--customboundary
Content-Disposition: form-data; name="index.html"; filename="index.html"
Content-Type: text/html

(index.html data)
--customboundary
Content-Disposition: form-data; name="style.css"; filename="style.css"
Content-Type: text/css

(style.css data)
--customboundary
Content-Disposition: form-data; name="Inter-Regular.ttf"; filename="Inter-Regular.ttf"
Content-Type: font/ttf

(Inter-Regular.ttf data)
--customboundary
Content-Disposition: form-data; name="Inter-Medium.ttf"; filename="Inter-Medium.ttf"
Content-Type: font/ttf

(Inter-Medium.ttf data)
--customboundary
Content-Disposition: form-data; name="Inter-Bold.ttf"; filename="Inter-Bold.ttf"
Content-Type: font/ttf

(Inter-Bold.ttf data)
--customboundary
Content-Disposition: form-data; name="SpaceMono-Regular.ttf"; filename="SpaceMono-Regular.ttf"
Content-Type: font/ttf

(SpaceMono-Regular.ttf data)
--customboundary--

Forms

HTML

With your invoice looking good, let's look at another feature of PDF generation: fillable forms.

You can easily add forms to your PDF by adding regular HTML input tags to your HTML file. Add the following HTML snippet to your index.html file below the div with the intro-table class.

HTML template section:

HTML
<!-- Add below the intro-table div -->
<div class="page" style="page-break-after: always">
  <div>
    <h4>Thank you for your purchase!</h4>
  </div>

  <div class="form">
    <label for="notes" class="label"> Notes: </label>
    <input type="text" id="notes" class="border-bottom" value="" />
  </div>

  <div class="signer">
    <div class="form signer-item">
      <label for="date" class="label">Date:</label>
      <input type="text" id="date" class="border-bottom" value="01/01/2021" />
    </div>

    <div class="form signer-item">
      <label for="signature" class="label">Issued by:</label>
      <input type="text" id="signature" class="border" value="Sign Here" />
    </div>
  </div>
</div>

CSS

You'll also make them look nice by adding some styling for the forms to your CSS file.

Add the following CSS snippet to your style.css file.

If you rerun the generation now, with the updated HTML and CSS, you should have fillable PDF forms on the second page.

HTML template section:

CSS
/* Add below .intro-form-item-border:last-child */

.form {
  display: flex;
  flex-direction: column;
  margin-top: 6rem;
}

.signer {
  display: flex;
  justify-content: space-between;
  gap: 2.5rem;
  margin: 2rem 0 2rem 0;
}

.signer-item {
  flex-grow: 1;
}

input {
  color: #4537de;
  font-family: "Space Mono", monospace;
  text-align: center;
  margin-top: 1.5rem;
  height: 4rem;
  width: 100%;
  box-sizing: border-box;
}

input#date,
input#notes {
  text-align: left;
}

input#signature {
  height: 8rem;
}

Headers and Footers

HTML

For your final trick, you'll add headers and footers to your invoice. To make use of this, add any HTML element as the first or last element of the body, and give it the pspdfkit-header or pspdfkit-footer ID.

Our PDF Generation support will take care of making sure this element is shown on all pages if the generated PDF ends up having multiple pages, and we'll also replace the {{ pageNumber }} and {{ pageCount }} placeholders with the correct values.

For this example, add the HTML for the headers shown in the sample code as the first and last elements of the body element in the index.html file.

In the header, you'll also include your logo, which you can find as part of our invoice example. Put logo.svg in the same folder as your index.html . You'll include the logo in the code later.

HTML template section:

HTML
<!-- Add as the first element in the body. -->
<div id="pspdfkit-header">
  <div class="header-columns">
    <div class="logotype">
      <img class="logo" src="logo.svg" />
      <p>Company</p>
    </div>

    <div>
      <p>[Company Info]</p>
    </div>
  </div>
</div>

 <!-- Add as the last element in the body. -->
<div id="pspdfkit-footer">
  <div class="footer-columns">
    <span>Invoice</span>
    <span>Page {{ pageNumber }} of {{ pageCount }}</span>
  </div>
</div>

CSS

You should also make sure your headers and footers look good by updating your CSS file one last time. Add the following to your style.css.

HTML template section:

CSS
/* Add below input#signature. */
#pspdfkit-header {
  font-size: 0.625rem;
  text-transform: uppercase;
  letter-spacing: 2px;
  font-weight: 400;
  color: #717885;
  margin-top: 2.5rem;
  margin-bottom: 2.5rem;
  width: 100%;
}

.header-columns {
  display: flex;
  justify-content: space-between;
  padding-left: 2.5rem;
  padding-right: 2.5rem;
}

.logo {
  height: 1.5rem;
  width: auto;
  margin-right: 1rem;
}

.logotype {
  display: flex;
  align-items: center;
  font-weight: 700;
}

#pspdfkit-footer {
  font-size: 0.5rem;
  text-transform: uppercase;
  letter-spacing: 1px;
  font-weight: 500;
  color: #717885;
  margin-top: 2.5rem;
  bottom: 2.5rem;
  position: absolute;
  width: 100%;
}

.footer-columns {
  display: flex;
  justify-content: space-between;
  padding-left: 2.5rem;
  padding-right: 2.5rem;
}

Code

Finally, you'll include logo.svg in your request.

And with that, you've used all features PDF Generation provides. More specifically, you used HTML to define the structure of your PDF, used CSS to style and lay out your content, added fonts and images to the PDF, and even included fillable forms.

The full invoice example contains everything you added here, and it adds the actual invoice and summary in the middle. It's a great starting point for your own PDF Generation templates. You can also check out all our other fully featured examples at the bottom of the page.

Command to generate a PDF file:

curl -X POST https://api.nutrient.io/build \
  -H "Authorization: Bearer your_api_key_here" \
  -o result.pdf \
  --fail \
  -F [email protected] \
  -F [email protected] \
  -F [email protected] \
  -F [email protected] \
  -F [email protected] \
  -F [email protected] \
  -F [email protected] \
  -F instructions='{
      "parts": [
        {
          "html": "index.html",
          "assets": [
            "style.css",
            "Inter-Regular.ttf",
            "Inter-Medium.ttf",
            "Inter-Bold.ttf",
            "SpaceMono-Regular.ttf",
            "logo.svg"
          ]
        }
      ]
    }'
curl -X POST https://api.nutrient.io/build ^
  -H "Authorization: Bearer your_api_key_here" ^
  -o result.pdf ^
  --fail ^
  -F [email protected] ^
  -F [email protected] ^
  -F [email protected] ^
  -F [email protected] ^
  -F [email protected] ^
  -F [email protected] ^
  -F [email protected] ^
  -F instructions="{\"parts\": [{\"html\": \"index.html\", \"assets\": [\"style.css\", \"Inter-Regular.ttf\", \"Inter-Medium.ttf\", \"Inter-Bold.ttf\", \"SpaceMono-Regular.ttf\", \"logo.svg\"]}]}"
package com.example.pspdfkit;

import java.io.File;
import java.io.IOException;
import java.nio.file.FileSystems;
import java.nio.file.Files;
import java.nio.file.StandardCopyOption;

import org.json.JSONArray;
import org.json.JSONObject;

import okhttp3.MediaType;
import okhttp3.MultipartBody;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.RequestBody;
import okhttp3.Response;

public final class PspdfkitApiExample {
  public static void main(final String[] args) throws IOException {
    final RequestBody body = new MultipartBody.Builder()
      .setType(MultipartBody.FORM)
      .addFormDataPart(
        "index.html",
        "index.html",
        RequestBody.create(
          MediaType.parse("text/html"),
          new File("index.html")
        )
      )
      .addFormDataPart(
        "style.css",
        "style.css",
        RequestBody.create(
          MediaType.parse("text/css"),
          new File("style.css")
        )
      )
      .addFormDataPart(
        "Inter-Regular.ttf",
        "Inter-Regular.ttf",
        RequestBody.create(
          MediaType.parse("font/ttf"),
          new File("Inter-Regular.ttf")
        )
      )
      .addFormDataPart(
        "Inter-Medium.ttf",
        "Inter-Medium.ttf",
        RequestBody.create(
          MediaType.parse("font/ttf"),
          new File("Inter-Medium.ttf")
        )
      )
      .addFormDataPart(
        "Inter-Bold.ttf",
        "Inter-Bold.ttf",
        RequestBody.create(
          MediaType.parse("font/ttf"),
          new File("Inter-Bold.ttf")
        )
      )
      .addFormDataPart(
        "SpaceMono-Regular.ttf",
        "SpaceMono-Regular.ttf",
        RequestBody.create(
          MediaType.parse("font/ttf"),
          new File("SpaceMono-Regular.ttf")
        )
      )
      .addFormDataPart(
        "logo.svg",
        "logo.svg",
        RequestBody.create(
          MediaType.parse("image/svg+xml"),
          new File("logo.svg")
        )
      )
      .addFormDataPart(
        "instructions",
        new JSONObject()
          .put("parts", new JSONArray()
            .put(new JSONObject()
              .put("html", "index.html")
              .put("assets", new JSONArray()
                .put("style.css")
                .put("Inter-Regular.ttf")
                .put("Inter-Medium.ttf")
                .put("Inter-Bold.ttf")
                .put("SpaceMono-Regular.ttf")
                .put("logo.svg")
              )
            )
          ).toString()
      )
      .build();

    final Request request = new Request.Builder()
      .url("https://api.nutrient.io/build")
      .method("POST", body)
      .addHeader("Authorization", "Bearer your_api_key_here")
      .build();

    final OkHttpClient client = new OkHttpClient()
      .newBuilder()
      .build();

    final Response response = client.newCall(request).execute();

    if (response.isSuccessful()) {
      Files.copy(
        response.body().byteStream(),
        FileSystems.getDefault().getPath("result.pdf"),
        StandardCopyOption.REPLACE_EXISTING
      );
    } else {
      // Handle the error
      throw new IOException(response.body().string());
    }
  }
}
using System;
using System.IO;
using System.Net;
using RestSharp;

namespace PspdfkitApiDemo
{
  class Program
  {
    static void Main(string[] args)
    {
      var client = new RestClient("https://api.nutrient.io/build");

      var request = new RestRequest(Method.POST)
        .AddHeader("Authorization", "Bearer your_api_key_here")
        .AddFile("index.html", "index.html")
        .AddFile("style.css", "style.css")
        .AddFile("Inter-Regular.ttf", "Inter-Regular.ttf")
        .AddFile("Inter-Medium.ttf", "Inter-Medium.ttf")
        .AddFile("Inter-Bold.ttf", "Inter-Bold.ttf")
        .AddFile("SpaceMono-Regular.ttf", "SpaceMono-Regular.ttf")
        .AddFile("logo.svg", "logo.svg")
        .AddParameter("instructions", new JsonObject
        {
          ["parts"] = new JsonArray
          {
            new JsonObject
            {
              ["html"] = "index.html",
              ["assets"] = new JsonArray
              {
                "style.css",
                "Inter-Regular.ttf",
                "Inter-Medium.ttf",
                "Inter-Bold.ttf",
                "SpaceMono-Regular.ttf",
                "logo.svg"
              }
            }
          }
        }.ToString());

      request.AdvancedResponseWriter = (responseStream, response) =>
      {
        if (response.StatusCode == HttpStatusCode.OK)
        {
          using (responseStream)
          {
            using var outputFileWriter = File.OpenWrite("result.pdf");
            responseStream.CopyTo(outputFileWriter);
          }
        }
        else
        {
          var responseStreamReader = new StreamReader(responseStream);
          Console.Write(responseStreamReader.ReadToEnd());
        }
      };

      client.Execute(request);
    }
  }
}
// This code requires Node.js. Do not run this code directly in a web browser.

const axios = require('axios')
const FormData = require('form-data')
const fs = require('fs')

const formData = new FormData()
formData.append('instructions', JSON.stringify({
  parts: [
    {
      html: "index.html",
      assets: [
        "style.css",
        "Inter-Regular.ttf",
        "Inter-Medium.ttf",
        "Inter-Bold.ttf",
        "SpaceMono-Regular.ttf",
        "logo.svg"
      ]
    }
  ]
}))
formData.append('index.html', fs.createReadStream('index.html'))
formData.append('style.css', fs.createReadStream('style.css'))
formData.append('Inter-Regular.ttf', fs.createReadStream('Inter-Regular.ttf'))
formData.append('Inter-Medium.ttf', fs.createReadStream('Inter-Medium.ttf'))
formData.append('Inter-Bold.ttf', fs.createReadStream('Inter-Bold.ttf'))
formData.append('SpaceMono-Regular.ttf', fs.createReadStream('SpaceMono-Regular.ttf'))
formData.append('logo.svg', fs.createReadStream('logo.svg'))

;(async () => {
  try {
    const response = await axios.post('https://api.nutrient.io/build', formData, {
      headers: formData.getHeaders({
        'Authorization': 'Bearer your_api_key_here'
      }),
      responseType: "stream"
    })

    response.data.pipe(fs.createWriteStream("result.pdf"))
  } catch (e) {
    const errorString = await streamToString(e.response.data)
    console.log(errorString)
  }
})()

function streamToString(stream) {
  const chunks = []
  return new Promise((resolve, reject) => {
    stream.on("data", (chunk) => chunks.push(Buffer.from(chunk)))
    stream.on("error", (err) => reject(err))
    stream.on("end", () => resolve(Buffer.concat(chunks).toString("utf8")))
  })
}
import requests
import json

response = requests.request(
  'POST',
  'https://api.nutrient.io/build',
  headers = {
    'Authorization': 'Bearer your_api_key_here'
  },
  files = {
    'index.html': open('index.html', 'rb'),
    'style.css': open('style.css', 'rb'),
    'Inter-Regular.ttf': open('Inter-Regular.ttf', 'rb'),
    'Inter-Medium.ttf': open('Inter-Medium.ttf', 'rb'),
    'Inter-Bold.ttf': open('Inter-Bold.ttf', 'rb'),
    'SpaceMono-Regular.ttf': open('SpaceMono-Regular.ttf', 'rb'),
    'logo.svg': open('logo.svg', 'rb')
  },
  data = {
    'instructions': json.dumps({
      'parts': [
        {
          'html': 'index.html',
          'assets': [
            'style.css',
            'Inter-Regular.ttf',
            'Inter-Medium.ttf',
            'Inter-Bold.ttf',
            'SpaceMono-Regular.ttf',
            'logo.svg'
          ]
        }
      ]
    })
  },
  stream = True
)

if response.ok:
  with open('result.pdf', 'wb') as fd:
    for chunk in response.iter_content(chunk_size=8096):
      fd.write(chunk)
else:
  print(response.text)
  exit()
<?php

$FileHandle = fopen('result.pdf', 'w+');

$curl = curl_init();

curl_setopt_array($curl, array(
  CURLOPT_URL => 'https://api.nutrient.io/build',
  CURLOPT_CUSTOMREQUEST => 'POST',
  CURLOPT_RETURNTRANSFER => true,
  CURLOPT_ENCODING => '',
  CURLOPT_POSTFIELDS => array(
    'instructions' => '{
      "parts": [
        {
          "html": "index.html",
          "assets": [
            "style.css",
            "Inter-Regular.ttf",
            "Inter-Medium.ttf",
            "Inter-Bold.ttf",
            "SpaceMono-Regular.ttf",
            "logo.svg"
          ]
        }
      ]
    }',
    'index.html' => new CURLFILE('index.html'),
    'style.css' => new CURLFILE('style.css'),
    'Inter-Regular.ttf' => new CURLFILE('Inter-Regular.ttf'),
    'Inter-Medium.ttf' => new CURLFILE('Inter-Medium.ttf'),
    'Inter-Bold.ttf' => new CURLFILE('Inter-Bold.ttf'),
    'SpaceMono-Regular.ttf' => new CURLFILE('SpaceMono-Regular.ttf'),
    'logo.svg' => new CURLFILE('logo.svg')
  ),
  CURLOPT_HTTPHEADER => array(
    'Authorization: Bearer your_api_key_here'
  ),
  CURLOPT_FILE => $FileHandle,
));

$response = curl_exec($curl);

curl_close($curl);

fclose($FileHandle);

POST https://api.nutrient.io/build HTTP/1.1
Content-Type: multipart/form-data; boundary=--customboundary
Authorization: Bearer your_api_key_here

--customboundary
Content-Disposition: form-data; name="instructions"
Content-Type: application/json

{
  "parts": [
    {
      "html": "index.html",
      "assets": [
        "style.css",
        "Inter-Regular.ttf",
        "Inter-Medium.ttf",
        "Inter-Bold.ttf",
        "SpaceMono-Regular.ttf",
        "logo.svg"
      ]
    }
  ]
}
--customboundary
Content-Disposition: form-data; name="index.html"; filename="index.html"
Content-Type: text/html

(index.html data)
--customboundary
Content-Disposition: form-data; name="style.css"; filename="style.css"
Content-Type: text/css

(style.css data)
--customboundary
Content-Disposition: form-data; name="Inter-Regular.ttf"; filename="Inter-Regular.ttf"
Content-Type: font/ttf

(Inter-Regular.ttf data)
--customboundary
Content-Disposition: form-data; name="Inter-Medium.ttf"; filename="Inter-Medium.ttf"
Content-Type: font/ttf

(Inter-Medium.ttf data)
--customboundary
Content-Disposition: form-data; name="Inter-Bold.ttf"; filename="Inter-Bold.ttf"
Content-Type: font/ttf

(Inter-Bold.ttf data)
--customboundary
Content-Disposition: form-data; name="SpaceMono-Regular.ttf"; filename="SpaceMono-Regular.ttf"
Content-Type: font/ttf

(SpaceMono-Regular.ttf data)
--customboundary
Content-Disposition: form-data; name="logo.svg"; filename="logo.svg"
Content-Type: image/svg+xml

(logo.svg data)
--customboundary--