Skip to Content

Threads

Threads are the comment pins placed on a markup. Use these endpoints to list, read, create, resolve, reopen, and delete threads, reposition a thread’s pin, and manage the screenshot attached to a thread. Authenticate with an exchange token (embedded feedback) or a workspace- or organization-scoped API key (server-to-server) — see the Authentication overview.

List threads

GET /api/v2/threads

List the threads on a markup. Filter and paginate with the query parameters.

GET /api/v2/threads
curl "https://api.markup.io/api/v2/threads?markupId=b3a33209-5d20-4560-b876-8e14993a01ef" \ -X GET \ -H "Authorization: Bearer <API-KEY-SECRET>" \ -H "Markup-API-Version: 2023-02-22" \ -H "Content-Type: application/json"
200 OK
{ "data": { "threads": [ {} ], "query": { "resolved": true }, "offset": 123, "total": 123, "cursorId": "some cursorId" } }

Response Body ProjectThreadsResponsePublic

OptionType
threads
query
offset number
total number
cursorId string

Request Query Params ListThreadsQuery

OptionType
markupId string
cursorId optionalstring
limit optionalnumber
withThreadOrMessageId optionalstring
resolved optionalboolean
order optional
viewMode optional
q optionalstring
offset optionalnumber

Get thread

GET /api/v2/threads/:id

Get a single thread by ID, including its messages.

GET /api/v2/threads/:id
curl "https://api.markup.io/api/v2/threads/9b83ce6d-8dc4-4464-8b61-e5e770442e61" \ -X GET \ -H "Authorization: Bearer <API-KEY-SECRET>" \ -H "Markup-API-Version: 2023-02-22" \ -H "Content-Type: application/json"
200 OK
{ "data": { "id": "some id", "createdAt": {}, "modifiedAt": {}, "scopes": [ "some string" ], "assignedRoleSlugs": [ "some string" ], "user": { "id": "some id", "createdAt": {}, "modifiedAt": {}, "name": "some name", "guest": true }, "number": 123, "resolved": true, "messages": [ { "id": "some id", "createdAt": {}, "modifiedAt": {}, "scopes": [ "some string" ], "assignedRoleSlugs": [ "some string" ], "threadId": "some threadId", "message": "some message", "messageJson": {}, "user": { "id": "some id", "createdAt": {}, "modifiedAt": {}, "name": "some name", "guest": true }, "userMetaData": {}, "attachments": [ { "id": "some id", "createdAt": {}, "modifiedAt": {}, "messageId": "some messageId", "url": "some url", "etag": "some etag", "mimeType": "some mimeType", "originalFilename": "some originalFilename", "filesize": 123 } ], "loomVideos": [ { "id": "some id", "createdAt": {}, "modifiedAt": {}, "messageId": "some messageId", "title": "some title", "sharedUrl": "some sharedUrl", "width": 123, "height": 123 } ], "read": true } ], "userMetaData": {}, "projectId": "some projectId", "proxyUrl": "some proxyUrl", "originalUrl": "some originalUrl", "canonicalUrl": "some canonicalUrl", "elements": [ { "id": "some id", "createdAt": {}, "modifiedAt": {}, "threadId": "some threadId", "path": "some path", "offsetXPercentage": 123, "offsetYPercentage": 123 } ], "parents": [ { "id": "some id", "createdAt": {}, "modifiedAt": {}, "path": "some path", "depth": 123, "proxyUrl": "some proxyUrl", "originalUrl": "some originalUrl", "canonicalUrl": "some canonicalUrl" } ], "viewModeId": "some viewModeId", "read": true, "unreadMessageCount": 123 } }

Response Body - ThreadResponsePublic

Request Path Params IdRequestParam

OptionType
id string

Create thread

POST /api/v2/threads

Create a new thread (comment pin) on a markup.

POST /api/v2/threads
curl "https://api.markup.io/api/v2/threads" \ -X POST \ -H "Authorization: Bearer <API-KEY-SECRET>" \ -H "Markup-API-Version: 2023-02-22" \ -H "Content-Type: application/json" \ --data '{ "isImageThread": true, "url": "some url", "canonicalUrl": "some canonicalUrl", "elements": [ { "elementPath": "some elementPath", "offsetXPercentage": 123, "offsetYPercentage": 123 } ], "offsetXPercentage": 123, "offsetYPercentage": 123, "message": {}, "browserContext": {}, "elementParents": [ { "url": "some url", "canonicalUrl": "some canonicalUrl", "path": "some path", "depth": 123 } ], "viewModeId": "e7a0bcc0-2882-470d-93ca-20ee9950e588" }'
200 OK
{ "data": { "id": "some id", "createdAt": {}, "modifiedAt": {}, "scopes": [ "some string" ], "assignedRoleSlugs": [ "some string" ], "user": { "id": "some id", "createdAt": {}, "modifiedAt": {}, "name": "some name", "guest": true }, "number": 123, "resolved": true, "messages": [ { "id": "some id", "createdAt": {}, "modifiedAt": {}, "scopes": [ "some string" ], "assignedRoleSlugs": [ "some string" ], "threadId": "some threadId", "message": "some message", "messageJson": {}, "user": { "id": "some id", "createdAt": {}, "modifiedAt": {}, "name": "some name", "guest": true }, "userMetaData": {}, "attachments": [ { "id": "some id", "createdAt": {}, "modifiedAt": {}, "messageId": "some messageId", "url": "some url", "etag": "some etag", "mimeType": "some mimeType", "originalFilename": "some originalFilename", "filesize": 123 } ], "loomVideos": [ { "id": "some id", "createdAt": {}, "modifiedAt": {}, "messageId": "some messageId", "title": "some title", "sharedUrl": "some sharedUrl", "width": 123, "height": 123 } ], "read": true } ], "userMetaData": {}, "projectId": "some projectId", "proxyUrl": "some proxyUrl", "originalUrl": "some originalUrl", "canonicalUrl": "some canonicalUrl", "elements": [ { "id": "some id", "createdAt": {}, "modifiedAt": {}, "threadId": "some threadId", "path": "some path", "offsetXPercentage": 123, "offsetYPercentage": 123 } ], "parents": [ { "id": "some id", "createdAt": {}, "modifiedAt": {}, "path": "some path", "depth": 123, "proxyUrl": "some proxyUrl", "originalUrl": "some originalUrl", "canonicalUrl": "some canonicalUrl" } ], "viewModeId": "some viewModeId", "read": true, "unreadMessageCount": 123 } }

Response Body - ThreadResponsePublic

Request Body CreateThreadRequest

OptionType
isImageThread boolean
projectId optionalstring
projectImageId optionalstring
url string
canonicalUrl string
elements CreateThreadRequestElement[]
offsetXPercentage number
offsetYPercentage number
startTime optionalnumber
endTime optionalnumber
message Delta
browserContext
elementParents ThreadElementParentRequest[]
viewModeId string
attachments optionalMessageRequestAttachment[]
loomVideos optionalLoomVideoRequest[]
width optionalnumber
height optionalnumber
annotations optional
textHighlight optional
pageNumber optionalnumber
requireDimensionsOverride optionalboolean
priority optionalnumber

Resolve thread

POST /api/v2/threads/:id/resolve

Mark a thread as resolved.

POST /api/v2/threads/:id/resolve
curl "https://api.markup.io/api/v2/threads/7e2e7df9-1b16-4ceb-8219-ff204fb57708/resolve" \ -X POST \ -H "Authorization: Bearer <API-KEY-SECRET>" \ -H "Markup-API-Version: 2023-02-22" \ -H "Content-Type: application/json"
200 OK
{ "data": { "threadId": "some threadId", "modifiedAt": 123 } }

Response Body ThreadResolvedResponse

OptionType
threadId string
modifiedAt number

Request Path Params IdRequestParam

OptionType
id string

Unresolve thread

POST /api/v2/threads/:id/unresolve

Reopen a previously resolved thread.

POST /api/v2/threads/:id/unresolve
curl "https://api.markup.io/api/v2/threads/9f55f067-02a1-4740-8b88-1822b1667188/unresolve" \ -X POST \ -H "Authorization: Bearer <API-KEY-SECRET>" \ -H "Markup-API-Version: 2023-02-22" \ -H "Content-Type: application/json"
200 OK
{ "data": { "threadId": "some threadId" } }

Response Body ThreadUnresolvedResponse

OptionType
threadId string

Request Path Params IdRequestParam

OptionType
id string

Mark thread as read

POST /api/v2/threads/:id/read

Mark a thread, and every message inside it, as read for the authenticated user. Read state is per user: with an exchange token it is recorded against the end user the token identifies, and with an API key it is recorded against the key’s API user. Returns 204 with no body.

POST /api/v2/threads/:id/read
curl "https://api.markup.io/api/v2/threads/b5d69388-a63a-4e47-8b30-1d401b05c270/read" \ -X POST \ -H "Authorization: Bearer <API-KEY-SECRET>" \ -H "Markup-API-Version: 2023-02-22" \ -H "Content-Type: application/json"
Response
204 No Content

Request Path Params IdRequestParam

OptionType
id string

Delete thread

DELETE /api/v2/threads/:id

Delete a thread and all of its messages.

DELETE /api/v2/threads/:id
curl "https://api.markup.io/api/v2/threads/b223a6e3-4656-49ad-a3c0-873c65194b0e" \ -X DELETE \ -H "Authorization: Bearer <API-KEY-SECRET>" \ -H "Markup-API-Version: 2023-02-22" \ -H "Content-Type: application/json"
Response
204 No Content

Request Path Params IdRequestParam

OptionType
id string

Move thread pin

PATCH /api/v2/threads/:id/move-pin

Reposition a thread’s pin on the markup.

PATCH /api/v2/threads/:id/move-pin
curl "https://api.markup.io/api/v2/threads/39f3a550-9efc-4d4f-9fc4-e82dbc47bb61/move-pin" \ -X PATCH \ -H "Authorization: Bearer <API-KEY-SECRET>" \ -H "Markup-API-Version: 2023-02-22" \ -H "Content-Type: application/json" \ --data '{ "url": "some url", "canonicalUrl": "some canonicalUrl", "elements": [ { "elementPath": "some elementPath", "offsetXPercentage": 123, "offsetYPercentage": 123 } ], "browserContext": {}, "elementParents": [ { "url": "some url", "canonicalUrl": "some canonicalUrl", "path": "some path", "depth": 123 } ], "viewportXPercentage": 123, "viewportYPercentage": 123 }'
200 OK
{ "data": { "id": "some id", "userId": "some userId", "projectId": "some projectId", "number": 123, "resolved": true, "proxyHostId": "some proxyHostId", "urlPath": "some urlPath", "canonicalUrl": "some canonicalUrl", "viewModeId": "some viewModeId" } }

Response Body - Thread

Request Path Params IdRequestParam

OptionType
id string

Request Body MoveThreadPinBody

OptionType
url string
canonicalUrl string
elements CreateThreadRequestElement[]
browserContext
elementParents ThreadElementParentRequest[]
viewportXPercentage number
viewportYPercentage number

Get screenshot upload policy

GET /api/v2/threads/:id/screenshot-upload-policy

Get a pre-signed S3 upload policy for attaching a screenshot to a thread. Upload the file to S3 with the returned policy, then call PUT /threads/:id/screenshot to attach it.

GET /api/v2/threads/:id/screenshot-upload-policy
curl "https://api.markup.io/api/v2/threads/47e0fcc1-0f66-40a4-9d22-65f096a07728/screenshot-upload-policy?fileId=f530691f-b840-40dd-8f6b-033fdf30db81&filesize=123&filename=some+filename&mimeType=some+mimeType" \ -X GET \ -H "Authorization: Bearer <API-KEY-SECRET>" \ -H "Markup-API-Version: 2023-02-22" \ -H "Content-Type: application/json"
200 OK
{ "data": { "key": "some key", "policy": {}, "bucket": "some bucket" } }

Response Body S3UploadPolicyResponse

OptionType
key string
policy PresignedPost
bucket string
metadata Record

Request Path Params IdRequestParam

OptionType
id string

Request Query Params ScreenshotUploadPolicyRequestQuery

OptionType
fileId string
filesize number
filename string
mimeType string

Set thread screenshot

PUT /api/v2/threads/:id/screenshot

Attach (or replace) the screenshot for a thread after uploading the file with the upload policy.

PUT /api/v2/threads/:id/screenshot
curl "https://api.markup.io/api/v2/threads/4aa3b253-8e49-45de-a6db-14b3741a823b/screenshot" \ -X PUT \ -H "Authorization: Bearer <API-KEY-SECRET>" \ -H "Markup-API-Version: 2023-02-22" \ -H "Content-Type: application/json" \ --data '{ "key": "some key", "etag": "some etag", "mimeType": "some mimeType", "width": 123, "height": 123, "filesize": 123, "fileId": "58dea1ba-c086-4651-8ce5-93541fb7fcc6" }'
200 OK
{ "data": { "id": "some id", "createdAt": {}, "modifiedAt": {}, "mimeType": "some mimeType", "width": 123, "height": 123, "filesize": 123, "type": "extension", "isProcessing": true } }

Response Body - ScreenshotResponse

Request Path Params IdRequestParam

OptionType
id string

Request Body CreateScreenshotRequest

OptionType
key string
etag string
mimeType string
width number
height number
filesize number
originalFilename optionalstring
fileId string

Get thread screenshot

GET /api/v2/threads/:id/screenshot

Get the screenshot attached to a thread.

GET /api/v2/threads/:id/screenshot
curl "https://api.markup.io/api/v2/threads/91414aae-3365-4b46-9613-55d7d59b5397/screenshot" \ -X GET \ -H "Authorization: Bearer <API-KEY-SECRET>" \ -H "Markup-API-Version: 2023-02-22" \ -H "Content-Type: application/json"
200 OK
{ "data": { "id": "some id", "createdAt": {}, "modifiedAt": {}, "mimeType": "some mimeType", "width": 123, "height": 123, "filesize": 123, "type": "extension", "isProcessing": true } }

Response Body - ScreenshotResponse

Request Path Params IdRequestParam

OptionType
id string

ScreenshotResponse

OptionType
id string
createdAt Unix_Timestamp_Utc_Milliseconds
modifiedAt Unix_Timestamp_Utc_Milliseconds
mimeType string
width number
height number
filesize number
type
isProcessing boolean

ThreadOrder

OptionType
ThreadOrder `page` | `pin_asc` | `pin_desc` | `activity` | `timestamp` | `priority_asc` | `priority_desc`

ViewModeCategory

OptionType
ViewModeCategory `desktop` | `tablet` | `mobile`

ThreadElementParentRequest

OptionType
url string
canonicalUrl string
path string
depth number

BrowserContextRequest

OptionType
browserName string
browserVersion string
os string
osVersion string
osVersionName string
platform string
screenWidth number
screenHeight number
viewportWidth number
viewportHeight number

CanvasThreadAnnotationRequest

OptionType
version string
scale number
originalWidth number
originalHeight number

ThreadTextHighlightRequest

OptionType
startKey string
endKey string
startOffset number
startIndex number
endOffset number
endIndex number
text string

IProjectThreadQuery

OptionType
resolved boolean

ThreadResponsePublic

WebpageThreadResponsePublic

OptionType
id string
createdAt Unix_Timestamp_Utc_Milliseconds
modifiedAt Unix_Timestamp_Utc_Milliseconds
scopes string[]
assignedRoleSlugs string[]
user AuthorUserResponse
number number
resolved boolean
messages MessageResponseWithReadState[]

The thread's messages, each carrying its own read flag for the caller.

userMetaData IUserResourceMetaData
lastActivityAt number
annotations AnnotationsResponse
projectId string
proxyUrl string
originalUrl string
canonicalUrl string
elements ThreadElementResponse[]
parents ThreadElementParentResponse[]
browserContext IBrowserContext
viewModeId string
read boolean

True when the thread and every message in it have been seen by the caller. Mark it read with POST /api/v2/threads/:id/read. There is no historical baseline: anything the caller has not marked read is reported unread, including threads that predate their first call.

unreadMessageCount number

Number of messages in the thread the caller has not seen since they last changed.

ImageThreadResponsePublic

OptionType
id string
createdAt Unix_Timestamp_Utc_Milliseconds
modifiedAt Unix_Timestamp_Utc_Milliseconds
scopes string[]
assignedRoleSlugs string[]
user AuthorUserResponse
number number
resolved boolean
messages MessageResponseWithReadState[]

The thread's messages, each carrying its own read flag for the caller.

userMetaData IUserResourceMetaData
lastActivityAt number
annotations AnnotationsResponse
projectImageId string
offsetXPercentage number
offsetYPercentage number
startTime number
endTime number
read boolean

True when the thread and every message in it have been seen by the caller. Mark it read with POST /api/v2/threads/:id/read. There is no historical baseline: anything the caller has not marked read is reported unread, including threads that predate their first call.

unreadMessageCount number

Number of messages in the thread the caller has not seen since they last changed.

PdfThreadResponsePublic

OptionType
id string
createdAt Unix_Timestamp_Utc_Milliseconds
modifiedAt Unix_Timestamp_Utc_Milliseconds
scopes string[]
assignedRoleSlugs string[]
user AuthorUserResponse
number number
resolved boolean
messages MessageResponseWithReadState[]

The thread's messages, each carrying its own read flag for the caller.

userMetaData IUserResourceMetaData
lastActivityAt number
annotations AnnotationsResponse
projectImageId string
offsetXPercentage number
offsetYPercentage number
pageNumber number
textHighlight TextHighlightResponse
read boolean

True when the thread and every message in it have been seen by the caller. Mark it read with POST /api/v2/threads/:id/read. There is no historical baseline: anything the caller has not marked read is reported unread, including threads that predate their first call.

unreadMessageCount number

Number of messages in the thread the caller has not seen since they last changed.

Thread

OptionType
Thread

WebpageThread

OptionType
id string
userId string
projectId string
number number
resolved boolean
proxyHostId string
urlPath string
canonicalUrl string
viewModeId string

ImageThread

OptionType
id string
userId string
projectId string
number number
resolved boolean
projectImageId string
offsetXPercentage number
offsetYPercentage number
startTime number
endTime number

PdfThread

OptionType
id string
userId string
projectId string
number number
resolved boolean
pageNumber number
pageRelativeX number
pageRelativeY number

ScreenshotType

OptionType
ScreenshotType `extension` | `native` | `static`