PDF digital signature API
Use the PDF digital signature API to apply a cryptographic digital signature to a PDF file. Digital signatures help prove document authenticity and detect changes made after signing.
The /sign endpoint handles digital signing. This differs from the Build API used by most document editing operations.
For signup, pricing, and task-level examples, refer to the digital signatures API task page.
The Processor API digital signature endpoint cryptographically signs PDF files as part of a processing workflow. For human eSignature workflows with recipients, signing sessions, reminders, and signing workflow state, use DWS Signer API.
Sign a PDF
The following example signs document.pdf and writes the signed output to result.pdf:
curl -X POST https://api.nutrient.io/sign \ -H "Authorization: Bearer your_api_key_here" \ -o result.pdf \ --fail \ -F file=@document.pdf \ -F data='{ "signatureType": "cades", "cadesLevel": "b-lt" }'curl -X POST https://api.nutrient.io/sign ^ -H "Authorization: Bearer your_api_key_here" ^ -o result.pdf ^ --fail ^ -F file=@document.pdf ^ -F data="{\"signatureType\": \"cades\", \"cadesLevel\": \"b-lt\"}"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.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( "file", "document.pdf", RequestBody.create( MediaType.parse("application/pdf"), new File("document.pdf") ) ) .addFormDataPart( "data", new JSONObject() .put("signatureType", "cades") .put("cadesLevel", "b-lt").toString() ) .build();
final Request request = new Request.Builder() .url("https://api.nutrient.io/sign") .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/sign");
var request = new RestRequest(Method.POST) .AddHeader("Authorization", "Bearer your_api_key_here") .AddFile("file", "document.pdf") .AddParameter("data", new JsonObject { ["signatureType"] = "cades", ["cadesLevel"] = "b-lt" }.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('data', JSON.stringify({ signatureType: "cades", cadesLevel: "b-lt"}))formData.append('file', fs.createReadStream('document.pdf'))
;(async () => { try { const response = await axios.post('https://api.nutrient.io/sign', 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 requestsimport json
response = requests.request( 'POST', 'https://api.nutrient.io/sign', headers = { 'Authorization': 'Bearer your_api_key_here' }, files = { 'file': open('document.pdf', 'rb') }, data = { 'data': json.dumps({ 'signatureType': 'cades', 'cadesLevel': 'b-lt' }) }, 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/sign', CURLOPT_CUSTOMREQUEST => 'POST', CURLOPT_RETURNTRANSFER => true, CURLOPT_ENCODING => '', CURLOPT_POSTFIELDS => array( 'data' => '{ "signatureType": "cades", "cadesLevel": "b-lt" }', 'file' => new CURLFILE('document.pdf') ), 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/sign HTTP/1.1Content-Type: multipart/form-data; boundary=--customboundaryAuthorization: Bearer your_api_key_here
--customboundaryContent-Disposition: form-data; name="data"Content-Type: application/json
{ "signatureType": "cades", "cadesLevel": "b-lt"}--customboundaryContent-Disposition: form-data; name="file"; filename="document.pdf"Content-Type: application/pdf
(file data)--customboundary--Create an invisible signature
If you omit the appearance, position, and formFieldName options, the API creates an invisible digital signature. The signature remains cryptographic, and PDF readers that support digital signature validation can validate it.
Use this data object to create an invisible signature:
{ "signatureType": "cades", "cadesLevel": "b-lt"}Use an invisible signature when you need document integrity and authenticity without rendering a signature appearance on the page.
Create a visible signature
To create a visible signature, provide a position object and an appearance object. The position.rect array uses PDF points and has the format [left, top, width, height].
Use this data object to create a visible signature:
{ "signatureType": "cades", "cadesLevel": "b-lt", "position": { "pageIndex": 0, "rect": [72, 650, 250, 80] }, "appearance": { "mode": "signatureAndDescription", "showWatermark": true, "showSignDate": true, "showDateTimezone": false }}Shell
Run this request to create a visible signature:
curl -X POST https://api.nutrient.io/sign \ -H "Authorization: Bearer $NUTRIENT_API_KEY" \ -F file=@document.pdf \ -F 'data={ "signatureType": "cades", "cadesLevel": "b-lt", "position": { "pageIndex": 0, "rect": [72, 650, 250, 80] }, "appearance": { "mode": "signatureAndDescription", "showWatermark": true, "showSignDate": true } };type=application/json' \ -o result.pdfSign an existing signature field
If the PDF already contains a signature form field, use formFieldName to sign that field.
Use this data object to sign an existing signature field:
{ "signatureType": "cades", "cadesLevel": "b-lt", "formFieldName": "signature-field", "appearance": { "mode": "signatureAndDescription", "showWatermark": true, "showSignDate": true }}If a field with the specified name doesn’t exist, the API can create it at the position specified by position. If a signature field with that name already exists, don’t also pass position.
Add a custom signature image
You can include an image in the multipart request and reference its content type in the signature appearance. Supported image content types include image/png and image/jpeg.
Run this request to add a custom signature image:
curl -X POST https://api.nutrient.io/sign \ -H "Authorization: Bearer $NUTRIENT_API_KEY" \ -F file=@document.pdf \ -F image=@signature-watermark.png \ -F 'data={ "signatureType": "cades", "cadesLevel": "b-lt", "position": { "pageIndex": 0, "rect": [72, 650, 250, 80] }, "appearance": { "mode": "signatureOnly", "contentType": "image/png", "showWatermark": true, "showSignDate": false } };type=application/json' \ -o result.pdfThe /sign endpoint also accepts a graphicImage multipart part for the graphic image used as part of the signature appearance.
Flatten before signing
Set flatten to true to flatten annotations and form fields before the API applies the signature. This keeps the document appearance stable and removes editable records before signing.
Use this data object to flatten before signing:
{ "signatureType": "cades", "cadesLevel": "b-lt", "flatten": true}Flattening removes annotations and form fields as editable records. Use it only when the signed output should be treated as a final artifact.
Sign a password-protected PDF
If the source PDF is password-protected, pass the password in the pspdfkit-pdf-password header.
Run this request to sign a password-protected PDF:
curl -X POST https://api.nutrient.io/sign \ -H "Authorization: Bearer $NUTRIENT_API_KEY" \ -H "pspdfkit-pdf-password: document-password" \ -F file=@protected-document.pdf \ -F 'data={ "signatureType": "cades", "cadesLevel": "b-lt" };type=application/json' \ -o result.pdfIf the password contains characters that HTTP header handling might modify, pass it as base64:<encoded-password>.
Use digital signing after other processing steps
Digital signing should usually be the final step in a document workflow. Apply content-changing operations before signing, such as:
- Merging PDFs
- Splitting or extracting pages
- Rotating pages
- Filling forms
- Flattening annotations or form fields
- Redacting content
- Adding watermarks
- Optimizing or linearizing the document
If you modify a PDF after signing it, the signature may become invalid or show that the document changed after signing.
Reference
A PDF digital signature request uses the /sign endpoint with a multipart body.
type CreateDigitalSignature = { // Flatten annotations and form fields before signing. flatten?: boolean,
// Name of an existing signature field to sign. // If the field does not exist, provide position to create a visible field. formFieldName?: string,
// Position for a visible signature appearance. position?: { pageIndex: number, rect: [number, number, number, number], },
// Visible signature appearance settings. appearance?: { mode?: "signatureOnly" | "signatureAndDescription" | "descriptionOnly", contentType?: "application/pdf" | "image/png" | "image/jpeg", showWatermark?: boolean, showSignDate?: boolean, showDateTimezone?: boolean, },
// Signature format options used by Processor API signing examples. signatureType?: "cades", cadesLevel?: "b-lt",};Multipart fields:
type SignRequest = { // Required PDF input. file: File,
// Optional JSON signing parameters. data?: CreateDigitalSignature,
// Optional watermark image for the signature appearance. image?: File,
// Optional graphic image for the signature appearance. graphicImage?: File,};Related API reference operations
- Refer to the digitally sign a PDF file endpoint API reference to apply a cryptographic digital signature to a PDF file.
- Refer to the build document endpoint API reference to prepare a PDF before signing by merging, filling, flattening, redacting, watermarking, or optimizing it.
Related guides
- Refer to the PDF form filling API guide.
- Refer to the PDF flatten API guide.
- Refer to the PDF merge API guide.
- Refer to the PDF split API guide.
- Refer to the PDF rotate API guide.
- Refer to the PDF watermark API guide.
- Refer to the tools and APIs guide.