Iraq Soft — QueueBridge Platform Hotline: 6554 WhatsApp: +964 772 228 4111
API Version 1.0 · Last updated 2026-05-19
Production-ready — REST + SignalR — .NET 6

QueueBridge API Reference
Build your integration in hours, not weeks.

Complete integration documentation for the QueueBridge Smart Queue Management System. Every REST endpoint, every real-time SignalR event, every data model — mapped to the exact source of truth in the running server. This document is the contract between your application and our queue platform.

URL
Base URL
http://localhost:5050
AUTH
Authentication
Bearer Token
RT
Realtime
SignalR /queueHub
API
Endpoints
62 · 9 resources
01 · Introduction

What is QueueBridge?

QueueBridge is a smart queue-management platform for hospitals, banks, ministries, and service centers. It coordinates four kinds of clients in real time: a kiosk where customers take tickets, an employee portal where staff call and complete tickets, large-screen displays in the waiting area, and an admin dashboard for operators.

This document covers everything an integration partner needs to embed QueueBridge into another product: how to authenticate, the full ticket lifecycle, the 62 REST endpoints exposed across 9 resource groups, and the SignalR real-time channel that broadcasts every state change as it happens.

System surface

ComponentDetail
StackASP.NET Core 6.0 · C# · SignalR · JSON file persistence
Default base URLhttp://localhost:5050 — configurable; production deployments typically expose behind a reverse proxy.
SignalR hub/queueHub — WebSocket transport, also reachable as ws://<host>/queueHub
Content typeapplication/json; charset=utf-8
JSON casingcamelCase — configured globally in Program.cs
Auth schemeBearer token (custom Base64 — see Authentication)
i18nServer error messages are returned in Arabic for end-user display. Partners should treat them as opaque strings and translate as needed.
Interactive SwaggerAvailable in development at /swagger

Conceptual model

Three nouns drive every workflow. Departments are top-level service categories (e.g. "Cardiology"). Each department has one or more Sub-Departments — the actual service desks (e.g. "Dr. Ali, Room 12"). Customers take Tickets against a sub-department. Each ticket carries a unique displayNumber like A-H-001 (department prefix · sub-department prefix · daily sequence), and moves through a fixed state machine documented in Ticket Lifecycle.

02 · Onboarding

Quick Start

Three calls to verify your integration is wired correctly. Replace localhost:5050 with the production host once provisioned.

1

Authenticate

Exchange username + password for a bearer token. The token embeds the user id, role, and a 24-hour expiry.

2

Read public data

Pull the departments visible to kiosk clients — no authentication required, ideal for a smoke test.

3

Subscribe to events

Connect to the SignalR hub to receive every ticket state change in real time. No polling.

Step 1 — Log in

# Returns: { user: { id, username, name, role, ... }, token: "<base64>" }
curl -X POST http://localhost:5050/api/auth/login \
  -H "Content-Type: application/json" \
  -d '{"username":"admin","password":"admin123"}'

Step 2 — Read kiosk departments

# Public — returns only departments where isActive AND showOnKiosk are true
curl http://localhost:5050/api/departments/kiosk

Step 3 — Subscribe to real-time updates

SignalR cannot be exercised with cURL — it requires the official @microsoft/signalr client. The full reference is at SignalR Hub; the snippet below is the minimum to verify connectivity.

// npm i @microsoft/signalr
import * as signalR from "@microsoft/signalr";

const connection = new signalR.HubConnectionBuilder()
  .withUrl("http://localhost:5050/queueHub")
  .withAutomaticReconnect()
  .build();

connection.on("TicketCreated", (ticket) => {
  console.log("New ticket:", ticket.displayNumber);
});

await connection.start();
await connection.invoke("JoinDisplay");
03 · Security

Authentication

QueueBridge uses a bearer-token scheme. The token is opaque to the client — treat it as a string. Tokens last 24 hours from the moment of issue.

Token format

The server issues a Base64-encoded string of the form userId:role:expiryTicks — partners do not need to decode it. Just store it from the login response and send it back on each request:

Authorization: Bearer eyJ1c2VySWQiOi...<token>

Roles and permissions

QueueBridge has two roles — admin and user — plus six granular permission codes. Admins implicitly hold every permission. The default permission set assigned to new user accounts is transfer.execute + department.lock.

Permission codeWhat it allows
transfer.executeTransfer tickets between sub-departments.
department.lockTemporarily close / reopen a sub-department.
ticket.view_without_callInspect a ticket without changing its state.
ticket.edit_numberManually edit a ticket's sequence number.
voice.generateTrigger voice-announcement endpoints (TTS).
user.manageCreate, update, and delete users.
Server-side enforcement

The current build returns tokens on login and exposes GET /api/auth/verify for validation, but per-endpoint authorization filters are not applied at the controller layer. Partners are encouraged to attach the Authorization header on every authenticated request — future versions will enforce it without API changes.

Login — full reference

See Auth & Users below for the endpoint signature. Expired tokens return 401 Unauthorized with the body { "error": "انتهت صلاحية الجلسة" } (English: "Session expired").

04 · Concept

Ticket Lifecycle

Every ticket lives in exactly one of seven states. The diagram below is the canonical state machine — every REST endpoint that mutates a ticket is labelled on the transition it triggers.

Figure 1 · Ticket state machine
stateDiagram-v2 direction LR [*] --> Waiting : POST /api/tickets Waiting --> Called : PUT /{id}/call Waiting --> Postponed : PUT /{id}/postpone Waiting --> Cancelled : (admin) Called --> Called : PUT /{id}/recall Called --> InProgress : PUT /{id}/start Called --> Completed : PUT /{id}/complete Called --> Transferred : PUT /{id}/transfer Called --> Postponed : PUT /{id}/postpone InProgress --> Completed : PUT /{id}/complete InProgress --> Transferred : PUT /{id}/transfer Postponed --> Waiting : PUT /{id}/reactivate Transferred --> [*] Completed --> [*] Cancelled --> [*]

State reference

EnumNumericMeaning
Waiting0Ticket issued; waiting to be called.
Called1An employee has called the ticket; customer is on their way to the counter.
InProgress2Service has started; the customer is at the counter.
Completed3Service finished. isSuccess distinguishes successful vs. failed outcomes.
Transferred4Ticket moved to a different sub-department; a new ticket may have been issued at the destination.
Postponed5Customer asked to wait; ticket can be reactivated later.
Cancelled6Ticket cancelled by an operator.
Important: JSON encoding

The status field is serialized as its integer value by default (e.g. 0 for Waiting). The reporting endpoints additionally emit a parallel statusAr field with the Arabic display label.

05 · Integration patterns

Core Workflows

Four end-to-end scenarios that cover ~95% of real integrations. Each diagram shows the wire-level interaction between client, REST API, and SignalR. The cURL playbook below each diagram lets you reproduce the workflow from the command line.

Workflow 1 — Customer takes a ticket

Sequence · Kiosk → API → Display
sequenceDiagram autonumber actor C as Customer participant K as Kiosk participant API as QueueBridge API participant DB as Storage participant HUB as SignalR Hub participant D as Display Screen C->>K: Tap "Cardiology > Dr. Ali" K->>API: POST /api/tickets {departmentId, subDepartmentId} API->>DB: CreateTicket() — increments daily sequence API->>HUB: emit "TicketCreated" HUB-->>D: TicketCreated event API-->>K: 200 {ticket, companyName, waitingBefore, ...} K->>K: Print thermal receipt K-->>C: Hand ticket A-H-007

Playbook

# 1. Issue a ticket
curl -X POST http://localhost:5050/api/tickets \
  -H "Content-Type: application/json" \
  -d '{"departmentId":"dept-123","subDepartmentId":"subdept-456"}'

# Response 200:
# { "ticket": { "id":"...", "displayNumber":"A-H-007", "status":0, ... },
#   "companyName":"...", "departmentName":"...", "roomNumber":"12", "waitingBefore":6 }

Workflow 2 — Employee calls the next ticket

Sequence · Portal → API → Display (with audio)
sequenceDiagram autonumber actor E as Employee participant P as Portal participant API as QueueBridge API participant HUB as SignalR Hub participant D as Display Screen P->>API: GET /api/tickets/waiting?subDeptId=... API-->>P: [Ticket, Ticket, ...] E->>P: Click "Call A-H-007" P->>API: PUT /api/tickets/{id}/call {userId, userName} API->>API: Build Arabic announcement (audioSpeech) API->>HUB: emit "TicketCalled" {ticket, roomNumber, audioSpeech} HUB-->>D: TicketCalled event D->>D: Speak announcement + flash ticket API-->>P: 200 {ticket: {status:1, calledAt:...}}

Playbook

# 1. Fetch the next waiting ticket(s)
curl "http://localhost:5050/api/tickets/waiting?subDeptId=subdept-456"

# 2. Call a specific ticket
curl -X PUT http://localhost:5050/api/tickets/ticket-789/call \
  -H "Content-Type: application/json" \
  -d '{"userId":"user-1","userName":"Ahmed"}'

# 3. (Optional) Re-announce
curl -X PUT http://localhost:5050/api/tickets/ticket-789/recall \
  -H "Content-Type: application/json" \
  -d '{"userId":"user-1","userName":"Ahmed"}'

# 4. Mark service started when the customer arrives
curl -X PUT http://localhost:5050/api/tickets/ticket-789/start \
  -H "Content-Type: application/json" -d '{}'

Workflow 3 — Transfer to another sub-department

Sequence · Transfer keeps audit trail
sequenceDiagram autonumber actor E as Employee participant P as Portal participant API as QueueBridge API participant HUB as SignalR Hub E->>P: Pick "Transfer to Pharmacy" P->>API: PUT /api/tickets/{id}/transfer
{newDepartmentId, newSubDepartmentId, note, keepSameNumber} API->>API: Append TicketHistory entry
set transferredFromSubDeptId API->>HUB: emit "TicketTransferred" API->>HUB: emit "QueueUpdated" (source) API->>HUB: emit "QueueUpdated" (target) API-->>P: 200 {updated ticket}

Playbook

curl -X PUT http://localhost:5050/api/tickets/ticket-789/transfer \
  -H "Content-Type: application/json" \
  -d '{
    "newDepartmentId":"dept-999",
    "newSubDepartmentId":"subdept-pharmacy-1",
    "note":"Needs prescription pickup",
    "keepSameNumber": false
  }'

Workflow 4 — Complete and report

Sequence · Completion drives reporting
sequenceDiagram autonumber actor E as Employee participant P as Portal participant API as QueueBridge API participant HUB as SignalR Hub participant R as Reporting Client E->>P: Click "Complete" P->>API: PUT /api/tickets/{id}/complete {isSuccess, note} API->>API: Set completedAt + close ticket API->>HUB: emit "TicketCompleted" API->>HUB: emit "QueueUpdated" API-->>P: 200 {ticket} Note over R,API: Later — analytics R->>API: GET /api/reports/daily-summary API-->>R: { totalTickets, completedTickets, avgServiceTime, successRate, ... }

Playbook

# 1. Complete with success
curl -X PUT http://localhost:5050/api/tickets/ticket-789/complete \
  -H "Content-Type: application/json" \
  -d '{"isSuccess":true,"note":"Prescribed antibiotics"}'

# 2. Pull today's KPIs
curl http://localhost:5050/api/reports/daily-summary

# 3. Drill down by sub-department
curl "http://localhost:5050/api/reports/subdepartments?fromDate=2026-05-19&toDate=2026-05-19"
06 · REST API

Auth & Users

Login, token verification, and user management. 7 endpoints.

POST /api/auth/login

Exchange username + password for a bearer token + user profile.

FieldTypeRequiredNotes
usernamestringREQUIREDCase-sensitive. Default seed: admin.
passwordstringREQUIREDPlain text over the wire — deploy behind HTTPS in production.
curl -X POST http://localhost:5050/api/auth/login \
  -H "Content-Type: application/json" \
  -d '{"username":"admin","password":"admin123"}'

200 OK

{
  "user": {
    "id": "a3f1...",
    "username": "admin",
    "name": "System Admin",
    "role": "admin",
    "assignedSubDepartmentIds": [],
    "permissions": []
  },
  "token": "YTNmMTpsYWRtaW46NjM4NDg..."
}

401 Unauthorized — invalid credentials

{ "error": "اسم المستخدم أو كلمة المرور غير صحيحة" }
GET /api/auth/verify

Validate a bearer token and return the associated user.

HeaderRequiredFormat
AuthorizationREQUIREDBearer <token>
curl http://localhost:5050/api/auth/verify \
  -H "Authorization: Bearer YTNmMTpsYWRtaW46..."

200 OK Returns { user: QueueUser }

401 Unauthorized Missing, malformed, or expired token. Expired returns body { "error": "انتهت صلاحية الجلسة" }.

GET /api/users

List all users. The response enriches assignedSubDepartmentNames from sub-department records for convenience.

curl http://localhost:5050/api/users

200 OKQueueUser[]. See Data Models.

GET /api/users/{id}

Get a single user by id.

ParamTypeNotes
idstring (GUID)User id.

200 OK QueueUser

404 Not Found

POST /api/users

Create a new user. Returns the created user with a generated id.

Send a QueueUser — see data model. Minimum fields: username, password, name, role.

{
  "username": "reception_1",
  "password": "secret",
  "name": "Reception Desk 1",
  "role": "user",
  "roomNumber": "R-12",
  "assignedSubDepartmentIds": ["subdept-456"],
  "permissions": ["transfer.execute", "department.lock"],
  "isActive": true
}
curl -X POST http://localhost:5050/api/users \
  -H "Content-Type: application/json" \
  -d '{"username":"u1","password":"p","name":"User One","role":"user"}'

201 Created Created user with Location header.

400 Bad Request Invalid input — e.g. employee limit reached per license.

409 Conflict Duplicate username.

PUT /api/users/{id}

Update an existing user. Empty password preserves the existing one.

200 OK Updated user. 404. 409 Conflict duplicate username.

DELETE /api/users/{id}

Delete a user. The system refuses to delete the last remaining admin.

204 No Content

400 Bad Request{ "error": "لا يمكن حذف آخر مدير" } ("Cannot delete the last admin").

Tickets

The core resource. 19 endpoints covering issue, query, call, complete, transfer, postpone, reactivate, and reporting.

POST /api/tickets

Issue a new ticket against a sub-department. Increments the daily sequence and emits TicketCreated.

FieldTypeRequiredNotes
departmentIdstringREQUIREDParent department id.
subDepartmentIdstringREQUIREDSub-department to issue against. Rejected with 423 if closed.
curl -X POST http://localhost:5050/api/tickets \
  -H "Content-Type: application/json" \
  -d '{"departmentId":"dept-123","subDepartmentId":"subdept-456"}'

200 OK Print-ready envelope:

{
  "ticket": {
    "id": "7d18...",
    "referenceNumber": "QT-20260519-00000007",
    "displayNumber": "A-H-007",
    "sequenceNumber": 7,
    "status": 0,
    "createdAt": "2026-05-19T10:30:00Z"
    // ... full Ticket model
  },
  "companyName": "Medical Center",
  "slogan": "Your health, our priority",
  "logo": "data:image/png;base64,...",
  "departmentName": "Cardiology",
  "subDepartmentName": "Dr. Ali",
  "roomNumber": "12",
  "waitingBefore": 6
}

423 Locked Sub-department is temporarily closed:

{
  "error": "هذا القسم مغلق مؤقتاً",
  "isClosed": true,
  "closedUntil": "2026-05-19T11:15:00Z",
  "remainingMinutes": 12,
  "reason": "Lunch break"
}

400 Bad Request Validation error — e.g. unknown sub-department.

GET/api/tickets

List every ticket ever issued. For large deployments prefer the filtered endpoints below.

curl http://localhost:5050/api/tickets

200 OK Ticket[]

GET/api/tickets/{id}

Fetch a single ticket including its full history audit trail.

200 OK Ticket · 404

GET/api/tickets/waiting

All tickets in Waiting state, optionally filtered by sub-department.

ParamTypeRequiredNotes
subDeptIdstringOPTIONALWhen provided, returns only tickets for this sub-department.
curl "http://localhost:5050/api/tickets/waiting?subDeptId=subdept-456"
GET/api/tickets/waiting/count/{subDeptId}

Lightweight count for kiosks — how many people are ahead.

{ "count": 14 }
GET/api/tickets/postponed

Tickets currently in Postponed state.

subDeptId optional — filter by sub-department.

GET/api/tickets/waiting/multi

Multi-sub-department waiting list for portals that serve several desks.

ParamTypeNotes
idsstringComma-separated list of sub-department ids. ?ids=a,b,c
GET/api/tickets/postponed/multi

Same as /waiting/multi but for postponed tickets.

GET/api/tickets/subdepartment/{subDeptId}

All tickets for a sub-department, regardless of state.

PUT/api/tickets/{id}/call

Call a ticket. Builds an Arabic audio-announcement string server-side and broadcasts TicketCalled to all listeners (including displays for voice playback).

FieldTypeNotes
userIdstringEmployee calling the ticket. Used as fallback for room number.
userNamestringDisplay name — recorded in history.
curl -X PUT http://localhost:5050/api/tickets/<id>/call \
  -H "Content-Type: application/json" \
  -d '{"userId":"user-1","userName":"Ahmed"}'

200 OK Updated Ticket with status=1 and calledAt set. 404 ticket not found.

SignalR event emitted: TicketCalled with payload { ticket, roomNumber, audioSpeech }.

PUT/api/tickets/{id}/recall

Re-announce a previously called ticket without changing its status. Useful when the customer didn't appear.

Same as /call: { userId, userName }.

PUT/api/tickets/{id}/start

Mark service as actively in progress (the customer arrived at the counter).

Optional { "note": "..." } or empty body.

Emits TicketStarted with the updated ticket.

PUT/api/tickets/{id}/complete

Close the ticket. Sets completedAt, isSuccess, and completionNote.

FieldTypeNotes
isSuccessboolDefault true. Set false for unsuccessful outcomes (counted separately in reports).
notestring?Free-form completion note.

Emits TicketCompleted then QueueUpdated for the sub-department.

PUT/api/tickets/{id}/transfer

Move a ticket to a different sub-department. Records the source in transferredFromSubDeptId / transferredFromDeptId.

FieldTypeNotes
newDepartmentIdstringDestination department.
newSubDepartmentIdstringDestination sub-department.
notestring?Reason for transfer.
keepSameNumberboolDefault false. When true, the ticket retains its existing displayNumber at the destination.

Emits TicketTransferred, plus two QueueUpdated events (source + destination).

PUT/api/tickets/{id}/update-number

Manually override a ticket's sequence number. Requires permission ticket.edit_number.

{
  "newSequenceNumber": 42,
  "userId": "user-1",
  "userName": "Ahmed"
}

Emits TicketNumberUpdated and QueueUpdated.

PUT/api/tickets/{id}/request-service

A specialized transfer: completes the current ticket and creates a new one in the destination sub-department, preserving continuity for the customer. Returns both tickets in the response.

{
  "newDepartmentId": "dept-pharmacy",
  "newSubDepartmentId": "subdept-pharmacy-1",
  "note": "Pickup prescription",
  "userId": "user-1",
  "userName": "Ahmed"
}
{
  "completed": { /* old ticket, status=3 */ },
  "created":   { /* new ticket, status=0 */ }
}

Emits TicketCompleted, QueueUpdated, TicketCreated, QueueUpdated in sequence.

PUT/api/tickets/{id}/postpone

Move a ticket to Postponed state. Customer may return later and be reactivated.

Optional { "note": "..." }.

Emits TicketPostponed and QueueUpdated.

PUT/api/tickets/{id}/reactivate

Return a postponed ticket to the Waiting queue.

Emits QueueUpdated for the sub-department.

GET/api/tickets/report/daily

Quick daily snapshot of tickets — lighter than the full Reports endpoints.

date optional — ISO date string. Defaults to today.

Departments

Top-level service categories. 8 endpoints.

GET/api/departments

All departments, active and inactive.

curl http://localhost:5050/api/departments

200 OK Department[]

GET/api/departments/{id}

Get a single department.

200 OK · 404

GET/api/departments/kiosk

Filtered list for kiosks — only departments where isActive=true AND showOnKiosk=true. Recommended for public-facing clients.

GET/api/departments/display

Filtered list for display screens — only departments where isActive=true AND showOnDisplay=true.

POST/api/departments

Create a department. Body is a Department object (see model).

curl -X POST http://localhost:5050/api/departments \
  -H "Content-Type: application/json" \
  -d '{
    "name":"Cardiology","nameEn":"Cardiology","prefix":"A",
    "color":"#2563eb","icon":"❤️",
    "isActive":true,"showOnKiosk":true,"showOnDisplay":true
  }'

201 Created

PUT/api/departments/{id}

Update an existing department.

DELETE/api/departments/{id}

Remove a department. Cascade behavior on sub-departments is defined by the service layer.

204 No Content · 404

POST/api/departments/reset-daily

Manually reset the daily sequence counters for all departments and sub-departments. Normally invoked automatically at dailyResetTime in settings.

{ "message": "Daily sequences reset" }

Sub-Departments

The actual service desks. Includes the temporary closure facility — closed sub-departments reject new ticket issuance with HTTP 423. 9 endpoints.

GET/api/subdepartments

List sub-departments. Optionally filtered to one parent department.

deptId optional — return only sub-departments under this department.

GET/api/subdepartments/{id}

Get a single sub-department, including closure status.

GET/api/subdepartments/kiosk/{deptId}

Kiosk-filtered list under a parent department (isActive=true AND showOnKiosk=true).

POST/api/subdepartments

Create a sub-department.

curl -X POST http://localhost:5050/api/subdepartments \
  -H "Content-Type: application/json" \
  -d '{
    "departmentId":"dept-123",
    "name":"Dr. Ali",
    "prefix":"H",
    "roomNumber":"12",
    "color":"#22c55e","icon":"👨‍⚕️",
    "isActive":true,"showOnKiosk":true,"showOnDisplay":true
  }'
PUT/api/subdepartments/{id}

Update a sub-department.

DELETE/api/subdepartments/{id}

Delete a sub-department.

204 No Content · 404

PUT/api/subdepartments/{id}/close

Temporarily close a sub-department for N minutes. Requires permission department.lock. Emits SubDeptClosureChanged.

FieldTypeNotes
minutesintDefault 15. Sets closedUntil = now + minutes.
reasonstring?Shown on kiosks and displays.
userId / userNamestring?Who closed it.
curl -X PUT http://localhost:5050/api/subdepartments/<id>/close \
  -H "Content-Type: application/json" \
  -d '{"minutes":30,"reason":"Lunch","userId":"user-1","userName":"Ahmed"}'
POST/api/subdepartments/close-multi

Close several sub-departments at once. Useful for end-of-day or break-room patterns.

{
  "ids": ["sub-1", "sub-2", "sub-3"],
  "minutes": 60,
  "reason": "Maintenance",
  "userId": "admin",
  "userName": "Admin"
}

200 OK List of updated sub-departments. 400 empty ids array.

SignalR: emits a single SubDeptClosureBulkChanged with the list.

PUT/api/subdepartments/{id}/reopen

Reopen a closed sub-department immediately, ignoring closedUntil.

Emits SubDeptClosureChanged.

Settings

Global queue configuration. 3 endpoints.

GET/api/settings

Read the active QueueSettings document (single global record). See the data model for the full field list.

PUT/api/settings

Replace the global settings. Send the full QueueSettings object — fields omitted will revert to defaults on disk.

Display Settings

Configure which departments and sub-departments are shown on each physical display screen. Lets you run multiple displays with different filtering. 6 endpoints.

GET/api/displaysettings

All display configurations.

GET/api/displaysettings/{id}

A single display configuration.

GET/api/displaysettings/{id}/departments

Returns the resolved { departments, subDepartments } that should appear on this display, taking showAllDepartments and the filter arrays into account.

{
  "departments": [ Department, ... ],
  "subDepartments": [ SubDepartment, ... ]
}
POST/api/displaysettings

Create a display configuration.

{
  "name": "Lobby East TV",
  "departmentIds": ["dept-1", "dept-2"],
  "subDepartmentIds": [],
  "showAllDepartments": false
}
PUT/api/displaysettings/{id}

Update a display configuration.

DELETE/api/displaysettings/{id}

Delete a display configuration.

Reports

Aggregated analytics. All date parameters accept ISO 8601 strings (e.g. 2026-05-19). 4 endpoints.

GET/api/reports/tickets

Detailed ticket report with computed waiting + service times. Useful for exporting to BI tools.

ParamTypeNotes
fromDatedateInclusive.
toDatedateInclusive.
departmentIdstring
subDepartmentIdstring
statusstringEnum name — e.g. Completed.
userIdstringEmployee that handled the ticket.
curl "http://localhost:5050/api/reports/tickets?fromDate=2026-05-01&toDate=2026-05-19&status=Completed"
{
  "id": "...",
  "referenceNumber": "QT-20260519-00000007",
  "displayNumber": "A-H-007",
  "departmentName": "Cardiology",
  "subDepartmentName": "Dr. Ali",
  "status": "Completed",
  "statusAr": "مكتمل",
  "createdAt": "2026-05-19T10:30:00Z",
  "calledAt": "2026-05-19T10:42:00Z",
  "startedAt": "2026-05-19T10:43:00Z",
  "completedAt": "2026-05-19T10:51:00Z",
  "waitingTime": "12د 0ث",    // formatted
  "serviceTime": "9د 0ث",
  "assignedUserId": "user-1",
  "assignedUserName": "Ahmed",
  "isSuccess": true,
  "historyCount": 4
}

The waitingTime and serviceTime strings are formatted with Arabic time units (س=hours, د=minutes, ث=seconds). Use the raw createdAt/calledAt/completedAt timestamps when computing in other locales.

GET/api/reports/subdepartments

Aggregate KPIs grouped by sub-department: counts, success rate, average waiting + service times.

fromDate, toDate (both optional).

{
  "subDepartmentId": "...",
  "subDepartmentName": "Dr. Ali",
  "departmentId": "...",
  "roomNumber": "12",
  "totalTickets": 120,
  "waitingTickets": 3,
  "calledTickets": 1,
  "inProgressTickets": 1,
  "completedTickets": 110,
  "transferredTickets": 3,
  "cancelledTickets": 2,
  "successRate": 97.3,
  "avgWaitingTime": 8.4,   // minutes
  "avgServiceTime": 6.7    // minutes
}
GET/api/reports/employees

KPIs grouped by employee: handled count, success rate, average service time.

fromDate, toDate.

{
  "userId": "user-1",
  "userName": "Ahmed",
  "totalHandled": 48,
  "completedCount": 45,
  "inProgressCount": 1,
  "transferredCount": 2,
  "successRate": 95.6,
  "avgServiceTime": 5.2
}
GET/api/reports/daily-summary

One-shot dashboard summary for a given day.

date optional — defaults to today.

{
  "date": "2026-05-19T00:00:00",
  "totalTickets": 340,
  "waitingTickets": 14,
  "calledTickets": 3,
  "inProgressTickets": 2,
  "completedTickets": 310,
  "transferredTickets": 8,
  "cancelledTickets": 3,
  "successRate": 96.5,
  "avgWaitingTime": 9.2,
  "avgServiceTime": 5.8
}

License

Hardware-bound activation and employee-seat enforcement. 5 endpoints.

GET/api/license

Active license details: plan, expiry, max employees.

GET/api/license/hardware-id

The server's hardware fingerprint — needed by Iraq Soft to issue an activation key.

{ "hardwareId": "7F-4C-..." }
GET/api/license/check

Quick boolean validity check, plus a human-readable message.

{ "isValid": true, "message": "License active" }
POST/api/license/activate

Apply an activation key.

{ "licenseKey": "XXXX-XXXX-XXXX-XXXX" }

200 OK { success:true, message, license }

400 Bad Request { success:false, message } — e.g. wrong hardware id, expired, or empty key.

GET/api/license/employee-limit

How many employees are currently provisioned and how many more the license allows.

{
  "currentCount": 12,
  "canAddMore": true,
  "remainingSlots": 8,
  "maxEmployees": 20
}

Text-to-Speech

Server-side proxy to Google Translate's TTS. Returns an MP3 stream. 1 endpoint.

GET/api/tts/speak

Stream a generated MP3 for the given text.

ParamTypeRequiredNotes
textstringREQUIREDURL-encoded source text.
langstringOPTIONALDefault ar. Accepts any code Google Translate supports.
curl "http://localhost:5050/api/tts/speak?text=Welcome&lang=en" --output welcome.mp3

200 OKContent-Type: audio/mpeg with binary MP3 body. 500 if Google upstream is unreachable.

07 · Realtime

SignalR Hub

Real-time updates are delivered over a single SignalR hub. Every state-changing REST call emits one or more events on this hub so subscribed clients update without polling.

Connection

PropertyValue
Hub URLhttp://<host>:5050/queueHub (also reachable over WebSocket as ws://<host>:5050/queueHub)
Client library@microsoft/signalr (Node/JS), Microsoft.AspNetCore.SignalR.Client (.NET), official clients available for Java, Python, C++.
TransportWebSockets preferred; server falls back to Server-Sent Events / Long Polling automatically.
AuthConnection-level auth is not currently enforced. Future versions may require the bearer token via the accessTokenFactory option.
// Reference connection setup with reconnect, error handling, and join
import * as signalR from "@microsoft/signalr";

const conn = new signalR.HubConnectionBuilder()
  .withUrl("http://localhost:5050/queueHub")
  .withAutomaticReconnect([0, 2000, 5000, 10000, 30000])
  .configureLogging(signalR.LogLevel.Information)
  .build();

conn.onreconnecting(err => console.warn("reconnecting", err));
conn.onreconnected(id => console.log("reconnected", id));
conn.onclose(err => console.error("closed", err));

// Subscribe to events BEFORE start()
conn.on("TicketCreated", ticket => {/* ... */});
conn.on("TicketCalled", ({ ticket, roomNumber, audioSpeech }) => {/* ... */});

await conn.start();
await conn.invoke("JoinDisplay");
// or:
await conn.invoke("JoinSubDepartment", "subdept-456");

Client → Server methods

These are invoked from the client via connection.invoke(name, args). They control which broadcast groups the connection belongs to.

MethodArgsEffect
JoinSubDepartment subDeptId: string Adds the connection to group subdept-{subDeptId}. (Currently informational — broadcasts are sent to Clients.All; groups exist for forward-compatible scoped delivery.)
LeaveSubDepartment subDeptId: string Removes the connection from the sub-department group.
JoinDisplay Adds the connection to the display group.

Server → Client events

Subscribe with connection.on(name, handler). Every event below is currently broadcast to all connected clients.

Event nameFires whenPayload shape
TicketCreatedPOST /api/tickets succeeds, or the destination side of /request-service.Ticket
TicketCalledPUT /{id}/call or PUT /{id}/recall.{ ticket: Ticket, roomNumber: string, audioSpeech: string }audioSpeech is the pre-built Arabic announcement.
TicketStartedPUT /{id}/start.Ticket
TicketCompletedPUT /{id}/complete, or the source side of /request-service.Ticket
TicketTransferredPUT /{id}/transfer.Ticket
TicketNumberUpdatedPUT /{id}/update-number.Ticket
TicketPostponedPUT /{id}/postpone.Ticket
SubDeptClosureChangedPUT /{id}/close or /{id}/reopen.SubDepartment (the updated one).
SubDeptClosureBulkChangedPOST /subdepartments/close-multi.SubDepartment[]
QueueUpdatedAfter complete, transfer, request-service, update-number, postpone, reactivate.string — the affected subDepartmentId. Listeners should re-query waiting lists for that sub-department.
Idempotency tip

Several REST calls emit multiple SignalR events in sequence (e.g. /request-service emits four). Treat event handlers as idempotent re-renderers — they should derive UI state from the current ticket payload, not from event ordering.

08 · Reference

Data Models

Schemas for every DTO returned by the API. All fields use camelCase. Nullable fields are marked ?.

Department

FieldTypeDefaultNotes
idstringauto GUIDPrimary key.
namestring""Arabic (or primary) display name.
nameEnstring""English display name.
prefixstring"A"Letter used in displayNumber.
colorstring"#3b82f6"Hex color for UI accents.
iconstring"🏥"Emoji icon. Used when useImage=false.
imageBase64string?nullOptional PNG/JPG as data URL.
useImageboolfalsePrefer imageBase64 over icon.
displayOrderint0Sort key for UI.
isActivebooltrueSoft on/off switch.
showOnKioskbooltrueVisibility on kiosks.
showOnDisplaybooltrueVisibility on display screens.
currentSequenceint0Daily ticket counter. Resets at dailyResetTime.
lastResetDatedatetimetodayTimestamp of the last reset.
createdAtdatetimeUTC nowAudit.

SubDepartment

FieldTypeDefaultNotes
idstringauto GUIDPrimary key.
departmentIdstring""Foreign key to Department.
name / nameEnstring""Display names.
prefixstring"1"Sub-department letter/digit in displayNumber.
color / icon / imageBase64 / useImageSame semantics as Department.
roomNumberstring""Physical room / counter label.
displayOrderint0Sort key.
isActivebooltrue
showOnKiosk / showOnDisplaybooltrue
currentSequence / lastResetDateint / datetimePer-sub-department daily counter.
assignedUserIdsstring[][]Employees responsible for this sub-department.
createdAtdatetimeUTC now
Closure state
isClosedboolfalsetrue means new tickets are rejected with 423.
closedUntildatetime?nullUTC time of automatic reopen.
closureReasonstring?nullFree-form reason.
closedByUserId / closedByUserNamestring?nullAudit.
closedAtdatetime?nullUTC time the closure was initiated.

Ticket

FieldTypeDefaultNotes
idstringauto GUIDPrimary key.
referenceNumberstring""Format QT-YYYYMMDD-NNNNNNNN. Globally unique across history.
departmentId / subDepartmentIdstringForeign keys.
departmentName / subDepartmentNamestringDenormalized for immutable audit.
departmentPrefix / subDepartmentPrefixstringDenormalized prefixes used to build displayNumber.
sequenceNumberint0Daily sequence inside the sub-department.
displayNumberstring""Human label, format {deptPrefix}-{subPrefix}-{NNN}, e.g. A-H-001.
statusint (enum)0See Ticket Lifecycle.
createdAtdatetimeUTC nowTime issued.
calledAtdatetime?nullSet on /call.
startedAtdatetime?nullSet on /start.
completedAtdatetime?nullSet on /complete.
assignedUserId / assignedUserNamestring?nullLast employee who acted.
notestring?nullWorking note set on /start / /postpone.
completionNotestring?nullNote recorded on /complete.
isSuccessbooltrueOutcome flag.
transferredFromDeptId / transferredFromSubDeptIdstring?nullSet on transfer.
transferNotestring?nullReason for transfer.
historyTicketHistory[][]Embedded changelog.

TicketHistory

FieldTypeNotes
timestampdatetimeUTC.
actionstringOne of Created, Called, Started, Completed, Transferred, Postponed, Reactivated, NumberUpdated.
userId / userNamestring?Actor.
notestring?Optional context.
fromDeptId / toDeptIdstring?Populated on transfer.

QueueUser

FieldTypeDefaultNotes
idstringauto GUIDPrimary key.
usernamestring""Unique login.
passwordstring""Hashed on the server. Pass empty on update to keep existing.
namestring""Display name.
rolestring"user"One of admin, user.
roomNumberstring""Fallback room used by /call if the sub-department has none.
assignedSubDepartmentIdsstring[][]Sub-departments this user can serve.
assignedSubDepartmentNamesstring[][]Enriched by GET /api/users for display purposes.
permissionsstring[][]Granular permission codes (see Authentication).
isActivebooltrue
createdAtdatetimeUTC now

QueueSettings

Single global record. All fields are present on every response.

FieldTypeDefaultPurpose
Branding
companyNamestring"مركز طبي"Shown on tickets and displays.
companyNameEnstring"Medical Center"
sloganstringSubtitle line.
logoBase64string""Data URL for the logo.
Kiosk UI
kioskSectionTitlestringTop-level kiosk heading.
kioskSectionSubtitlestringSub-title under it.
kioskSubdeptTitle / kioskSubdeptSubtitlestringSub-department screen labels.
kioskAutoResetSecondsint10Auto-return to home after ticket print. 0 = instant.
ticketLabelstring"وصل"Word used for "ticket" in announcements.
Printing
receiptWidthint80Receipt width in millimeters.
receiptFontSizestring"medium"small / medium / large.
Display screen
displayShowCompanyNamebooltrue
displayShowLogobooltrue
displayShowWaitingCountbooltrueShow count of waiting tickets per sub-department.
displayShowWaitingNamesboolfalseShow ticket numbers of those still waiting.
displayScrollSpeedint31 (slow) — 5 (fast).
displayCallDurationint5Seconds a called ticket stays highlighted.
displayEnableSound / displayEnableVoiceAnnouncementbooltrueToggle audio cues.
displayShowPostponedTicketsbooltrueRender postponed list.
roomLabelstring"الغرفة"Word used for "room" in announcements (override to "Window", "Counter", etc.).
Voice engine
displayVoiceEnginestring"browser"browser / files / google.
displayVoiceLocalestring"ar-SA"BCP-47 tag.
displayVoiceNamestring?nullOptional specific voice name in the browser.
displayVoiceRatedouble0.90.5 – 2.0.
displayVoicePitchdouble1.00.5 – 2.0.
dailyResetTimetimespan00:00:00Wall-clock time at which daily sequences reset.

DisplaySettings

FieldTypeDefaultNotes
idstringauto GUIDPrimary key.
namestring"شاشة الاستقبال"Friendly name.
departmentIdsstring[][]Departments shown when showAllDepartments=false.
subDepartmentIdsstring[][]Additional sub-departments shown.
showAllDepartmentsbooltrueBypass filters and show everything.
createdAtdatetimeUTC now
09 · Reference

Error Reference

QueueBridge uses standard HTTP status codes. Error bodies follow one of two shapes, depending on the controller:

// Shape 1 — single error message
{ "error": "<message, often in Arabic>" }

// Shape 2 — used by License and a few others
{ "success": false, "message": "<message>" }
CodeMeaningWhen
200OKSuccessful GET / PUT / POST that returns content.
201CreatedSuccessful POST that creates a resource. Includes Location header.
204No ContentSuccessful DELETE.
400Bad RequestValidation failed, malformed JSON, or business-rule rejection (e.g. license limit reached, last admin deletion).
401UnauthorizedMissing/invalid/expired token. Returned by /auth/login on bad credentials and /auth/verify on bad tokens.
403ForbiddenReserved — future use for permission enforcement.
404Not FoundResource (ticket, user, department, etc.) does not exist.
409ConflictDuplicate username on user create/update.
423LockedSub-department temporarily closed. Returned by POST /api/tickets. Special payload — see below.
500Server ErrorUnhandled exception. Upstream failure for /api/tts/speak.

The 423 closed-department payload

This response is unique and worth handling specially — it lets the client display a friendly "back in N minutes" message instead of a generic error.

{
  "error":            "هذا القسم مغلق مؤقتاً",   // "This section is temporarily closed"
  "isClosed":         true,
  "closedUntil":      "2026-05-19T11:15:00Z",         // UTC ISO 8601
  "remainingMinutes": 12,
  "reason":           "Lunch break"                  // nullable
}
A note on Arabic error strings

Many error messages are returned in Arabic because they're designed to be shown directly on kiosks. They're stable strings — partners may map them client-side. Examples: "اسم المستخدم أو كلمة المرور غير صحيحة" (invalid credentials), "انتهت صلاحية الجلسة" (session expired), "لا يمكن حذف آخر مدير" (cannot delete last admin), "هذا القسم مغلق مؤقتاً" (sub-department closed), "مفتاح الترخيص مطلوب" (license key required).

10 · Operations

CORS, Rate Limits, Versioning

CORS

The server enables a permissive CORS policy that accepts any origin, method, and header, and allows credentials — this is required for SignalR over WebSockets to function across origins. Production deployments behind a reverse proxy should narrow the allowed origin list at the proxy layer.

Rate limits

No rate limiting is currently applied at the API layer. The intended deployment topology — a private LAN with a small number of trusted kiosks, portals, and displays — does not benefit from it. Partners exposing the API over public networks should add a rate-limit layer at their gateway.

Versioning

The current API uses unversioned paths (/api/...). Future breaking changes will be introduced under a versioned prefix (/api/v2/...). The v1 surface documented here will remain available for the duration of any partner agreement.

Pagination

List endpoints currently return full collections. For deployments that grow beyond ~10,000 tickets/day, contact us before integrating with bulk endpoints — pagination is on the roadmap.

11 · Appendix

Appendix

Glossary

TermMeaning
DepartmentTop-level service category. Holds the first letter of displayNumber.
Sub-DepartmentA specific service desk — usually mapped to a physical room or counter. Holds the second segment of displayNumber.
DisplayA large-screen monitor in the waiting area showing called and waiting tickets. Configured via DisplaySettings.
KioskA customer-facing touchscreen used to issue tickets.
PortalThe employee-facing UI used to call and complete tickets.
displayNumberThe human-friendly ticket label, e.g. A-H-001 = department A, sub-department H, sequence 001.
referenceNumberThe globally unique identifier across all history: QT-YYYYMMDD-NNNNNNNN.
audioSpeechPre-built Arabic announcement string, e.g. "الوصل رقم سبعة يتفضل إلى الغرفة اثنا عشر". Generated server-side; consumed by displays.

Default seed credentials

Change before production

A fresh installation seeds two users. Rotate both passwords on first deploy.

admin / admin123 — role: admin

user1 / user123 — role: user

Storage notes

QueueBridge persists state in JSON files (tickets.json, users.json, departments.json, subdepartments.json, queue_settings.json, display_settings.json) next to the server binary. Backups are as simple as copying that folder. There is no external database to provision.

Contact & support

ChannelDetail
WhatsApp (preferred for integration questions)+964 772 228 4111
Hotline6554
OfficeIraq Soft — Baghdad, Electronic University
WebQueueBridge product site