Import XFDF annotations API
Use the import XFDF annotations API to apply XFDF annotation data to a PDF. XML Forms Data Format (XFDF) is a common Extensible Markup Language (XML)-based format for exchanging PDF annotations between tools.
The /build endpoint handles XFDF import. Add the source PDF as a parts item, and add an applyXfdf action that references the XFDF file.
For signup, pricing, and task-level examples, refer to the XFDF import API task page.
Import XFDF annotations
The following example imports annotations from annotations.xfdf into annotations.pdf and writes the output to result.pdf:
curl -X POST https://api.nutrient.io/build \ -H "Authorization: Bearer your_api_key_here" \ -o result.pdf \ --fail \ -F document=@annotations.pdf \ -F annotations.xfdf=@annotations.xfdf \ -F instructions='{ "parts": [ { "file": "document" } ], "actions": [ { "type": "applyXfdf", "file": "annotations.xfdf" } ] }'curl -X POST https://api.nutrient.io/build ^ -H "Authorization: Bearer your_api_key_here" ^ -o result.pdf ^ --fail ^ -F document=@annotations.pdf ^ -F annotations.xfdf=@annotations.xfdf ^ -F instructions="{\"parts\": [{\"file\": \"document\"}], \"actions\": [{\"type\": \"applyXfdf\", \"file\": \"annotations.xfdf\"}]}"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( "document", "annotations.pdf", RequestBody.create( MediaType.parse("application/pdf"), new File("annotations.pdf") ) ) .addFormDataPart( "annotations.xfdf", "annotations.xfdf", RequestBody.create( MediaType.parse("application/octet-stream"), new File("annotations.xfdf") ) ) .addFormDataPart( "instructions", new JSONObject() .put("parts", new JSONArray() .put(new JSONObject() .put("file", "document") ) ) .put("actions", new JSONArray() .put(new JSONObject() .put("type", "applyXfdf") .put("file", "annotations.xfdf") ) ).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("document", "annotations.pdf") .AddFile("annotations.xfdf", "annotations.xfdf") .AddParameter("instructions", new JsonObject { ["parts"] = new JsonArray { new JsonObject { ["file"] = "document" } }, ["actions"] = new JsonArray { new JsonObject { ["type"] = "applyXfdf", ["file"] = "annotations.xfdf" } } }.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: [ { file: "document" } ], actions: [ { type: "applyXfdf", file: "annotations.xfdf" } ]}))formData.append('document', fs.createReadStream('annotations.pdf'))formData.append('annotations.xfdf', fs.createReadStream('annotations.xfdf'))
;(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 requestsimport json
response = requests.request( 'POST', 'https://api.nutrient.io/build', headers = { 'Authorization': 'Bearer your_api_key_here' }, files = { 'document': open('annotations.pdf', 'rb'), 'annotations.xfdf': open('annotations.xfdf', 'rb') }, data = { 'instructions': json.dumps({ 'parts': [ { 'file': 'document' } ], 'actions': [ { 'type': 'applyXfdf', 'file': 'annotations.xfdf' } ] }) }, 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": [ { "file": "document" } ], "actions": [ { "type": "applyXfdf", "file": "annotations.xfdf" } ] }', 'document' => new CURLFILE('annotations.pdf'), 'annotations.xfdf' => new CURLFILE('annotations.xfdf') ), 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.1Content-Type: multipart/form-data; boundary=--customboundaryAuthorization: Bearer your_api_key_here
--customboundaryContent-Disposition: form-data; name="instructions"Content-Type: application/json
{ "parts": [ { "file": "document" } ], "actions": [ { "type": "applyXfdf", "file": "annotations.xfdf" } ]}--customboundaryContent-Disposition: form-data; name="document"; filename="annotations.pdf"Content-Type: application/pdf
(document data)--customboundaryContent-Disposition: form-data; name="annotations.xfdf"; filename="annotations.xfdf"Content-Type: application/octet-stream
(annotations.xfdf data)--customboundary--Import XFDF from URLs
For remotely hosted source files, send a JSON request and pass URLs for both the PDF and the XFDF file. Use this instructions object:
{ "parts": [ { "file": { "url": "https://example.com/document.pdf" } } ], "actions": [ { "type": "applyXfdf", "file": { "url": "https://example.com/annotations.xfdf" } } ]}Shell
Run this request to import XFDF from URLs:
curl -X POST https://api.nutrient.io/build \ -H "Authorization: Bearer $NUTRIENT_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "parts": [ { "file": { "url": "https://example.com/document.pdf" } } ], "actions": [ { "type": "applyXfdf", "file": { "url": "https://example.com/annotations.xfdf" } } ] }' \ -o result.pdfControl rich text conversion
By default, richTextEnabled is true. This converts plain text annotations to rich text annotations when importing XFDF. Set it to false if you want all imported text annotations to remain plain text annotations.
Use this instructions object to disable rich text conversion:
{ "parts": [ { "file": "document" } ], "actions": [ { "type": "applyXfdf", "file": "annotations.xfdf", "richTextEnabled": false } ]}Ignore page rotation
Use ignorePageRotation when you need to ignore page rotation while applying XFDF data. This can help when XFDF coordinates were produced by a system that doesn’t account for page rotation the same way.
Use this instructions object to ignore page rotation:
{ "parts": [ { "file": "document" } ], "actions": [ { "type": "applyXfdf", "file": "annotations.xfdf", "ignorePageRotation": true } ]}Import and flatten XFDF annotations
If the imported annotations should no longer be editable, add a flatten action after applyXfdf. Actions run in the order specified in the actions array.
Use this instructions object to import and flatten XFDF annotations:
{ "parts": [ { "file": "document" } ], "actions": [ { "type": "applyXfdf", "file": "annotations.xfdf" }, { "type": "flatten" } ]}Flattening turns annotations into regular page content. Use it only when the output no longer needs editable annotations.
Import XFDF into selected pages
The applyXfdf action applies to the assembled PDF in the request. If you first extract pages with parts[].pages, Nutrient DWS Processor API applies the XFDF to that extracted output.
Use this instructions object to import XFDF into selected pages:
{ "parts": [ { "file": "document", "pages": { "start": 0, "end": 2 } } ], "actions": [ { "type": "applyXfdf", "file": "annotations.xfdf" } ]}Make sure the page indexes and annotation coordinates in the XFDF match the PDF that the action applies to.
Import XFDF into password-protected PDFs
If the source PDF is password-protected, include the password on the part. Use this instructions object:
{ "parts": [ { "file": "protected_document", "password": "document-password" } ], "actions": [ { "type": "applyXfdf", "file": "annotations.xfdf" } ]}Nutrient DWS Processor API uses the password only to open the source document for processing. To set a password on the output PDF, configure output.user_password, output.owner_password, and output.user_permissions.
Combine XFDF import with other actions
The /build endpoint can import XFDF annotations and then apply additional actions. For example, you can import annotations, watermark the document, and then flatten the result.
Use this instructions object to combine XFDF import with other actions:
{ "parts": [ { "file": "document" } ], "actions": [ { "type": "applyXfdf", "file": "annotations.xfdf" }, { "type": "watermark", "text": "REVIEWED", "width": "50%", "height": "20%", "opacity": 0.3, "rotation": 45 }, { "type": "flatten" } ]}For workflows that include cryptographic signing, import and optionally flatten annotations before signing the final PDF.
Reference
An XFDF import request uses the Build API actions array with an applyXfdf action:
type ApplyXfdfAction = { type: "applyXfdf",
// Multipart field name, or a remote URL object pointing to an XFDF file. file: string | { url: string },
// If `true`, ignores page rotation when applying XFDF data. ignorePageRotation?: boolean,
// If `true`, plain text annotations are converted to rich text annotations. // If `false`, all text annotations are plain text annotations. richTextEnabled?: boolean,};
type FilePart = { // Multipart field name, or a remote URL object. file: string | { url: string },
// Optional password for encrypted input PDFs. password?: string,
// Optional page range to use before XFDF import. pages?: { start?: number, end?: number, },};
type BuildInstructions = { parts: FilePart[], actions: ApplyXfdfAction[], output?: { type?: "pdf", },};Related API reference operations
- Refer to the build document endpoint API reference to import XFDF annotations and combine import with flattening, watermarking, security, or other document actions.
Related guides
- Refer to the import Instant JSON API guide.
- Refer to the PDF flatten API guide.
- Refer to the PDF form filling API guide.
- Refer to the PDF watermark API guide.
- Refer to the PDF digital signature API guide.
- Refer to the tools and APIs guide.