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, aULID
will be generated and returned in the response. -
user_id
— This is the annotation creator. If it isn’t provided, this value defaults tonull
. -
group
— This is the annotation group. If it isn’t provided, this value defaults tonull
.
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 theupdatedBy
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_id
s 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.