Messages and questions
Two different things that look alike.
A message is a private conversation between one buyer and you, about one listing. A question is public: it renders on the product page for every future buyer, and so does your answer. That difference should drive how your UI treats them — an unanswered message costs you one sale, an unanswered question sits on the listing costing you the next several.
Chat protection applies to you too#
Every message you send through this API passes through the same scan a seller typing in the Biklo dashboard goes through. There is no quieter path, and integrating through an ERP does not exempt you.
A message carrying an email address or a phone number is either masked before it is stored or refused outright:
{
"error": {
"code": "blocked_by_chat_protection",
"message": "This message was blocked because it appears to contain contact details.",
"field": "body",
"detected": ["email"],
"request_id": "req_b3lyqbw6i2gxhjy8kclazdi4"
}
}
Two rules for your integration:
- Show your user the
bodywe return, not the one they typed. Whenchat_protection.was_maskedistruethey differ, and the buyer will read ours. - Surface
detectedwhen a message is refused. "Blocked" on its own generates a support ticket; "we cannot send phone numbers in chat" does not.
Take deliveries and tracking off the list of things to work around: order emails already carry everything the buyer needs.
Reading the inbox#
curl "https://sandbox-api.biklo.com/v1/threads?unread_only=true" \
-H "Authorization: Bearer $KEY"
Poll with updated_since, or register the message.received webhook and stop polling.
The webhook fires only for messages from the buyer — your own replies are not echoed
back, so you will not react to your own writes.
A buyer is a display name and an id. There is no email and no phone number here, by design; when you need a delivery address it is on the order, not on the conversation.
Replying#
curl -X POST https://sandbox-api.biklo.com/v1/threads/107373/messages \
-H "Authorization: Bearer $KEY" -H "Content-Type: application/json" \
-d '{"body": "Yes, it is in stock — we ship within 24 hours."}'
Then mark it read:
curl -X POST https://sandbox-api.biklo.com/v1/threads/107373/read \
-H "Authorization: Bearer $KEY"
If you skip this, the seller's own Biklo dashboard keeps showing conversations as unread that your users have already answered — and someone will answer them twice.
Questions#
curl "https://sandbox-api.biklo.com/v1/questions?unanswered_only=true" \
-H "Authorization: Bearer $KEY"
Every response carries unanswered_count, so you can badge the queue without a second
call.
curl -X POST https://sandbox-api.biklo.com/v1/questions/31/answer \
-H "Authorization: Bearer $KEY" -H "Content-Type: application/json" \
-d '{"text": "Size L — suits 180-190 cm."}'
Check is_published. Answers are moderated the same way they are on the site. When
it is false the answer is held for review and is not on the listing yet — say so,
rather than showing it as live and leaving your user to wonder why nobody can see it.
Testing this without buyers#
The sandbox has no buyers, and buyers are the ones who start conversations and ask questions. So the sandbox lends you one:
curl -X POST https://sandbox-api.biklo.com/v1/sandbox/messages \
-H "Authorization: Bearer $KEY" -H "Content-Type: application/json" \
-d '{"product_id": 237233, "body": "Is this still available?"}'
curl -X POST https://sandbox-api.biklo.com/v1/sandbox/questions \
-H "Authorization: Bearer $KEY" -H "Content-Type: application/json" \
-d '{"product_id": 237233, "text": "What frame size is this?"}'
Both write through the real path — chat protection included — so what you build against is what production does. Neither endpoint exists in production.
