PDF digital signature API
Use the PDF digital signature API to apply a cryptographic digital signature to a PDF file. The signature covers the signed PDF byte range, enabling validation to detect changes to signed content and whether later modifications exist.
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.
DWS Processor provides the cryptographic signing operation. It doesn’t authenticate an individual signer, record consent or intent, or provide a signing ceremony or audit trail. For human eSignature workflows with recipients, signing sessions, reminders, and signing workflow state, use DWS Signer API.
How production signing works
When DWS Processor receives a signing request, it associates the authenticated API request with a DWS organization account. It derives an organization-specific certificate identity from that account and sends it to the signing service in a short-lived, server-signed token. Clients can’t select or replace this identity in the signing request.
The signing service obtains a short-lived leaf certificate whose common name identifies Nutrient DWS Processor API. The certificate’s organizational unit (OU) includes an identifier derived from the DWS organization ID. This attributes the signing operation to that DWS organization account, but it doesn’t identify a human or independently prove which real-world legal entity controls the account. A relying party needs to know the expected DWS organization ID and compare it with the certificate identity.
DWS Processor forces SHA-256 and B-LT for signing requests. B-LT includes an RFC 3161 timestamp and embeds the certificate chain and revocation information needed to validate the signature after the short-lived leaf certificate expires. signatureType and cadesLevel aren’t public Processor API request options.
Paid production plans use production certificates that chain to a publicly trusted root. Recognition still depends on the viewer and trust store. The free plan uses a private test certificate. Use the free plan to test your integration, but don’t treat its certificate identity as equivalent to a paid production signature.
Integrity, authenticity, identity, and non-repudiation
These terms describe different guarantees:
| Property | What the signature establishes | What it doesn’t establish |
|---|---|---|
| Document integrity | The signature cryptographically covers the signed PDF byte range. Validation can detect changes to that signed content and whether later modifications exist. | It doesn’t prevent an earlier signature from being removed or a modified document from being signed again. |
| Authenticity and account attribution | The production certificate chain and organization-specific certificate identity provide evidence that the signing request was processed for a particular DWS organization account. | They don’t independently prove that the account belongs to a particular real-world legal entity. The verifier must know the expected DWS organization ID. |
| Human signer identity | Nothing by itself. DWS Processor authenticates an API request associated with an organization account. | It doesn’t identify an individual, verify a government ID, prove that a person controlled the credential, or record that person’s consent or intent. Your application must supply this evidence. |
| Non-repudiation | The signature, timestamp, and validation material can strengthen evidence about document integrity and organization-account attribution. | They don’t guarantee that an individual can’t deny signing. That conclusion depends on how your workflow authenticates and authorizes the person, records consent and intent, and retains audit evidence. |
B-LT is a technical signature profile, not an eIDAS assurance category. A DWS Processor signature can contribute to a legally enforceable workflow, but it isn’t categorically legally binding on its own. Legal effect depends on the complete workflow and jurisdiction. It also depends on document type, signer authentication and authorization, consent and intent, and retained evidence.
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.pdfcurl -X POST https://api.nutrient.io/sign ^ -H "Authorization: Bearer your_api_key_here" ^ -o result.pdf ^ --fail ^ -F file=@document.pdfpackage 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 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") ) ) .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");
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('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 requests
response = requests.request( 'POST', 'https://api.nutrient.io/sign', headers = { 'Authorization': 'Bearer your_api_key_here' }, files = { 'file': open('document.pdf', 'rb') }, 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( '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="file"; filename="document.pdf"Content-Type: application/pdf
(file data)--customboundary--Create an invisible signature
If you omit the data multipart field, or omit its appearance, position, and formFieldName options, the API creates an invisible digital signature. The signature remains cryptographic, and compatible PDF readers can validate it.
Use an invisible signature when you need tamper evidence 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:
{ "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={ "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:
{ "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={ "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 field 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:
{ "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 \ -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, validation can show that the document changed after the signature was applied. The signature doesn’t prevent someone from removing it and applying a new signature to a modified document, so relying parties should verify the expected DWS organization account.
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, },};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.