Server API for Annotation

Annotations are stored in the JSON annotation format on the server. PSPDFKit Server provides the endpoints listed below for working with annotations.

Overview

How to Create Annotations

How to Update Annotations

How to Create and Update Annotation Response

How to Delete Annotations

How to Retrieve Annotations

Other Endpoints

How to Create Annotations

Creating Annotations

To create new annotations, send a POST request with a JSON body containing the contents of one or more annotations to /api/documents/:document_id/annotations. You need to specify the application/json content-type header. Optionally, you can include the following properties in the payload:

  • id — This is the annotation ID. If it isn’t specified, a ULID will be generated and returned in the response.

  • user_id — This is the annotation creator. If it isn’t provided, this value defaults to null.

  • group — This is the annotation group. If it isn’t provided, this value defaults to null.

The content property expects a JSON object in our JSON annotation format:

Request

POST /api/documents/:document_id/annotations
Content-Type: application/json
Authorization: Token token="<secret token>"

{
  "annotations": [
    {
      "id": "01BS98XZSCFV5QARF948FZWNG5",
      "group": "notes",
      "content": {
        "bbox": [146, 383, 24, 24],
        "opacity": 1,
        "pageIndex": 1,
        "text": {
          format: "plain",
          value: "This is a yellow note annotation",
        },
        "type": "pspdfkit/note",
        "v": 2
      }
    },
    {
      // Another annotation...
    }
  ]
}

ℹ️ Note: Supplying attachment data as Base64-encoded content directly in the JSON isn’t supported. To add attachments, see the section on [how to create an annotation with an attachment][creating an annotation with an attachment] below.

$ curl http://localhost:5000/api/documents/document_id/annotations \
  -X 'POST' \
  -H 'accept: application/json' \
  -H 'Authorization: Token token=secret' \
  -H 'Content-Type: application/json' \
  -d '{
  "annotations": [
    {
      "id": "01BS98XZSCFV5QARF948FZWNG5",
      "group": "notes",
      "content": {
        "bbox": [146, 383, 24, 24],
        "opacity": 1,
        "pageIndex": 0,
        "text": {
          format: "plain",
          value: "This is a yellow note annotation",
        },
        "type": "pspdfkit/note",
        "v": 2
      }
    }
  ]
}'

Creating Annotations with Attachments

To create new annotations with attachments, send a multipart/form-data POST request and the JSON annotation file containing the annotation’s contents to /api/documents/:document_id/annotations. You need to specify the multipart/form-data content-type header. For each attachment that’s referenced in the annotation and isn’t already uploaded to the server, you have to attach the attachment to the request as additional form data, where the name is the SHA256 of the attachment data:

Request

POST /api/documents/:document_id/annotations
Content-Type: multipart/form-data
Authorization: Token token="<secret token>"

--customboundary
Content-Disposition: form-data; name="annotation"; filename="annotation.json"
Content-Type: application/json
{
  "annotations": [
    {
      "content": {
        "bbox": [100,100,100,100],
        "opacity": 1,
        "pageIndex": 0,
        "type": "pspdfkit/image",
        "contentType":"image/png",
        "imageAttachmentId": "ffe23a235e3b733a498376f869292110e6663473712373ea6b4c0b02b469583d",
        "v": 1
      }
    },
    {
      "content": {
        "bbox": [200,100,100,100],
        "opacity": 1,
        "pageIndex": 0,
        "type": "pspdfkit/image",
        "contentType":"image/png",
        "imageAttachmentId": "2f26918ed90bc8985d69d78277e30f6b3e7236f9e55a07db47eace3867654f10",
        "v": 1
      }
    }
  ]
}

--customboundary
Content-Disposition: form-data; name="ffe23a235e3b733a498376f869292110e6663473712373ea6b4c0b02b469583d"; filename="example.png"
Content-Type: image/png

<Attachment data>

--
Content-Disposition: form-data; name="2f26918ed90bc8985d69d78277e30f6b3e7236f9e55a07db47eace3867654f10"; filename="example2.png"
Content-Type: image/png

<Attachment data>
$ curl http://localhost:5000/api/documents/document_id/annotations \
  -X 'POST' \
  -H 'Authorization: Token token=secret' \
  -H 'Content-Type: multipart/form-data' \
  -F annotation='
      { "annotations":
        [
          {
            "content": {
              "bbox": [100, 100, 100, 100],
              "opacity": 1,
              "contentType":"image/png",
              "imageAttachmentId": "ffe23a235e3b733a498376f869292110e6663473712373ea6b4c0b02b469583d",
              "pageIndex": 0,
              "type": "pspdfkit/image",
              "v": 1
            }
          },
          {
            "content": {
              "bbox": [200, 100, 100, 100],
              "opacity": 1,
              "contentType":"image/png",
              "imageAttachmentId": "2f26918ed90bc8985d69d78277e30f6b3e7236f9e55a07db47eace3867654f10",
              "pageIndex": 0,
              "type": "pspdfkit/image",
              "v": 1
            }
          }
        ]
      }' \
  -F ffe23a235e3b733a498376f869292110e6663473712373ea6b4c0b02b469583d=@example.png \
  -F 2f26918ed90bc8985d69d78277e30f6b3e7236f9e55a07db47eace3867654f10=@example2.png

Creating Annotations in a Specific Layer

To create new annotations in a specific layer, send a POST request with a JSON body containing the annotation’s contents to /api/documents/:document_id/layers/:layer_name/annotations.

Just as you did when creating annotations in a document, you need to specify the application/json content-type header:

Request

POST /api/documents/:document_id/layers/:layer_name/annotations
Content-Type: application/json
Authorization: Token token="<secret token>"

Creating Annotations with Attachments in a Specific Layer

To create new annotations with attachments in a specific layer, send a multipart/form-data POST request and the JSON annotation file containing the annotation’s contents to /api/documents/:document_id/layers/:layer_name/annotations.

Just as you did when creating annotations with attachments in a document, you need to specify the multipart/form-data content-type header:

Request

POST /api/documents/:document_id/layers/:layer_name/annotations
Content-Type: multipart/form-data
Authorization: Token token="<secret token>"

How to Update Annotations

Updating Annotations

To update existing annotations, send a PUT request with a JSON body containing the new contents of one or more annotations to /api/documents/:document_id/annotations, specifying the application/json content type. For each annotation, the id field is required and should match the id of an existing annotation.

The content property expects a JSON object in our JSON annotation format:

You can optionally include the following properties:

  • user_id — This updates the updatedBy property of the annotation.

  • group — This updates the annotation group.

Request

PUT /api/documents/:document_id/annotations
Content-Type: application/json
Authorization: Token token="<secret token>"

{
  "annotations": [
    {
      "id": "01BS98XZSCFV5QARF948FZWNG5",
      "content": {
        "bbox": [146, 383, 24, 24],
        "opacity": 1,
        "pageIndex": 1,
        "text": {
          format: "plain",
          value: "This text has been updated.",
        },
        "type": "pspdfkit/note",
        "v": 2
      }
    },
    {
      // Another annotation...
    }
  ]
}
$ curl http://localhost:5000/api/documents/document_id/annotations \
  -X 'PUT' \
  -H 'accept: application/json' \
  -H 'Authorization: Token token=secret' \
  -H 'Content-Type: application/json' \
  -d '{
  "annotations": [
    {
      "id": "01BS98XZSCFV5QARF948FZWNG5",
      "content": {
        "bbox": [146, 383, 24, 24],
        "opacity": 1,
        "pageIndex": 0,
        "text": {
          format: "plain",
          value: "This text has been updated.",
        },
        "type": "pspdfkit/note",
        "v": 2
      }
    }
  ]
}'

Updating Annotations with Attachments

To update existing annotations and also update their attachments, send a multipart/form-data PUT request and the JSON annotation file containing the new contents — and optionally, a user_id — to /api/documents/:document_id/annotations/:annotation_id, specifying the multipart/form-data content type. For each annotation, the id field is required and should match the id of an existing annotation. For each attachment that’s referenced by the annotations and isn’t already uploaded to the server, you have to attach the attachment to the request as additional form data, where the name is the SHA256 hash encoded attachment data:

Request

PUT /api/documents/:document_id/annotations
Content-Type: multipart/form-data
Authorization: Token token="<secret token>"

--customboundary
Content-Disposition: form-data; name="annotation"; filename="annotation.json"
Content-Type: application/json
{
  "annotations": [
    {
      "id": "01BS98XZSCFV5QARF948FZWNG5",
      "content": {
        "bbox": [100,100,100,100],
        "opacity": 1,
        "pageIndex": 0,
        "type": "pspdfkit/image",
        "contentType":"image/png",
        "imageAttachmentId": "ffe23a235e3b733a498376f869292110e6663473712373ea6b4c0b02b469583d",
        "v": 1
      }
    },
    {
      "id": "F1BS98XZSCFV5QARF948FZWNGG",
      "content": {
        "bbox": [200,100,100,100],
        "opacity": 1,
        "pageIndex": 0,
        "type": "pspdfkit/image",
        "contentType":"image/png",
        "imageAttachmentId": "2f26918ed90bc8985d69d78277e30f6b3e7236f9e55a07db47eace3867654f10",
        "v": 1
      }
    }
  ]
}

--customboundary
Content-Disposition: form-data; name="ffe23a235e3b733a498376f869292110e6663473712373ea6b4c0b02b469583d"; filename="example.png"
Content-Type: image/png

<Attachment data>

--
Content-Disposition: form-data; name="2f26918ed90bc8985d69d78277e30f6b3e7236f9e55a07db47eace3867654f10"; filename="example2.png"
Content-Type: image/png

<Attachment data>
$ curl http://localhost:5000/api/documents/document_id/annotations \
  -X 'PUT' \
  -H 'Authorization: Token token=secret' \
  -H 'Content-Type: multipart/form-data' \
  -F annotation='
      { "annotations":
        [
          {
            "id": "01BS98XZSCFV5QARF948FZWNG5",
            "content": {
              "bbox": [100, 100, 100, 100],
              "opacity": 1,
              "contentType":"image/png",
              "imageAttachmentId": "ffe23a235e3b733a498376f869292110e6663473712373ea6b4c0b02b469583d",
              "pageIndex": 0,
              "type": "pspdfkit/image",
              "v": 1
            }
          },
          {
            "id": "F1BS98XZSCFV5QARF948FZWNGG",
            "content": {
              "bbox": [200, 100, 100, 100],
              "opacity": 1,
              "contentType":"image/png",
              "imageAttachmentId": "2f26918ed90bc8985d69d78277e30f6b3e7236f9e55a07db47eace3867654f10",
              "pageIndex": 0,
              "type": "pspdfkit/image",
              "v": 1
            }
          }
        ]
      }' \
  -F ffe23a235e3b733a498376f869292110e6663473712373ea6b4c0b02b469583d=@example.png \
  -F 2f26918ed90bc8985d69d78277e30f6b3e7236f9e55a07db47eace3867654f10=@example2.png

Updating Annotations in a Specific Layer

To update existing annotations in a specific layer, send a PUT request with a JSON body containing the annotation’s contents — and optionally, a user_id and a group — to /api/documents/:document_id/layers/:layer_name/annotations. For each annotation, the id field is required and should match the id of an existing annotation.

Just as you did when updating annotations in a document, you need to specify the application/json content-type header:

Request

PUT /api/documents/:document_id/layers/:layer_name/annotations
Content-Type: application/json
Authorization: Token token="<secret token>"

Updating Annotations with Attachments in a Specific Layer

To update new annotations with attachments in a specific layer, send a multipart/form-data PUT request and the JSON annotation file containing the annotation’s contents — and optionally, a user_id — to /api/documents/:document_id/layers/:layer_name/annotations. For each annotation, the id field is required and should match the id of an existing annotation.

Just as you did when updating annotations with attachments in a document, you need to specify the multipart/form-data content-type header:

Request

PUT /api/documents/:document_id/layers/:layer_name/annotations
Content-Type: multipart/form-data
Authorization: Token token="<secret token>"

Create and Update Annotation Response

For all create and update annotation requests, the server will validate the content and return an HTTP response with the status 200 and the following JSON payload in the case of success:

HTTP/1.1 200 OK
Content-Type: application/json

{
  "data": [
    {
      "id": "01BS98XZSCFV5QARF948FZWNG5",
      "group": "notes",
      "content": {
        "bbox": [146, 383, 24, 24],
        "opacity": 1,
        "pageIndex": 1,
        "text": {
          format: "plain",
          value: "This is a yellow note annotation",
        },
        "type": "pspdfkit/note",
        "v": 2
      }
    },
    {
      // Another annotation...
    }
  ],
  "details": "All the provided annotations were successfully created.",
  "failing_paths": [],
  "request_id": "request_id",
  "result": "success",
  "status": 200
}

Here, data contains the list of the annotations which were successfully created.

In case of partial failure where some annotations were created and some were not, the failing_paths array will contain the list of failed annotations along with the reason of failure. The value of result will be partial_failure, and the status will still be 200. The format for failing_paths is as follows:

"failing_paths": [
  {
    "details": "Error details",
    "path": "$.annotations[0]"
  }
]

In the case where all the annotations were processed but couldn’t be added or updated, the data array will be empty, and failing_paths will contain the list of all of the failed annotations, along with the reason for failure. Here, the value of result will be failure and the status will be 200, since the request was processed successfully.

If there’s a validation error in the request, the following response will be returned:

HTTP/1.1 422 Unprocessable Entity
Content-Type: application/json

{
  "error": {
    "reason": "<error description>"
  }
}

How to Delete Annotations

Deleting Annotations

To delete one or more annotations, make a DELETE request to /api/documents/:document_id/annotations with a payload containing a list of annotation IDs to delete:

DELETE /api/documents/:document_id/annotations
Authorization: Token token="<secret token>"
{"annotationIds": ["01BS98XZSCFV5QARF948FZWNG5", "01BS98XZSCFV5QARF948FZWNG6"]}
$ curl http://localhost:5000/api/documents/document_id/annotations
   -X DELETE
   -H "Authorization: Token token=secret"
   -d '{"annotationIds": ["01BS98XZSCFV5QARF948FZWNG5", "01BS98XZSCFV5QARF948FZWNG6"]}'

You can also pass the "all" string for the "annotationIds", in which case, all annotations in the document’s default layer will be removed:

DELETE /api/documents/:document_id/annotations
Authorization: Token token="<secret token>"
{"annotationIds": "all"}
$ curl http://localhost:5000/api/documents/document_id/annotations
   -X DELETE
   -H "Authorization: Token token=secret"
   -d '{"annotationIds": "all"}'

The server will return an HTTP response with the status 200 and an empty body when the request succeeds.

Deleting Annotations in a Specific Layer

To delete a set of annotations in a specific layer, make a DELETE request to /api/documents/:document_id/layers/:layer_name/annotations with a payload containing a list of annotation IDs to delete:

DELETE /api/documents/:document_id/annotations
Authorization: Token token="<secret token>"
{"annotationIds": ["01BS98XZSCFV5QARF948FZWNG5", "01BS98XZSCFV5QARF948FZWNG6"]}
$ curl http://localhost:5000/api/documents/document_id/layers/my-layer/annotations
   -X DELETE
   -H "Authorization: Token token=secret"
   -d '{"annotationIds": ["01BS98XZSCFV5QARF948FZWNG5", "01BS98XZSCFV5QARF948FZWNG6"]}'

You can also pass the "all" string for the "annotationIds", in which case, all annotations in the layer will be removed:

DELETE /api/documents/:document_id/layers/my-layer/annotations
Authorization: Token token="<secret token>"
{"annotationIds": "all"}
$ curl http://localhost:5000/api/documents/document_id/annotations
   -X DELETE
   -H "Authorization: Token token=secret"
   -d '{"annotation_ids": "all"}'

The server will return an HTTP response with the status 200 and an empty body in the case of success.

How to Retrieve Annotations

Fetching Document Annotations

To fetch all annotations in a given document, the following endpoint is provided. It can return a response in a few different formats based on the Accept header included in the request.

When Accept: application/x-ndjson is used (recommended), the response will be returned as Newline delimited JSON, allowing the client to process annotations individually or in batches before the complete response body has been received:

Request

GET /api/documents/:document_id/annotations
Accept: application/x-ndjson
Authorization: Token token="<secret token>"
$ curl http://localhost:5000/api/documents/document_id/annotations \
   -H "Accept: application/x-ndjson" \
   -H "Authorization: Token token=secret"

Response

HTTP/1.1 200 OK
Content-Type: application/x-ndjson

{"id": "01BS98XZSCFV5QARF948FZWNG5", "content": {...}, "createdBy": "alice", "updatedBy": "bob", "group": null}
{"id": "01BS98Y0A0YDX4A54K04NQPQ6T", "content": {...}, "createdBy": "alice", "updatedBy": "bob", "group": "notes"}
{"id": "01BS98Y0QFZF5K044JVMDD7W0T", "content": {...}, "createdBy": "alice", "updatedBy": "bob", "group": null}
...

When Accept: application/json is used, only the first 1,000 annotations are returned. If the document has more than 1,000 annotations, "truncated": true will be included in the response data:

Request

GET /api/documents/:document_id/annotations
Accept: application/json
Authorization: Token token="<secret token>"
$ curl http://localhost:5000/api/documents/document_id/annotations \
   -H "Accept: application/json" \
   -H "Authorization: Token token=secret"

Response

HTTP/1.1 200 OK
Content-Type: application/json

{
  "data": {
    "annotations": [
      {"id": "01BS98XZSCFV5QARF948FZWNG5", "content": {...}, "createdBy": "alice", "updatedBy": "bob", "group": null},
      {"id": "01BS98Y0A0YDX4A54K04NQPQ6T", "content": {...}, "createdBy": "alice", "updatedBy": "bob", "group": "notes"},
      {"id": "01BS98Y0QFZF5K044JVMDD7W0T", "content": {...}, "createdBy": "alice", "updatedBy": "bob", "group": null},
      ...
    ],
    "truncated": true
  }
}

Each annotation includes the user_ids of both the person who created it and the person who last modified it. For annotations created or updated in the browser, the user_id is extracted from the JWT used for authentication. The group is a mutable property of the annotation and can be assigned or changed both in the browser and with the API.

Fetching Page Annotations

To fetch annotations for a specific page, the following endpoint is provided. It can return a response in a few different formats based on the Accept header included in the request. See the previous section for more details:

Request

GET /api/documents/:document_id/pages/:page_index/annotations
Accept: application/x-ndjson
Authorization: Token token="<secret token>"
$ curl http://localhost:5000/api/documents/document_id/pages/1/annotations \
   -H "Accept: application/x-ndjson" \
   -H "Authorization: Token token=secret"

Response

HTTP/1.1 200 OK
Content-Type: application/x-ndjson

{"id": "01BS98XZSCFV5QARF948FZWNG5", "content": {...}, "createdBy": "alice", "updatedBy": "bob", "group": null}
{"id": "01BS98Y0A0YDX4A54K04NQPQ6T", "content": {...}, "createdBy": "alice", "updatedBy": "bob", "group": "notes"}
{"id": "01BS98Y0QFZF5K044JVMDD7W0T", "content": {...}, "createdBy": "alice", "updatedBy": "bob", "group": null}
...

Fetching Document Annotations from a Specific Layer

All the endpoints described above work on the default layer of a document but can be modified to operate on a specific named layer belonging to the same document.

PSPDFKit introduced the Instant layers concept in PSPDFKit Server 2017.9. This allows you to manage multiple versions of a document’s contents (e.g. annotations) while uploading the document only once.

To fetch annotations from a specific layer in a document, you can extend the relevant endpoint and scope by the layer name by replacing documents/:document_id with documents/:document_id/layers/:layer_name.

Your request will now look like this:

GET /api/documents/:document_id/layers/:layer_name/annotations
Accept: application/x-ndjson
Authorization: Token token="<secret token>"

The same principles apply to all the use cases that follow.

Fetching Document Annotations from a Specific Page of a Specific Layer

To retrieve the annotations from a specific page of a specific layer, send a GET request using the page index in the URL by replacing documents/:document_id with documents/:document_id/layers/:layer_name/pages/:page_index.

This is how the request will look now:

GET /api/documents/:document_id/layers/:layer_name/pages/:page_index/annotations
Accept: application/x-ndjson
Authorization: Token token="<secret token>"

Retrieving an Annotation

To retrieve an existing annotation, send a GET request to /api/documents/:document_id/annotations/:annotation_id:

Request

GET /api/documents/:document_id/annotations/:annotation_id
Authorization: Token token="<secret token>"
$ curl http://localhost:5000/api/documents/document_id/annotations/01BS98XZSCFV5QARF948FZWNG5 \
   -H "Authorization: Token token=secret"

Response

The server will return the annotation in the response as JSON data:

HTTP/1.1 200 OK
Content-Type: application/json

{
  "id": "01BS98XZSCFV5QARF948FZWNG5",
  "createdBy": "alice",
  "updatedBy": "alice",
  "group": "notes",
  "content": {
    "bbox": [146.89599609375, 383.48397827148438, 24, 24],
    "opacity": 1,
    "pageIndex": 1,
    "text": {
      format: "plain",
      value: "Example text",
    },
    "type": "pspdfkit/note",
    "v": 2
  }
}

When no annotation with the requested annotation_id exists, the server responds with the following:

HTTP/1.1 404 Not Found

Retrieving an Annotation in a Specific Layer

To retrieve an existing annotation, send a GET request to /api/documents/:document_id/layers/:layer_name/annotations/:annotation_id:

GET /api/documents/:document_id/layers/:layer_name/annotations/:annotation_id
Authorization: Token token="<secret token>"

Common Option

Accessing Password-Protected Documents

To access password-protected documents via the upstream API, you need to include the pspdfkit-pdf-password header in all requests that work on a password-protected document.