This HTML page is not optimized for LLM or AI agent consumption. Fetch the Markdown version instead: /guides/dws-viewer/developer-guides/reviewer-isolated-layers.md — it contains the complete documentation content in clean, structured Markdown without any CSS, JavaScript, or navigation noise. Reviewer-isolated layers

Use reviewer-isolated layers when multiple reviewers need to work on the same document without seeing or modifying one another’s review state.

In this workflow, you upload the PDF once, create one layer per reviewer, add comments to that reviewer’s layer, generate a session token scoped to that layer, and export that layer as a PDF when the review is complete.

For this workflow, store reviewer state in DWS Viewer layers rather than managing Instant JSON yourself.

Workflow overview

  1. Upload the shared document once.
  2. Create one DWS Viewer layer per reviewer, or let Web SDK create it on first write.
  3. Add comments in that reviewer’s layer.
  4. Generate a session token scoped to that document and layer.
  5. Open the document in Nutrient Web SDK with that session token.
  6. Export the reviewed layer as a PDF with comments.

1. Create a reviewer layer

Create a dedicated layer for each reviewer with POST /viewer/documents/{documentId}/layers:

Terminal window
curl -X POST "https://api.nutrient.io/viewer/documents/<document_id>/layers" \
-H "Content-Type: application/json" \
-H "Authorization: Bearer <api_key>" \
--fail \
-d '{
"name": "reviewer-alice"
}'

Response:

{
"data": {
"name": "reviewer-alice",
"errors": []
}
}

Use a stable layer name for each reviewer — for example, reviewer-alice, reviewer-bob, or an internal reviewer ID.

Optional: Let Web SDK create the layer on first write

If you don’t need to seed a reviewer’s layer from your backend before they start reviewing, you can skip explicit layer creation and mint a session token scoped to that layer name instead.

DWS Viewer layers use copy-on-write semantics — a layer is created on a reviewer’s first write through Web SDK, not when you mint the session token. See open a document in Web SDK for how to load the document with their session. This can be useful when you assign many potential reviewer layers but expect only a subset of reviewers to actually make changes.

2. Create comments in that layer

To create a new comment thread inside the reviewer’s layer, add the root annotation and the first comment together with POST /viewer/documents/{documentId}/layers/{layerName}/comments:

Terminal window
curl -X POST "https://api.nutrient.io/viewer/documents/<document_id>/layers/reviewer-alice/comments" \
-H "Content-Type: application/json" \
-H "Authorization: Bearer <api_key>" \
--fail \
-d '{
"annotation": {
"content": {
"v": 2,
"type": "pspdfkit/comment-marker",
"pageIndex": 0,
"bbox": [72, 72, 32, 32],
"icon": "comment",
"text": "Please verify clause 4.2",
"isCommentThreadRoot": true
}
},
"comments": [
{
"content": {
"text": "Please verify clause 4.2",
"creatorName": "Alice Reviewer"
}
}
]
}'

Response:

{
"data": {
"annotation": {
"id": "<root_annotation_id>"
},
"comments": [
{
"id": "<comment_id>"
}
]
}
}

If you want to add another reply to the same thread later, use POST /viewer/documents/{documentId}/layers/{layerName}/annotations/{annotationId}/comments:

Terminal window
curl -X POST "https://api.nutrient.io/viewer/documents/<document_id>/layers/reviewer-alice/annotations/<root_annotation_id>/comments" \
-H "Content-Type: application/json" \
-H "Authorization: Bearer <api_key>" \
--fail \
-d '{
"comments": [
{
"content": {
"text": "Flagging this for legal review.",
"creatorName": "Alice Reviewer"
}
}
]
}'

3. Generate a layer-scoped session token

Generate the reviewer’s session token with POST /viewer/sessions. Set both document_id and layer in allowed_documents. The layer value restricts access to that reviewer’s layer:

A reviewer-specific browser session should be scoped with allowed_documents[].layer. Otherwise, the reviewer won’t be restricted to the intended layer.

Terminal window
curl -X POST https://api.nutrient.io/viewer/sessions \
-H "Content-Type: application/json" \
-H "Authorization: Bearer <api_key>" \
--fail \
-d '{
"allowed_documents": [
{
"document_id": "<document_id>",
"layer": "reviewer-alice",
"permissions": ["read", "write", "download"]
}
],
"exp": 1793769299
}'

Response:

{
"jwt": "<reviewer_layer_session_token>"
}

4. Open the reviewer layer in Nutrient Web SDK

Pass the returned top-level jwt value to NutrientViewer.load():

await NutrientViewer.load({
container: document.querySelector("#viewer-container"),
session: "<reviewer_layer_session_token>",
});

5. Export the reviewed layer as a PDF with comments

When the reviewer is done, export that layer as a PDF with comments using GET /viewer/documents/{documentId}/layers/{layerName}/pdf:

Terminal window
curl -L "https://api.nutrient.io/viewer/documents/<document_id>/layers/reviewer-alice/pdf?comments=true" \
-H "Authorization: Bearer <api_key>" \
--fail \
-o reviewer-alice.pdf

If you want a PDF/A export instead, add type=pdfa and a conformance value supported by the endpoint.

End-to-end Node.js example

Below is an end-to-end example of the workflow above in Node.js. It uses node-fetch to make API requests, but you can use any HTTP client library.

const apiKey = process.env.NUTRIENT_DWS_VIEWER_API_KEY;
const documentId = "<document_id>";
const layerName = "reviewer-alice";
const headers = {
"Content-Type": "application/json",
Authorization: `Bearer ${apiKey}`,
};
// 1. Create the reviewer layer.
await fetch(`https://api.nutrient.io/viewer/documents/${documentId}/layers`, {
method: "POST",
headers,
body: JSON.stringify({ name: layerName }),
});
// 2. Create a comment thread in that layer.
const commentResponse = await fetch(
`https://api.nutrient.io/viewer/documents/${documentId}/layers/${layerName}/comments`,
{
method: "POST",
headers,
body: JSON.stringify({
annotation: {
content: {
v: 2,
type: "pspdfkit/comment-marker",
pageIndex: 0,
bbox: [72, 72, 32, 32],
icon: "comment",
text: "Please verify clause 4.2",
isCommentThreadRoot: true,
},
},
comments: [
{
content: {
text: "Please verify clause 4.2",
creatorName: "Alice Reviewer",
},
},
],
}),
},
);
const commentResult = await commentResponse.json();
const rootAnnotationId = commentResult.data.annotation.id;
// 3. Add another reply in the same layer.
await fetch(
`https://api.nutrient.io/viewer/documents/${documentId}/layers/${layerName}/annotations/${rootAnnotationId}/comments`,
{
method: "POST",
headers,
body: JSON.stringify({
comments: [
{
content: {
text: "Flagging this for legal review.",
creatorName: "Alice Reviewer",
},
},
],
}),
},
);
// 4. Generate a layer-scoped browser session.
const sessionResponse = await fetch("https://api.nutrient.io/viewer/sessions", {
method: "POST",
headers,
body: JSON.stringify({
allowed_documents: [
{
document_id: documentId,
layer: layerName, // Restricts access to only this layer
permissions: ["read", "write", "download"],
},
],
exp: Math.floor(Date.now() / 1000) + 60 * 60,
}),
});
const { jwt } = await sessionResponse.json();
console.log("Session token:", jwt);
// 5. Export the reviewed layer as PDF with comments.
const pdfResponse = await fetch(
`https://api.nutrient.io/viewer/documents/${documentId}/layers/${layerName}/pdf?comments=true`,
{
headers: {
Authorization: `Bearer ${apiKey}`,
},
},
);
const pdfBuffer = Buffer.from(await pdfResponse.arrayBuffer());