Messages
Reading conversations, messages and photo attachments needs messages:read;
starting a conversation, sending a message and marking messages read need
messages:write. You see every conversation your account takes part in, both the
ones where buyers write to you about your listings and the ones where you are the buyer. Conversations
are shared with the website and the app: a message sent here shows up in the other side's chat
immediately, and their reply reaches you through GET /conversations/{id}/messages
or the message.received webhook.
List conversations
GET /conversationsConversations with at least one message, most recent activity first. A conversation that was opened but never written in is not listed, like on the website.
Query parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
unread |
boolean | Optional | true lists only conversations with messages from the other side you have not marked read. |
since |
string | Optional | ISO date/datetime. Only conversations with activity (last_message_at) at or after this point. |
page / per_page |
integer | Optional | See Pagination. Default 30 per page. |
Response fields
Returns a paginated list of conversations:
| Field | Type | Description |
|---|---|---|
id |
integer | The conversation's id. |
role |
string | seller when the counterpart is writing to you about your shop or listing, buyer when you started the conversation with another seller. |
counterpart |
object | The other side: id, name, slug (their shop slug, null without a shop) and deleted. A deleted account keeps resolving as "Verwijderde gebruiker" with deleted: true. |
listing |
object or null | The listing the conversation is about (id, name, subtitle, url). Null for a general shop conversation. |
last_message |
object or null | The latest message: id, preview (first 120 characters, [foto] for a photo-only message), mine and sent_at. |
unread_count |
integer | Messages from the other side you have not marked read yet. |
last_message_at / created_at |
string or null | ISO 8601 timestamps. |
curl "https://tcgplein.nl/api/v1/conversations?unread=true" \
-H "Authorization: Bearer tcgp_live_K7mP2xQ9vZ4nR8wT3yL6bC1jD5sF0gH7uM2oA9iE"
Get a conversation
GET /conversations/{id}
One conversation, same shape as a list row. Unlike the list it also returns a conversation without
messages (one you just opened with POST /conversations). A conversation you do
not take part in answers 404, never 403, so ids
cannot be probed.
List messages
GET /conversations/{id}/messages
The messages of a conversation, newest first. Reading never marks anything read: an integration
decides when a human has actually seen the messages and then calls
POST /conversations/{id}/read.
Query parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
since |
string | Optional | ISO datetime. Only messages sent strictly after this point; poll with the newest sent_at you have seen. |
page / per_page |
integer | Optional | See Pagination. Default 50 per page. |
Response fields
| Field | Type | Description |
|---|---|---|
id / conversation_id |
integer | The message and its conversation. |
body |
string | The text, up to 2000 characters. Empty for a photo-only message. |
mine |
boolean | True when your account sent it (on the website, in the app or through the API). |
sender |
object | id, name and deleted. |
attachments |
array | Photos on the message: id, name, mime, size (bytes), is_image and url, which is GET /attachments/{id} below. |
sent_at / read_at |
string or null | ISO 8601. read_at is set once the recipient has marked the message read. |
curl "https://tcgplein.nl/api/v1/conversations/312/messages?since=2026-10-15T14:00:00Z" \
-H "Authorization: Bearer tcgp_live_K7mP2xQ9vZ4nR8wT3yL6bC1jD5sF0gH7uM2oA9iE"
Download an attachment
GET /attachments/{id}
Streams the photo bytes with its Content-Type, for a participant of the
conversation it belongs to. The same bearer key and messages:read scope apply;
the URL is not public and not signed, so pass the key as on every other call. Add
?download=1 for a Content-Disposition: attachment
response. Attachments on support tickets or disputes are not reachable here and answer
404.
Start a conversation
POST /conversationsOpens a conversation, or returns the existing one for the same pair and anchor. Pass exactly one of:
| Field | Type | Description |
|---|---|---|
listing_id |
integer | Ask another seller about their listing; you become the buyer. Your own listing gets 422. |
seller_id |
integer | A general conversation with a seller, not tied to a listing. The id must belong to a seller (an account with a shop); your own id or any other account gets 422. |
order_id |
integer | The conversation about one of your orders (as seller or as buyer); it merges with the pre-sale chat about the same listing. Someone else's order gets 404. |
Returns the conversation (same shape as GET /conversations/{id}) with
201 when it was just created and 200 when it already
existed. The counterpart does not see it until the first message is sent.
curl -X POST "https://tcgplein.nl/api/v1/conversations" \
-H "Authorization: Bearer tcgp_live_K7mP2xQ9vZ4nR8wT3yL6bC1jD5sF0gH7uM2oA9iE" \
-H "Content-Type: application/json" \
-d '{"order_id": 9012}'
Send a message
POST /conversations/{id}/messages
Text only can be sent as JSON; a message with photos is sent as multipart/form-data.
Either body or at least one attachment is required.
| Field | Type | Description |
|---|---|---|
body |
string | Up to 2000 characters. Leading and trailing whitespace is trimmed. |
attachments[] |
file | Up to 5 photos (jpg, png, webp or gif), each at most 25 MB. JPEG, PNG and WebP are scaled down to 2048 pixels on the longest edge and stored as WebP, so name, mime and size in the response describe the stored file, not your upload. GIFs are stored as uploaded. |
Returns 201 with the created message (same shape as a list row). The other side
gets the website's bell notification and, if they have one registered, a
message.received webhook; the notification is deduplicated per conversation over
five minutes, so a burst of messages produces one bell.
curl -X POST "https://tcgplein.nl/api/v1/conversations/312/messages" \
-H "Authorization: Bearer tcgp_live_K7mP2xQ9vZ4nR8wT3yL6bC1jD5sF0gH7uM2oA9iE" \
-H "Content-Type: application/json" \
-d '{"body": "Ja, de kaart is nog beschikbaar. Ik verstuur morgen."}'
Mark a conversation read
POST /conversations/{id}/read
Marks every message from the other side as read, which clears the conversation's
unread_count and the unread badge on the website and in the app. No body.
curl -X POST "https://tcgplein.nl/api/v1/conversations/312/read" \
-H "Authorization: Bearer tcgp_live_K7mP2xQ9vZ4nR8wT3yL6bC1jD5sF0gH7uM2oA9iE"