Biklo for developersTESTsandbox-api.biklo.comchange

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:

  1. Show your user the body we return, not the one they typed. When chat_protection.was_masked is true they differ, and the buyer will read ours.
  2. Surface detected when 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.