This endpoint requires a valid JWT Bearer token with a kitchen or service role (server, pos_staff, restaurant_staff, chef, kitchen_staff, manager, owner, super_admin). Accessible via the API gateway at /api/v1/coursing/*.
Course Management API
Multi-course meal management with course firing controls, table synchronization, meal pacing, and timing alerts.
Overview
The Course Management API enables restaurants to coordinate multi-course dining experiences. Courses progress through a lifecycle from held to fired to complete, with optional table-wide synchronization so all guests at a table receive each course together.
| Feature | Description | Use Case |
|---|---|---|
| Course Templates | Reusable course configurations per location | Define standard course sequences (Appetizer, Entree, Dessert) |
| Course Firing | Fire, hold, skip, and complete courses per order | Kitchen coordination for multi-course meals |
| Table Sync | Synchronize courses across orders at one table | Ensure all guests at a table are served together |
| Meal Pacing | Set dining pace (leisurely, normal, quick, custom) | Adjust timing gaps between courses |
| Course Timing | Min/target/max gap and prep time per template | Fine-grained control over course intervals |
| Timing Alerts | Alerts when courses are delayed | Proactive kitchen management |
Course Lifecycle
Held --> Fired --> InProgress --> Complete
| ^
+-------> Skipped |
| |
+<---- (hold again) <--------------+
| Status | Description |
|---|---|
held | Course is waiting to be fired |
fired | Course has been sent to the kitchen |
in_progress | Kitchen is actively preparing course items |
complete | All items in the course are ready/served |
skipped | Course was skipped entirely |
Course Template Model
{
"id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"tenant_id": "550e8400-e29b-41d4-a716-446655449100",
"location_id": "550e8400-e29b-41d4-a716-446655449110",
"name": "Entree",
"display_order": 2,
"default_delay_minutes": 10,
"auto_fire": true,
"hold_by_default": false,
"display_color": "#4CAF50",
"is_active": true,
"created_at": "2026-01-15T10:00:00Z",
"updated_at": "2026-01-15T10:00:00Z"
}
Order Course Model
{
"id": "f1e2d3c4-b5a6-7890-fedc-ba0987654321",
"tenant_id": "550e8400-e29b-41d4-a716-446655449100",
"location_id": "550e8400-e29b-41d4-a716-446655449110",
"order_id": "ord-xyz789",
"course_id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"course_number": 2,
"course_name": "Entree",
"status": "fired",
"fire_at": "2026-01-20T19:25:00Z",
"fired_at": "2026-01-20T19:26:00Z",
"completed_at": null,
"fired_by": "550e8400-e29b-41d4-a716-446655449120",
"hold_reason": null,
"notes": null,
"item_count": 4,
"items_completed": 1,
"created_at": "2026-01-20T19:00:00Z",
"updated_at": "2026-01-20T19:26:00Z"
}
Course Templates
Create Course Template
POST /api/v1/coursing/templates
Authorization: Bearer {access_token}
Content-Type: application/json
Request:
{
"location_id": "550e8400-e29b-41d4-a716-446655449110",
"name": "Appetizer",
"display_order": 1,
"default_delay_minutes": 0,
"auto_fire": false,
"hold_by_default": true,
"display_color": "#FF9800"
}
Response: 201 Created
{
"id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"tenant_id": "550e8400-e29b-41d4-a716-446655449100",
"location_id": "550e8400-e29b-41d4-a716-446655449110",
"name": "Appetizer",
"display_order": 1,
"default_delay_minutes": 0,
"auto_fire": false,
"hold_by_default": true,
"display_color": "#FF9800",
"is_active": true,
"created_at": "2026-01-15T10:00:00Z",
"updated_at": "2026-01-15T10:00:00Z"
}
List Course Templates
GET /api/v1/coursing/templates?location_id={location_id}&active_only=true
Authorization: Bearer {access_token}
| Query Parameter | Type | Default | Description |
|---|---|---|---|
location_id | UUID | required | Location to list templates for |
active_only | boolean | true | Only return active templates |
Response: 200 OK
[
{
"id": "tmpl-001",
"name": "Appetizer",
"display_order": 1,
"default_delay_minutes": 0,
"auto_fire": false,
"hold_by_default": true,
"display_color": "#FF9800",
"is_active": true
},
{
"id": "tmpl-002",
"name": "Soup/Salad",
"display_order": 2,
"default_delay_minutes": 8,
"auto_fire": true,
"hold_by_default": false,
"display_color": "#8BC34A",
"is_active": true
},
{
"id": "tmpl-003",
"name": "Entree",
"display_order": 3,
"default_delay_minutes": 12,
"auto_fire": true,
"hold_by_default": false,
"display_color": "#4CAF50",
"is_active": true
},
{
"id": "tmpl-004",
"name": "Dessert",
"display_order": 4,
"default_delay_minutes": 15,
"auto_fire": false,
"hold_by_default": true,
"display_color": "#9C27B0",
"is_active": true
}
]
Get Course Template
GET /api/v1/coursing/templates/{template_id}
Authorization: Bearer {access_token}
Response: 200 OK -- Returns a single CourseTemplate object.
Update Course Template
PUT /api/v1/coursing/templates/{template_id}
Authorization: Bearer {access_token}
Content-Type: application/json
Request: All fields are optional; only provided fields are updated.
{
"name": "Appetizer Course",
"display_order": 1,
"default_delay_minutes": 5,
"auto_fire": true,
"hold_by_default": false,
"display_color": "#FFC107",
"is_active": true
}
Response: 200 OK -- Returns the updated CourseTemplate.
Delete Course Template
Soft-deletes a course template (marks as inactive).
DELETE /api/v1/coursing/templates/{template_id}
Authorization: Bearer {access_token}
Response: 204 No Content
Order Courses
Get Order Courses
List all courses for an order with their current status.
GET /api/v1/coursing/orders/{order_id}/courses
Authorization: Bearer {access_token}
Response: 200 OK
[
{
"id": "oc-001",
"order_id": "ord-xyz789",
"course_id": "tmpl-001",
"course_number": 1,
"course_name": "Appetizer",
"status": "complete",
"fired_at": "2026-01-20T19:05:00Z",
"completed_at": "2026-01-20T19:15:00Z",
"fired_by": "550e8400-e29b-41d4-a716-446655449120",
"item_count": 2,
"items_completed": 2
},
{
"id": "oc-002",
"order_id": "ord-xyz789",
"course_id": "tmpl-003",
"course_number": 2,
"course_name": "Entree",
"status": "in_progress",
"fired_at": "2026-01-20T19:20:00Z",
"completed_at": null,
"fired_by": "550e8400-e29b-41d4-a716-446655449120",
"item_count": 4,
"items_completed": 1
},
{
"id": "oc-003",
"order_id": "ord-xyz789",
"course_id": "tmpl-004",
"course_number": 3,
"course_name": "Dessert",
"status": "held",
"fired_at": null,
"completed_at": null,
"fired_by": null,
"item_count": 2,
"items_completed": 0
}
]
Get Course Overview
Get a summary view of all courses for an order, including current progress.
GET /api/v1/coursing/orders/{order_id}/courses/overview
Authorization: Bearer {access_token}
Response: 200 OK
{
"order_id": "ord-xyz789",
"courses": [
{
"course_number": 1,
"course_name": "Appetizer",
"status": "complete",
"item_count": 2,
"items_completed": 2,
"fire_at": null,
"fired_at": "2026-01-20T19:05:00Z",
"completed_at": "2026-01-20T19:15:00Z"
},
{
"course_number": 2,
"course_name": "Entree",
"status": "in_progress",
"item_count": 4,
"items_completed": 1,
"fire_at": null,
"fired_at": "2026-01-20T19:20:00Z",
"completed_at": null
},
{
"course_number": 3,
"course_name": "Dessert",
"status": "held",
"item_count": 2,
"items_completed": 0,
"fire_at": "2026-01-20T19:45:00Z",
"fired_at": null,
"completed_at": null
}
],
"current_course": 2,
"total_courses": 3,
"all_complete": false
}
Fire Course
Fire (release) a specific course for an order. This sends the course to the kitchen for preparation.
POST /api/v1/coursing/orders/{order_id}/courses/fire
Authorization: Bearer {access_token}
Content-Type: application/json
Request:
{
"course_number": 2,
"notes": "Table requested entrees early"
}
Response: 200 OK -- Returns the updated OrderCourse with status fired.
Hold Course
Place a course on hold, preventing it from being sent to the kitchen.
POST /api/v1/coursing/orders/{order_id}/courses/hold
Authorization: Bearer {access_token}
Content-Type: application/json
Request:
{
"course_number": 3,
"reason": "Guest stepped away from table"
}
Response: 200 OK -- Returns the updated OrderCourse with status held.
Fire All Courses
Fire all remaining held courses for an order at once.
POST /api/v1/coursing/orders/{order_id}/courses/fire-all
Authorization: Bearer {access_token}
Content-Type: application/json
Request:
{
"delay_minutes": 5
}
| Field | Type | Description |
|---|---|---|
delay_minutes | integer (optional) | Stagger delay between each course firing |
Response: 200 OK -- Returns an array of all fired OrderCourse objects.
Skip Course
Skip a course entirely. The course will not be prepared.
POST /api/v1/coursing/orders/{order_id}/courses/{course_number}/skip
Authorization: Bearer {access_token}
Response: 200 OK -- Returns the updated OrderCourse with status skipped.
Complete Course
Mark a course as complete.
POST /api/v1/coursing/orders/{order_id}/courses/{course_number}/complete
Authorization: Bearer {access_token}
Response: 200 OK -- Returns the updated OrderCourse with status complete.
Update Course Progress
Update the number of completed items for a course. When all items are completed, the course status automatically transitions to complete.
PUT /api/v1/coursing/orders/{order_id}/courses/{course_number}/progress
Authorization: Bearer {access_token}
Content-Type: application/json
Request:
{
"items_completed": 3
}
Response: 200 OK -- Returns the updated OrderCourse.
Table Synchronization
Table sync ensures all orders at the same table progress through courses together. When enabled, courses are fired for the entire table rather than individual orders.
Create Table Sync
Link multiple orders at a table for synchronized coursing.
POST /api/v1/coursing/tables
Authorization: Bearer {access_token}
Content-Type: application/json
Request:
{
"location_id": "550e8400-e29b-41d4-a716-446655449110",
"table_id": "tbl-007",
"order_ids": ["ord-001", "ord-002", "ord-003"]
}
Response: 201 Created
{
"id": "sync-abc123",
"tenant_id": "550e8400-e29b-41d4-a716-446655449100",
"location_id": "550e8400-e29b-41d4-a716-446655449110",
"table_id": "tbl-007",
"current_course": 1,
"order_ids": ["ord-001", "ord-002", "ord-003"],
"current_course_complete": false,
"course_started_at": null,
"next_fire_at": null,
"created_at": "2026-01-20T19:00:00Z",
"updated_at": "2026-01-20T19:00:00Z"
}
Get Table Sync Status
Get the synchronization status for a table, including per-order readiness.
GET /api/v1/coursing/tables/{table_id}
Authorization: Bearer {access_token}
Response: 200 OK
{
"table_id": "tbl-007",
"current_course": 2,
"orders": [
{
"order_id": "ord-001",
"current_course_status": "complete",
"is_ready": true
},
{
"order_id": "ord-002",
"current_course_status": "in_progress",
"is_ready": false
},
{
"order_id": "ord-003",
"current_course_status": "complete",
"is_ready": true
}
],
"all_orders_ready": false,
"next_fire_at": "2026-01-20T19:35:00Z"
}
Delete Table Sync
Remove table synchronization. Orders return to individual course management.
DELETE /api/v1/coursing/tables/{table_id}
Authorization: Bearer {access_token}
Response: 204 No Content
Add Order to Table Sync
Add a late-arriving guest's order to an existing table sync.
POST /api/v1/coursing/tables/{table_id}/orders/{order_id}
Authorization: Bearer {access_token}
Response: 200 OK -- Returns the updated TableCourseSync.
Remove Order from Table Sync
Remove an order from table synchronization (e.g., guest leaving early).
DELETE /api/v1/coursing/tables/{table_id}/orders/{order_id}
Authorization: Bearer {access_token}
Response: 200 OK -- Returns the updated TableCourseSync.
Fire Table Course
Fire a specific course for all orders at a table simultaneously.
POST /api/v1/coursing/tables/{table_id}/fire/{course_number}
Authorization: Bearer {access_token}
Response: 200 OK -- Returns TableSyncStatus showing the updated state for all orders.
Check and Advance Table
Check if the current course is complete for all orders at the table and automatically advance to the next course if ready.
POST /api/v1/coursing/tables/{table_id}/advance
Authorization: Bearer {access_token}
Response: 200 OK -- Returns TableSyncStatus if advanced, or null if not yet ready to advance.
Timing Configuration
Configure course timing behavior at the location level.
Get Timing Config
GET /api/v1/coursing/config/{location_id}
Authorization: Bearer {access_token}
Response: 200 OK
{
"tenant_id": "550e8400-e29b-41d4-a716-446655449100",
"location_id": "550e8400-e29b-41d4-a716-446655449110",
"default_course_delay": 5,
"auto_fire_enabled": true,
"table_sync_enabled": true,
"rush_mode_multiplier": 0.5,
"rush_mode_active": false
}
Update Timing Config
PUT /api/v1/coursing/config/{location_id}
Authorization: Bearer {access_token}
Content-Type: application/json
Request: All fields are optional.
{
"default_course_delay": 8,
"auto_fire_enabled": true,
"table_sync_enabled": true,
"rush_mode_multiplier": 0.6,
"rush_mode_active": false
}
Response: 200 OK -- Returns the updated CourseTimingConfig.
Toggle Rush Mode
Enable or disable rush mode for a location. Rush mode reduces all course timing delays by the configured multiplier (default 0.5x).
POST /api/v1/coursing/config/{location_id}/rush
Authorization: Bearer {access_token}
Content-Type: application/json
Request:
{
"enabled": true
}
Response: 200 OK -- Returns the updated CourseTimingConfig with rush_mode_active reflecting the new state.
Meal Pacing
Control the overall dining pace for an order. Pace adjusts the time gap between courses.
| Pace | Effect | Gap Modifier |
|---|---|---|
leisurely | Relaxed dining experience | +5 minutes |
normal | Standard timing | No change |
quick | Faster service | -3 minutes |
custom | Specify exact gap in minutes | Custom value |
Get Meal Pace
GET /api/v1/coursing/orders/{order_id}/pace
Authorization: Bearer {access_token}
Response: 200 OK
{
"order_id": "ord-xyz789",
"pace": "leisurely",
"set_by": "550e8400-e29b-41d4-a716-446655449120",
"set_at": "2026-01-20T19:02:00Z"
}
Set Meal Pace
PUT /api/v1/coursing/orders/{order_id}/pace
Authorization: Bearer {access_token}
Content-Type: application/json
Request:
{
"pace": "quick",
"custom_gap_minutes": null,
"location_id": "550e8400-e29b-41d4-a716-446655449110"
}
For custom pacing:
{
"pace": "custom",
"custom_gap_minutes": 8,
"location_id": "550e8400-e29b-41d4-a716-446655449110"
}
Response: 200 OK -- Returns the updated MealPace.
Course Timing (per Template)
Fine-grained timing configuration per course template. Controls the gap between courses, prep time, and hold time at the pass.
Get Course Timing
GET /api/v1/coursing/templates/{template_id}/timing
Authorization: Bearer {access_token}
Response: 200 OK
{
"min_gap_minutes": 6,
"target_gap_minutes": 10,
"max_gap_minutes": 15,
"prep_time_minutes": 12,
"hold_time_minutes": 5
}
Returns null if no custom timing is configured for this template.
Set Course Timing
PUT /api/v1/coursing/templates/{template_id}/timing
Authorization: Bearer {access_token}
Content-Type: application/json
Request:
{
"min_gap_minutes": 5,
"target_gap_minutes": 10,
"max_gap_minutes": 18,
"prep_time_minutes": 15,
"hold_time_minutes": 3
}
| Field | Type | Description |
|---|---|---|
min_gap_minutes | integer | Minimum time gap after the previous course (minutes) |
target_gap_minutes | integer | Ideal time gap after the previous course (minutes) |
max_gap_minutes | integer | Maximum time gap before triggering an alert (minutes) |
prep_time_minutes | integer | Estimated kitchen preparation time (minutes) |
hold_time_minutes | integer | Maximum time food can wait at the pass (minutes) |
Response: 200 OK -- Returns the updated CourseTiming.
Ready/Served Status
Track when courses are ready at the pass and when they are delivered to the table.
Mark Course Ready
Mark a course as ready at the pass/window.
POST /api/v1/coursing/orders/{order_id}/courses/{course_number}/ready
Authorization: Bearer {access_token}
Response: 200 OK -- Returns the updated OrderCourse.
Mark Course Served
Mark a course as served to the table.
POST /api/v1/coursing/orders/{order_id}/courses/{course_number}/served
Authorization: Bearer {access_token}
Response: 200 OK -- Returns the updated OrderCourse.
Get Course Timing Status
Get the real-time timing status for a specific course, including schedule adherence and alert level.
GET /api/v1/coursing/orders/{order_id}/courses/{course_number}/timing-status
Authorization: Bearer {access_token}
Response: 200 OK
{
"scheduled_fire_time": "2026-01-20T19:25:00Z",
"estimated_ready_time": "2026-01-20T19:37:00Z",
"is_on_time": true,
"delay_minutes": null,
"alert_level": "normal"
}
When delayed:
{
"scheduled_fire_time": "2026-01-20T19:25:00Z",
"estimated_ready_time": "2026-01-20T19:42:00Z",
"is_on_time": false,
"delay_minutes": 7,
"alert_level": "warning"
}
| Alert Level | Condition |
|---|---|
normal | On schedule or delay under 5 minutes |
warning | Delay between 5 and 10 minutes |
critical | Delay over 10 minutes |
blocked | Cannot proceed due to a dependency issue |
Timing Alerts
Monitor and acknowledge timing alerts across a location.
Get Timing Alerts
GET /api/v1/coursing/alerts?location_id={location_id}&include_acknowledged=false
Authorization: Bearer {access_token}
| Query Parameter | Type | Default | Description |
|---|---|---|---|
location_id | UUID | required | Location to get alerts for |
include_acknowledged | boolean | false | Include previously acknowledged alerts |
Response: 200 OK
[
{
"id": "alert-001",
"tenant_id": "550e8400-e29b-41d4-a716-446655449100",
"location_id": "550e8400-e29b-41d4-a716-446655449110",
"order_id": "ord-xyz789",
"course_id": "tmpl-003",
"course_number": 2,
"course_name": "Entree",
"table_name": "Table 7",
"alert_level": "warning",
"message": "Course is 7 minutes delayed",
"delay_minutes": 7,
"acknowledged": false,
"acknowledged_by": null,
"acknowledged_at": null,
"created_at": "2026-01-20T19:37:00Z"
},
{
"id": "alert-002",
"tenant_id": "550e8400-e29b-41d4-a716-446655449100",
"location_id": "550e8400-e29b-41d4-a716-446655449110",
"order_id": "ord-abc456",
"course_id": "tmpl-004",
"course_number": 3,
"course_name": "Dessert",
"table_name": "Table 12",
"alert_level": "critical",
"message": "Course is significantly delayed",
"delay_minutes": 15,
"acknowledged": false,
"acknowledged_by": null,
"acknowledged_at": null,
"created_at": "2026-01-20T19:40:00Z"
}
]
Acknowledge Timing Alert
POST /api/v1/coursing/alerts/{alert_id}/acknowledge
Authorization: Bearer {access_token}
Response: 200 OK
Error Codes
| Code | Message | Description |
|---|---|---|
TEMPLATE_NOT_FOUND | Course template not found | Invalid template ID |
ORDER_NOT_FOUND | Order not found | Invalid order ID |
COURSE_NOT_FOUND | Course not found | Invalid course number for this order |
COURSE_ALREADY_FIRED | Course already fired | Cannot fire a course that is not held |
COURSE_ALREADY_COMPLETE | Course already complete | Cannot modify a completed course |
TABLE_SYNC_NOT_FOUND | Table sync not found | No sync exists for this table |
ORDER_NOT_IN_SYNC | Order not in table sync | Order is not part of this table sync |
INVALID_COURSE_NUMBER | Invalid course number | Course number must be positive |
ALERT_NOT_FOUND | Alert not found | Invalid alert ID |
Integration with KDS
Course firing integrates directly with the Kitchen Display System. When a course is fired:
- Tickets are released -- Held KDS tickets for the fired course transition from
heldtopendingstatus - Station routing -- Course items are routed to the appropriate kitchen stations (grill, fry, prep, etc.)
- Expo view -- The Expo View aggregates course status across all stations for the expediter
- Progress tracking -- As station tickets are completed, course item progress is updated automatically
Typical Multi-Course Flow
1. Order placed with 3 courses (Appetizer, Entree, Dessert)
2. Course 1 (Appetizer) auto-fires --> KDS tickets created at stations
3. Kitchen completes Appetizer items --> Course 1 marked complete
4. Server marks Course 1 served
5. After delay, Course 2 (Entree) fires --> new KDS tickets
6. Kitchen completes Entree items --> Course 2 marked complete
7. Server marks Course 2 served
8. Dessert course held until manually fired by server
9. Server fires Course 3 (Dessert) --> final KDS tickets
10. Kitchen completes Dessert --> Course 3 marked complete
Best Practices
- Use templates consistently -- Define standard course sequences per location and apply them to all dine-in orders
- Enable table sync for parties -- Synchronize courses for tables with multiple orders so all guests eat together
- Set appropriate timing -- Configure
prep_time_minutesbased on actual kitchen capacity to avoid premature firing - Monitor timing alerts -- React to warning-level alerts before they escalate to critical
- Use rush mode during peaks -- Toggle rush mode during busy periods to reduce course gaps across the location
- Hold dessert by default -- Set
hold_by_default: trueon dessert templates so servers control when to send
Related Documentation
- KDS Expo View - Expo view and course firing from the KDS perspective
- Kitchen Display API - Kitchen display system and ticket management
- Orders API - Order management
- Tables API - Table management and seating