Appearance
Response Schemas
This page documents the common response formats and data structures used across the KitchenClick Ecommerce API.
Response Envelope
All API responses follow a consistent structure:
Success Response
json
{
"status": "success",
"message": "Optional success message",
"data": {
// Response payload
},
"meta": {
// Pagination or additional metadata
}
}Error Response
json
{
"status": "error",
"message": "Human-readable error description",
"errors": {
"field_name": ["Error message 1", "Error message 2"]
},
"error_code": "specific_error_code"
}Response Fields
| Field | Type | Description |
|---|---|---|
status | string | success or error |
message | string | Human-readable message |
data | object/array | Response payload (success only) |
meta | object | Pagination or metadata |
errors | object | Field-specific errors (error only) |
error_code | string | Machine-readable error code |
Pagination
List endpoints return paginated results:
json
{
"status": "success",
"data": [...],
"meta": {
"current_page": 1,
"from": 1,
"to": 25,
"per_page": 25,
"total": 150,
"last_page": 6,
"path": "/api/v1/ecommerce/locations/loc_xxxxx/menus",
"links": {
"first": "...?page=1",
"prev": null,
"next": "...?page=2",
"last": "...?page=6"
}
}
}Query Parameters
| Parameter | Type | Default | Description |
|---|---|---|---|
page | integer | 1 | Page number |
per_page | integer | 25 | Items per page (max: 100) |
ID Formats (Hashkeys)
All entities use opaque, prefixed identifiers (never raw integer IDs). They are exposed in responses as entity-specific _id fields — e.g. location_id, item_id, order_id — carrying these prefixed values:
| Entity | Prefix | Example |
|---|---|---|
| Location | loc_ | loc_00000k1L2m3N4o5 |
| Concept | con_ | con_00000a1B2c3D4e5 |
| Menu | mnu_ | mnu_00000f5G6h7I8j9 |
| Menu Item | itm_ | itm_00000p1Q2r3S4t5 |
| Option Group | opg_ | opg_00000u1V2w3X4y5 |
| Modifier | mod_ | mod_00000z1A2b3C4d5 |
| Order | ord_ | ord_00000e5F6g7H8i9 |
| Kiosk | ksk_ | ksk_00000j1K2l3M4n5 |
Data Types
Money
Monetary values are represented as decimal numbers with 2 decimal places:
json
{
"base_price": 12.99,
"tax_amount": 1.07,
"total": 14.06
}Timestamps
All timestamps are ISO 8601 format in UTC:
json
{
"created_at": "2024-01-15T14:30:00Z",
"updated_at": "2024-01-15T15:45:30Z"
}Phone Numbers
Phone numbers are stored with formatting:
json
{
"phone": "(555) 123-4567"
}Entity Schemas
Field names
These are simplified reference shapes. Entity identifiers are exposed as entity-specific _id fields (e.g. location_id, item_id, order_id) carrying the prefixed values from the ID Formats table — never raw integer IDs. For the exact fields of any endpoint, the per-endpoint reference page is authoritative.
Location
typescript
interface Location {
location_id: string; // loc_xxxxx
name: string;
address: {
street: string;
city: string;
state: string;
zip: string;
country: string;
};
coordinates?: {
latitude: number;
longitude: number;
};
phone?: string;
timezone: string; // IANA timezone
status: 'active' | 'inactive';
order_types: OrderType[];
prep_time_minutes: number;
minimum_order?: number;
delivery_settings?: {
radius_miles: number;
fee: number;
minimum_order: number;
};
}Concept
typescript
interface Concept {
concept_id: string; // con_xxxxx
name: string;
description?: string;
concept_type: string;
logo_url?: string;
hero_url?: string;
gallery_urls?: string[];
status: 'active' | 'inactive';
}Menu
typescript
interface Menu {
menu_id: string; // mnu_xxxxx
name: string;
description?: string;
menu_type: MenuType;
hero_url?: string;
status: 'draft' | 'live' | 'archived';
display_order: number;
valid_from?: string; // ISO date
valid_to?: string; // ISO date
availability_rules?: {
days: DayOfWeek[];
time_ranges: TimeRange[];
};
channel_restrictions?: Channel[];
}
type MenuType = 'regular' | 'breakfast' | 'lunch' | 'dinner' |
'late_night' | 'happy_hour' | 'seasonal';
type DayOfWeek = 'monday' | 'tuesday' | 'wednesday' | 'thursday' |
'friday' | 'saturday' | 'sunday';
interface TimeRange {
start: string; // HH:MM
end: string; // HH:MM
}MenuItem
typescript
interface MenuItem {
item_id: string; // itm_xxxxx
name: string;
description?: string;
type: 'category' | 'subcategory' | 'item';
parent_id?: string;
base_price?: number; // For type: 'item'
hero_url?: string;
gallery_images?: GalleryImage[];
nutritional_info?: NutritionalInfo;
allergens?: Allergen[];
dietary_tags?: DietaryTag[];
tags?: string[];
prep_time_minutes?: number;
station_routing?: string[];
is_featured: boolean;
is_86d: boolean;
display_order: number;
status: 'draft' | 'live';
children?: MenuItem[]; // For categories
option_groups?: OptionGroup[]; // For items
}
interface GalleryImage {
url: string;
alt_text?: string;
caption?: string;
sort_order: number;
}
interface NutritionalInfo {
calories?: number;
protein?: number;
carbs?: number;
fat?: number;
sodium?: number;
sugar?: number;
}
type Allergen = 'dairy' | 'eggs' | 'fish' | 'shellfish' |
'tree_nuts' | 'peanuts' | 'wheat' | 'gluten' |
'soy' | 'sesame';
type DietaryTag = 'vegetarian' | 'vegan' | 'gluten-free' |
'keto' | 'halal' | 'kosher';OptionGroup
typescript
interface OptionGroup {
option_group_id: string; // opg_xxxxx
name: string;
display_name?: string;
description?: string;
is_required: boolean;
min_selections: number;
max_selections: number;
sort_order: number;
modifiers: Modifier[];
}
interface Modifier {
modifier_id: string; // mod_xxxxx
name: string;
price_adjustment: number;
price_adjustment_type?: 'fixed' | 'percentage';
is_default: boolean;
sort_order: number;
}Order
typescript
interface Order {
order_id: string; // ord_xxxxx
order_number: string;
location_id: string;
kiosk_id?: string;
channel: Channel;
customer: CustomerInfo;
order_type: OrderType;
status: OrderStatus;
scheduled_at?: string;
placed_at: string;
confirmed_at?: string;
started_at?: string;
ready_at?: string;
completed_at?: string;
estimated_ready_time?: string;
totals: OrderTotals;
items: OrderItem[];
special_instructions?: string;
delivery_info?: DeliveryInfo;
payment_info?: PaymentInfo;
}
interface CustomerInfo {
name: string;
phone: string;
email?: string;
}
interface OrderTotals {
subtotal: number;
tax: number;
tax_breakdown?: TaxLine[];
deposits?: number;
fees?: Fee[];
delivery_fee?: number;
tip?: number;
total: number;
}
interface TaxLine {
name: string;
rate: number;
amount: number;
}
interface Fee {
name: string;
amount: number;
}
interface OrderItem {
item_id: string;
name: string;
quantity: number;
unit_price: number;
line_total: number;
status?: ItemStatus;
customizations?: string[];
special_instructions?: string;
}
interface DeliveryInfo {
address: string;
address_line2?: string;
city: string;
state: string;
zip: string;
instructions?: string;
}
// `order_type` is normalized to one of these canonical values.
// Input aliases are also accepted (e.g. `pickup` -> `takeout`,
// `dinein`/`dine-in` -> `dine_in`, `drive-thru` -> `drive_thru`).
type OrderType = 'takeout' | 'delivery' | 'dine_in' | 'drive_thru' |
'curbside' | 'catering' | 'special_order';
type OrderStatus = 'pending' | 'confirmed' | 'scheduled' | 'preparing' |
'ready' | 'out_for_delivery' | 'completed' | 'cancelled';
type ItemStatus = 'pending' | 'preparing' | 'ready';
type Channel = 'ecommerce' | 'kiosk' | 'pos' | 'mobile' | 'third_party';Kiosk
Note: the kiosk endpoints use
_hashkey-suffixed field names (e.g.kiosk_hashkey,location_hashkey), unlike the rest of the ecommerce API which uses_id. See Kiosks.
typescript
interface Kiosk {
kiosk_hashkey: string; // ksk_xxxxx
name: string;
location_hashkey: string;
status: 'active' | 'inactive' | 'maintenance';
last_heartbeat_at?: string;
config: KioskConfig;
}
interface KioskConfig {
idle_timeout_seconds: number;
order_types: OrderType[];
payment_methods: PaymentMethod[];
receipt_options: ReceiptOption[];
ui_theme: 'light' | 'dark';
language: string;
accessibility_mode: boolean;
}
type PaymentMethod = 'credit_card' | 'debit_card' | 'cash' |
'gift_card' | 'mobile_pay';
type ReceiptOption = 'print' | 'email' | 'sms' | 'none';Error Codes
Authentication Errors
| Code | HTTP | Description |
|---|---|---|
unauthorized | 401 | Missing or invalid token |
forbidden | 403 | Insufficient permissions |
token_expired | 401 | Access token has expired |
invalid_scope | 403 | Token lacks required scope |
Validation Errors
| Code | HTTP | Description |
|---|---|---|
validation_failed | 422 | Request validation failed |
invalid_hashkey | 400 | Invalid entity identifier |
missing_required_field | 400 | Required field not provided |
Business Logic Errors
| Code | HTTP | Description |
|---|---|---|
location_closed | 400 | Location not accepting orders |
item_unavailable | 400 | Item is 86'd or inactive |
invalid_modifiers | 400 | Modifier selection invalid |
minimum_not_met | 400 | Order minimum not reached |
delivery_unavailable | 400 | Delivery not available |
Payment Errors
| Code | HTTP | Description |
|---|---|---|
card_declined | 400 | Payment card declined |
insufficient_funds | 400 | Insufficient funds |
payment_failed | 400 | General payment failure |
presentment_expired | 400 | Payment presentment expired |
Rate Limiting
| Code | HTTP | Description |
|---|---|---|
rate_limit_exceeded | 429 | Too many requests |
Rate Limit Headers
All responses include rate limit information:
X-RateLimit-Limit: 100
X-RateLimit-Remaining: 95
X-RateLimit-Reset: 1705330800| Header | Description |
|---|---|
X-RateLimit-Limit | Maximum requests per window |
X-RateLimit-Remaining | Requests remaining |
X-RateLimit-Reset | Unix timestamp when limit resets |
Changelog
| Date | Change |
|---|---|
| 2026-06-17 | Aligned entity ID field names to the _id convention used by the API; corrected the OrderType and Channel enums and the DeliveryInfo shape; noted that kiosk endpoints use _hashkey-suffixed names. |
| 2026-01-15 | Initial publication. |