In-order messaging built on chat rooms. Every room belongs to an order and has a
type:
ORDER_CLIENT_DRIVER — the customer ↔ assigned-driver chat.ORDER_ADMIN_SUPPORT — the assigned-driver ↔ back-office support thread (the admin
side lives under Admin order support).ORDER_ADMIN_CLIENT_SUPPORT — the client ↔ back-office support thread (same admin
side under Admin order support).All share the same room/message endpoints.
Paths are relative to the /api/v1 prefix; all routes require
Authorization: Bearer {token}.
Flow. First resolve the room for the order (
POST /orders/{orderId}/chat-rooms) to get itsidand real-timechannel, then read and post messages byroomId. Reads never create or mutate anything.
Availability. The client↔driver room opens once the order is paid (
CONFIRMEDonward, throughDELIVERED). Both support rooms (ORDER_ADMIN_SUPPORTandORDER_ADMIN_CLIENT_SUPPORT) are available while the order is active (WAITING_FOR_PAYMENT/CONFIRMEDthroughARRIVED). See the order lifecycle.
Real-time. Every response that resolves or lists a room returns its
channelfield — subscribe to whatever the server returns rather than building the name yourself.
See Common errors.
| Method | URI | Description |
|---|---|---|
POST |
/orders/{orderId}/chat-rooms |
Resolve (create if needed) the order's room of a given type |
GET |
/chat-rooms/{roomId}/messages |
List the room's last 50 messages |
POST |
/chat-rooms/{roomId}/messages |
Send a message |
POST |
/chat-rooms/{roomId}/read |
Mark the room read for the caller |
All chat failures share one envelope: { "error": "ChatError", "message": "…" }.
Resolves the order's chat room of the requested type. Idempotent. Returns the room
id, its channel, and the last 50 messages so the caller can subscribe and render
immediately.
Who may open which type:
type |
Allowed caller |
|---|---|
ORDER_CLIENT_DRIVER |
The order's client or assigned driver |
ORDER_ADMIN_SUPPORT |
The order's assigned driver only |
ORDER_ADMIN_CLIENT_SUPPORT |
The order's client only |
Request body
| Field | Type | Required | Rules |
|---|---|---|---|
type |
string | yes | ORDER_CLIENT_DRIVER, ORDER_ADMIN_SUPPORT, or ORDER_ADMIN_CLIENT_SUPPORT |
Response 200 (client↔driver room)
{
"id": 7,
"type": "ORDER_CLIENT_DRIVER",
"channel": "chat.rooms.7",
"messages": [
{
"id": 31,
"order_id": 42,
"sender_id": 1,
"sender_role": "CLIENT",
"sender_name": "Juan",
"content": "Estoy en el portĂłn.",
"type": "TEXT",
"created_at": "2026-06-26T12:31:00+00:00"
}
]
}
A support room (ORDER_ADMIN_SUPPORT or ORDER_ADMIN_CLIENT_SUPPORT) additionally
returns the assignment status:
{
"id": 12,
"type": "ORDER_ADMIN_SUPPORT",
"channel": "chat.rooms.12",
"status": { "state": "pool", "assigned_admin": null },
"messages": []
}
status.state is pool (waiting for an admin) or claimed (an admin is handling it —
then assigned_admin is { "id", "name" }).
Errors
| Status | Body | When |
|---|---|---|
404 |
{ "error": "ChatError", "message": "Pedido no encontrado." } |
Unknown order |
403 |
{ "error": "ChatError", "message": "No perteneces a este chat." } |
Caller may not open this room type for the order |
400 |
{ "error": "ChatError", "message": "Chat no disponible para esta orden." } |
Client↔driver room requested before the order is paid |
400 |
{ "error": "ChatError", "message": "Soporte no disponible para esta orden." } |
Support room requested while the order is not active (and none exists yet) |
422 |
ValidationError |
Missing/invalid type |
Returns the room's channel plus the last 50 messages, oldest first. Read-only;
requires the caller to be an active member of the room. sender_role is the emitter's
role in the room (CLIENT, DRIVER, or ADMIN).
Response 200
{
"channel": "chat.rooms.7",
"messages": [
{
"id": 31,
"order_id": 42,
"sender_id": 1,
"sender_role": "CLIENT",
"sender_name": "Juan",
"content": "Estoy en el portĂłn.",
"type": "TEXT",
"created_at": "2026-06-26T12:31:00+00:00"
}
]
}
Errors
| Status | Body | When |
|---|---|---|
404 |
{ "error": "ChatError", "message": "Chat no encontrado." } |
Unknown room |
403 |
{ "error": "ChatError", "message": "No perteneces a este chat." } |
Caller is not an active member |
Sends a message to the room. Requires active membership and that the order is still in the room's messaging window (paid for a client↔driver room, active for a support room).
Request body
| Field | Type | Required | Rules |
|---|---|---|---|
content |
string | yes | 1–500 chars |
Response 201
{
"message": {
"id": 32,
"order_id": 42,
"sender_id": 3,
"sender_role": "DRIVER",
"sender_name": "Pedro",
"content": "Voy llegando.",
"type": "TEXT",
"created_at": "2026-06-26T12:32:00+00:00"
},
"channel": "chat.rooms.7"
}
Errors
| Status | Body | When |
|---|---|---|
404 |
{ "error": "ChatError", "message": "Chat no encontrado." } |
Unknown room |
403 |
{ "error": "ChatError", "message": "No perteneces a este chat." } |
Caller is not an active member |
400 |
{ "error": "ChatError", "message": "Chat no disponible para esta orden." } |
Client↔driver order not paid |
400 |
{ "error": "ChatError", "message": "Soporte no disponible para esta orden." } |
Support order no longer active |
Marks the room read up to now for the calling member.
Response 200
{ "read_at": "2026-06-26T12:33:00+00:00" }
Errors — same 404 / 403 as the message endpoints.
# Resolve the client↔driver room for order 42
curl -X POST /api/v1/orders/42/chat-rooms \
-H "Authorization: Bearer {token}" \
-H "Content-Type: application/json" \
-d '{"type": "ORDER_CLIENT_DRIVER"}'
# Read the conversation (roomId from the resolve response)
curl /api/v1/chat-rooms/7/messages -H "Authorization: Bearer {token}"
# Send a message
curl -X POST /api/v1/chat-rooms/7/messages \
-H "Authorization: Bearer {token}" \
-H "Content-Type: application/json" \
-d '{"content": "Voy llegando."}'
# Open the support room for order 42 (driver token)
curl -X POST /api/v1/orders/42/chat-rooms \
-H "Authorization: Bearer {driver_token}" \
-H "Content-Type: application/json" \
-d '{"type": "ORDER_ADMIN_SUPPORT"}'