{"components":{"responses":{"BadRequest":{"content":{"application/json":{"example":{"error":"Invalid request","status":"error"},"schema":{"$ref":"#/components/schemas/StandardError"}},"text/plain":{"schema":{"type":"string"}}},"description":"Invalid or missing request data."},"Conflict":{"content":{"application/json":{"example":{"error":"Conflict"},"schema":{"$ref":"#/components/schemas/StandardError"}},"text/plain":{"schema":{"type":"string"}}},"description":"Request conflicts with existing state."},"Error":{"content":{"application/json":{"example":{"error":"Error"},"schema":{"$ref":"#/components/schemas/StandardError"}},"text/plain":{"schema":{"type":"string"}}},"description":"Endpoint-specific error response."},"Forbidden":{"content":{"application/json":{"example":{"error":"Forbidden"},"schema":{"$ref":"#/components/schemas/StandardError"}},"text/plain":{"schema":{"type":"string"}}},"description":"Authenticated user does not have permission for the requested resource."},"InternalServerError":{"content":{"application/json":{"example":{"error":"Internal server error","status":"error"},"schema":{"$ref":"#/components/schemas/StandardError"}},"text/plain":{"schema":{"type":"string"}}},"description":"Unexpected server error."},"NotFound":{"content":{"application/json":{"example":{"error":"Not found"},"schema":{"$ref":"#/components/schemas/StandardError"}},"text/plain":{"schema":{"type":"string"}}},"description":"Requested resource was not found."},"NotImplemented":{"content":{"application/json":{"example":{"error":{"code":"NOT_IMPLEMENTED","message":"This combination is not yet implemented"}},"schema":{"$ref":"#/components/schemas/StandardError"}},"text/plain":{"schema":{"type":"string"}}},"description":"The requested metric or combination is not implemented."},"ServiceUnavailable":{"content":{"application/json":{"example":{"error":"Service unavailable"},"schema":{"$ref":"#/components/schemas/StandardError"}},"text/plain":{"schema":{"type":"string"}}},"description":"Required service dependency is unavailable."},"Unauthorized":{"content":{"application/json":{"example":{"msg":"unauthorized"},"schema":{"$ref":"#/components/schemas/StandardError"}},"text/plain":{"schema":{"type":"string"}}},"description":"Missing, invalid or expired authentication token."},"ValidationError":{"content":{"application/json":{"example":{"error":"Validation error"},"schema":{"$ref":"#/components/schemas/StandardError"}},"text/plain":{"schema":{"type":"string"}}},"description":"Request validation failed."}},"schemas":{"AdminResponse":{"type":"object"},"CalendarAddBooking":{"properties":{"booking":{"$ref":"#/components/schemas/CalendarBooking"},"date":{"description":"Booking date/time accepted by dateutil parser. Prefer ISO-8601.","type":"string"},"type":{"enum":["bookings"],"type":"string"}},"required":["type","date","booking"],"type":"object"},"CalendarAddEmployeesSchedule":{"properties":{"date":{"description":"Optional common date. If omitted, each schedule item must carry its own date.","type":"string"},"employeesSchedule":{"items":{"$ref":"#/components/schemas/EmployeeScheduleInput"},"type":"array"},"repetition":{"description":"Optional recurrence type.","type":"string"},"repetitionEnd":{"description":"Required when repetition is sent.","type":"string"},"type":{"enum":["employeesSchedule"],"type":"string"}},"required":["type","employeesSchedule"],"type":"object"},"CalendarAddEvent":{"properties":{"date":{"description":"Event start date/time.","type":"string"},"endDate":{"description":"Event end date/time.","type":"string"},"eventType":{"type":"string"},"impact":{"description":"Possible impact score saved as possibleImpact.","type":"number"},"location":{"type":"string"},"name":{"type":"string"},"repetition":{"type":"string"},"repetitionEnd":{"type":"string"},"type":{"enum":["events"],"type":"string"}},"required":["type","date","endDate","name","location","impact","eventType"],"type":"object"},"CalendarAddMovieSession":{"properties":{"movie_id":{"type":"string"},"revenue":{"type":"number"},"room":{"type":"string"},"session_datetime":{"format":"date-time","type":"string"},"tickets_sold":{"type":"integer"},"type":{"enum":["moviesSessions"],"type":"string"}},"required":["type","session_datetime"],"type":"object"},"CalendarAddPromotion":{"properties":{"promotion":{"$ref":"#/components/schemas/CalendarPromotion"},"type":{"enum":["promotions"],"type":"string"}},"required":["type","promotion"],"type":"object"},"CalendarAddRequest":{"discriminator":{"mapping":{"bookings":"#/components/schemas/CalendarAddBooking","employeesSchedule":"#/components/schemas/CalendarAddEmployeesSchedule","events":"#/components/schemas/CalendarAddEvent","medicalAbsence":"#/components/schemas/CalendarAddVacation","moviesSessions":"#/components/schemas/CalendarAddMovieSession","promotions":"#/components/schemas/CalendarAddPromotion","restaurantSchedule":"#/components/schemas/CalendarAddRestaurantSchedule","specialDishes":"#/components/schemas/CalendarAddSpecialDishes","vacations":"#/components/schemas/CalendarAddVacation"},"propertyName":"type"},"oneOf":[{"$ref":"#/components/schemas/CalendarAddSpecialDishes"},{"$ref":"#/components/schemas/CalendarAddBooking"},{"$ref":"#/components/schemas/CalendarAddVacation"},{"$ref":"#/components/schemas/CalendarAddRestaurantSchedule"},{"$ref":"#/components/schemas/CalendarAddEmployeesSchedule"},{"$ref":"#/components/schemas/CalendarAddEvent"},{"$ref":"#/components/schemas/CalendarAddPromotion"},{"$ref":"#/components/schemas/CalendarAddMovieSession"}]},"CalendarAddRestaurantSchedule":{"properties":{"closed":{"description":"When true, stores the day as closed and opening hours are ignored.","type":"boolean"},"date":{"description":"Schedule date. Prefer YYYY-MM-DD.","type":"string"},"repetition":{"description":"Optional recurrence type consumed by getRepetitionDates.","type":"string"},"repetitionEnd":{"description":"Required when repetition is sent.","type":"string"},"restaurantClosing":{"example":"15:00","type":"string"},"restaurantClosingAfterBreak":{"example":"23:00","nullable":true,"type":"string"},"restaurantOpening":{"example":"09:00","type":"string"},"restaurantOpeningAfterBreak":{"example":"18:00","nullable":true,"type":"string"},"type":{"enum":["restaurantSchedule"],"type":"string"}},"required":["type","date"],"type":"object"},"CalendarAddSpecialDishes":{"properties":{"date":{"description":"Date accepted by dateutil parser. Prefer YYYY-MM-DD.","type":"string"},"specialDishes":{"items":{"$ref":"#/components/schemas/CalendarSpecialDishInput"},"type":"array"},"type":{"enum":["specialDishes"],"type":"string"}},"required":["type","date","specialDishes"],"type":"object"},"CalendarAddVacation":{"properties":{"date":{"description":"Initial absence date. Prefer YYYY-MM-DD.","type":"string"},"employeeId":{"type":"string"},"finalDate":{"description":"Final absence date. Prefer YYYY-MM-DD.","type":"string"},"type":{"enum":["vacations","medicalAbsence"],"type":"string"}},"required":["type","employeeId","date","finalDate"],"type":"object"},"CalendarBooking":{"properties":{"clients_amount":{"type":"integer"},"date":{"format":"date-time","type":"string"},"id":{"type":"string"},"name":{"type":"string"},"nr_persons":{"description":"Some clients send number of persons with this field.","type":"integer"},"restaurant_id":{"readOnly":true,"type":"string"},"source":{"type":"string"},"status":{"type":"string"}},"type":"object"},"CalendarEvent":{"properties":{"eventFinalDate":{"format":"date-time","type":"string"},"eventInitialDate":{"format":"date-time","type":"string"},"eventType":{"type":"string"},"id":{"type":"string"},"location":{"type":"string"},"name":{"type":"string"},"possibleImpact":{"type":"number"}},"type":"object"},"CalendarPromotion":{"properties":{"_id":{"readOnly":true,"type":"string"},"discount":{"type":"number"},"endDate":{"description":"Promotion end date.","type":"string"},"id_":{"description":"Used by update flows.","type":"string"},"name":{"type":"string"},"products":{"items":{"type":"string"},"type":"array"},"startDate":{"description":"Promotion start date.","type":"string"}},"required":["startDate","endDate"],"type":"object"},"CalendarPublicResponse":{"properties":{"bookings":{"items":{"$ref":"#/components/schemas/CalendarBooking"},"type":"array"},"events":{"items":{"$ref":"#/components/schemas/CalendarEvent"},"type":"array"},"fixtures":{"items":{"type":"object"},"type":"array"},"general_events":{"items":{"type":"object"},"type":"array"},"holidays":{"items":{"type":"object"},"type":"array"},"movies":{"type":"object"},"moviesRevenue":{"type":"object"},"moviesSessions":{"type":"object"},"promotions":{"items":{"$ref":"#/components/schemas/CalendarPromotion"},"type":"array"},"restaurantSchedule":{"items":{"$ref":"#/components/schemas/CalendarRestaurantSchedule"},"type":"array"},"school_holidays":{"items":{"type":"object"},"type":"array"},"specialDishes":{"items":{"$ref":"#/components/schemas/CalendarSpecialDish"},"type":"array"},"strikes":{"items":{"type":"object"},"type":"array"},"weather":{"items":{"type":"object"},"type":"array"}},"type":"object"},"CalendarResponse":{"properties":{"value":{"oneOf":[{"$ref":"#/components/schemas/CalendarTeamResponse"},{"$ref":"#/components/schemas/CalendarPublicResponse"}]}},"type":"object"},"CalendarRestaurantSchedule":{"properties":{"closed":{"type":"boolean"},"date":{"format":"date","type":"string"},"finalHour":{"nullable":true,"type":"string"},"finalHourAfterLunch":{"nullable":true,"type":"string"},"initialHour":{"nullable":true,"type":"string"},"initialHourAfterLunch":{"nullable":true,"type":"string"}},"type":"object"},"CalendarSpecialDish":{"properties":{"date":{"format":"date","type":"string"},"price":{"type":"number"},"product_id":{"type":"string"}},"type":"object"},"CalendarSpecialDishInput":{"properties":{"price":{"type":"number"},"product_id":{"description":"Existing product id, or `{name}` to create a new product before adding the dish.","oneOf":[{"type":"string"},{"type":"integer"},{"properties":{"name":{"type":"string"}},"required":["name"],"type":"object"}]}},"required":["product_id","price"],"type":"object"},"CalendarTeamResponse":{"properties":{"employeesSchedule":{"description":"Employee schedules keyed by date and employee id when `team=true`.","type":"object"},"vacations":{"items":{"type":"object"},"type":"array"}},"type":"object"},"ClientsAndForecastResponse":{"description":"Date-keyed client sales, forecasts and objectives grouped by meal.","type":"object"},"ComissionRule":{"properties":{"_id":{"readOnly":true,"type":"string"},"awardsRanking":{"items":{"$ref":"#/components/schemas/ComissionRuleScale"},"type":"array"},"comission_type":{"enum":["fixed_value","percentage"],"type":"string"},"comission_value":{"oneOf":[{"type":"number"},{"type":"string"}]},"consider_all_sales":{"type":"boolean"},"consider_menu_products":{"type":"boolean"},"consider_tax":{"type":"boolean"},"created_at":{"readOnly":true,"type":"string"},"dates":{"items":{"$ref":"#/components/schemas/ComissionRuleDate"},"type":"array"},"day_of_month":{"items":{"type":"integer"},"type":"array"},"description":{"type":"string"},"distribution_period":{"enum":["day","week","month","date_intervals"],"type":"string"},"distribution_type":{"enum":["revenue_percentage","worked_hours","even_all_employees","ranking"],"type":"string"},"employeesWithComissionsLimit":{"type":"integer"},"employees_type":{"items":{"type":"string"},"type":"array"},"families":{"items":{"type":"string"},"type":"array"},"filter_by_hour":{"type":"boolean"},"ignore_products":{"items":{"type":"string"},"type":"array"},"kpi":{"type":"string"},"kpiReferenceValue":{"oneOf":[{"type":"number"},{"type":"string"}]},"kpisGroupValue":{"type":"boolean"},"meal":{"type":"string"},"minimumTickets":{"type":"number"},"objectives":{"properties":{"date_intervals":{"$ref":"#/components/schemas/ComissionRuleObjective"},"day":{"$ref":"#/components/schemas/ComissionRuleObjective"},"month":{"$ref":"#/components/schemas/ComissionRuleObjective"},"week":{"$ref":"#/components/schemas/ComissionRuleObjective"}},"type":"object"},"products":{"items":{"type":"string"},"type":"array"},"quantityMetric":{"enum":["quantity","ticket_count"],"type":"string"},"ranking":{"type":"boolean"},"restaurants":{"items":{"type":"string"},"type":"array"},"rule_type":{"enum":["comission_per_unit_sold_employee","comission_per_unit_sold_team","comission_per_kpi_employee","comission_per_kpi_team","revenue_distribution"],"type":"string"},"scales_period":{"enum":["no_values_scale","day","week","month","date_intervals"],"type":"string"},"subfamilies":{"items":{"type":"string"},"type":"array"},"tablesFilter":{"items":{"type":"string"},"type":"array"},"updated_at":{"readOnly":true,"type":"string"},"values_scale":{"items":{"$ref":"#/components/schemas/ComissionRuleScale"},"type":"array"},"week_days":{"items":{"oneOf":[{"type":"integer"},{"type":"string"}]},"type":"array"}},"required":["description","rule_type","restaurants","dates"],"type":"object"},"ComissionRuleDate":{"properties":{"finalDate":{"description":"End of the rule interval. Any date-time accepted by the API date parser may be used.","example":"2026-07-31","type":"string"},"initialDate":{"description":"Beginning of the rule interval. Any date-time accepted by the API date parser may be used.","example":"2026-07-01","type":"string"},"limitedHours":{"description":"Start and end hours used when `filter_by_hour` is true.","example":[9,24],"items":{"type":"number"},"maxItems":2,"minItems":2,"type":"array"}},"required":["initialDate","finalDate"],"type":"object"},"ComissionRuleObjective":{"properties":{"active":{"type":"boolean"},"comission_type":{"enum":["fixed_value","percentage","multiply_comissions","objective_ratio"],"type":"string"},"comission_value":{"oneOf":[{"type":"number"},{"type":"string"}]},"consider_tax_objective":{"type":"boolean"},"distribution_type":{"enum":["revenue_percentage","worked_hours","even_all_employees"],"type":"string"},"minimum_percentage":{"type":"number"},"objective":{"oneOf":[{"type":"number"},{"type":"string"}]},"objective_type":{"enum":["static_objective","dynamic","percentage_comparison_homologous"],"type":"string"}},"type":"object"},"ComissionRuleScale":{"properties":{"distribution_type":{"enum":["revenue_percentage","worked_hours","even_all_employees"],"type":"string"},"max":{"type":"number"},"min":{"type":"number"},"type":{"enum":["fixed_value","percentage"],"type":"string"},"value":{"type":"number"}},"type":"object"},"ComissionRulesResponse":{"properties":{"rules":{"items":{"$ref":"#/components/schemas/ComissionRule"},"type":"array"}},"required":["rules"],"type":"object"},"CommissionsResponse":{"oneOf":[{"description":"When `restaurantId` contains multiple comma-separated restaurants, keys are restaurant ids.","type":"object"},{"description":"Returned for a single restaurant when `simple=true`.","items":{"$ref":"#/components/schemas/SimpleCommission"},"type":"array"}]},"CurrentRestaurantRequest":{"properties":{"id":{"description":"Restaurant id to make current for the authenticated user.","type":"string"},"isEmployee":{"default":false,"description":"When true, updates employeeCurrentRestaurant instead of currentRestaurant.","type":"boolean"}},"required":["id"],"type":"object"},"CustomerBooking":{"properties":{"created_at":{"format":"date-time","type":"string"},"date":{"format":"date-time","type":"string"},"id":{"type":"string"},"name":{"example":"Jane Doe","type":"string"},"nr_persons":{"example":4,"type":"integer"},"source":{"example":"OpenTable","type":"string"},"status":{"default":"confirmed","type":"string"},"store_id":{"example":"store-001","type":"string"}},"required":["store_id","nr_persons","date"],"type":"object"},"Employee":{"properties":{"active":{"type":"boolean"},"email":{"format":"email","nullable":true,"type":"string"},"id":{"type":"string"},"internal_id":{"type":"string"},"name":{"type":"string"},"type":{"example":"waiter","type":"string"}},"type":"object"},"EmployeeDiscountsCancelationsResponse":{"properties":{"cancelation_products":{"type":"object"},"cancelation_reasons":{"items":{"type":"string"},"type":"array"},"cancelation_tickets":{"type":"object"},"data":{"items":{"type":"object"},"type":"array"},"discount_products":{"type":"object"},"discount_reasons":{"items":{"type":"string"},"type":"array"},"discount_reasons_employees":{"type":"object"},"discount_tickets":{"type":"object"},"reasons_employees":{"type":"object"}},"type":"object"},"EmployeeSchedule":{"properties":{"absence":{"default":false,"type":"boolean"},"afterBreak_finalHour":{"example":"22:00","nullable":true,"type":"string"},"afterBreak_initialHour":{"example":"18:00","nullable":true,"type":"string"},"date":{"format":"date","type":"string"},"employee_id":{"type":"string"},"finalHour":{"example":"17:00","type":"string"},"initialHour":{"example":"09:00","type":"string"},"job":{"example":"waiter","type":"string"},"justified":{"default":false,"description":"True when an absence is justified.","type":"boolean"}},"type":"object"},"EmployeeScheduleInput":{"properties":{"absence":{"default":false,"type":"boolean"},"afterBreak_finalHour":{"example":"22:00","nullable":true,"type":"string"},"afterBreak_initialHour":{"example":"18:00","nullable":true,"type":"string"},"date":{"description":"Required when the top-level request has no date. Prefer YYYY-MM-DD.","type":"string"},"employee_id":{"description":"ADECI employee id. Required unless employee_internal_id resolves to an existing or newly created employee.","type":"string"},"employee_internal_id":{"description":"External/internal employee identifier used by imports. The API resolves or creates the employee before saving the schedule.","type":"string"},"finalHour":{"example":"17:00","type":"string"},"id":{"description":"Ignored on repeated inserts.","type":"string"},"initialHour":{"example":"09:00","type":"string"},"job":{"example":"waiter","type":"string"},"justified":{"default":false,"description":"True when an absence is justified.","type":"boolean"},"name":{"description":"Employee name used when employee_internal_id must create a missing employee.","type":"string"}},"required":["initialHour","finalHour"],"type":"object"},"EmployeesResponse":{"properties":{"employees":{"type":"object"},"logins":{"type":"object"}},"type":"object"},"ExternalDataUploadRequest":{"properties":{"api_key":{"example":"<YOUR_EXTERNAL_API_KEY>","type":"string"},"location":{"properties":{"address":{"type":"string"},"latitude":{"type":"number"},"longitude":{"type":"number"}},"type":"object"},"products_sales":{"items":{"properties":{"date":{"format":"date","type":"string"},"product_id":{"type":"string"},"sales":{"type":"integer"}},"required":["date","product_id","sales"],"type":"object"},"type":"array"},"restaurant_id":{"description":"Partner restaurant identifier. `restaurant_source_id` is also accepted.","type":"string"},"restaurant_source_id":{"type":"string"},"sales":{"items":{"properties":{"date":{"format":"date","type":"string"},"nr_persons":{"type":"integer"},"nr_sales":{"type":"integer"},"sales":{"type":"number"}},"required":["date","nr_sales","sales","nr_persons"],"type":"object"},"type":"array"},"store_name":{"example":"Demo Store","type":"string"}},"required":["api_key"],"type":"object"},"FeatureImportanceDriver":{"properties":{"feature_name":{"type":"string"},"features":{"items":{"type":"string"},"type":"array"},"percentage":{"maximum":100,"minimum":0,"type":"number"},"shap_value":{"type":"number"}},"required":["feature_name","shap_value","percentage","features"],"type":"object"},"FeatureImportanceResponse":{"properties":{"base_value":{"type":"number"},"forecast_source":{"$ref":"#/components/schemas/ForecastSource"},"pred_value":{"type":"number"},"restaurant_id":{"type":"string"},"run_versions":{"items":{"$ref":"#/components/schemas/FeatureImportanceRunVersion"},"type":"array"},"shap_values_sorted":{"items":{"$ref":"#/components/schemas/FeatureImportanceDriver"},"type":"array"}},"required":["restaurant_id","base_value","pred_value","shap_values_sorted","run_versions"],"type":"object"},"FeatureImportanceRunVersion":{"properties":{"created_at":{"format":"date-time","nullable":true,"type":"string"},"date":{"format":"date","type":"string"},"is_forecast":{"enum":[0,1],"nullable":true,"type":"integer"},"meal":{"type":"string"},"run_version":{"nullable":true,"type":"string"}},"required":["date","meal"],"type":"object"},"Forecast":{"properties":{"date":{"format":"date","type":"string"},"forecast":{"type":"number"},"meal":{"nullable":true,"type":"string"},"product_id":{"nullable":true,"type":"string"},"restaurant_id":{"nullable":true,"type":"string"}},"type":"object"},"ForecastSource":{"description":"Authoritative forecast storage selected by the restaurant plan. The shift-pro plan uses per-meal revenue forecasts.","properties":{"aggregate_per_meal":{"type":"boolean"},"forecast_by_meal":{"type":"boolean"},"mode":{"enum":["all_day","per_meal"],"type":"string"}},"required":["mode","forecast_by_meal","aggregate_per_meal"],"type":"object"},"HRPlanningResponse":{"properties":{"daily":{"type":"object"},"employee_hours":{"type":"object"},"hourly":{"type":"object"},"meal":{"type":"object"}},"type":"object"},"HourlyClientsAndForecastResponse":{"description":"Date-keyed hourly forecast data. Each date contains meal keys, each meal contains integer time-slot keys, and each slot contains a forecast plus the requested actual metric.","type":"object"},"HourlyForecastSlot":{"description":"Forecast and actual value for one time slot. Exactly one of `clients`, `tickets`, `revenue` or `products` is returned, matching the requested metric.","properties":{"clients":{"description":"Actual clients served.","type":"number"},"forecast":{"nullable":true,"type":"number"},"products":{"description":"Actual product quantity sold.","type":"number"},"revenue":{"description":"Actual monetary revenue.","type":"number"},"tickets":{"description":"Actual ticket or transaction count.","type":"number"}},"required":["forecast"],"type":"object"},"Ingredient":{"properties":{"family":{"nullable":true,"type":"string"},"id":{"type":"string"},"name":{"type":"string"},"unit":{"nullable":true,"type":"string"}},"type":"object"},"IntegrationMapping":{"properties":{"adeci_store_id":{"type":"string"},"needsCreation":{"default":false,"type":"boolean"},"workplace_id":{"type":"string"},"workplace_name":{"type":"string"}},"type":"object"},"KpiByDateResponse":{"properties":{"data":{"type":"object"},"metadata":{"type":"object"},"value":{"type":"object"}},"type":"object"},"KpiIndicatorData":{"description":"A numerator-only indicator returns a sum. Adding denominator returns a ratio.","properties":{"denominator":{"$ref":"#/components/schemas/KpiIndicatorTerm"},"numerator":{"$ref":"#/components/schemas/KpiIndicatorTerm"}},"required":["numerator"],"type":"object"},"KpiIndicatorTerm":{"properties":{"families":{"items":{"type":"string"},"type":"array"},"metric":{"enum":["units","revenue"],"type":"string"},"parameter":{"enum":["products","revenue","customers","clients","tickets","bookings","bookingCovers"],"type":"string"},"products":{"items":{"oneOf":[{"type":"string"},{"properties":{"id":{"type":"string"},"name":{"type":"string"}},"required":["id"],"type":"object"}]},"type":"array"},"subfamilies":{"items":{"type":"string"},"type":"array"},"tax":{"default":true,"type":"boolean"}},"type":"object"},"KpiOnDemandRequest":{"properties":{"indicator_data":{"$ref":"#/components/schemas/KpiIndicatorData"}},"required":["indicator_data"],"type":"object"},"KpiValueResponse":{"properties":{"data":{"properties":{"kpi_name":{"type":"string"},"value":{"oneOf":[{"type":"number"},{"type":"object"},{"items":{"type":"object"},"type":"array"}]}},"type":"object"},"metadata":{"type":"object"}},"type":"object"},"LoginRequest":{"properties":{"email":{"example":"client@example.com","format":"email","type":"string"},"password":{"example":"<YOUR_PASSWORD>","format":"password","type":"string"}},"required":["email","password"],"type":"object"},"LoginResponse":{"properties":{"access_token":{"description":"JWT access token. Use as `Authorization: Bearer <YOUR_API_TOKEN>`.","type":"string"},"refresh_token":{"description":"JWT refresh token for `/users/renewToken`.","type":"string"},"user":{"$ref":"#/components/schemas/User"}},"required":["access_token","refresh_token","user"],"type":"object"},"MetricsErrorEnvelope":{"properties":{"code":{"example":"INVALID_COMBINATION","type":"string"},"details":{"type":"object"},"message":{"example":"Unsupported query combination","type":"string"}},"required":["code","message"],"type":"object"},"MetricsResponse":{"properties":{"data":{"oneOf":[{"type":"object"},{"items":{"type":"object"},"type":"array"}]},"metadata":{"type":"object"}},"type":"object"},"Pagination":{"properties":{"page":{"minimum":0,"type":"integer"},"rowsPerPage":{"minimum":1,"type":"integer"}},"type":"object"},"Product":{"properties":{"active":{"type":"boolean"},"family":{"nullable":true,"type":"string"},"internal_id":{"type":"string"},"name":{"type":"string"},"price":{"nullable":true,"type":"number"},"subfamily":{"nullable":true,"type":"string"}},"type":"object"},"ProductSalesAndForecastResponse":{"properties":{"values":{"$ref":"#/components/schemas/SalesAndForecastByDate"}},"type":"object"},"ProductSalesByTypeResponse":{"type":"object"},"Restaurant":{"properties":{"_id":{"type":"string"},"brand":{"nullable":true,"type":"string"},"country":{"example":"PT","type":"string"},"integrations":{"type":"object"},"latitude":{"nullable":true,"type":"number"},"location":{"example":"Lisbon","type":"string"},"longitude":{"nullable":true,"type":"number"},"meals":{"type":"object"},"name":{"example":"ADECI Demo Restaurant","type":"string"},"profiles":{"type":"object"},"schedule":{"type":"object"},"tax":{"type":"boolean"}},"type":"object"},"Sale":{"properties":{"employee":{"nullable":true,"type":"string"},"id":{"type":"string"},"initialTime":{"format":"date-time","type":"string"},"products":{"items":{"type":"object"},"type":"array"},"restaurant_id":{"type":"string"},"table":{"nullable":true,"type":"string"},"total":{"type":"number"},"total_without_tax":{"type":"number"}},"type":"object"},"SalesAndForecastByDate":{"type":"object"},"SalesByTypeResponse":{"type":"object"},"SalesForecastDay":{"properties":{"events":{"items":{"type":"object"},"type":"array"},"forecast":{"nullable":true,"oneOf":[{"type":"number"},{"type":"object"}]},"internalConsumption":{"nullable":true,"oneOf":[{"type":"number"},{"type":"object"}]},"sales":{"nullable":true,"oneOf":[{"type":"number"},{"type":"object"}]}},"type":"object"},"SimpleCommission":{"properties":{"commission":{"type":"number"},"employee_id":{"type":"string"},"quantity":{"type":"number"},"rule_id":{"type":"string"}},"required":["employee_id","rule_id","commission","quantity"],"type":"object"},"StandardError":{"properties":{"error":{"oneOf":[{"type":"string"},{"$ref":"#/components/schemas/MetricsErrorEnvelope"}]},"msg":{"example":"unauthorized","type":"string"},"status":{"example":"error","type":"string"}},"type":"object"},"StandardSuccess":{"properties":{"status":{"example":"success","type":"string"}},"type":"object"},"TokenRefreshResponse":{"allOf":[{"$ref":"#/components/schemas/LoginResponse"}]},"User":{"properties":{"currentRestaurant":{"nullable":true,"type":"string"},"email":{"format":"email","type":"string"},"id":{"type":"string"},"locale":{"example":"pt","type":"string"},"name":{"type":"string"},"restaurants":{"items":{"$ref":"#/components/schemas/Restaurant"},"type":"array"}},"type":"object"},"ValidationError":{"properties":{"error":{"example":"Missing required fields: email","type":"string"}},"type":"object"},"WrappedSalesAndForecastByDate":{"properties":{"forecast_source":{"$ref":"#/components/schemas/ForecastSource"},"value":{"$ref":"#/components/schemas/SalesAndForecastByDate"}},"type":"object"}},"securitySchemes":{"BearerAuth":{"bearerFormat":"JWT","description":"ADECI access JWT. Header format: `Authorization: Bearer <YOUR_API_TOKEN>`.","scheme":"bearer","type":"http"},"ExternalApiKeyQuery":{"description":"External partner API key for `/external` GET endpoints.","in":"query","name":"api_key","type":"apiKey"},"RefreshBearerAuth":{"bearerFormat":"JWT","description":"ADECI refresh JWT used by `/users/renewToken`.","scheme":"bearer","type":"http"}}},"info":{"description":"Client-facing OpenAPI documentation for the ADECI Flask API.\n\nThe specification is generated dynamically from the Flask route\nmap and route source metadata, then restricted to the\nclient-approved public integration surface. The exposed routes\nare enriched with reusable schemas and examples for\nauthentication, calendar use cases, commissions, KPI queries,\nforecasts, employees, sales and product-sales analytics.\n\nAuthentication uses JWT bearer tokens for ADECI application\nendpoints. Send the access token returned by /users/login as:\n\nAuthorization: Bearer <YOUR_API_TOKEN>\n\nAccess tokens are configured to expire after 4 hours. Use\n/users/renewToken with the refresh token when a new access token\nis needed. The application also appends fresh tokens to many\nauthenticated JSON responses via an after_request hook.","title":"ADECI API","version":"1.0.0"},"openapi":"3.0.3","paths":{"/api/v2/kpi/by-date/{indicator}":{"get":{"description":"Summary:\n    Return KPI values by date for the current restaurant.\n\nPath Parameters:\n    - indicator (str): KPI/indicator key to calculate.\n\nAuthentication: required. Send `Authorization: Bearer <YOUR_API_TOKEN>`.\n\n**Query Parameters**\n1. `start_date` (optional, string/date)\n2. `end_date` (optional, string/date)\n3. `period` (optional, string)\n4. `meal` (optional, string)\n5. `time_group_by` (optional, string; allowed: daily, monthly, yearly; default: daily)\n6. `tax` (optional, boolean)\n7. `table_id` (optional, string)\n8. `group_by_table` (optional, boolean)\n\n**Responses**\n1. `200`: KPI time series.\n2. `400`: Invalid request or missing required data.\n3. `401`: Missing, invalid or expired authentication token.\n4. `404`: Requested resource was not found.\n5. `500`: Unexpected server error.","operationId":"get_kpi_get_kpi_by_date","parameters":[{"description":"JWT token header. Use the access token from /users/login unless the endpoint requires a refresh token.","in":"header","name":"Authorization","required":true,"schema":{"example":"Bearer <YOUR_API_TOKEN>","type":"string"}},{"in":"path","name":"indicator","required":true,"schema":{"type":"string"}},{"in":"query","name":"start_date","required":false,"schema":{"format":"date","type":"string"}},{"in":"query","name":"end_date","required":false,"schema":{"format":"date","type":"string"}},{"in":"query","name":"period","required":false,"schema":{"type":"string"}},{"in":"query","name":"meal","required":false,"schema":{"type":"string"}},{"in":"query","name":"time_group_by","required":false,"schema":{"default":"daily","enum":["daily","monthly","yearly"],"type":"string"}},{"in":"query","name":"tax","required":false,"schema":{"type":"boolean"}},{"in":"query","name":"table_id","required":false,"schema":{"type":"string"}},{"in":"query","name":"group_by_table","required":false,"schema":{"type":"boolean"}}],"responses":{"200":{"content":{"application/json":{"examples":{"daily":{"summary":"Daily KPI values","value":{"data":{"2026-06-01":18.75,"2026-06-02":19.1},"metadata":{"end_date":"2026-06-30T23:59:59","start_date":"2026-06-01T00:00:00","tax":true,"time_group_by":"daily"}}},"grouped_by_table":{"summary":"Table-grouped KPI values","value":{"data":{"T1":{"2026-06-01":0.82},"T2":{"2026-06-01":0.76}},"metadata":{"end_date":"2026-06-30T23:59:59","start_date":"2026-06-01T00:00:00","time_group_by":"daily"}}},"monthly":{"summary":"Monthly KPI values","value":{"metadata":{"end_date":"2026-06-30T23:59:59","start_date":"2026-06-01T00:00:00","time_group_by":"monthly"},"value":{"2026-06":18.93}}}},"schema":{"$ref":"#/components/schemas/KpiByDateResponse"}}},"description":"KPI time series."},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"$ref":"#/components/responses/NotFound"},"500":{"$ref":"#/components/responses/InternalServerError"}},"security":[{"BearerAuth":[]}],"summary":"Get KPI values by date","tags":["KPIs"]}},"/api/v2/kpi/custom":{"post":{"description":"Summary:\n    Calculate custom KPI values on demand for the current restaurant.\n\nRequest Body:\n    JSON object with `indicator_data`. The numerator is required. An optional\n    denominator turns the custom KPI into a ratio. Product terms may provide\n    product names, product ids, or `{id, name}` objects.\n\nAuthentication: required. Send `Authorization: Bearer <YOUR_API_TOKEN>`.\n\n**Query Parameters**\n1. `start_date` (optional, string/date)\n2. `end_date` (optional, string/date)\n3. `period` (optional, string)\n4. `meal` (optional, string)\n5. `time_group_by` (optional, string; allowed: daily, monthly, yearly; default: daily)\n6. `as_percent` (optional, boolean; default: True)\n\n**Responses**\n1. `100`: Error\n2. `200`: On-demand KPI values.\n3. `400`: Invalid request or missing required data.\n4. `401`: Missing, invalid or expired authentication token.\n5. `500`: Unexpected server error.","operationId":"post_kpi_get_on_demand_kpis","parameters":[{"description":"JWT token header. Use the access token from /users/login unless the endpoint requires a refresh token.","in":"header","name":"Authorization","required":true,"schema":{"example":"Bearer <YOUR_API_TOKEN>","type":"string"}},{"in":"query","name":"start_date","required":false,"schema":{"format":"date","type":"string"}},{"in":"query","name":"end_date","required":false,"schema":{"format":"date","type":"string"}},{"in":"query","name":"period","required":false,"schema":{"type":"string"}},{"in":"query","name":"meal","required":false,"schema":{"type":"string"}},{"in":"query","name":"time_group_by","required":false,"schema":{"default":"daily","enum":["daily","monthly","yearly"],"type":"string"}},{"in":"query","name":"as_percent","required":false,"schema":{"default":true,"type":"boolean"}}],"requestBody":{"content":{"application/json":{"examples":{"clients_per_ticket_ratio":{"summary":"Clients per ticket ratio","value":{"indicator_data":{"denominator":{"families":[],"metric":"tickets","parameter":"tickets","products":[],"subfamilies":[]},"numerator":{"families":[],"metric":"clients","parameter":"clients","products":[],"subfamilies":[]}}}},"family_units_sum":{"summary":"Units sold for selected families","value":{"indicator_data":{"numerator":{"families":["Burgers","Desserts"],"metric":"units","parameter":"products","products":[],"subfamilies":[],"tax":true}}}},"product_revenue_ratio":{"summary":"Product revenue as percentage of total revenue","value":{"indicator_data":{"denominator":{"metric":"revenue","parameter":"revenue","tax":true},"numerator":{"families":[],"metric":"revenue","parameter":"products","products":[{"id":"prod_001","name":"Burger"}],"subfamilies":[],"tax":true}}}}},"schema":{"$ref":"#/components/schemas/KpiOnDemandRequest"}}},"required":true},"responses":{"100":{"$ref":"#/components/responses/Error"},"200":{"content":{"application/json":{"example":{"data":{"2026-06-01":18.5,"2026-06-02":19.2},"metadata":{"end_date":"2026-06-30T23:59:59","percentage":true,"start_date":"2026-06-01T00:00:00","time_group_by":"daily"}},"schema":{"$ref":"#/components/schemas/KpiByDateResponse"}}},"description":"On-demand KPI values."},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"500":{"$ref":"#/components/responses/InternalServerError"}},"security":[{"BearerAuth":[]}],"summary":"Calculate an on-demand custom KPI","tags":["KPIs"]}},"/api/v2/kpi/custom/{indicator_id}":{"get":{"description":"Summary:\n    Return a saved custom KPI by id for the current restaurant.\n\nPath Parameters:\n    - indicator_id (str): Custom indicator id.\n\nAuthentication: required. Send `Authorization: Bearer <YOUR_API_TOKEN>`.\n\n**Query Parameters**\n1. `start_date` (optional, string/date)\n2. `end_date` (optional, string/date)\n3. `period` (optional, string)\n4. `meal` (optional, string)\n5. `time_group_by` (optional, string; allowed: daily, monthly, yearly; default: daily)\n\n**Responses**\n1. `200`: Custom KPI values.\n2. `400`: Invalid request or missing required data.\n3. `401`: Missing, invalid or expired authentication token.\n4. `404`: Requested resource was not found.\n5. `500`: Unexpected server error.","operationId":"get_kpi_get_custom_kpi","parameters":[{"description":"JWT token header. Use the access token from /users/login unless the endpoint requires a refresh token.","in":"header","name":"Authorization","required":true,"schema":{"example":"Bearer <YOUR_API_TOKEN>","type":"string"}},{"in":"path","name":"indicator_id","required":true,"schema":{"type":"string"}},{"in":"query","name":"start_date","required":false,"schema":{"format":"date","type":"string"}},{"in":"query","name":"end_date","required":false,"schema":{"format":"date","type":"string"}},{"in":"query","name":"period","required":false,"schema":{"type":"string"}},{"in":"query","name":"meal","required":false,"schema":{"type":"string"}},{"in":"query","name":"time_group_by","required":false,"schema":{"default":"daily","enum":["daily","monthly","yearly"],"type":"string"}}],"responses":{"200":{"content":{"application/json":{"example":{"value":{"2026-06-01":12.4,"2026-06-02":13.1}},"schema":{"$ref":"#/components/schemas/KpiByDateResponse"}}},"description":"Custom KPI values."},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"$ref":"#/components/responses/NotFound"},"500":{"$ref":"#/components/responses/InternalServerError"}},"security":[{"BearerAuth":[]}],"summary":"Get a saved custom KPI by id","tags":["KPIs"]}},"/api/v2/kpi/existing":{"get":{"description":"Summary:\n    Return the KPI keys available for the authenticated user's current restaurant.\n\nAuthentication: required. Send `Authorization: Bearer <YOUR_API_TOKEN>`.\n\n**Responses**\n1. `200`: Available KPI keys.\n2. `400`: Invalid request or missing required data.\n3. `401`: Missing, invalid or expired authentication token.\n4. `500`: Unexpected server error.","operationId":"get_kpi_get_existing_kpis","parameters":[{"description":"JWT token header. Use the access token from /users/login unless the endpoint requires a refresh token.","in":"header","name":"Authorization","required":true,"schema":{"example":"Bearer <YOUR_API_TOKEN>","type":"string"}}],"responses":{"200":{"content":{"application/json":{"example":{"kpis":["meanTicket","occupancyRate","rotationRate","mostSoldProduct"]},"schema":{"properties":{"kpis":{"items":{"type":"string"},"type":"array"}},"type":"object"}}},"description":"Available KPI keys."},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"500":{"$ref":"#/components/responses/InternalServerError"}},"security":[{"BearerAuth":[]}],"summary":"List KPIs available for the current restaurant","tags":["KPIs"]}},"/api/v2/kpi/value/{kpi_name}":{"get":{"description":"Summary:\n    Return one KPI value for the current restaurant.\n\nPath Parameters:\n    - kpi_name (str): KPI key to calculate.\n\nAuthentication: required. Send `Authorization: Bearer <YOUR_API_TOKEN>`.\n\n**Query Parameters**\n1. `start_date` (optional, string/date)\n2. `end_date` (optional, string/date)\n3. `period` (optional, string)\n4. `tax` (optional, boolean)\n5. `table_id` (optional, string): Optional table id or comma-separated table ids for supported table KPIs.\n6. `group_by_table` (optional, boolean)\n\n**Responses**\n1. `200`: KPI value.\n2. `400`: Invalid request or missing required data.\n3. `401`: Missing, invalid or expired authentication token.\n4. `404`: Requested resource was not found.\n5. `500`: Unexpected server error.","operationId":"get_kpi_get_kpi_value","parameters":[{"description":"JWT token header. Use the access token from /users/login unless the endpoint requires a refresh token.","in":"header","name":"Authorization","required":true,"schema":{"example":"Bearer <YOUR_API_TOKEN>","type":"string"}},{"in":"path","name":"kpi_name","required":true,"schema":{"type":"string"}},{"in":"query","name":"start_date","required":false,"schema":{"format":"date","type":"string"}},{"in":"query","name":"end_date","required":false,"schema":{"format":"date","type":"string"}},{"in":"query","name":"period","required":false,"schema":{"example":"current_month","type":"string"}},{"in":"query","name":"tax","required":false,"schema":{"type":"boolean"}},{"description":"Optional table id or comma-separated table ids for supported table KPIs.","in":"query","name":"table_id","required":false,"schema":{"type":"string"}},{"in":"query","name":"group_by_table","required":false,"schema":{"type":"boolean"}}],"responses":{"200":{"content":{"application/json":{"examples":{"single_value":{"summary":"Single KPI value","value":{"data":{"kpi_name":"meanTicket","value":18.75},"metadata":{"end_date":"2026-06-30T23:59:59","start_date":"2026-06-01T00:00:00","tax":true}}},"table_grouped_value":{"summary":"Grouped table KPI value","value":{"data":{"kpi_name":"occupancyRate","value":{"T1":0.82,"T2":0.76}},"metadata":{"end_date":"2026-06-30T23:59:59","start_date":"2026-06-01T00:00:00"}}}},"schema":{"$ref":"#/components/schemas/KpiValueResponse"}}},"description":"KPI value."},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"$ref":"#/components/responses/NotFound"},"500":{"$ref":"#/components/responses/InternalServerError"}},"security":[{"BearerAuth":[]}],"summary":"Get a single KPI value","tags":["KPIs"]}},"/calendar":{"get":{"description":"Returns calendar-related records for the selected restaurant. The current restaurant is used by default; `restaurant_id` may select any restaurant in the authenticated user's `restaurants` collection. When `team=true`, the response is limited to employee schedules and vacations. Otherwise it includes bookings, special dishes, events, restaurant schedule changes, holidays, promotions, strikes, weather and cinema/movie fields when available.\n\nAuthentication: required. Send `Authorization: Bearer <YOUR_API_TOKEN>`.\n\n**Query Parameters**\n1. `restaurantId` (optional, string): Optional query parameter `restaurantId`.\n2. `initialDate` (optional, string/date): Start date. Prefer YYYY-MM-DD.\n3. `finalDate` (optional, string/date): End date. Prefer YYYY-MM-DD.\n4. `team` (optional, boolean; default: False): When true, returns only employeesSchedule and vacations.\n5. `events_filters` (optional, string): JSON string with optional filters for external events. Currently supports min_attendance.\n6. `restaurant_id` (optional, string): Restaurant in the authenticated user's restaurants collection. Defaults to currentRestaurant.\n\n**Responses**\n1. `150`: Error\n2. `200`: Calendar data.\n3. `400`: Invalid request or missing required data.\n4. `401`: Missing, invalid or expired authentication token.\n5. `500`: Unexpected server error.","operationId":"get_getCalendar","parameters":[{"description":"Optional query parameter `restaurantId`.","in":"query","name":"restaurantId","required":false,"schema":{"type":"string"}},{"description":"JWT token header. Use the access token from /users/login unless the endpoint requires a refresh token.","in":"header","name":"Authorization","required":true,"schema":{"example":"Bearer <YOUR_API_TOKEN>","type":"string"}},{"description":"Start date. Prefer YYYY-MM-DD.","in":"query","name":"initialDate","required":false,"schema":{"format":"date","type":"string"}},{"description":"End date. Prefer YYYY-MM-DD.","in":"query","name":"finalDate","required":false,"schema":{"format":"date","type":"string"}},{"description":"When true, returns only employeesSchedule and vacations.","in":"query","name":"team","required":false,"schema":{"default":false,"type":"boolean"}},{"description":"JSON string with optional filters for external events. Currently supports min_attendance.","in":"query","name":"events_filters","required":false,"schema":{"example":"{\"min_attendance\":250}","type":"string"}},{"description":"Restaurant in the authenticated user's restaurants collection. Defaults to currentRestaurant.","in":"query","name":"restaurant_id","required":false,"schema":{"type":"string"}}],"responses":{"150":{"$ref":"#/components/responses/Error"},"200":{"content":{"application/json":{"examples":{"restaurant_calendar":{"summary":"Restaurant calendar response","value":{"value":{"bookings":[{"clients_amount":4,"date":"2026-06-01T20:00:00","id":"booking_001","name":"Jane Doe"}],"events":[{"eventType":"concert","id":"event_001","location":"Lisbon","name":"Concert","possibleImpact":4}],"fixtures":[{"away_team":"Team B","date":"2026-06-01T20:00:00","home_team":"Team A","score_normalized":0.72}],"general_events":[{"date":"2026-06-01","name":"City fair","possibleImpact":3}],"holidays":[{"date":"2026-06-10","name":"Public holiday"}],"movies":{"2026-06-01":[{"_id":"movie_001","title":"Demo Film"}]},"moviesRevenue":{"movie_001":{"revenue":765,"tickets":85}},"moviesSessions":{"2026-06-01":[{"movie_id":"movie_001","session_datetime":"2026-06-01T21:30:00"}]},"promotions":[{"_id":"promo_001","endDate":"2026-06-15","name":"Lunch discount","startDate":"2026-06-01"}],"restaurantSchedule":[{"closed":false,"date":"2026-06-01","finalHour":"23:00","initialHour":"09:00"}],"school_holidays":[{"finalDate":"2026-06-07","initialDate":"2026-06-01","name":"School break"}],"specialDishes":[{"date":"2026-06-01","price":14.5,"product_id":"prod_001"}],"strikes":[{"date":"2026-06-03","name":"Transport strike"}],"weather":[{"date":"2026-06-01","temperature":24.5,"weather_type":"clear"}]}}},"team_calendar":{"summary":"Team calendar response","value":{"value":{"employeesSchedule":{"2026-06-01":{"123":{"date":"2026-06-01","employee_id":"123","finalHour":"17:00","initialHour":"09:00","job":"waiter"}}},"vacations":[{"employeeId":"123","finalDate":"2026-06-12","id":"vac_001","initialDate":"2026-06-08","type":"vacations"}]}}}},"schema":{"$ref":"#/components/schemas/CalendarResponse"}}},"description":"Calendar data."},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"500":{"$ref":"#/components/responses/InternalServerError"}},"security":[{"BearerAuth":[]}],"summary":"Get restaurant calendar data","tags":["Calendar"]},"post":{"description":"Creates one calendar item or a repeated set of items for the current restaurant. The required `type` field selects the payload shape. Supported types are `specialDishes`, `bookings`, `vacations`, `medicalAbsence`, `restaurantSchedule`, `employeesSchedule`, `events`, `promotions` and `moviesSessions`.\n\nAuthentication: required. Send `Authorization: Bearer <YOUR_API_TOKEN>`.\n\n**Responses**\n1. `200`: Calendar item created.\n2. `400`: Invalid request or missing required data.\n3. `401`: Missing, invalid or expired authentication token.\n4. `500`: Unexpected server error.","operationId":"post_addToCalendar","parameters":[{"description":"JWT token header. Use the access token from /users/login unless the endpoint requires a refresh token.","in":"header","name":"Authorization","required":true,"schema":{"example":"Bearer <YOUR_API_TOKEN>","type":"string"}}],"requestBody":{"content":{"application/json":{"examples":{"booking":{"description":"The top-level `date` is parsed and copied into `booking.date`; restaurant_id is injected from the authenticated user.","summary":"Add a booking","value":{"booking":{"clients_amount":4,"name":"Jane Doe","notes":"Window table if available","source":"phone","status":"confirmed"},"date":"2026-06-01T20:00:00","type":"bookings"}},"employee_schedule_absence":{"description":"Creates a schedule row marked as an absence for one employee on the selected date.","summary":"Add employee schedule absence","value":{"date":"2026-06-03","employeesSchedule":[{"absence":true,"employee_id":"123","finalHour":"23:59","initialHour":"00:00","job":"waiter","justified":true}],"type":"employeesSchedule"}},"employee_schedule_import_by_internal_id":{"description":"When `employee_id` is omitted, `employee_internal_id` is resolved. Missing employees are created with the provided name/job.","summary":"Add employee schedules from imported internal ids","value":{"employeesSchedule":[{"date":"2026-06-01","employee_internal_id":"EXT-7788","finalHour":"17:00","initialHour":"09:00","job":"waiter","name":"Alice Silva"}],"type":"employeesSchedule"}},"employee_schedule_item_dates":{"description":"If top-level `date` is omitted or not parseable, each schedule row must include its own `date`.","summary":"Add employee shifts with per-row dates","value":{"employeesSchedule":[{"date":"2026-06-01","employee_id":"123","finalHour":"17:00","initialHour":"09:00","job":"waiter"},{"date":"2026-06-02","employee_id":"124","finalHour":"18:00","initialHour":"10:00","job":"bar"}],"type":"employeesSchedule"}},"employee_schedule_repeating":{"description":"Creates repeated employee schedule rows. The controller adds the recurrence id and generated dates to every schedule row.","summary":"Add repeating employee shifts","value":{"date":"2026-06-01","employeesSchedule":[{"afterBreak_finalHour":"22:00","afterBreak_initialHour":"18:00","employee_id":"123","finalHour":"17:00","initialHour":"09:00","job":"waiter"}],"repetition":"weekly","repetitionEnd":"2026-07-31","type":"employeesSchedule"}},"employee_schedule_single_date":{"description":"Top-level `date` applies to every schedule row; each row must include employee_id, initialHour and finalHour.","summary":"Add employee shifts for one date","value":{"date":"2026-06-01","employeesSchedule":[{"afterBreak_finalHour":"22:00","afterBreak_initialHour":"18:00","employee_id":"123","finalHour":"17:00","initialHour":"09:00","job":"waiter"},{"absence":false,"employee_id":"124","finalHour":"19:00","initialHour":"11:00","job":"kitchen","justified":false}],"type":"employeesSchedule"}},"event":{"description":"`impact` is saved as the event possibleImpact; `eventType` is saved as the event type.","summary":"Add one event","value":{"date":"2026-06-05T18:00:00","endDate":"2026-06-05T23:00:00","eventType":"concert","impact":4,"location":"Lisbon","name":"Concert near restaurant","type":"events"}},"event_repeating":{"description":"Repeats the event from `date` until `repetitionEnd`, preserving the duration between `date` and `endDate`.","summary":"Add repeating events","value":{"date":"2026-06-05T18:00:00","endDate":"2026-06-05T23:00:00","eventType":"music","impact":3,"location":"Lisbon","name":"Weekly live music","repetition":"weekly","repetitionEnd":"2026-07-31","type":"events"}},"medical_absence":{"description":"Same payload as vacation, but saved with absence type `medicalAbsence`.","summary":"Add employee medical absence","value":{"date":"2026-06-08","employeeId":"emp_001","finalDate":"2026-06-10","type":"medicalAbsence"}},"movie_session":{"description":"`cinema_id` is injected from the authenticated user's current restaurant.","summary":"Add cinema movie session","value":{"movie_id":"movie_001","revenue":765.0,"room":"Room 1","session_datetime":"2026-06-01T21:30:00","tickets_sold":85,"type":"moviesSessions"}},"promotion":{"description":"The `promotion` object is parsed, then passed through to promotionsDB.populateFromObject.","summary":"Add promotion","value":{"promotion":{"channel":"in_store","discount":10,"endDate":"2026-06-15","name":"Lunch menu discount","products":["prod_001","prod_002"],"startDate":"2026-06-01"},"type":"promotions"}},"restaurant_schedule_closed":{"description":"When `closed=true`, opening-hour fields are ignored.","summary":"Mark one restaurant day as closed","value":{"closed":true,"date":"2026-06-10","type":"restaurantSchedule"}},"restaurant_schedule_open":{"description":"Stores the restaurant opening intervals for one date.","summary":"Add one restaurant opening schedule","value":{"date":"2026-06-01","restaurantClosing":"15:00","restaurantClosingAfterBreak":"23:00","restaurantOpening":"09:00","restaurantOpeningAfterBreak":"18:00","type":"restaurantSchedule"}},"restaurant_schedule_repeating_closed":{"description":"Creates repeated closed restaurant schedule records.","summary":"Add repeating closed days","value":{"closed":true,"date":"2026-06-02","repetition":"weekly","repetitionEnd":"2026-07-31","type":"restaurantSchedule"}},"restaurant_schedule_repeating_open":{"description":"`repetition` may be daily, weekly, monthly, or any other value which currently falls back to yearly.","summary":"Add repeating restaurant opening schedules","value":{"date":"2026-06-01","repetition":"weekly","repetitionEnd":"2026-07-31","restaurantClosing":"15:00","restaurantClosingAfterBreak":"23:00","restaurantOpening":"09:00","restaurantOpeningAfterBreak":"18:00","type":"restaurantSchedule"}},"special_dishes_existing_product":{"description":"Creates special-dish records for one date. `product_id` may be an existing product id.","summary":"Add special dishes using existing product ids","value":{"date":"2026-06-01","specialDishes":[{"price":14.5,"product_id":"prod_001"},{"price":9.9,"product_id":"prod_002"}],"type":"specialDishes"}},"special_dishes_new_product":{"description":"When `product_id` is an object with `name`, the controller creates a product before adding the special dish.","summary":"Add a special dish and create the product","value":{"date":"2026-06-01","specialDishes":[{"price":6.5,"product_id":{"name":"Chef special tart"}}],"type":"specialDishes"}},"vacation":{"description":"Creates an absence interval with type `vacations`.","summary":"Add employee vacation","value":{"date":"2026-06-08","employeeId":"emp_001","finalDate":"2026-06-12","type":"vacations"}}},"schema":{"$ref":"#/components/schemas/CalendarAddRequest"}}},"required":true},"responses":{"200":{"content":{"application/json":{"examples":{"created_empty_response":{"summary":"Most successful insert responses","value":{}},"employee_schedule_response":{"summary":"Employee schedule insert response","value":true},"special_dish_created_product_response":{"summary":"Special dish response when a product was created","value":{"products":{"prod_001":{"id":"prod_001","name":"Chef special tart","price":6.5}}}}},"schema":{"oneOf":[{"type":"object"},{"type":"boolean"}],"type":"object"}}},"description":"Calendar item created."},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"500":{"$ref":"#/components/responses/InternalServerError"}},"security":[{"BearerAuth":[]}],"summary":"Add a calendar item","tags":["Calendar"]}},"/clientsAndForecast":{"get":{"description":"Summary:\n    Return actual clients/tickets, forecasts and objectives by date and meal.\n\nAuthentication: required. Send `Authorization: Bearer <YOUR_API_TOKEN>`.\n\n**Query Parameters**\n1. `initialPeriod` (optional, string/date)\n2. `finalPeriod` (optional, string/date)\n\n**Responses**\n1. `200`: Clients and forecast by date/meal.\n2. `400`: Invalid request or missing required data.\n3. `401`: Missing, invalid or expired authentication token.\n4. `500`: Unexpected server error.","operationId":"get_getClientsAndForecast","parameters":[{"description":"JWT token header. Use the access token from /users/login unless the endpoint requires a refresh token.","in":"header","name":"Authorization","required":true,"schema":{"example":"Bearer <YOUR_API_TOKEN>","type":"string"}},{"in":"query","name":"initialPeriod","required":false,"schema":{"format":"date","type":"string"}},{"in":"query","name":"finalPeriod","required":false,"schema":{"format":"date","type":"string"}}],"responses":{"200":{"content":{"application/json":{"example":{"2026-06-01":{"events":[{"name":"Concert near restaurant","possibleImpact":4}],"forecast":{"dinner":130,"lunch":90},"objectives":{"all":235,"dinner":140,"lunch":95},"sales":{"dinner":126,"lunch":82},"temperature":24.5,"weather_type":"clear"},"2026-06-02":{"events":[],"forecast":{"dinner":122,"lunch":80},"objectives":{"dinner":125,"lunch":85},"sales":{"dinner":118,"lunch":74},"temperature":21.0,"weather_type":"cloudy"}},"schema":{"$ref":"#/components/schemas/ClientsAndForecastResponse"}}},"description":"Clients and forecast by date/meal."},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"500":{"$ref":"#/components/responses/InternalServerError"}},"security":[{"BearerAuth":[]}],"summary":"Get clients/tickets sold and forecast by meal","tags":["Forecasts"]}},"/comission_rules":{"get":{"description":"Returns commission rules for the authenticated user's current restaurant. Pass `restaurants` to return rules associated with any of several restaurants.\n\nAuthentication: required. Send `Authorization: Bearer <YOUR_API_TOKEN>`.\n\n**Query Parameters**\n1. `restaurants` (optional, string): Comma-separated restaurant ids. Defaults to the current restaurant when omitted.\n\n**Responses**\n1. `200`: Commission rules.\n2. `400`: Invalid request or missing required data.\n3. `401`: Missing, invalid or expired authentication token.\n4. `500`: Unexpected server error.","operationId":"get_get_comission_rules","parameters":[{"description":"JWT token header. Use the access token from /users/login unless the endpoint requires a refresh token.","in":"header","name":"Authorization","required":true,"schema":{"example":"Bearer <YOUR_API_TOKEN>","type":"string"}},{"description":"Comma-separated restaurant ids. Defaults to the current restaurant when omitted.","example":"restaurant_123,restaurant_456","in":"query","name":"restaurants","required":false,"schema":{"type":"string"}}],"responses":{"200":{"content":{"application/json":{"example":{"rules":[{"_id":"66b37833b566c8a3847ce13e","comission_type":"fixed_value","comission_value":0.5,"consider_menu_products":true,"consider_tax":true,"dates":[{"finalDate":"2026-07-31T23:59:59","initialDate":"2026-07-01T00:00:00","limitedHours":[0,24]}],"day_of_month":[],"description":"Dessert sales commission","employees_type":["waiter"],"families":[],"filter_by_hour":false,"ignore_products":[],"products":["7 - Siciliana"],"restaurants":["restaurant_123"],"rule_type":"comission_per_unit_sold_employee","scales_period":"no_values_scale","subfamilies":[],"week_days":[]}]},"schema":{"$ref":"#/components/schemas/ComissionRulesResponse"}}},"description":"Commission rules."},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"500":{"$ref":"#/components/responses/InternalServerError"}},"security":[{"BearerAuth":[]}],"summary":"List commission rules","tags":["Employees & HR"]}},"/commissions":{"get":{"description":"Summary:\n    Calculate employee commissions and bonuses.\n\nAuthentication: required. Send `Authorization: Bearer <YOUR_API_TOKEN>`.\n\n**Query Parameters**\n1. `initialPeriod` (optional, string/date)\n2. `finalPeriod` (optional, string/date)\n3. `employeeId` (optional, string)\n4. `employeeName` (optional, string)\n5. `onlyProducts` (optional, boolean)\n6. `simple` (optional, boolean; default: False): When true, returns commission and quantity totals for the full selected period for each employee-rule pair.\n7. `restaurantId` (optional, string): Defaults to current restaurant. A comma-separated list calculates multiple restaurants.\n\n**Responses**\n1. `200`: Commissions response.\n2. `400`: Invalid request or missing required data.\n3. `401`: Missing, invalid or expired authentication token.\n4. `500`: Unexpected server error.","operationId":"get_get_commissions","parameters":[{"description":"JWT token header. Use the access token from /users/login unless the endpoint requires a refresh token.","in":"header","name":"Authorization","required":true,"schema":{"example":"Bearer <YOUR_API_TOKEN>","type":"string"}},{"in":"query","name":"initialPeriod","required":false,"schema":{"format":"date","type":"string"}},{"in":"query","name":"finalPeriod","required":false,"schema":{"format":"date","type":"string"}},{"in":"query","name":"employeeId","required":false,"schema":{"type":"string"}},{"in":"query","name":"employeeName","required":false,"schema":{"type":"string"}},{"in":"query","name":"onlyProducts","required":false,"schema":{"type":"boolean"}},{"description":"When true, returns commission and quantity totals for the full selected period for each employee-rule pair.","in":"query","name":"simple","required":false,"schema":{"default":false,"type":"boolean"}},{"description":"Defaults to current restaurant. A comma-separated list calculates multiple restaurants.","in":"query","name":"restaurantId","required":false,"schema":{"type":"string"}}],"responses":{"200":{"content":{"application/json":{"examples":{"multiple_restaurants":{"summary":"Multiple restaurant commissions","value":{"restaurant_123":{"commissions":[{"commission":87.5,"employee_id":"123","quantity":14,"rule_id":"rule_001"}],"employees":{"123":{"active":true,"id":"123","name":"Alice Silva","type":"waiter"}}},"restaurant_456":{"commissions":[{"commission":43.25,"employee_id":"225","quantity":8,"rule_id":"rule_002"}],"employees":{"225":{"active":true,"id":"225","name":"Bruno Costa","type":"bar"}}}}},"single_restaurant":{"summary":"Single restaurant simplified commissions","value":[{"commission":87.5,"employee_id":"123","quantity":14,"rule_id":"rule_001"}]}},"schema":{"$ref":"#/components/schemas/CommissionsResponse"}}},"description":"Commissions response."},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"500":{"$ref":"#/components/responses/InternalServerError"}},"security":[{"BearerAuth":[]}],"summary":"Calculate employee commissions and bonuses","tags":["Employees & HR"]}},"/currentRestaurant":{"put":{"description":"Updates the user's active restaurant context. Most analytics endpoints resolve data from this current restaurant rather than accepting a restaurant id in the request.\n\nAuthentication: required. Send `Authorization: Bearer <YOUR_API_TOKEN>`.\n\n**Responses**\n1. `200`: Current restaurant updated.\n2. `400`: Invalid request or missing required data.\n3. `401`: Missing, invalid or expired authentication token.\n4. `403`: The authenticated user does not have access to the requested restaurant.\n5. `500`: Unexpected server error.","operationId":"put_updateCurrentRestaurant","parameters":[{"description":"JWT token header. Use the access token from /users/login unless the endpoint requires a refresh token.","in":"header","name":"Authorization","required":true,"schema":{"example":"Bearer <YOUR_API_TOKEN>","type":"string"}}],"requestBody":{"content":{"application/json":{"example":{"id":"restaurant_123","isEmployee":false},"schema":{"$ref":"#/components/schemas/CurrentRestaurantRequest"}}},"required":true},"responses":{"200":{"content":{"application/json":{"example":{"user":{"currentRestaurant":"restaurant_456","email":"client@example.com","id":"user_123","name":"Demo User","restaurants":[{"_id":"restaurant_456","name":"Downtown ADECI"}]}},"schema":{"properties":{"user":{"$ref":"#/components/schemas/User"}},"type":"object"}}},"description":"Current restaurant updated."},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"content":{"application/json":{"example":{"error":"User does not have access to this restaurant"},"schema":{"properties":{"error":{"type":"string"}},"type":"object"}}},"description":"The authenticated user does not have access to the requested restaurant."},"500":{"$ref":"#/components/responses/InternalServerError"}},"security":[{"BearerAuth":[]}],"summary":"Select the authenticated user's current restaurant","tags":["Restaurant Context"]}},"/employees":{"get":{"description":"Summary:\n    List employees for the current restaurant with login metadata.\n\nAuthentication: required with a refresh JWT. Send `Authorization: Bearer <YOUR_REFRESH_TOKEN>`.\n\n**Responses**\n1. `200`: Employees and login metadata.\n2. `400`: Invalid request or missing required data.\n3. `401`: Missing, invalid or expired authentication token.\n4. `500`: Unexpected server error.","operationId":"get_getEmployees","parameters":[{"description":"JWT token header. Use the access token from /users/login unless the endpoint requires a refresh token.","in":"header","name":"Authorization","required":true,"schema":{"example":"Bearer <YOUR_API_TOKEN>","type":"string"}}],"responses":{"200":{"content":{"application/json":{"example":{"employees":{"123":{"active":true,"email":"alice@example.com","id":"123","internal_id":"EMP-001","name":"Alice Silva","type":"waiter"},"124":{"active":true,"id":"124","internal_id":"EMP-002","name":"Bruno Costa","type":"kitchen"}},"logins":{"alice@example.com":{"last_login":"2026-06-01T09:15:00","restaurant_id":"restaurant_123"}}},"schema":{"$ref":"#/components/schemas/EmployeesResponse"}}},"description":"Employees and login metadata."},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"500":{"$ref":"#/components/responses/InternalServerError"}},"security":[{"RefreshBearerAuth":[]}],"summary":"List employees and recent logins for the current restaurant","tags":["Employees & HR"]}},"/employees/discounts_cancelations":{"get":{"description":"Summary:\n    Return employee discount and cancellation analytics.\n\nAuthentication: required. Send `Authorization: Bearer <YOUR_API_TOKEN>`.\n\n**Query Parameters**\n1. `initialPeriod` (optional, string/date)\n2. `finalPeriod` (optional, string/date)\n3. `cancelationReason` (optional, string)\n4. `discountReason` (optional, string)\n5. `zone` (optional, string)\n\n**Responses**\n1. `200`: Discount and cancellation analytics.\n2. `400`: Invalid request or missing required data.\n3. `401`: Missing, invalid or expired authentication token.\n4. `500`: Unexpected server error.","operationId":"get_get_employees_discounts_and_cancelations","parameters":[{"description":"JWT token header. Use the access token from /users/login unless the endpoint requires a refresh token.","in":"header","name":"Authorization","required":true,"schema":{"example":"Bearer <YOUR_API_TOKEN>","type":"string"}},{"in":"query","name":"initialPeriod","required":false,"schema":{"format":"date","type":"string"}},{"in":"query","name":"finalPeriod","required":false,"schema":{"format":"date","type":"string"}},{"in":"query","name":"cancelationReason","required":false,"schema":{"type":"string"}},{"in":"query","name":"discountReason","required":false,"schema":{"type":"string"}},{"in":"query","name":"zone","required":false,"schema":{"type":"string"}}],"responses":{"200":{"content":{"application/json":{"example":{"cancelation_products":{"123":[{"name":"Dessert","product_id":"prod_002","value":9.0}]},"cancelation_reasons":["customer_changed_order"],"cancelation_tickets":{"123":[{"sale_id":"sale_002","value":9.0}]},"data":[{"cancelation":9.0,"date":"2026-06-01","discount":18.5,"employee_id":"123","meal":"lunch"}],"discount_products":{"123":[{"name":"Burger","product_id":"prod_001","value":8.5}]},"discount_reasons":["manager_discount"],"discount_reasons_employees":{"123":{"manager_discount":2}},"discount_tickets":{"123":[{"sale_id":"sale_001","value":18.5}]},"reasons_employees":{"123":{"customer_changed_order":1}}},"schema":{"$ref":"#/components/schemas/EmployeeDiscountsCancelationsResponse"}}},"description":"Discount and cancellation analytics."},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"500":{"$ref":"#/components/responses/InternalServerError"}},"security":[{"BearerAuth":[]}],"summary":"Get discounts and cancellations by employee","tags":["Employees & HR"]}},"/featureImportance":{"get":{"description":"Returns stored SHAP contributions aligned to the client-facing revenue or client forecast. The authenticated user's current restaurant is used by default; a supplied restaurant_id may select any restaurant in the user's `restaurants` collection. All-day explanations use the same flat/per-meal source selected by the restaurant's plan (shift-pro uses per-meal revenue forecasts) and never fall back to a different forecast table. Positive values push the prediction up and negative values push it down. Percentages use absolute contribution magnitude.\n\nAuthentication: required. Send `Authorization: Bearer <YOUR_API_TOKEN>`.\n\n**Query Parameters**\n1. `restaurantId` (optional, string): Optional query parameter `restaurantId`.\n2. `since` (required, string/date)\n3. `until` (required, string/date)\n4. `restaurant_id` (optional, string): Restaurant in the authenticated user's restaurants collection. Defaults to currentRestaurant.\n5. `model_type` (optional, string; allowed: revenue, clients; default: revenue)\n6. `meal` (optional, string; default: all_day)\n\n**Responses**\n1. `100`: Error\n2. `200`: Ranked SHAP drivers aligned with the forecast.\n3. `400`: Invalid date range or model type.\n4. `401`: Missing, invalid or expired authentication token.\n5. `403`: Restaurant is outside the authenticated scope.\n6. `404`: No feature importance data is available.\n7. `500`: Unexpected server error.","operationId":"get_get_feature_importance","parameters":[{"description":"Optional query parameter `restaurantId`.","in":"query","name":"restaurantId","required":false,"schema":{"type":"string"}},{"description":"JWT token header. Use the access token from /users/login unless the endpoint requires a refresh token.","in":"header","name":"Authorization","required":true,"schema":{"example":"Bearer <YOUR_API_TOKEN>","type":"string"}},{"in":"query","name":"since","required":true,"schema":{"format":"date","type":"string"}},{"in":"query","name":"until","required":true,"schema":{"format":"date","type":"string"}},{"description":"Restaurant in the authenticated user's restaurants collection. Defaults to currentRestaurant.","in":"query","name":"restaurant_id","required":false,"schema":{"type":"string"}},{"in":"query","name":"model_type","required":false,"schema":{"default":"revenue","enum":["revenue","clients"],"type":"string"}},{"in":"query","name":"meal","required":false,"schema":{"default":"all_day","type":"string"}}],"responses":{"100":{"$ref":"#/components/responses/Error"},"200":{"content":{"application/json":{"example":{"base_value":2150.0,"forecast_source":{"aggregate_per_meal":false,"forecast_by_meal":false,"mode":"all_day"},"pred_value":2450.0,"restaurant_id":"restaurant_123","run_versions":[{"created_at":"2026-07-31T23:00:00","date":"2026-08-01","is_forecast":1,"meal":"all_day","run_version":"run_2026_08_01"}],"shap_values_sorted":[{"feature_name":"eventos","features":["event_count"],"percentage":70.0,"shap_value":210.0},{"feature_name":"chuva","features":["precipitation"],"percentage":30.0,"shap_value":-90.0}]},"schema":{"$ref":"#/components/schemas/FeatureImportanceResponse"}}},"description":"Ranked SHAP drivers aligned with the forecast."},"400":{"description":"Invalid date range or model type."},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"description":"Restaurant is outside the authenticated scope."},"404":{"description":"No feature importance data is available."},"500":{"$ref":"#/components/responses/InternalServerError"}},"security":[{"BearerAuth":[]}],"summary":"Get forecast SHAP feature importance","tags":["Forecasts"]}},"/hourlyClientsAndForecast":{"get":{"description":"Returns actual values and forecasts grouped by date, meal and time slot. Time-slot keys are integer indexes rather than clock hours: divide an hour into `hourly_parts` equal slots, then calculate a slot's start time as `slot_key * (60 / hourly_parts)` minutes after midnight. For example, with `hourly_parts=4`, slots 48, 49 and 52 represent 12:00, 12:15 and 13:00. Dates on which every meal is closed are omitted unless actual rows exist.\n\nAuthentication: required. Send `Authorization: Bearer <YOUR_API_TOKEN>`.\n\n**Query Parameters**\n1. `restaurantId` (optional, string): Optional query parameter `restaurantId`.\n2. `restaurant_id` (optional, string): Restaurant in the authenticated user's restaurants collection. Defaults to currentRestaurant.\n3. `initialPeriod` (required, string/date): First date to include, in YYYY-MM-DD format.\n4. `finalPeriod` (required, string/date): Last date to include, in YYYY-MM-DD format.\n5. `metric` (optional, string; allowed: clients, tickets, revenue, products; default: clients): Actual and forecast metric to return. Invalid values fall back to `clients`.\n6. `hourly_parts` (optional, integer; default: 1): Number of equal slots per hour: 1 is hourly, 2 is 30 minutes and 4 is 15 minutes.\n\n**Responses**\n1. `200`: Hourly actuals and forecast grouped by date, meal and time slot.\n2. `400`: Invalid request or missing required data.\n3. `401`: Missing, invalid or expired authentication token.\n4. `500`: Unexpected server error.","operationId":"get_getHourlyClientsAndForecast","parameters":[{"description":"Optional query parameter `restaurantId`.","in":"query","name":"restaurantId","required":false,"schema":{"type":"string"}},{"description":"JWT token header. Use the access token from /users/login unless the endpoint requires a refresh token.","in":"header","name":"Authorization","required":true,"schema":{"example":"Bearer <YOUR_API_TOKEN>","type":"string"}},{"description":"Restaurant in the authenticated user's restaurants collection. Defaults to currentRestaurant.","in":"query","name":"restaurant_id","required":false,"schema":{"type":"string"}},{"description":"First date to include, in YYYY-MM-DD format.","in":"query","name":"initialPeriod","required":true,"schema":{"format":"date","type":"string"}},{"description":"Last date to include, in YYYY-MM-DD format.","in":"query","name":"finalPeriod","required":true,"schema":{"format":"date","type":"string"}},{"description":"Actual and forecast metric to return. Invalid values fall back to `clients`.","in":"query","name":"metric","required":false,"schema":{"default":"clients","enum":["clients","tickets","revenue","products"],"type":"string"}},{"description":"Number of equal slots per hour: 1 is hourly, 2 is 30 minutes and 4 is 15 minutes.","in":"query","name":"hourly_parts","required":false,"schema":{"default":1,"minimum":1,"type":"integer"}}],"responses":{"200":{"content":{"application/json":{"examples":{"clients_hourly":{"summary":"Hourly client forecast (default)","value":{"2026-06-01":{"dinner":{"19":{"clients":29,"forecast":31.0}},"lunch":{"12":{"clients":17,"forecast":18.5},"13":{"clients":22,"forecast":24.0}}}}},"revenue_quarter_hour":{"description":"Request with `metric=revenue&hourly_parts=4`; slot 48 starts at 12:00.","summary":"Revenue forecast in 15-minute slots","value":{"2026-06-01":{"lunch":{"48":{"forecast":425.5,"revenue":401.25},"49":{"forecast":390.0,"revenue":376.8}}}}}},"schema":{"$ref":"#/components/schemas/HourlyClientsAndForecastResponse"}}},"description":"Hourly actuals and forecast grouped by date, meal and time slot."},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"500":{"$ref":"#/components/responses/InternalServerError"}},"security":[{"BearerAuth":[]}],"summary":"Get hourly actuals and forecast by meal","tags":["Forecasts"]}},"/monetarySalesAndForecast":{"get":{"description":"Uses the same plan-based flat/per-meal source policy as V2 metrics and returns that selection in forecast_source.\n\nAuthentication: required. Send `Authorization: Bearer <YOUR_API_TOKEN>`.\n\n**Query Parameters**\n1. `restaurantId` (optional, string): Optional query parameter `restaurantId`.\n2. `restaurant_id` (optional, string): Restaurant in the authenticated user's restaurants collection. Defaults to currentRestaurant.\n3. `initialPeriod` (optional, string/date)\n4. `finalPeriod` (optional, string/date)\n5. `tax` (optional, boolean): Tax option consumed by get_tax; accepted values include true/false style strings.\n6. `previous_years_calc` (optional, boolean; default: False)\n\n**Responses**\n1. `200`: Monetary sales and forecast.\n2. `400`: Invalid request or missing required data.\n3. `401`: Missing, invalid or expired authentication token.\n4. `500`: Unexpected server error.","operationId":"get_getMonetarySalesAndForecast","parameters":[{"description":"Optional query parameter `restaurantId`.","in":"query","name":"restaurantId","required":false,"schema":{"type":"string"}},{"description":"JWT token header. Use the access token from /users/login unless the endpoint requires a refresh token.","in":"header","name":"Authorization","required":true,"schema":{"example":"Bearer <YOUR_API_TOKEN>","type":"string"}},{"description":"Restaurant in the authenticated user's restaurants collection. Defaults to currentRestaurant.","in":"query","name":"restaurant_id","required":false,"schema":{"type":"string"}},{"in":"query","name":"initialPeriod","required":false,"schema":{"format":"date","type":"string"}},{"in":"query","name":"finalPeriod","required":false,"schema":{"format":"date","type":"string"}},{"description":"Tax option consumed by get_tax; accepted values include true/false style strings.","in":"query","name":"tax","required":false,"schema":{"type":"boolean"}},{"in":"query","name":"previous_years_calc","required":false,"schema":{"default":false,"type":"boolean"}}],"responses":{"200":{"content":{"application/json":{"example":{"forecast_source":{"aggregate_per_meal":false,"forecast_by_meal":false,"mode":"all_day"},"value":{"2026-06-01":{"events":[{"name":"City fair","possibleImpact":3}],"forecast":2600.0,"objective":2550.0,"sales":2450.5,"temperature":24.5,"weather_type":"clear"},"2026-06-02":{"events":[],"forecast":2100.0,"objective":2200.0,"sales":1980.0,"temperature":21.0,"weather_type":"cloudy"}}},"schema":{"$ref":"#/components/schemas/WrappedSalesAndForecastByDate"}}},"description":"Monetary sales and forecast."},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"500":{"$ref":"#/components/responses/InternalServerError"}},"security":[{"BearerAuth":[]}],"summary":"Get monetary sales and revenue forecast by date","tags":["Forecasts"]}},"/productSales/type":{"get":{"description":"Summary:\n    Return product sales grouped by type/meal for the current restaurant.\n\nAuthentication: required. Send `Authorization: Bearer <YOUR_API_TOKEN>`.\n\n**Query Parameters**\n1. `initialPeriod` (optional, string/date)\n2. `finalPeriod` (optional, string/date)\n3. `tax` (optional, boolean)\n\n**Responses**\n1. `200`: Product sales by type keyed by date.\n2. `400`: Invalid request or missing required data.\n3. `401`: Missing, invalid or expired authentication token.\n4. `500`: Unexpected server error.","operationId":"get_getProductSalesPerType","parameters":[{"description":"JWT token header. Use the access token from /users/login unless the endpoint requires a refresh token.","in":"header","name":"Authorization","required":true,"schema":{"example":"Bearer <YOUR_API_TOKEN>","type":"string"}},{"in":"query","name":"initialPeriod","required":false,"schema":{"format":"date","type":"string"}},{"in":"query","name":"finalPeriod","required":false,"schema":{"format":"date","type":"string"}},{"in":"query","name":"tax","required":false,"schema":{"type":"boolean"}}],"responses":{"200":{"content":{"application/json":{"example":{"2026-06-01":{"all":{"prod_001":42,"prod_002":18},"dinner":{"prod_001":22,"prod_002":10},"lunch":{"prod_001":20,"prod_002":8}},"2026-06-02":{"all":{"prod_001":42},"dinner":{"prod_001":24},"lunch":{"prod_001":18}}},"schema":{"$ref":"#/components/schemas/ProductSalesByTypeResponse"}}},"description":"Product sales by type keyed by date."},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"500":{"$ref":"#/components/responses/InternalServerError"}},"security":[{"BearerAuth":[]}],"summary":"Get product sales grouped by sale type/meal","tags":["Product Analytics"]}},"/productSalesAndForecast":{"get":{"description":"Summary:\n    Return product sales, internal consumption and forecast by date.\n\nAuthentication: required. Send `Authorization: Bearer <YOUR_API_TOKEN>`.\n\n**Query Parameters**\n1. `restaurantId` (optional, string): Optional query parameter `restaurantId`.\n2. `restaurant_id` (optional, string): Restaurant in the authenticated user's restaurants collection. Defaults to currentRestaurant.\n3. `initialPeriod` (optional, string/date)\n4. `finalPeriod` (optional, string/date)\n5. `product` (optional, string): Optional product id filter. Supports comma-separated values and repeated `product` params.\n6. `tax` (optional, boolean)\n\n**Responses**\n1. `200`: Product sales and forecast.\n2. `400`: Invalid request or missing required data.\n3. `401`: Missing, invalid or expired authentication token.\n4. `500`: Unexpected server error.","operationId":"get_getProductSalesAndForecast","parameters":[{"description":"Optional query parameter `restaurantId`.","in":"query","name":"restaurantId","required":false,"schema":{"type":"string"}},{"description":"JWT token header. Use the access token from /users/login unless the endpoint requires a refresh token.","in":"header","name":"Authorization","required":true,"schema":{"example":"Bearer <YOUR_API_TOKEN>","type":"string"}},{"description":"Restaurant in the authenticated user's restaurants collection. Defaults to currentRestaurant.","in":"query","name":"restaurant_id","required":false,"schema":{"type":"string"}},{"in":"query","name":"initialPeriod","required":false,"schema":{"format":"date","type":"string"}},{"in":"query","name":"finalPeriod","required":false,"schema":{"format":"date","type":"string"}},{"description":"Optional product id filter. Supports comma-separated values and repeated `product` params.","in":"query","name":"product","required":false,"schema":{"type":"string"}},{"in":"query","name":"tax","required":false,"schema":{"type":"boolean"}}],"responses":{"200":{"content":{"application/json":{"examples":{"daily_product_values":{"summary":"Daily product totals","value":{"values":{"2026-06-01":{"events":[],"forecast":{"prod_001":45,"prod_002":20},"internalConsumption":{"prod_001":2},"sales":{"prod_001":42,"prod_002":18},"temperature":24.5,"weather_type":"clear"}}}},"per_meal_product_values":{"summary":"Per-meal product totals when the restaurant plan forecasts by meal","value":{"values":{"2026-06-01":{"events":[],"forecast":{"dinner":{"prod_001":24},"lunch":{"prod_001":21}},"internalConsumption":{"lunch":{"prod_001":1}},"sales":{"dinner":{"prod_001":22},"lunch":{"prod_001":20}},"weather_type":"clear"}}}}},"schema":{"$ref":"#/components/schemas/ProductSalesAndForecastResponse"}}},"description":"Product sales and forecast."},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"500":{"$ref":"#/components/responses/InternalServerError"}},"security":[{"BearerAuth":[]}],"summary":"Get product sales, internal consumption and forecast","tags":["Forecasts"]}},"/sales/type":{"get":{"description":"Summary:\n    Return monetary sales grouped by type/meal for the current restaurant.\n\nAuthentication: required. Send `Authorization: Bearer <YOUR_API_TOKEN>`.\n\n**Query Parameters**\n1. `initialPeriod` (optional, string/date)\n2. `finalPeriod` (optional, string/date)\n3. `tax` (optional, boolean)\n\n**Responses**\n1. `200`: Sales by type keyed by date.\n2. `400`: Invalid request or missing required data.\n3. `401`: Missing, invalid or expired authentication token.\n4. `500`: Unexpected server error.","operationId":"get_getSalesPerType","parameters":[{"description":"JWT token header. Use the access token from /users/login unless the endpoint requires a refresh token.","in":"header","name":"Authorization","required":true,"schema":{"example":"Bearer <YOUR_API_TOKEN>","type":"string"}},{"in":"query","name":"initialPeriod","required":false,"schema":{"format":"date","type":"string"}},{"in":"query","name":"finalPeriod","required":false,"schema":{"format":"date","type":"string"}},{"in":"query","name":"tax","required":false,"schema":{"type":"boolean"}}],"responses":{"200":{"content":{"application/json":{"example":{"2026-06-01":{"all":2450.5,"dinner":1330.0,"events":[{"name":"Concert near restaurant","possibleImpact":4}],"lunch":1120.5,"weather":{"temperature":24.5,"weather_type":"clear"}},"2026-06-02":{"all":1980.0,"dinner":1000.0,"events":[],"lunch":980.0,"weather":{"temperature":21.0,"weather_type":"cloudy"}}},"schema":{"$ref":"#/components/schemas/SalesByTypeResponse"}}},"description":"Sales by type keyed by date."},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"500":{"$ref":"#/components/responses/InternalServerError"}},"security":[{"BearerAuth":[]}],"summary":"Get monetary sales grouped by sale type/meal with context","tags":["Sales Analytics"]}},"/users":{"get":{"description":"Summary:\n    Return the authenticated user's profile.\n\nAuthentication: required. Send `Authorization: Bearer <YOUR_API_TOKEN>`.\n\n**Responses**\n1. `200`: Current user profile.\n2. `400`: Invalid request or missing required data.\n3. `401`: Missing, invalid or expired authentication token.\n4. `404`: Requested resource was not found.\n5. `500`: Unexpected server error.","operationId":"get_getUser","parameters":[{"description":"JWT token header. Use the access token from /users/login unless the endpoint requires a refresh token.","in":"header","name":"Authorization","required":true,"schema":{"example":"Bearer <YOUR_API_TOKEN>","type":"string"}}],"responses":{"200":{"content":{"application/json":{"example":{"currentRestaurant":"restaurant_123","email":"client@example.com","id":"user_123","locale":"pt","name":"Demo User","restaurants":[{"_id":"restaurant_123","country":"PT","location":"Lisbon","name":"ADECI Demo Restaurant"}]},"schema":{"$ref":"#/components/schemas/User"}}},"description":"Current user profile."},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"$ref":"#/components/responses/NotFound"},"500":{"$ref":"#/components/responses/InternalServerError"}},"security":[{"BearerAuth":[]}],"summary":"Get the authenticated user profile","tags":["Authentication"]}},"/users/login":{"post":{"description":"Authenticates a user with email and password. On success the API returns an `access_token`, `refresh_token` and user profile. Use the access token in future requests as `Authorization: Bearer <YOUR_API_TOKEN>`. Access tokens expire after 4 hours according to the Flask-JWT configuration.\n\nAuthentication: not required unless noted by the endpoint description.\n\n**Responses**\n1. `200`: Login succeeded.\n2. `400`: Invalid request or missing required data.\n3. `401`: Login failed.\n4. `500`: Unexpected server error.","operationId":"post_user_login","parameters":[],"requestBody":{"content":{"application/json":{"example":{"email":"client@example.com","password":"<YOUR_PASSWORD>"},"schema":{"$ref":"#/components/schemas/LoginRequest"}}},"required":true},"responses":{"200":{"content":{"application/json":{"example":{"access_token":"<YOUR_API_TOKEN>","refresh_token":"<YOUR_REFRESH_TOKEN>","user":{"currentRestaurant":"restaurant_123","email":"client@example.com","id":"user_123","name":"Demo User"}},"schema":{"$ref":"#/components/schemas/LoginResponse"}}},"description":"Login succeeded."},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"content":{"application/json":{"example":{"error":"Email and password do not match."},"schema":{"$ref":"#/components/schemas/StandardError"}}},"description":"Login failed."},"500":{"$ref":"#/components/responses/InternalServerError"}},"security":[],"summary":"Log in and receive ADECI JWT tokens","tags":["Authentication"]}},"/users/renewToken":{"post":{"description":"Requires the refresh token in `Authorization: Bearer <YOUR_REFRESH_TOKEN>` and returns a new token pair.\n\nAuthentication: required with a refresh JWT. Send `Authorization: Bearer <YOUR_REFRESH_TOKEN>`.\n\n**Responses**\n1. `200`: Token renewal succeeded.\n2. `400`: Invalid request or missing required data.\n3. `401`: Missing, invalid or expired authentication token.\n4. `500`: Unexpected server error.","operationId":"post_refresh_expiring_token","parameters":[{"description":"JWT token header. Use the access token from /users/login unless the endpoint requires a refresh token.","in":"header","name":"Authorization","required":true,"schema":{"example":"Bearer <YOUR_API_TOKEN>","type":"string"}}],"responses":{"200":{"content":{"application/json":{"example":{"access_token":"<YOUR_API_TOKEN>","refresh_token":"<YOUR_REFRESH_TOKEN>","user":{"currentRestaurant":"restaurant_123","email":"client@example.com","id":"user_123","name":"Demo User","restaurants":[{"_id":"restaurant_123","name":"ADECI Demo Restaurant"}]}},"schema":{"$ref":"#/components/schemas/TokenRefreshResponse"}}},"description":"Token renewal succeeded."},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"500":{"$ref":"#/components/responses/InternalServerError"}},"security":[{"RefreshBearerAuth":[]}],"summary":"Renew access and refresh tokens","tags":["Authentication"]}}},"servers":[{"description":"Current ADECI API host","url":"/"}],"tags":[{"description":"Login, token renewal and authenticated user profile.","name":"Authentication"},{"description":"Select the active restaurant used by analytics and calendar endpoints.","name":"Restaurant Context"},{"description":"Restaurant calendar records, bookings, events, promotions, opening schedules and employee schedules.","name":"Calendar"},{"description":"Revenue, clients/tickets and product forecast endpoints.","name":"Forecasts"},{"description":"Sales totals grouped by type or meal.","name":"Sales Analytics"},{"description":"Product-level sales analytics.","name":"Product Analytics"},{"description":"Employees, commissions, discounts and cancellations by employee.","name":"Employees & HR"},{"description":"Available KPI keys, KPI values, KPI time series and custom KPI calculations.","name":"KPIs"}],"x-adeci-documentation":{"error_standardization_recommendation":"Current endpoints use several error shapes: plain strings, {error: ...}, {status: error, error: ...}, {msg: ...}, and the metrics envelope {error: {code, message, details}}. A future API version should standardize on one envelope with stable machine-readable codes.","generator":"api_documentation.openapi.build_openapi_spec","maintenance":"Routes are discovered from Flask's live URL map. Use the openapi_operation decorator or ROUTE_OVERRIDES for details that cannot be inferred from code."}}
