Annotations
Annotations are stored in the JSON annotation format on the server. PSPDFKit Server provides the following endpoints for working with annotations:
-
Fetching Document Annotations from a Specific Page of a Specific Layer
-
Creating an Annotation with an Attachment in a Specific Layer
-
Updating an Annotation with an Attachment in a Specific Layer
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/abc/annotations \ -H "Accept: application/x-ndjson" \ -H "Authorization: Token token=<secret token>"
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/abc/annotations \ -H "Accept: application/json" \ -H "Authorization: Token token=<secret token>"
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/abc/pages/1/annotations \ -H "Accept: application/x-ndjson" \ -H "Authorization: Token token=<secret token>"
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} ...
Creating an Annotation
To create a new annotation, send a POST
request with a JSON body containing the annotation’s contents 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>" PSPDFKit-API-Version: 2017.7 { "id": "01BS98XZSCFV5QARF948FZWNG5", "user_id": "bob" "group": "notes", "content": { "bbox": [146.89599609375, 383.48397827148438, 24, 24], "opacity": 1, "pageIndex": 1, "text": "This is a yellow note annotation", "type": "pspdfkit/note", "v": 1 } }
$ curl http://localhost:5000/api/documents/abc/annotations \ -X POST \ -H "Authorization: Token token=<secret token>" \ -H "PSPDFKit-API-Version: 2017.7" \ -d '{"id": "01BS98XZSCFV5QARF948FZWNG5", "user_id": "bob", "content":{"bbox": [146, 383, 24, 24], ...}}'
Response
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": { "annotation_id": "01BS98XZSCFV5QARF948FZWNG5" } }
If there’s a validation error, the following response will be returned:
HTTP/1.1 422 Unprocessable Entity Content-Type: application/json { "error": { "reason": "<error description>" } }
Creating an Annotation with an Attachment
To create a new annotation with an attachment, 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 is referenced in the annotation and is not 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>" PSPDFKit-API-Version: 2017.7 --customboundary Content-Disposition: form-data; name="annotation"; filename="annotation.json" Content-Type: application/json { "content": { "bbox": [100,100,100,100], "opacity": 1, "pageIndex": 0, "type": "pspdfkit/image", "contentType":"image/png", "imageAttachmentId": "ffe23a235e3b733a498376f869292110e6663473712373ea6b4c0b02b469583d", "v": 1 } } --customboundary Content-Disposition: form-data; name="ffe23a235e3b733a498376f869292110e6663473712373ea6b4c0b02b469583d"; filename="example.png" Content-Type: image/png <Attachment data>
$ curl -XPOST http://localhost:5000/api/documents/abc/annotations \ -X POST \ -H pspdfkit-api-version\:\ 2017.7 \ -H Authorization\:\ Token\ token\=secret \ -H Content-Type\:\ multipart/form-data \ -F annotation=" \ { \ \"content\": { \ \"v\":1, \ \"bbox\":[100,100,100,100], \ \"contentType\":\"image/png\", \ \"imageAttachmentId\": \"ffe23a235e3b733a498376f869292110e6663473712373ea6b4c0b02b469583d\", \ \"opacity\":1, \ \"pageIndex\":0, \ \"type\":\"pspdfkit/image\" \ } \ }" \ -F ffe23a235e3b733a498376f869292110e6663473712373ea6b4c0b02b469583d=@example.png
Response
The server will validate the content of the attachment 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": { "annotation_id": "01BS98XZSCFV5QARF948FZWNG5" } }
If there’s a validation error, the following response will be returned:
HTTP/1.1 422 Unprocessable Entity Content-Type: application/json { "error": { "reason": "<error description>" } }
Updating an Annotation
To update an existing annotation, send a PUT
request with a JSON body containing the new contents to /api/documents/:document_id/annotations/:annotation_id
, specifying the application/json
content type.
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/:annotation_id Content-Type: application/json Authorization: Token token="<secret token>" PSPDFKit-API-Version: 2017.7 { "id": "01BS98XZSCFV5QARF948FZWNG5", "user_id": "alice", "group": "alice-notes", "content": { "bbox": [146.89599609375, 383.48397827148438, 24, 24], "opacity": 1, "pageIndex": 1, "text": "This text has been updated.", "type": "pspdfkit/note", "v": 1 } }
$ curl http://localhost:5000/api/documents/abc/annotations/01BS98XZSCFV5QARF948FZWNG5 \ -X PUT \ -H "Authorization: Token token=<secret token>" \ -H "PSPDFKit-API-Version: 2017.7" \ -d '{"id": "01BS98XZSCFV5QARF948FZWNG5", "user_id": "alice", "content": {"bbox": [146, 383, 24, 24], ...}}'
Response
The server will validate the content and return an HTTP response with the status 200 and an empty body in the case of success.
If there’s a validation error, the following response will be returned:
HTTP/1.1 422 Unprocessable Entity Content-Type: application/json { "error": { "reason": "<error description>" } }
Updating an Annotation with an Attachment
To update an existing annotation and also update its attachment, 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 attachment that is referenced in the annotation and is not 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/:annotation_id Content-Type: multipart/form-data Authorization: Token token="<secret token>" PSPDFKit-API-Version: 2017.7 --customboundary Content-Disposition: form-data; name="annotation"; filename="annotation.json" Content-Type: application/json { "user_id": "adsf", "content": { "v":1, "bbox":[100,100,382,286], "contentType":"image/png", "imageAttachmentId": "0c76f21f309939d871362e080b4160634ff06bc07076b7940c42a53f89a99e0c", "opacity":1, "pageIndex":0, "type":"pspdfkit/image" } } --customboundary Content-Disposition: form-data; name="0c76f21f309939d871362e080b4160634ff06bc07076b7940c42a53f89a99e0c"; filename="example.png" Content-Type: image/png <Attachment data>
$ curl http\://127.0.0.1\:5000/api/documents/1/annotations/01CNGKC7BRB7HR8THJJ5K66XGX \ -X PUT \ -H accept\:\ application/json \ -H pspdfkit-api-version\:\ 2017.7 \ -H Authorization\:\ Token\ token\=secret \ -H Content-Type\:\ multipart/form-data \ -F annotation=" \ { \ \"user_id\": \"adsf\", \ \"content\": { \ \"v\":1, \ \"bbox\":[100,100,382,286], \ \"contentType\":\"image/png\", \ \"imageAttachmentId\": \"0c76f21f309939d871362e080b4160634ff06bc07076b7940c42a53f89a99e0c\", \ \"opacity\":1, \ \"pageIndex\":0, \ \"type\":\"pspdfkit/image\" \ } \ } \ " \ -F 0c76f21f309939d871362e080b4160634ff06bc07076b7940c42a53f89a99e0c=@/Users/max/Desktop/challenger-deep.png
Response
The server will validate the content and return an HTTP response with the status 200 and an empty body in the case of success.
If there’s a validation error, the following response will be returned:
HTTP/1.1 422 Unprocessable Entity Content-Type: application/json { "error": { "reason": "<error description>" } }
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/abc/annotations/01BS98XZSCFV5QARF948FZWNG5 \ -H "Authorization: Token token=<secret token>"
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": "Example text", "type": "pspdfkit/note", "v": 1 } }
When no annotation with the requested annotation_id
exists, the server responds with the following:
HTTP/1.1 404 Not Found
Deleting an Annotation
To delete an existing annotation, send a DELETE
request to
/api/documents/:document_id/annotations/:annotation_id
:
Request
DELETE /api/documents/:document_id/annotations/:annotation_id Authorization: Token token="<secret token>"
$ curl http://localhost:5000/api/documents/abc/annotations/01BS98XZSCFV5QARF948FZWNG5 \ -X DELETE -H "Authorization: Token token=<secret token>"
Response
The server will return an HTTP response with the status 200 and an empty body.
Deleting a Set of Annotations
In order to delete a set of 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/abc/annotations -X DELETE -H "Authorization: Token token=<secret token>" -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/abc/annotations -X DELETE -H "Authorization: Token token=<secret token>" -d '{"annotationIds": "all"}'
The server will return an HTTP response with the status 200 and an empty body when the request succeeds.
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
:
/api/documents/:document_id/layers/:layer_name/annotations
.
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>"
Creating an Annotation in a Specific Layer
To create a new annotation 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
:
POST /api/documents/:document_id/layers/:layer_name/annotations Content-Type: application/json Authorization: Token token="<secret token>" PSPDFKit-API-Version: 2017.7
Creating an Annotation with an Attachment in a Specific Layer
To create a new annotation with an attachment 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 an annotation][adding an annotation with an attachment to a document] in a document, you need to specify the multipart/form-data
content-type header:
POST /api/documents/:document_id/layers/:layer_name/annotations Content-Type: multipart/form-data Authorization: Token token="<secret token>" PSPDFKit-API-Version: 2017.7
Updating an Annotation in a Specific Layer
To update an existing annotation in a specific layer, send a PUT
request with a JSON body containing the new contents — and optionally, a user_id
and a group
— to /api/documents/:document_id/layers/:layer_name/annotations/:annotation_id
, specifying the application/json
content type:
PUT /api/documents/:document_id/layers/:layer_name/annotations/:annotation_id Content-Type: application/json Authorization: Token token="<secret token>" PSPDFKit-API-Version: 2017.7
Updating an Annotation with an Attachment in a Specific Layer
To update an existing annotation and also update its attachment, 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/layers/:layer_name/annotations/:annotation_id
, specifying the multipart/form-data
content type:
PUT /api/documents/:document_id/layers/:layer_name/annotations/:annotation_id Content-Type: multipart/form-data Authorization: Token token="<secret token>" PSPDFKit-API-Version: 2017.7
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>"
Deleting an Annotation in a Specific Layer
To delete an existing annotation, send a DELETE
request to /api/documents/:document_id/layers/:layer_name/annotations/:annotation_id
:
DELETE /api/documents/:document_id/layers/:layer_name/annotations/:annotation_id Authorization: Token token="<secret token>"
Deleting a Set of Annotations in a Specific Layer
In order 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/abc/layers/my-layer/annotations -X DELETE -H "Authorization: Token token=<secret token>" -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/abc/annotations -X DELETE -H "Authorization: Token token=<secret token>" -d '{"annotation_ids": "all"}'
The server will return an HTTP response with the status 200 and an empty body in the case of success.
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.