Skip to main content
General - Chatrooms API Documentation and Postman Guide.
This page covers the endpoints of ChatroomController (rules and JSON built by app/Services/ChatroomService.php). A chatroom is the group chat of a classroom: it has the classroom’s slug, but it is its own entity with its own tables, so ClassroomController never creates or edits it (it only deletes it along with a permanently deleted classroom, section 5). The apps create it right after the classroom (classroom stepper, step 2). All endpoints run behind auth:sanctum, the “user is not blocked” check, and the per-company policy middleware. Real-time events go through Laravel Reverb (Pusher protocol); when no WebSocket server is configured the apps poll the messages endpoint instead.

Table of Contents

  1. Base URL & Headers
  2. Who Can Do What
    • 2.1 Roles in a Chatroom
    • 2.2 The Chatroom Object
    • 2.3 The Message Object
    • 2.4 Database Tables
  3. Endpoints
    • 3.1 List Chatrooms
    • 3.2 Create a Chatroom
    • 3.3 Show a Chatroom
    • 3.4 Update a Chatroom (title, members, slug)
    • 3.5 List Messages
    • 3.6 Upload an Image
    • 3.7 Send a Message
    • 3.8 Open / Close the Chat to Learners
    • 3.9 Wipe the History
    • 3.10 Real-time Configuration
    • 3.11 Back Up a Conversation to R2 (admins)
    • 3.12 Delete a Chatroom (admins)
  4. Real Time (Laravel Reverb)
    • 4.1 Channel Authorization
    • 4.2 Events
    • 4.3 Server Setup
  5. Notifications and Clean-up
  6. Postman Setup

1. Base URL & Headers

{slug} is the classroom slug. A slug may contain (, ), [, ], ,, : or =: URL-encode it in the path (chatrooms/cloud%5B2026%5D).

2. Who Can Do What

2.1 Roles in a Chatroom

Anyone else gets 403. Creating a chatroom (3.2) is open to super-admins, admins, content managers and instructors (an instructor only for a chatroom they lead). Members always belong to the chatroom’s company.

2.2 The Chatroom Object

2.3 The Message Object

2.4 Database Tables

Four tables, created by the migration 2026_10_20_000000_create_chatrooms_tables. Solid lines are foreign keys; dotted lines are links the code keeps without a foreign key. What each link means in practice:
  • chatrooms.slug = classrooms.classroom_slug. Both columns are unique on their own and there is no foreign key between them: the chatroom is a separate entity. The apps pass the classroom’s slug on create and send the new one after a classroom rename (3.4). A generated classroom slug skips the slugs already held by a chatroom, and permanently deleting a classroom deletes its chatroom. An admin can also delete a chatroom on its own (3.12): the classroom stays, and its slug is free for a new chatroom.
  • Members. One chatroom_members row per person, role = instructor or learner; chatrooms.instructor_id always names the same user as the instructor row. Company admins and super-admins read every chatroom of their company without a row.
  • Deleting a user keeps their messages (sender_id becomes null, shown as a deleted account) and removes their member rows.
  • last_notification_id points at the member’s latest unread “new message” notification, so a burst of messages makes one notification (section 5).
  • Admin CSV backups (3.11) are recorded in the files table like any other R2 file, without a link to the chatroom, so they stay when the chatroom is deleted (3.12).
An image’s rows over its life:

3. Endpoints

3.1 List Chatrooms

GET /chatrooms The admin table (/admin-chatrooms), the instructor table (/instructor-chatrooms) and the student’s Chatrooms tab. Scoped by role: a super-admin sees every chatroom (optionally one company), an admin every chatroom of their company (member or not), anyone else the chatrooms they lead or belong to. Query parameters Example request
Responses
✅ 200 OK (admin caller)

3.2 Create a Chatroom

POST /chatrooms Called by the classroom stepper (step 2, “Create chatroom”) once the classroom exists. Members are given by e-mail and resolved to users of the caller’s company (super-admins may pass company_id). E-mails are trimmed and lower-cased, duplicates are ignored, and the instructor’s e-mail in student_emails is dropped. Body parameters Example request
Responses
✅ 201 Created: the chatroom as in 3.3 (with members).

3.3 Show a Chatroom

GET /chatrooms/{slug} The chatroom (2.2) with its members. A 404 means the classroom has no chatroom yet; the stepper reads it that way. Members, company admins and content managers may call it.
✅ 200 OK: same body as the 201 of 3.2. A student caller gets no email in instructor or members, and viewer { "role": "learner", "can_post": true, "can_moderate": false }.

3.4 Update a Chatroom (title, members, slug)

PUT /chatrooms/{slug} Sent by the apps after a classroom is saved, so the chatroom keeps the classroom’s title, members and slug. An omitted key is kept; student_emails replaces the learners ([] removes them all). The chatroom’s instructor may change the learners (and the title / slug) but not hand the chatroom to another instructor (403); admins and content managers may change everything. A removed member loses access at once (REST 403). Body parameters
✅ 200 OK: the chatroom as in 3.3. Broadcasts chatroom.updated (4.2).

3.5 List Messages

GET /chatrooms/{slug}/messages Keyset pagination, oldest first in the response. With no parameter: the newest page. before={id}: the page before that message (scrolling up). after={id}: everything after it, oldest first (catching up after a reconnect, or polling when no WebSocket is open; after=0 = from the start). Members and company admins only. Query parameters Example request
✅ 200 OK

3.6 Upload an Image

POST /chatrooms/{slug}/attachments The paperclip: one image per request, sent later with a message (3.7). Only JPG, PNG, GIF and WEBP up to 20 MB (checked on the file’s content, not its name; no SVG). Stored on Cloudflare R2 under files/all-chatrooms/{slug}/ (since 01/11/2026, app/Services/R2StoragePathService.php; older images stay under chatrooms/{slug}/). Requires the right to post (a learner gets 403 while the chat is closed to learners). An upload that is never sent is deleted after a day (section 5). Body: multipart/form-data with the field image.
✅ 201 Created
The server must accept 20 MB request bodies: PHP upload_max_filesize / post_max_size and nginx client_max_body_size (4.3).

3.7 Send a Message

POST /chatrooms/{slug}/messages Text and / or up to 4 images uploaded with 3.6. At least one of the two is required. The images must be the sender’s own, of this chatroom, and not sent yet. On success the message is broadcast (message.sent, 4.2) to everyone else (send the X-Socket-ID header so the sender’s own socket is skipped), and a message from the instructor or an admin notifies the learners (section 5). A failed broadcast never fails the request. Headers: optional X-Socket-ID: {{socket_id}} (from the WebSocket connection). Body parameters
✅ 201 Created: the message (2.3).

3.8 Open / Close the Chat to Learners

POST /chatrooms/{slug}/learners-can-post Instructor and admins. false = only the instructor and the admins can post (learners still read). Broadcast as chatroom.updated, so learners’ composers lock without a reload.
✅ 200 OK: the chatroom as in 3.3, "learners_can_post": false.

3.9 Wipe the History

DELETE /chatrooms/{slug}/messages Instructor and admins. Deletes every message and every image of the chatroom (the R2 objects too), for everyone. Broadcast as chatroom.cleared.
This is a hard delete: there is no archive and no undo. The apps ask for confirmation first.
✅ 200 OK

3.10 Real-time Configuration

GET /realtime/config Where the apps open their WebSocket. enabled: false (broadcast driver log / null, or missing keys) makes them poll 3.5 with after every 5 seconds instead. The key is the public Reverb app key; the secret is never returned.
✅ 200 OK (Reverb configured)
Without a WebSocket server: { "enabled": false }.

3.11 Back Up a Conversation to R2 (admins)

POST /chatrooms/{slug}/backup Admins of the chatroom’s company and super-admins only (instructors and learners get 403). Writes the whole history, oldest first, to a CSV file on Cloudflare R2 and records it as a files row (type 3, so it shows with the other R2 files). Each call makes a new file; nothing is deleted. Where: files/backups/conversations/{slug}/{adminId}_{Ymd-His}_{random}.csv, for example files/backups/conversations/cloud-101/5_20261020-101500_ab12cd34.csv (Tunis time; adminId = the admin running the backup; before 01/11/2026: files/conversations/{slug}-{Ymd-His}-{random}.csv). The 8 random characters keep the file’s address from being guessed; share the url only with people allowed to read the conversation. File: UTF-8 with a byte-order mark (Excel shows Arabic and accents), comma-separated, RFC 4180 quoting, one row per message: A cell that starts with =, +, - or @ gets a leading ', so a spreadsheet never runs it as a formula. Example request
✅ 201 Created
In the apps: Back up to the cloud (CSV) in the Chatrooms table’s row menu and in the conversation page’s details panel (admins only), then a dialog with the path and an “Open the file” link.

3.12 Delete a Chatroom (admins)

DELETE /chatrooms/{slug} Admins of the chatroom’s company and super-admins only (since 04/11/2026). The instructor and the learners get 403: they can wipe the history (3.9), not delete the chatroom. Deletes the chatroom with its members, every message and every image (the R2 objects too), like permanently deleting its classroom does (section 5). Broadcast as chatroom.deleted (4.2). What stays:
  • The classroom. Its slug is free again, so 3.2 can create a new chatroom for it.
  • The CSV backups of 3.11 (files/backups/conversations/{slug}/): back the conversation up first if you want to keep it.
  • The “new message” notifications the learners already received.
This is a hard delete: there is no archive and no undo. The apps ask the admin to type “delete” first.
Example request
✅ 200 OK
In the apps: Delete chatroom in the Chatrooms table’s row menu and in the conversation page’s details panel (admins only). A 404 also takes the row out of the table, since the chatroom is gone either way. A backend older than 04/11/2026 has no such route and answers 405; the apps then say the server cannot delete chatrooms yet, and nothing is deleted.

4. Real Time (Laravel Reverb)

The apps connect to wss://{host}:{port}/app/{key}?protocol=7 (Pusher protocol v7), subscribe to private-chatroom.{id} (the chatroom id: slugs may hold characters channel names reject), and answer the server’s pings. The Flutter client is lib/core/services/realtime/realtime_service.dart (web_socket_channel, same code on web, Android and iOS); it reconnects with backoff and catches up with 3.5 after after every (re)subscription.

4.1 Channel Authorization

POST /broadcasting/auth (Sanctum bearer token, not-blocked check) Signs a private-channel subscription for members of the chatroom (instructor, learners) and the company’s admins / super-admins. Body: socket_id (from pusher:connection_established) and channel_name.
✅ 200 OK

4.2 Events

All on private-chatroom.{id}; data is the JSON below. Events are sent at once (ShouldBroadcastNow, no queue needed). Laravel reaches Reverb with a 3 s connect / 5 s total timeout; if Reverb is down the request still succeeds and the error is only reported.

4.3 Server Setup

The step-by-step guide with every command is Chatrooms Setup Guide (the backend repository’s CHATROOMS.md holds the same steps). No Redis and no queue worker are needed: Reverb only uses Redis when several Reverb processes share events (REVERB_SCALING_ENABLED), and the chat events are sent during the request. In short:
  1. Environment (backend .env, one Reverb app per environment):
    REVERB_HOST / PORT / SCHEME are where Laravel posts events (the local Reverb process); REVERB_CLIENT_* are what 3.10 tells the apps to open (the public host behind nginx). Then php artisan optimize.
  2. Keep php artisan reverb:start running under supervisor or systemd, as the site’s user, restarted automatically; run php artisan reverb:restart after each deploy.
  3. Proxy the WebSocket through nginx (Plesk: the backend domain → Apache & nginx Settings → Additional nginx directives). The box is one field for the whole vhost, so the /app/ block goes next to the upload limit of point 4 and the existing /storage/ CORS block; they do not conflict. The full box:
    ^~ keeps Plesk’s pattern-based locations from taking the request first. The check (101 Switching Protocols = working, Laravel’s 404 = block missing, 502 = Reverb not running) is in Chatrooms server setup.
  4. Allow 20 MB uploads: PHP upload_max_filesize = 25M, post_max_size = 25M, nginx client_max_body_size 25m;.
  5. Run the scheduler every minute (php artisan schedule:run) for the daily clean-up of section 5.
  6. Check: 3.10 answers enabled: true, and a message sent in one browser shows up at once in another.

5. Notifications and Clean-up

  • “New message in chatroom X”: a message from the instructor or an admin creates an in-app notification (type 8, Announcement; never an e-mail) for each learner, in the sender’s language. A learner whose previous chatroom notification is still unread gets no new one, so a burst of messages is one notification. They show in the usual notification list (Notifications).
  • Unsent images: php artisan chatrooms:prune-attachments (daily, app/Console/Kernel.php) deletes uploads that were never sent with a message after 24 hours (--hours= to change), file included.
  • Classroom life cycle: archiving a classroom does not touch its chatroom. Permanently deleting a classroom (DELETE /classroom/{id}/force_delete) deletes its chatroom too since 20/10/2026: members, messages and images. The other way round, deleting a chatroom (3.12) never touches its classroom.

6. Postman Setup

Environment variables Collection headers
Bodies are raw JSON, except 3.6 (form-data, image as a file). Smoke-test flow
  1. As an admin, create a classroom, then its chatroom with two learners’ e-mails (3.2): 201, learners_count: 2.
  2. As a learner, list (3.1): one chatroom, viewer.role: "learner", no email anywhere.
  3. As the instructor, send a message (3.7); as the learner, read it (3.5) and see the “New message in chatroom …” notification.
  4. As the learner, upload an image (3.6) and send it with a text (3.7).
  5. As the instructor, close the chat (3.8 false); as the learner, sending answers 403.
  6. As the admin, back the conversation up (3.11): 201, a files/backups/conversations/cloud-101/….csv path; open the url.
  7. As the admin, wipe the history (3.9): deleted_messages: 3; 3.5 answers an empty data.
  8. As the instructor, delete the chatroom (3.12): 403. As the admin: 200; 3.3 now answers 404, and 3.2 with the same slug creates a new chatroom (201).