Skip to main content
Authenticated API

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.

FeatureDescriptionUse Case
Course TemplatesReusable course configurations per locationDefine standard course sequences (Appetizer, Entree, Dessert)
Course FiringFire, hold, skip, and complete courses per orderKitchen coordination for multi-course meals
Table SyncSynchronize courses across orders at one tableEnsure all guests at a table are served together
Meal PacingSet dining pace (leisurely, normal, quick, custom)Adjust timing gaps between courses
Course TimingMin/target/max gap and prep time per templateFine-grained control over course intervals
Timing AlertsAlerts when courses are delayedProactive kitchen management

Course Lifecycle

Held --> Fired --> InProgress --> Complete
| ^
+-------> Skipped |
| |
+<---- (hold again) <--------------+
StatusDescription
heldCourse is waiting to be fired
firedCourse has been sent to the kitchen
in_progressKitchen is actively preparing course items
completeAll items in the course are ready/served
skippedCourse 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 ParameterTypeDefaultDescription
location_idUUIDrequiredLocation to list templates for
active_onlybooleantrueOnly 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
}
FieldTypeDescription
delay_minutesinteger (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.

PaceEffectGap Modifier
leisurelyRelaxed dining experience+5 minutes
normalStandard timingNo change
quickFaster service-3 minutes
customSpecify exact gap in minutesCustom 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
}
FieldTypeDescription
min_gap_minutesintegerMinimum time gap after the previous course (minutes)
target_gap_minutesintegerIdeal time gap after the previous course (minutes)
max_gap_minutesintegerMaximum time gap before triggering an alert (minutes)
prep_time_minutesintegerEstimated kitchen preparation time (minutes)
hold_time_minutesintegerMaximum 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 LevelCondition
normalOn schedule or delay under 5 minutes
warningDelay between 5 and 10 minutes
criticalDelay over 10 minutes
blockedCannot 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 ParameterTypeDefaultDescription
location_idUUIDrequiredLocation to get alerts for
include_acknowledgedbooleanfalseInclude 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

CodeMessageDescription
TEMPLATE_NOT_FOUNDCourse template not foundInvalid template ID
ORDER_NOT_FOUNDOrder not foundInvalid order ID
COURSE_NOT_FOUNDCourse not foundInvalid course number for this order
COURSE_ALREADY_FIREDCourse already firedCannot fire a course that is not held
COURSE_ALREADY_COMPLETECourse already completeCannot modify a completed course
TABLE_SYNC_NOT_FOUNDTable sync not foundNo sync exists for this table
ORDER_NOT_IN_SYNCOrder not in table syncOrder is not part of this table sync
INVALID_COURSE_NUMBERInvalid course numberCourse number must be positive
ALERT_NOT_FOUNDAlert not foundInvalid alert ID

Integration with KDS

Course firing integrates directly with the Kitchen Display System. When a course is fired:

  1. Tickets are released -- Held KDS tickets for the fired course transition from held to pending status
  2. Station routing -- Course items are routed to the appropriate kitchen stations (grill, fry, prep, etc.)
  3. Expo view -- The Expo View aggregates course status across all stations for the expediter
  4. 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

  1. Use templates consistently -- Define standard course sequences per location and apply them to all dine-in orders
  2. Enable table sync for parties -- Synchronize courses for tables with multiple orders so all guests eat together
  3. Set appropriate timing -- Configure prep_time_minutes based on actual kitchen capacity to avoid premature firing
  4. Monitor timing alerts -- React to warning-level alerts before they escalate to critical
  5. Use rush mode during peaks -- Toggle rush mode during busy periods to reduce course gaps across the location
  6. Hold dessert by default -- Set hold_by_default: true on dessert templates so servers control when to send