Every endpoint is rate-limited (300 req/min) and idempotent for mutations via the Idempotency-Key header. Webhook payloads are signed with HMAC-SHA256 and must be verified before processing.
Orders
POST
/v1/ordersCreate an order from a room
Request
curl -X POST https://api.castille.mt/v1/orders \
-H "Authorization: Bearer sk_live_••••" \
-H "Content-Type: application/json" \
-d '{"roomNumber":204,"category":"fnb","items":[{"id":"m1","qty":1}]}'Response 200
{
"id": "o_3RAa9",
"status": "pending",
"total": 28,
"paymentUrl": "https://pay.stripe.com/..."
}GET
/v1/orders?status=pendingList orders with optional filters
Request
curl https://api.castille.mt/v1/orders?status=pending \ -H "Authorization: Bearer sk_live_••••"
Response 200
{
"data": [
{
"id": "o_3RAa9",
"roomNumber": 204,
"category": "fnb",
"status": "pending",
"total": 28
}
],
"page": 1,
"total": 24
}GET
/v1/orders/{id}Retrieve a single order
Request
curl https://api.castille.mt/v1/orders/{id} \
-H "Authorization: Bearer sk_live_••••"Response 200
{
"id": "o_3RAa9",
"status": "preparing",
"items": [
{
"name": "Lampuki",
"qty": 1
}
]
}PATCH
/v1/orders/{id}Update status
Request
curl -X PATCH https://api.castille.mt/v1/orders/{id} \
-H "Authorization: Bearer sk_live_••••" \
-H "Content-Type: application/json" \
-d '{"status":"preparing"}'Response 200
{
"id": "o_3RAa9",
"status": "preparing"
}DELETE
/v1/orders/{id}Cancel and refund order
Request
curl https://api.castille.mt/v1/orders/{id} \
-H "Authorization: Bearer sk_live_••••"Response 200
{
"id": "o_3RAa9",
"status": "cancelled",
"refundId": "re_1NaB2"
}Menu & Catalog
GET
/v1/menu?category=fnbList active menu items by category
Request
curl https://api.castille.mt/v1/menu?category=fnb \ -H "Authorization: Bearer sk_live_••••"
Response 200
{
"data": [
{
"id": "m1",
"name": "Lampuki alla Maltija",
"price": 28,
"available": true
}
]
}POST
/v1/menuAdd menu item (admin)
Request
curl -X POST https://api.castille.mt/v1/menu \
-H "Authorization: Bearer sk_live_••••" \
-H "Content-Type: application/json" \
-d '{"name":"New dish","category":"fnb","price":24,"available":true}'Response 200
{
"id": "m_new",
"created": true
}PATCH
/v1/menu/{id}Update availability or price
Request
curl -X PATCH https://api.castille.mt/v1/menu/{id} \
-H "Authorization: Bearer sk_live_••••" \
-H "Content-Type: application/json" \
-d '{"available":false}'Response 200
{
"id": "m1",
"available": false
}Rooms
GET
/v1/roomsList rooms with QR URLs
Request
curl https://api.castille.mt/v1/rooms \ -H "Authorization: Bearer sk_live_••••"
Response 200
{
"data": [
{
"id": "r1",
"number": 101,
"qrUrl": "https://castille.app/room/101"
}
]
}GET
/v1/rooms/{number}/qr.pngDownload a room's QR code as PNG (1024×1024)
Request
curl https://api.castille.mt/v1/rooms/{number}/qr.png \
-H "Authorization: Bearer sk_live_••••"Response 200
{
"contentType": "image/png",
"size": "12kb"
}Notifications
POST
/v1/notify/whatsappInternal — send a templated WhatsApp message
Request
curl -X POST https://api.castille.mt/v1/notify/whatsapp \
-H "Authorization: Bearer sk_live_••••" \
-H "Content-Type: application/json" \
-d '{"to":"+356 9900 2201","template":"order_received","vars":{"room":204,"total":"€28"}}'Response 200
{
"messageId": "wamid.abc",
"status": "queued"
}POST
/v1/notify/emailInternal — send transactional email
Request
curl -X POST https://api.castille.mt/v1/notify/email \
-H "Authorization: Bearer sk_live_••••" \
-H "Content-Type: application/json" \
-d '{"to":"owner@castille.mt","template":"new_order","vars":{"id":"o_3RAa9"}}'Response 200
{
"messageId": "em_1Nx",
"status": "sent"
}Payments
POST
/v1/payments/checkoutCreate Stripe Checkout session for an order
Request
curl -X POST https://api.castille.mt/v1/payments/checkout \
-H "Authorization: Bearer sk_live_••••" \
-H "Content-Type: application/json" \
-d '{"orderId":"o_3RAa9"}'Response 200
{
"url": "https://checkout.stripe.com/c/pay/cs_test_..."
}POST
/v1/webhooks/stripeWebhook — signed event from Stripe
Request
curl https://api.castille.mt/v1/webhooks/stripe \ -H "Authorization: Bearer sk_live_••••"
Response 200
{
"received": true
}