API Contract -- Cacao Ixchel
This document defines the REST API contract. The frontend builds against this contract. The backend implements it. Both sides agree on request and response shapes, status codes, and error formats.
1. Inventory Endpoints
GET /api/inventory
Returns all inventory items, optionally filtered by stage or farm.
Query parameters:
stage(optional): "fermenting" | "drying" | "roasting" | "finished"farm(optional): farm ID
Response (200 OK):
{
items: Array<{
id: string;
farm_name: string;
farm_id: string;
cacao_variety: string;
stage: "fermenting" | "drying" | "roasting" | "finished";
weight_kg: number;
harvest_date: string; // ISO 8601
product_count: number; // number of products from this batch
available_count: number; // products not allocated to orders
}>
}
GET /api/inventory/:id
Returns a single inventory item with batch traceability.
Response (200 OK):
{
id: string;
farm: { id: string; name: string; location: string; cacao_variety: string; };
stage: string;
weight_kg: number;
harvest_date: string;
flavour_profile: string | null;
products: Array<{
id: string;
name: string;
type: "bar" | "drinking_chocolate" | "nibs";
quantity_available: number;
allocated_to: string | null; // order customer name or null
}>
}
Response (404 Not Found):
{ error: "Batch not found" }
PUT /api/inventory/:id
Updates the stage of a batch. Valid transitions: fermenting -> drying -> roasting -> finished. Reverse transitions are not allowed.
Request body:
{ stage: "fermenting" | "drying" | "roasting" | "finished" }
Response (200 OK):
{ id: string; stage: string; updated_at: string; }
Response (400 Bad Request):
{ error: "Invalid stage transition", current: string, requested: string }
2. Order Endpoints
POST /api/orders
Creates a new order with product allocation. Prevents double-allocation: if any requested product is already allocated to another order, the request fails.
Request body:
{
customer_name: string; // required, non-empty, no whitespace-only
country: string; // required
products: Array<{ product_id: string; quantity: number; }> // quantity must be positive integer
}
Response (201 Created):
{
id: string;
customer_name: string;
country: string;
status: "confirmed";
products: Array<{ id: string; name: string; }>;
created_at: string;
}
Response (400 Bad Request):
{ error: string; field?: string; }
Examples: "Customer name is required" (field: "customer_name"), "Quantity must be a positive integer" (field: "products")
Response (409 Conflict):
{ error: "Product already allocated", product_id: string; allocated_to: string; }
GET /api/orders
Returns all orders, optionally filtered by status.
Query parameters:
status(optional): "confirmed" | "in_production" | "shipped" | "delivered"
Response (200 OK):
{
orders: Array<{
id: string;
customer_name: string;
country: string;
status: "confirmed" | "in_production" | "shipped" | "delivered";
product_count: number;
created_at: string;
}>
}
PUT /api/orders/:id/status
Updates an order's status.
Request body:
{ status: "confirmed" | "in_production" | "shipped" | "delivered" }
Response (200 OK):
{ id: string; status: string; updated_at: string; }
Response (404 Not Found):
{ error: "Order not found" }