curl --request GET \
--url https://{host}/v1/conversations \
--header 'Authorization: Bearer <token>'const options = {method: 'GET', headers: {Authorization: 'Bearer <token>'}};
fetch('https://{host}/v1/conversations', options)
.then(res => res.json())
.then(res => console.log(res))
.catch(err => console.error(err));<?php
$curl = curl_init();
curl_setopt_array($curl, [
CURLOPT_URL => "https://{host}/v1/conversations",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => "",
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => "GET",
CURLOPT_HTTPHEADER => [
"Authorization: Bearer <token>"
],
]);
$response = curl_exec($curl);
$err = curl_error($curl);
curl_close($curl);
if ($err) {
echo "cURL Error #:" . $err;
} else {
echo $response;
}import requests
url = "https://{host}/v1/conversations"
headers = {"Authorization": "Bearer <token>"}
response = requests.get(url, headers=headers)
print(response.text){
"data": [
{
"type": "conversations",
"id": "3312",
"attributes": {
"side": "seller",
"unread": true,
"archived": false,
"noReplyNeeded": false,
"buyer": {
"name": "Jens Urlbauer",
"initials": "JU",
"estimatedCountry": "DE"
},
"blocked": false,
"blockedByYou": false,
"purpose": "propose_price",
"lastMessage": {
"id": "88121",
"excerpt": "Thank you, I will think about it over the weekend.",
"from": "buyer",
"mine": false,
"createdAt": "2026-09-21T10:02:47+02:00",
"removed": false,
"priceProposal": null
},
"agreedPrice": 1300000,
"order": null,
"createdAt": "2026-09-20T10:00:00.000000Z",
"updatedAt": "2026-09-21T08:02:47.000000Z"
},
"relationships": {
"watch": {
"links": {
"related": "https://api.wristler.eu/v1/conversations/3312/watch"
}
},
"seller": {
"links": {
"related": "https://api.wristler.eu/v1/conversations/3312/seller"
}
},
"messages": {
"links": {
"related": "https://api.wristler.eu/v1/conversations/3312/messages"
}
}
},
"links": {
"self": "https://api.wristler.eu/v1/conversations/3312"
}
}
],
"meta": {
"page": {
"perPage": 25,
"hasMore": false
},
"unread": 4
}
}{
"errors": [
{
"status": "404",
"code": "watch_reserved",
"title": "Not Found",
"detail": "<string>",
"meta": {},
"source": {
"pointer": "/data/attributes/reference",
"parameter": "<string>"
}
}
],
"jsonapi": {
"version": "1.0"
}
}List threads
Scope: messages:read
The threads you are the buyer in, newest activity first. Cursor-paged.
Buyer-side for every account, including the people who run shops: messaging in the app is
the buyer’s side, and a shop’s own correspondence belongs to the seller environment, which
somebody has to switch into. filter[side]=seller is the opt-in and returns the threads of
the shops you belong to; nothing else sends it, and an administrator gets no platform-wide
view either way. meta.unread follows whichever side you asked for, so the badge and the
list can never disagree.
The same applies to what reaches a phone: a message or an offer notifies the buyer as
a message/offer_* push, and the shop’s staff under the shop category (data.side: "seller") — see the devices guide.
meta.unread is how many threads are waiting on you on the side you are browsing (and
for the one business filter[seller] names), across the whole list rather than this
page — a badge has to be right on every screen, and paging a list to count it is the
wrong shape for that. The other filters do not narrow it.
lastMessage is the line a row draws, and it is an attribute rather than an
include: id, the excerpt as plain text (“Photo” / “2 photos” for a photo sent on its
own, “Price proposal” for an offer without words of its own, null when removed),
createdAt, from (buyer, seller or system), mine, removed, and
priceProposal when the last message is an offer: amount, currency, from,
status, open, payable and generatedBody, as on the message itself. null for a
thread with no messages yet.
Draw the row from that rather than include=messages, which sends every message of
every thread — two threads of fourteen and fifteen messages were two thirds of the
answer, growing with every reply, and the list is polled. unread on the thread is
the dot; it is per side, answered for whoever asked.
For the watch on the row, include=watch.cover and include=watch.watchModel: one
photo and the name, in the answer you already asked for, instead of a request per
visible row.
buyer is who the shop is talking to: name and initials, as the dashboard shows
them above a thread. Only on the shop’s side; null on the buyer’s own threads, where
the other end is seller, and for a buyer who has since deleted their account.
estimatedCountry is where the buyer seemed to be when they first wrote (ISO 3166-1
alpha-2, an estimate from their connection, null when unknown): a guide to where an
order would be shipped, never their address.
The rest of a thread: side (buyer or seller, which end of it you are), unread
and archived from your end, noReplyNeeded (the shop’s; null on the buyer’s side),
blocked and blockedByYou (see Messaging (Blocked buyers); blocked: true with
blockedByYou: false also when Wristler blocked the buyer’s account), purpose (why it was
started, free text such as start_conversation, propose_price, request_availability
or aftercare, and null on older threads — analytics, not something to branch on),
createdAt, and updatedAt, the last-activity clock the list sorts on.
What the website’s chat header shows, on both sides: agreedPrice is the newest
proposal that still stands, in cents — the price that counts in this thread rather than
the listing’s — and null when none does. order is the order the thread led to once
the watch is sold and this buyer paid for it: { "id", "number" }, where id opens it
at /v1/me/orders/{id} for the buyer or /v1/sellers/{sellerId}/orders/{id} for the
shop and number is what people quote. null otherwise.
curl --request GET \
--url https://{host}/v1/conversations \
--header 'Authorization: Bearer <token>'const options = {method: 'GET', headers: {Authorization: 'Bearer <token>'}};
fetch('https://{host}/v1/conversations', options)
.then(res => res.json())
.then(res => console.log(res))
.catch(err => console.error(err));<?php
$curl = curl_init();
curl_setopt_array($curl, [
CURLOPT_URL => "https://{host}/v1/conversations",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => "",
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => "GET",
CURLOPT_HTTPHEADER => [
"Authorization: Bearer <token>"
],
]);
$response = curl_exec($curl);
$err = curl_error($curl);
curl_close($curl);
if ($err) {
echo "cURL Error #:" . $err;
} else {
echo $response;
}import requests
url = "https://{host}/v1/conversations"
headers = {"Authorization": "Bearer <token>"}
response = requests.get(url, headers=headers)
print(response.text){
"data": [
{
"type": "conversations",
"id": "3312",
"attributes": {
"side": "seller",
"unread": true,
"archived": false,
"noReplyNeeded": false,
"buyer": {
"name": "Jens Urlbauer",
"initials": "JU",
"estimatedCountry": "DE"
},
"blocked": false,
"blockedByYou": false,
"purpose": "propose_price",
"lastMessage": {
"id": "88121",
"excerpt": "Thank you, I will think about it over the weekend.",
"from": "buyer",
"mine": false,
"createdAt": "2026-09-21T10:02:47+02:00",
"removed": false,
"priceProposal": null
},
"agreedPrice": 1300000,
"order": null,
"createdAt": "2026-09-20T10:00:00.000000Z",
"updatedAt": "2026-09-21T08:02:47.000000Z"
},
"relationships": {
"watch": {
"links": {
"related": "https://api.wristler.eu/v1/conversations/3312/watch"
}
},
"seller": {
"links": {
"related": "https://api.wristler.eu/v1/conversations/3312/seller"
}
},
"messages": {
"links": {
"related": "https://api.wristler.eu/v1/conversations/3312/messages"
}
}
},
"links": {
"self": "https://api.wristler.eu/v1/conversations/3312"
}
}
],
"meta": {
"page": {
"perPage": 25,
"hasMore": false
},
"unread": 4
}
}{
"errors": [
{
"status": "404",
"code": "watch_reserved",
"title": "Not Found",
"detail": "<string>",
"meta": {},
"source": {
"pointer": "/data/attributes/reference",
"parameter": "<string>"
}
}
],
"jsonapi": {
"version": "1.0"
}
}Authorizations
Query Parameters
Related resources to return alongside. Combine them with commas: a list of
threads with include=watch,watch.cover,seller is one call for a whole mailbox
screen, photo included. messages is the whole thread and belongs on one thread,
not on the list — lastMessage is the attribute a row draws from.
watch, watch.cover, watch.watchModel, seller, messages One business. With filter[side]=seller it is the shop whose threads you want, which
is what staff at two businesses need after switching into one of them; on the buyer
side it means "my threads with this business".
It only ever narrows. An id belonging to a business you do not work at answers an
empty list, never somebody else's correspondence, and meta.unread narrows with it.
The shop's "Unanswered", with filter[side]=seller -- exactly the website's filter
of that name: not archived, the last message not yours (so a thread with no messages
yet, or one a colleague answered last, is waiting too), and not a thread the shop
marked noReplyNeeded unless the buyer wrote since. false for the rest. On the
buyer's side it is a 400 seller_side_filter: a buyer has nothing to answer.
Response
A page of threads, with meta.unread beside it.