Use the PDF flatten API to turn annotations into regular PDF page content. Flattening makes annotations non-editable, so use it when preparing a document for sharing, archiving, printing, or signing.
The /build endpoint handles flattening. Add the source PDF as a parts item, and add a flatten action.
For signup, pricing, and task-level examples, refer to the flatten PDF API task page.
Flatten all annotations
The following example flattens all annotations in document.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=@document.pdf \ -F instructions='{ "parts": [ { "file": "document" } ], "actions": [ { "type": "flatten" } ] }'curl -X POST https://api.nutrient.io/build ^ -H "Authorization: Bearer your_api_key_here" ^ -o result.pdf ^ --fail ^ -F document=@document.pdf ^ -F instructions="{\"parts\": [{\"file\": \"document\"}], \"actions\": [{\"type\": \"flatten\"}]}"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", "document.pdf", RequestBody.create( MediaType.parse("application/pdf"), new File("document.pdf") ) ) .addFormDataPart( "instructions", new JSONObject() .put("parts", new JSONArray() .put(new JSONObject() .put("file", "document") ) ) .put("actions", new JSONArray() .put(new JSONObject() .put("type", "flatten") ) ).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", "document.pdf") .AddParameter("instructions", new JsonObject { ["parts"] = new JsonArray { new JsonObject { ["file"] = "document" } }, ["actions"] = new JsonArray { new JsonObject { ["type"] = "flatten" } } }.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: "flatten" } ]}))formData.append('document', fs.createReadStream('document.pdf'))
;(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('document.pdf', 'rb') }, data = { 'instructions': json.dumps({ 'parts': [ { 'file': 'document' } ], 'actions': [ { 'type': 'flatten' } ] }) }, 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": "flatten" } ] }', 'document' => 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/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": "flatten" } ]}--customboundaryContent-Disposition: form-data; name="document"; filename="document.pdf"Content-Type: application/pdf
(document data)--customboundary--Flatten a PDF from a URL
For remotely hosted source files, send a JSON request and pass the source URL in parts[].file.url. Use this instructions object:
{ "parts": [ { "file": { "url": "https://example.com/annotated-document.pdf" } } ], "actions": [ { "type": "flatten" } ]}Shell
Run this request to flatten a PDF from a URL:
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/annotated-document.pdf" } } ], "actions": [ { "type": "flatten" } ] }' \ -o result.pdfFlatten selected annotations
By default, the flatten action flattens all annotations. To flatten only specific annotations, pass their IDs in annotationIds.
Annotation IDs can be annotation IDs or PDF object IDs. Use this option when you want to make only some annotations permanent while leaving the rest editable.
Use this instructions object to flatten selected annotations:
{ "parts": [ { "file": "document" } ], "actions": [ { "type": "flatten", "annotationIds": [ "01H9Z0Y6J3V7R6W8K9M0N1P2Q3", 42 ] } ]}Flatten after importing annotations
The /build endpoint can import annotations and then flatten them in the same request. Actions run in the order specified in the actions array.
The following example imports annotations from XML Forms Data Format (XFDF) and then flattens the result:
{ "parts": [ { "file": "document" } ], "actions": [ { "type": "applyXfdf", "file": "annotations" }, { "type": "flatten" } ]}You can use the same pattern with Instant JSON by using the applyInstantJson action before flatten.
Flatten selected pages
The flatten action applies to the assembled PDF in the request. To flatten only a range of pages, first extract the pages you want to flatten with parts[].pages, flatten that output, and then merge it back into the final document if needed.
The following example extracts pages zero through two and flattens only those extracted pages in the output PDF:
{ "parts": [ { "file": "document", "pages": { "start": 0, "end": 2 } } ], "actions": [ { "type": "flatten" } ]}To create a final PDF that contains both flattened and editable sections, process the flattened section first, and then merge it with the other page ranges. For merge workflows, refer to the PDF merge API guide.
Flatten 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": "flatten" } ]}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 flattening with other actions
Flattening is usually a finalization step. Run content-changing operations such as merging, page extraction, annotation import, form filling, rotation, and watermarking before flattening when the output should no longer be editable.
The following example merges two PDFs, adds a watermark, and then flattens annotations into the final output:
{ "parts": [ { "file": "first_half" }, { "file": "second_half" } ], "actions": [ { "type": "watermark", "text": "FINAL", "width": "50%", "height": "20%", "opacity": 0.3, "rotation": 45 }, { "type": "flatten" } ]}For workflows that include signing, flatten the document before signing the final PDF.
Reference
A PDF flatten request uses the Build API actions array with a flatten action:
type FlattenAction = { type: "flatten",
// Optional annotation IDs or PDF object IDs to flatten. // If omitted, all annotations are flattened. annotationIds?: Array<string | number>,};
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 extract before flattening. pages?: { start?: number, end?: number, },};
type BuildInstructions = { parts: FilePart[], actions: FlattenAction[], output?: { type?: "pdf", },};Related API reference operations
- Refer to the build document endpoint API reference for flattening annotations, selecting page ranges, importing annotations, and applying follow-up actions in a single request.
Related guides
- 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 PDF security API guide.
- Refer to the tools and APIs guide.