{
	"openapi": "3.1.0",
	"info": {
		"title": "Exponata Public Integration API",
		"version": "1.0.0",
		"description": "Bearer-token protected API for external store synchronization, exposition setup, expectation targets, protocol/photo reads, and fill-rate analytics."
	},
	"servers": [
		{
			"url": "https://expo.wie.dev",
			"description": "Production"
		}
	],
	"security": [
		{
			"bearerAuth": []
		}
	],
	"tags": [
		{
			"name": "Stores",
			"description": "Create, update, retrieve, and page through stores using your external store IDs."
		},
		{
			"name": "Contacts",
			"description": "Replace and read the operational contacts attached to a store."
		},
		{
			"name": "Expectations",
			"description": "Define expected fill-rate targets by weekday, protocol time, display type, and product category."
		},
		{
			"name": "Expositions",
			"description": "Manage the displays, shelves, or product areas that Exponata monitors in each store."
		},
		{
			"name": "Protocols",
			"description": "Read scheduled protocol runs, completion status, image counts, and average fill-rate results."
		},
		{
			"name": "Photos",
			"description": "List evaluated photos and related protocol metadata for a store and time range."
		},
		{
			"name": "Fill rates",
			"description": "Aggregate measured fill rates and compare them with configured expectations.",
			"x-displayName": "Fill rates"
		}
	],
	"paths": {
		"/v1/stores": {
			"get": {
				"tags": [
					"Stores"
				],
				"summary": "List stores",
				"parameters": [
					{
						"$ref": "#/components/parameters/Limit100"
					},
					{
						"name": "cursor",
						"in": "query",
						"schema": {
							"type": "string"
						},
						"description": "External ID returned as nextCursor from the previous page."
					}
				],
				"responses": {
					"200": {
						"description": "Paged stores",
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/StoreListResponse"
								}
							}
						}
					},
					"400": {
						"$ref": "#/components/responses/ValidationFailed"
					},
					"401": {
						"$ref": "#/components/responses/Unauthorized"
					},
					"503": {
						"$ref": "#/components/responses/NotConfigured"
					}
				},
				"operationId": "listStores",
				"description": "Returns stores ordered by external ID. Use nextCursor to request the next page."
			}
		},
		"/v1/stores/{externalId}": {
			"get": {
				"tags": [
					"Stores"
				],
				"summary": "Get a store by external ID",
				"parameters": [
					{
						"$ref": "#/components/parameters/ExternalId"
					}
				],
				"responses": {
					"200": {
						"description": "Store",
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/Store"
								}
							}
						}
					},
					"404": {
						"$ref": "#/components/responses/NotFound"
					}
				},
				"operationId": "getStore",
				"description": "Returns one store by the external ID used in your source system."
			},
			"put": {
				"tags": [
					"Stores"
				],
				"summary": "Create or update a store by external ID",
				"parameters": [
					{
						"$ref": "#/components/parameters/ExternalId"
					}
				],
				"requestBody": {
					"required": true,
					"content": {
						"application/json": {
							"schema": {
								"$ref": "#/components/schemas/StoreUpsert"
							}
						}
					}
				},
				"responses": {
					"200": {
						"description": "Upserted store",
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/Store"
								}
							}
						}
					},
					"400": {
						"$ref": "#/components/responses/ValidationFailed"
					}
				},
				"operationId": "upsertStore",
				"description": "Creates or updates one store. The path externalId is the public identifier; body fields are optional so you can patch only changed attributes."
			}
		},
		"/v1/stores/{externalId}/contacts": {
			"get": {
				"tags": [
					"Contacts"
				],
				"summary": "List store contacts",
				"parameters": [
					{
						"$ref": "#/components/parameters/ExternalId"
					}
				],
				"responses": {
					"200": {
						"description": "Contacts",
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/ContactListResponse"
								}
							}
						}
					},
					"404": {
						"$ref": "#/components/responses/NotFound"
					}
				},
				"operationId": "listStoreContacts",
				"description": "Returns all contacts configured for the store."
			},
			"put": {
				"tags": [
					"Contacts"
				],
				"summary": "Replace store contacts",
				"parameters": [
					{
						"$ref": "#/components/parameters/ExternalId"
					}
				],
				"requestBody": {
					"required": true,
					"content": {
						"application/json": {
							"schema": {
								"$ref": "#/components/schemas/ReplaceContacts"
							}
						}
					}
				},
				"responses": {
					"200": {
						"description": "Replaced contacts",
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/ContactListResponse"
								}
							}
						}
					},
					"400": {
						"$ref": "#/components/responses/ValidationFailed"
					},
					"404": {
						"$ref": "#/components/responses/NotFound"
					}
				},
				"operationId": "replaceStoreContacts",
				"description": "Replaces the full contact list for the store. Send an empty items array to clear contacts."
			}
		},
		"/v1/stores/{externalId}/expectations": {
			"get": {
				"tags": [
					"Expectations"
				],
				"summary": "List fill-rate expectations",
				"parameters": [
					{
						"$ref": "#/components/parameters/ExternalId"
					}
				],
				"responses": {
					"200": {
						"description": "Expectations",
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/ExpectationListResponse"
								}
							}
						}
					},
					"404": {
						"$ref": "#/components/responses/NotFound"
					}
				},
				"operationId": "listStoreExpectations",
				"description": "Returns the fill-rate expectations used when calculating analytics deltas and below-expectation flags."
			},
			"put": {
				"tags": [
					"Expectations"
				],
				"summary": "Replace fill-rate expectations",
				"parameters": [
					{
						"$ref": "#/components/parameters/ExternalId"
					}
				],
				"requestBody": {
					"required": true,
					"content": {
						"application/json": {
							"schema": {
								"$ref": "#/components/schemas/ReplaceExpectations"
							}
						}
					}
				},
				"responses": {
					"200": {
						"description": "Replaced expectations",
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/ExpectationListResponse"
								}
							}
						}
					},
					"400": {
						"$ref": "#/components/responses/ValidationFailed"
					},
					"404": {
						"$ref": "#/components/responses/NotFound"
					}
				},
				"operationId": "replaceStoreExpectations",
				"description": "Replaces the full expectation set for the store. Expectations match by display type, weekday, protocol time, and optionally product category."
			}
		},
		"/v1/stores/{externalId}/expositions": {
			"get": {
				"tags": [
					"Expositions"
				],
				"summary": "List store expositions",
				"parameters": [
					{
						"$ref": "#/components/parameters/ExternalId"
					}
				],
				"responses": {
					"200": {
						"description": "Expositions",
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/ExpositionListResponse"
								}
							}
						}
					},
					"404": {
						"$ref": "#/components/responses/NotFound"
					}
				},
				"operationId": "listStoreExpositions",
				"description": "Returns the monitored displays configured for the store."
			},
			"put": {
				"tags": [
					"Expositions"
				],
				"summary": "Upsert store expositions",
				"parameters": [
					{
						"$ref": "#/components/parameters/ExternalId"
					}
				],
				"requestBody": {
					"required": true,
					"content": {
						"application/json": {
							"schema": {
								"$ref": "#/components/schemas/PutExpositions"
							}
						}
					}
				},
				"responses": {
					"200": {
						"description": "Upserted expositions",
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/ExpositionListResponse"
								}
							}
						}
					},
					"400": {
						"$ref": "#/components/responses/ValidationFailed"
					},
					"404": {
						"$ref": "#/components/responses/NotFound"
					}
				},
				"operationId": "upsertStoreExpositions",
				"description": "Creates or updates monitored displays for the store. Existing expositions with the same expositionId are updated."
			}
		},
		"/v1/stores/{externalId}/protocols": {
			"get": {
				"tags": [
					"Protocols"
				],
				"summary": "List protocols for a store",
				"parameters": [
					{
						"$ref": "#/components/parameters/ExternalId"
					},
					{
						"$ref": "#/components/parameters/From"
					},
					{
						"$ref": "#/components/parameters/To"
					},
					{
						"$ref": "#/components/parameters/ProtocolStatus"
					},
					{
						"$ref": "#/components/parameters/Limit100"
					}
				],
				"responses": {
					"200": {
						"description": "Protocols",
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/ProtocolListResponse"
								}
							}
						}
					},
					"400": {
						"$ref": "#/components/responses/ValidationFailed"
					},
					"404": {
						"$ref": "#/components/responses/NotFound"
					}
				},
				"operationId": "listStoreProtocols",
				"description": "Returns protocol runs for the store in the requested time range, newest first."
			}
		},
		"/v1/stores/{externalId}/protocols/{protocolId}": {
			"get": {
				"tags": [
					"Protocols"
				],
				"summary": "Get protocol details",
				"parameters": [
					{
						"$ref": "#/components/parameters/ExternalId"
					},
					{
						"name": "protocolId",
						"in": "path",
						"required": true,
						"schema": {
							"type": "string"
						}
					}
				],
				"responses": {
					"200": {
						"description": "Protocol",
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/ProtocolDetail"
								}
							}
						}
					},
					"404": {
						"$ref": "#/components/responses/NotFound"
					}
				},
				"operationId": "getStoreProtocol",
				"description": "Returns one protocol run with the photos captured for that protocol."
			}
		},
		"/v1/stores/{externalId}/photos": {
			"get": {
				"tags": [
					"Photos"
				],
				"summary": "List photos for a store",
				"parameters": [
					{
						"$ref": "#/components/parameters/ExternalId"
					},
					{
						"$ref": "#/components/parameters/From"
					},
					{
						"$ref": "#/components/parameters/To"
					},
					{
						"$ref": "#/components/parameters/Limit500"
					},
					{
						"$ref": "#/components/parameters/ProtocolStatus"
					},
					{
						"$ref": "#/components/parameters/ImageStatus"
					},
					{
						"name": "protocolId",
						"in": "query",
						"schema": {
							"type": "string"
						}
					},
					{
						"name": "expositionId",
						"in": "query",
						"schema": {
							"type": "string",
							"format": "uuid"
						}
					},
					{
						"name": "cursor",
						"in": "query",
						"schema": {
							"type": "string"
						}
					}
				],
				"responses": {
					"200": {
						"description": "Photos",
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/PhotoListResponse"
								}
							}
						}
					},
					"400": {
						"$ref": "#/components/responses/ValidationFailed"
					},
					"404": {
						"$ref": "#/components/responses/NotFound"
					}
				},
				"operationId": "listStorePhotos",
				"description": "Returns photos captured for the store in the requested time range. Use filters to narrow by protocol, exposition, image status, or protocol status."
			}
		},
		"/v1/stores/{externalId}/fill-rates": {
			"get": {
				"tags": [
					"Fill rates"
				],
				"summary": "Get store fill rates",
				"parameters": [
					{
						"$ref": "#/components/parameters/ExternalId"
					},
					{
						"$ref": "#/components/parameters/From"
					},
					{
						"$ref": "#/components/parameters/To"
					},
					{
						"$ref": "#/components/parameters/ProtocolStatusWithDefault"
					},
					{
						"name": "groupBy",
						"in": "query",
						"schema": {
							"type": "string",
							"enum": [
								"protocol",
								"exposition",
								"category"
							],
							"default": "protocol"
						}
					}
				],
				"responses": {
					"200": {
						"description": "Fill-rate groups",
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/FillRateResponse"
								}
							}
						}
					},
					"400": {
						"$ref": "#/components/responses/ValidationFailed"
					},
					"404": {
						"$ref": "#/components/responses/NotFound"
					}
				},
				"operationId": "getStoreFillRates",
				"description": "Aggregates fill-rate results for one store and compares them with expectations. Choose groupBy=protocol, exposition, or category."
			}
		},
		"/v1/stores/{externalId}/expositions/{expositionId}/fill-rate": {
			"get": {
				"tags": [
					"Fill rates"
				],
				"summary": "Get exposition photo fill rates",
				"parameters": [
					{
						"$ref": "#/components/parameters/ExternalId"
					},
					{
						"name": "expositionId",
						"in": "path",
						"required": true,
						"schema": {
							"type": "string",
							"format": "uuid"
						}
					},
					{
						"$ref": "#/components/parameters/From"
					},
					{
						"$ref": "#/components/parameters/To"
					},
					{
						"$ref": "#/components/parameters/ProtocolStatusWithDefault"
					}
				],
				"responses": {
					"200": {
						"description": "Exposition fill rates",
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/ExpositionFillRateResponse"
								}
							}
						}
					},
					"400": {
						"$ref": "#/components/responses/ValidationFailed"
					},
					"404": {
						"$ref": "#/components/responses/NotFound"
					}
				},
				"operationId": "getExpositionFillRate",
				"description": "Returns photo-level fill-rate results for one exposition in one store, including expectation comparison per photo."
			}
		},
		"/v1/fill-rate": {
			"get": {
				"tags": [
					"Fill rates"
				],
				"summary": "Get cross-store fill rates",
				"parameters": [
					{
						"$ref": "#/components/parameters/From"
					},
					{
						"$ref": "#/components/parameters/To"
					},
					{
						"$ref": "#/components/parameters/ProtocolStatusWithDefault"
					},
					{
						"name": "groupBy",
						"in": "query",
						"schema": {
							"type": "string",
							"enum": [
								"store",
								"region",
								"expositionType",
								"productCategory",
								"day",
								"week"
							],
							"default": "store"
						}
					},
					{
						"name": "externalIds",
						"in": "query",
						"schema": {
							"type": "string"
						},
						"description": "Comma-separated external store IDs."
					},
					{
						"name": "minFillRate",
						"in": "query",
						"schema": {
							"type": "number",
							"minimum": 0,
							"maximum": 100
						}
					},
					{
						"name": "maxFillRate",
						"in": "query",
						"schema": {
							"type": "number",
							"minimum": 0,
							"maximum": 100
						}
					},
					{
						"name": "belowExpectationOnly",
						"in": "query",
						"schema": {
							"type": "boolean",
							"default": false
						}
					}
				],
				"responses": {
					"200": {
						"description": "Cross-store fill-rate groups",
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/CrossStoreFillRateResponse"
								}
							}
						}
					},
					"400": {
						"$ref": "#/components/responses/ValidationFailed"
					}
				},
				"operationId": "getCrossStoreFillRates",
				"description": "Aggregates fill-rate results across stores. Use externalIds to limit the stores and groupBy to choose the reporting dimension."
			}
		}
	},
	"components": {
		"securitySchemes": {
			"bearerAuth": {
				"type": "http",
				"scheme": "bearer"
			}
		},
		"parameters": {
			"ExternalId": {
				"name": "externalId",
				"in": "path",
				"required": true,
				"schema": {
					"type": "string"
				},
				"description": "Store identifier from your system. This is the public store key used in API paths.",
				"example": "store-123"
			},
			"From": {
				"name": "from",
				"in": "query",
				"required": true,
				"schema": {
					"type": "string",
					"format": "date-time"
				},
				"description": "Inclusive start of the requested date-time range.",
				"example": "2026-06-15T00:00:00Z"
			},
			"To": {
				"name": "to",
				"in": "query",
				"required": true,
				"schema": {
					"type": "string",
					"format": "date-time"
				},
				"description": "Exclusive end of the requested date-time range.",
				"example": "2026-06-16T00:00:00Z"
			},
			"Limit100": {
				"name": "limit",
				"in": "query",
				"schema": {
					"type": "integer",
					"minimum": 1,
					"maximum": 100,
					"default": 100
				},
				"description": "Maximum number of items to return. The default and maximum are 100.",
				"example": 100
			},
			"Limit500": {
				"name": "limit",
				"in": "query",
				"schema": {
					"type": "integer",
					"minimum": 1,
					"maximum": 500,
					"default": 100
				},
				"description": "Maximum number of photos to return. The default is 100 and the maximum is 500.",
				"example": 100
			},
			"ProtocolStatus": {
				"name": "status",
				"in": "query",
				"schema": {
					"$ref": "#/components/schemas/ProtocolStatus"
				},
				"description": "Protocol status filter.",
				"example": "completed"
			},
			"ProtocolStatusWithDefault": {
				"name": "status",
				"in": "query",
				"schema": {
					"allOf": [
						{
							"$ref": "#/components/schemas/ProtocolStatus"
						}
					],
					"default": "completed"
				},
				"description": "Protocol status filter. Defaults to completed for analytics endpoints.",
				"example": "completed"
			},
			"ImageStatus": {
				"name": "status",
				"in": "query",
				"schema": {
					"$ref": "#/components/schemas/ImageStatus"
				},
				"description": "Image processing status filter.",
				"example": "completed"
			}
		},
		"responses": {
			"ValidationFailed": {
				"description": "Validation failed",
				"content": {
					"application/json": {
						"schema": {
							"$ref": "#/components/schemas/Error"
						}
					}
				}
			},
			"Unauthorized": {
				"description": "Unauthorized",
				"content": {
					"application/json": {
						"schema": {
							"$ref": "#/components/schemas/Error"
						}
					}
				}
			},
			"NotFound": {
				"description": "Not found",
				"content": {
					"application/json": {
						"schema": {
							"$ref": "#/components/schemas/Error"
						}
					}
				}
			},
			"NotConfigured": {
				"description": "API token is not configured",
				"content": {
					"application/json": {
						"schema": {
							"$ref": "#/components/schemas/Error"
						}
					}
				}
			}
		},
		"schemas": {
			"Error": {
				"type": "object",
				"required": [
					"error"
				],
				"properties": {
					"error": {
						"type": "object",
						"required": [
							"code",
							"message"
						],
						"properties": {
							"code": {
								"type": "string",
								"enum": [
									"not_configured",
									"unauthorized",
									"validation_failed",
									"not_found"
								]
							},
							"message": {
								"type": "string"
							},
							"issues": {
								"type": "array",
								"items": {
									"$ref": "#/components/schemas/Issue"
								}
							}
						}
					}
				}
			},
			"Issue": {
				"type": "object",
				"required": [
					"path",
					"message"
				],
				"properties": {
					"path": {
						"type": "string"
					},
					"message": {
						"type": "string"
					},
					"code": {
						"type": "string"
					}
				}
			},
			"StoreType": {
				"type": [
					"string",
					"null"
				],
				"enum": [
					"Biuro",
					"Centrum Handlowe",
					"Dark Store",
					"Dworzec",
					"Kiosk",
					"Publiczny",
					"Stacja Benzynowa",
					"Ulica",
					null
				]
			},
			"ProtocolStatus": {
				"type": "string",
				"enum": [
					"awaiting_submission",
					"awaiting_processing",
					"processing",
					"completed",
					"failed"
				]
			},
			"ImageStatus": {
				"type": "string",
				"enum": [
					"pending",
					"uploaded",
					"annotating",
					"annotated",
					"evaluating",
					"completed",
					"failed"
				]
			},
			"ProductCategory": {
				"type": "string",
				"enum": [
					"SANDWICHES",
					"SWEET_SNACKS",
					"SALTY_SNACKS",
					"SALADS_PASTES",
					"READY_MEALS",
					"DRINKS",
					"ROLLS",
					"BAGUETTES",
					"BREADS"
				]
			},
			"ExpositionType": {
				"type": "string",
				"enum": [
					"FRIDGE_DRINKS",
					"FRIDGE_SALADS",
					"SANDWICHES_CHILLED_COUNTER",
					"SWEET_SNACKS_NEUTRAL_COUNTER",
					"SWEET_SNACKS_HEATED_COUNTER",
					"SALTY_SNACKS_NEUTRAL_COUNTER",
					"SALTY_SNACKS_HEATED_COUNTER",
					"SALTY_SNACKS_CHILLED_COUNTER",
					"SALADS_PASTES_GOURMET_RACK",
					"SALADS_PASTES_CHILLED_DISPLAY",
					"READY_MEALS_GOURMET_RACK",
					"READY_MEALS_CHILLED_DISPLAY",
					"READY_MEALS_NOT_OFFERED",
					"DRINKS_GOURMET_RACK",
					"DRINKS_CHILLED_DISPLAY",
					"DRINKS_DRINK_FRIDGE",
					"ROLLS_BREAD_RACK",
					"ROLLS_BACKBAR_CRATES",
					"ROLLS_BREAD_BASKETS",
					"ROLLS_NEUTRAL_COUNTER",
					"ROLLS_LIMITED_OFFER",
					"ROLLS_NOT_OFFERED",
					"BAGUETTES_BREAD_RACK",
					"BAGUETTES_BACKBAR_CRATES",
					"BAGUETTES_BREAD_BASKETS",
					"BAGUETTES_NEUTRAL_COUNTER",
					"BAGUETTES_LIMITED_OFFER",
					"BAGUETTES_NOT_OFFERED",
					"BREADS_BREAD_RACK",
					"BREADS_BACKBAR_CRATES",
					"BREADS_BREAD_BASKETS",
					"BREADS_NEUTRAL_COUNTER",
					"BREADS_LIMITED_OFFER",
					"BREADS_NOT_OFFERED"
				]
			},
			"OpeningTime": {
				"type": "object",
				"required": [
					"weekday",
					"from",
					"to"
				],
				"properties": {
					"weekday": {
						"type": "integer",
						"minimum": 0,
						"maximum": 6
					},
					"from": {
						"type": "string"
					},
					"to": {
						"type": "string"
					}
				},
				"description": "Opening interval for one weekday. Weekday uses 0 for Sunday through 6 for Saturday."
			},
			"Store": {
				"type": "object",
				"required": [
					"storeId",
					"externalId",
					"name",
					"location",
					"regionId",
					"storeType",
					"openingTimes",
					"protocolScheduleId",
					"protocolCronEnabled",
					"onboardingComplete",
					"loginCode"
				],
				"properties": {
					"storeId": {
						"type": "string",
						"format": "uuid"
					},
					"externalId": {
						"type": [
							"string",
							"null"
						]
					},
					"name": {
						"type": "string"
					},
					"location": {
						"type": [
							"string",
							"null"
						]
					},
					"regionId": {
						"type": [
							"string",
							"null"
						],
						"format": "uuid"
					},
					"storeType": {
						"$ref": "#/components/schemas/StoreType"
					},
					"openingTimes": {
						"type": "array",
						"items": {
							"$ref": "#/components/schemas/OpeningTime"
						}
					},
					"protocolScheduleId": {
						"type": "string",
						"format": "uuid"
					},
					"protocolCronEnabled": {
						"type": "boolean"
					},
					"onboardingComplete": {
						"type": "boolean"
					},
					"loginCode": {
						"type": "string"
					}
				},
				"description": "A store as exposed by the public API. externalId is the public identifier supplied by your system."
			},
			"StoreUpsert": {
				"type": "object",
				"properties": {
					"externalId": {
						"type": "string"
					},
					"name": {
						"type": "string"
					},
					"location": {
						"type": [
							"string",
							"null"
						]
					},
					"regionId": {
						"type": [
							"string",
							"null"
						],
						"format": "uuid"
					},
					"storeType": {
						"$ref": "#/components/schemas/StoreType"
					},
					"openingTimes": {
						"type": [
							"array",
							"null"
						],
						"items": {
							"$ref": "#/components/schemas/OpeningTime"
						}
					},
					"protocolScheduleId": {
						"type": "string",
						"format": "uuid"
					},
					"protocolCronEnabled": {
						"type": "boolean"
					},
					"onboardingComplete": {
						"type": "boolean"
					},
					"loginCode": {
						"type": "string"
					}
				},
				"description": "Fields accepted when creating or updating a store. Omitted fields keep their current values on update."
			},
			"StoreListResponse": {
				"type": "object",
				"required": [
					"items",
					"nextCursor"
				],
				"properties": {
					"items": {
						"type": "array",
						"items": {
							"$ref": "#/components/schemas/Store"
						}
					},
					"nextCursor": {
						"type": [
							"string",
							"null"
						]
					}
				}
			},
			"Contact": {
				"type": "object",
				"required": [
					"contactId",
					"contactType",
					"name",
					"phone",
					"email",
					"hasWhatsapp",
					"createdAt"
				],
				"properties": {
					"contactId": {
						"type": "string",
						"format": "uuid"
					},
					"contactType": {
						"type": "string",
						"enum": [
							"store",
							"regional_manager"
						]
					},
					"name": {
						"type": "string"
					},
					"phone": {
						"type": "string"
					},
					"email": {
						"type": [
							"string",
							"null"
						],
						"format": "email"
					},
					"hasWhatsapp": {
						"type": "boolean"
					},
					"createdAt": {
						"type": "string",
						"format": "date-time"
					}
				}
			},
			"ReplaceContacts": {
				"type": "object",
				"required": [
					"contacts"
				],
				"properties": {
					"contacts": {
						"type": "array",
						"items": {
							"type": "object",
							"required": [
								"name",
								"phone"
							],
							"properties": {
								"contactType": {
									"type": "string",
									"enum": [
										"store",
										"regional_manager"
									],
									"default": "store"
								},
								"name": {
									"type": "string"
								},
								"phone": {
									"type": "string"
								},
								"email": {
									"type": [
										"string",
										"null"
									],
									"format": "email"
								},
								"hasWhatsapp": {
									"type": "boolean",
									"default": false
								}
							}
						}
					}
				}
			},
			"ContactListResponse": {
				"type": "object",
				"required": [
					"externalId",
					"items"
				],
				"properties": {
					"externalId": {
						"type": [
							"string",
							"null"
						]
					},
					"items": {
						"type": "array",
						"items": {
							"$ref": "#/components/schemas/Contact"
						}
					}
				}
			},
			"Expectation": {
				"type": "object",
				"required": [
					"expectationId",
					"productCategory",
					"displayType",
					"weekday",
					"protocolTime",
					"expectedFillRatio",
					"updatedAt"
				],
				"properties": {
					"expectationId": {
						"type": "string",
						"format": "uuid"
					},
					"productCategory": {
						"anyOf": [
							{
								"$ref": "#/components/schemas/ProductCategory"
							},
							{
								"type": "null"
							}
						]
					},
					"displayType": {
						"$ref": "#/components/schemas/ExpositionType"
					},
					"weekday": {
						"type": "integer",
						"minimum": 0,
						"maximum": 6
					},
					"protocolTime": {
						"type": "string",
						"pattern": "^([01]\\d|2[0-3]):[0-5]\\d$"
					},
					"expectedFillRatio": {
						"type": "integer",
						"minimum": 0,
						"maximum": 100
					},
					"updatedAt": {
						"type": "string",
						"format": "date-time"
					}
				},
				"description": "Configured fill-rate target used for expectation comparison."
			},
			"ReplaceExpectations": {
				"type": "object",
				"required": [
					"expectations"
				],
				"properties": {
					"expectations": {
						"type": "array",
						"items": {
							"$ref": "#/components/schemas/ExpectationInput"
						}
					}
				}
			},
			"ExpectationInput": {
				"type": "object",
				"required": [
					"productCategory",
					"displayType",
					"weekday",
					"protocolTime",
					"expectedFillRatio"
				],
				"properties": {
					"productCategory": {
						"anyOf": [
							{
								"$ref": "#/components/schemas/ProductCategory"
							},
							{
								"type": "null"
							}
						]
					},
					"displayType": {
						"$ref": "#/components/schemas/ExpositionType"
					},
					"weekday": {
						"type": "integer",
						"minimum": 0,
						"maximum": 6
					},
					"protocolTime": {
						"type": "string",
						"pattern": "^([01]\\d|2[0-3]):[0-5]\\d$"
					},
					"expectedFillRatio": {
						"type": "integer",
						"minimum": 0,
						"maximum": 100
					}
				},
				"description": "Fill-rate target input. Set productCategory to null to create a fallback expectation for the display type and time slot."
			},
			"ExpectationListResponse": {
				"type": "object",
				"required": [
					"externalId",
					"items"
				],
				"properties": {
					"externalId": {
						"type": "string"
					},
					"items": {
						"type": "array",
						"items": {
							"$ref": "#/components/schemas/Expectation"
						}
					}
				}
			},
			"Exposition": {
				"type": "object",
				"required": [
					"expositionId",
					"label",
					"friendlyTitle",
					"productCategories",
					"expositionType",
					"status"
				],
				"properties": {
					"expositionId": {
						"type": "string",
						"format": "uuid"
					},
					"label": {
						"type": "string"
					},
					"friendlyTitle": {
						"type": [
							"string",
							"null"
						]
					},
					"productCategories": {
						"type": "array",
						"items": {
							"$ref": "#/components/schemas/ProductCategory"
						}
					},
					"expositionType": {
						"$ref": "#/components/schemas/ExpositionType"
					},
					"status": {
						"type": "string",
						"enum": [
							"active",
							"disabled"
						]
					}
				},
				"description": "A monitored display or product area in a store."
			},
			"PutExpositions": {
				"type": "object",
				"required": [
					"expositions"
				],
				"properties": {
					"expositions": {
						"type": "array",
						"maxItems": 500,
						"items": {
							"type": "object",
							"required": [
								"label",
								"productCategories",
								"expositionType"
							],
							"properties": {
								"expositionId": {
									"type": "string",
									"format": "uuid"
								},
								"label": {
									"type": "string"
								},
								"friendlyTitle": {
									"type": [
										"string",
										"null"
									]
								},
								"productCategories": {
									"type": "array",
									"items": {
										"$ref": "#/components/schemas/ProductCategory"
									}
								},
								"expositionType": {
									"$ref": "#/components/schemas/ExpositionType"
								},
								"status": {
									"type": "string",
									"enum": [
										"active",
										"disabled"
									],
									"default": "active"
								}
							}
						}
					}
				}
			},
			"ExpositionListResponse": {
				"type": "object",
				"required": [
					"externalId",
					"storeId",
					"items"
				],
				"properties": {
					"externalId": {
						"type": "string"
					},
					"storeId": {
						"type": "string",
						"format": "uuid"
					},
					"items": {
						"type": "array",
						"items": {
							"$ref": "#/components/schemas/Exposition"
						}
					}
				}
			},
			"ProtocolSummary": {
				"type": "object",
				"additionalProperties": true,
				"description": "A scheduled protocol run with aggregate status and fill-rate information."
			},
			"ProtocolDetail": {
				"allOf": [
					{
						"$ref": "#/components/schemas/ProtocolSummary"
					},
					{
						"type": "object",
						"properties": {
							"images": {
								"type": "array",
								"items": {
									"type": "object",
									"additionalProperties": true
								}
							}
						}
					}
				],
				"description": "Detailed protocol run including captured images."
			},
			"ProtocolListResponse": {
				"type": "object",
				"required": [
					"externalId",
					"items",
					"nextCursor"
				],
				"properties": {
					"externalId": {
						"type": "string"
					},
					"items": {
						"type": "array",
						"items": {
							"$ref": "#/components/schemas/ProtocolSummary"
						}
					},
					"nextCursor": {
						"type": [
							"string",
							"null"
						]
					}
				}
			},
			"PhotoListResponse": {
				"type": "object",
				"additionalProperties": true,
				"description": "Paged photo results for one store and time range."
			},
			"FillRateResponse": {
				"type": "object",
				"description": "Fill-rate aggregation for one store.",
				"required": [
					"externalId",
					"groupBy",
					"protocolStatus",
					"from",
					"to",
					"summary",
					"items"
				],
				"properties": {
					"externalId": {
						"type": "string"
					},
					"groupBy": {
						"type": "string",
						"enum": [
							"protocol",
							"exposition",
							"category"
						]
					},
					"protocolStatus": {
						"$ref": "#/components/schemas/ProtocolStatus"
					},
					"from": {
						"type": "string",
						"format": "date-time"
					},
					"to": {
						"type": "string",
						"format": "date-time"
					},
					"summary": {
						"type": "object",
						"required": [
							"protocolCount",
							"imageCount",
							"averageFillRate",
							"expectedAverageFillRate",
							"delta",
							"belowExpectationCount"
						],
						"properties": {
							"protocolCount": {
								"type": "integer",
								"minimum": 0
							},
							"imageCount": {
								"type": "integer",
								"minimum": 0
							},
							"averageFillRate": {
								"type": [
									"number",
									"null"
								],
								"minimum": 0,
								"maximum": 100
							},
							"expectedAverageFillRate": {
								"type": [
									"number",
									"null"
								],
								"minimum": 0,
								"maximum": 100
							},
							"delta": {
								"type": [
									"number",
									"null"
								]
							},
							"belowExpectationCount": {
								"type": "integer",
								"minimum": 0
							}
						}
					},
					"items": {
						"type": "array",
						"items": {
							"type": "object",
							"required": [
								"key",
								"protocolCount",
								"imageCount",
								"averageFillRate",
								"expectation"
							],
							"properties": {
								"key": {
									"type": "string",
									"description": "Group key for the selected groupBy value."
								},
								"protocolId": {
									"type": "string"
								},
								"externalId": {
									"type": [
										"string",
										"null"
									]
								},
								"storeName": {
									"type": "string"
								},
								"regionName": {
									"type": [
										"string",
										"null"
									]
								},
								"expositionId": {
									"type": "string",
									"format": "uuid"
								},
								"expositionType": {
									"$ref": "#/components/schemas/ExpositionType"
								},
								"productCategory": {
									"type": [
										"string",
										"null"
									]
								},
								"scheduledDate": {
									"type": "string",
									"format": "date"
								},
								"scheduledAt": {
									"type": "string",
									"example": "12:00:00"
								},
								"scheduledDatetime": {
									"type": "string",
									"format": "date-time"
								},
								"week": {
									"type": "string",
									"example": "2026-W25"
								},
								"status": {
									"$ref": "#/components/schemas/ProtocolStatus"
								},
								"protocolCount": {
									"type": "integer",
									"minimum": 0
								},
								"imageCount": {
									"type": "integer",
									"minimum": 0
								},
								"averageFillRate": {
									"type": [
										"number",
										"null"
									],
									"minimum": 0,
									"maximum": 100
								},
								"expectation": {
									"type": "object",
									"required": [
										"expectedAverageFillRate",
										"delta",
										"belowExpectation",
										"comparedImageCount",
										"missingExpectationCount",
										"ambiguousExpectationCount"
									],
									"properties": {
										"expectedAverageFillRate": {
											"type": [
												"number",
												"null"
											],
											"minimum": 0,
											"maximum": 100
										},
										"delta": {
											"type": [
												"number",
												"null"
											],
											"description": "Measured fill rate minus expected fill rate."
										},
										"belowExpectation": {
											"type": [
												"boolean",
												"null"
											]
										},
										"comparedImageCount": {
											"type": "integer",
											"minimum": 0
										},
										"missingExpectationCount": {
											"type": "integer",
											"minimum": 0
										},
										"ambiguousExpectationCount": {
											"type": "integer",
											"minimum": 0
										}
									}
								}
							}
						}
					}
				}
			},
			"ExpositionFillRateResponse": {
				"type": "object",
				"description": "Photo-level fill-rate result for one exposition in one store.",
				"required": [
					"externalId",
					"expositionId",
					"protocolStatus",
					"from",
					"to",
					"summary",
					"items"
				],
				"properties": {
					"externalId": {
						"type": "string"
					},
					"expositionId": {
						"type": "string",
						"format": "uuid"
					},
					"protocolStatus": {
						"$ref": "#/components/schemas/ProtocolStatus"
					},
					"from": {
						"type": "string",
						"format": "date-time"
					},
					"to": {
						"type": "string",
						"format": "date-time"
					},
					"summary": {
						"type": "object",
						"required": [
							"protocolCount",
							"imageCount",
							"averageFillRate",
							"expectedAverageFillRate",
							"delta",
							"belowExpectationCount"
						],
						"properties": {
							"protocolCount": {
								"type": "integer",
								"minimum": 0
							},
							"imageCount": {
								"type": "integer",
								"minimum": 0
							},
							"averageFillRate": {
								"type": [
									"number",
									"null"
								],
								"minimum": 0,
								"maximum": 100
							},
							"expectedAverageFillRate": {
								"type": [
									"number",
									"null"
								],
								"minimum": 0,
								"maximum": 100
							},
							"delta": {
								"type": [
									"number",
									"null"
								]
							},
							"belowExpectationCount": {
								"type": "integer",
								"minimum": 0
							}
						}
					},
					"items": {
						"type": "array",
						"items": {
							"type": "object",
							"required": [
								"photoId",
								"protocolId",
								"expositionId",
								"imageUrl",
								"scheduledDate",
								"scheduledAt",
								"scheduledDatetime",
								"protocolStatus",
								"imageStatus",
								"fillRate",
								"expectation"
							],
							"properties": {
								"photoId": {
									"type": "string",
									"format": "uuid"
								},
								"protocolId": {
									"type": "string",
									"format": "uuid"
								},
								"expositionId": {
									"type": "string",
									"format": "uuid"
								},
								"imageUrl": {
									"type": "string",
									"format": "uri"
								},
								"scheduledDate": {
									"type": "string",
									"format": "date"
								},
								"scheduledAt": {
									"type": "string",
									"example": "12:00:00"
								},
								"scheduledDatetime": {
									"type": "string",
									"format": "date-time"
								},
								"protocolStatus": {
									"$ref": "#/components/schemas/ProtocolStatus"
								},
								"imageStatus": {
									"$ref": "#/components/schemas/ImageStatus"
								},
								"fillRate": {
									"type": "number",
									"minimum": 0,
									"maximum": 100
								},
								"expectation": {
									"type": "object",
									"required": [
										"expectedAverageFillRate",
										"delta",
										"belowExpectation",
										"comparedImageCount",
										"missingExpectationCount",
										"ambiguousExpectationCount"
									],
									"properties": {
										"expectedAverageFillRate": {
											"type": [
												"number",
												"null"
											],
											"minimum": 0,
											"maximum": 100
										},
										"delta": {
											"type": [
												"number",
												"null"
											],
											"description": "Measured fill rate minus expected fill rate."
										},
										"belowExpectation": {
											"type": [
												"boolean",
												"null"
											]
										},
										"comparedImageCount": {
											"type": "integer",
											"minimum": 0
										},
										"missingExpectationCount": {
											"type": "integer",
											"minimum": 0
										},
										"ambiguousExpectationCount": {
											"type": "integer",
											"minimum": 0
										}
									}
								}
							}
						}
					}
				}
			},
			"CrossStoreFillRateResponse": {
				"type": "object",
				"description": "Fill-rate aggregation across stores.",
				"required": [
					"groupBy",
					"protocolStatus",
					"from",
					"to",
					"filters",
					"summary",
					"items"
				],
				"properties": {
					"groupBy": {
						"type": "string",
						"enum": [
							"store",
							"region",
							"expositionType",
							"productCategory",
							"day",
							"week"
						]
					},
					"protocolStatus": {
						"$ref": "#/components/schemas/ProtocolStatus"
					},
					"from": {
						"type": "string",
						"format": "date-time"
					},
					"to": {
						"type": "string",
						"format": "date-time"
					},
					"filters": {
						"type": "object",
						"required": [
							"externalIds",
							"minFillRate",
							"maxFillRate",
							"belowExpectationOnly"
						],
						"properties": {
							"externalIds": {
								"type": "array",
								"items": {
									"type": "string"
								}
							},
							"minFillRate": {
								"type": [
									"number",
									"null"
								],
								"minimum": 0,
								"maximum": 100
							},
							"maxFillRate": {
								"type": [
									"number",
									"null"
								],
								"minimum": 0,
								"maximum": 100
							},
							"belowExpectationOnly": {
								"type": "boolean"
							}
						}
					},
					"summary": {
						"type": "object",
						"required": [
							"protocolCount",
							"imageCount",
							"averageFillRate",
							"expectedAverageFillRate",
							"delta",
							"belowExpectationCount"
						],
						"properties": {
							"protocolCount": {
								"type": "integer",
								"minimum": 0
							},
							"imageCount": {
								"type": "integer",
								"minimum": 0
							},
							"averageFillRate": {
								"type": [
									"number",
									"null"
								],
								"minimum": 0,
								"maximum": 100
							},
							"expectedAverageFillRate": {
								"type": [
									"number",
									"null"
								],
								"minimum": 0,
								"maximum": 100
							},
							"delta": {
								"type": [
									"number",
									"null"
								]
							},
							"belowExpectationCount": {
								"type": "integer",
								"minimum": 0
							}
						}
					},
					"items": {
						"type": "array",
						"items": {
							"type": "object",
							"required": [
								"key",
								"protocolCount",
								"imageCount",
								"averageFillRate",
								"expectation"
							],
							"properties": {
								"key": {
									"type": "string",
									"description": "Group key for the selected groupBy value."
								},
								"protocolId": {
									"type": "string"
								},
								"externalId": {
									"type": [
										"string",
										"null"
									]
								},
								"storeName": {
									"type": "string"
								},
								"regionName": {
									"type": [
										"string",
										"null"
									]
								},
								"expositionId": {
									"type": "string",
									"format": "uuid"
								},
								"expositionType": {
									"$ref": "#/components/schemas/ExpositionType"
								},
								"productCategory": {
									"type": [
										"string",
										"null"
									]
								},
								"scheduledDate": {
									"type": "string",
									"format": "date"
								},
								"scheduledAt": {
									"type": "string",
									"example": "12:00:00"
								},
								"scheduledDatetime": {
									"type": "string",
									"format": "date-time"
								},
								"week": {
									"type": "string",
									"example": "2026-W25"
								},
								"status": {
									"$ref": "#/components/schemas/ProtocolStatus"
								},
								"protocolCount": {
									"type": "integer",
									"minimum": 0
								},
								"imageCount": {
									"type": "integer",
									"minimum": 0
								},
								"averageFillRate": {
									"type": [
										"number",
										"null"
									],
									"minimum": 0,
									"maximum": 100
								},
								"expectation": {
									"type": "object",
									"required": [
										"expectedAverageFillRate",
										"delta",
										"belowExpectation",
										"comparedImageCount",
										"missingExpectationCount",
										"ambiguousExpectationCount"
									],
									"properties": {
										"expectedAverageFillRate": {
											"type": [
												"number",
												"null"
											],
											"minimum": 0,
											"maximum": 100
										},
										"delta": {
											"type": [
												"number",
												"null"
											],
											"description": "Measured fill rate minus expected fill rate."
										},
										"belowExpectation": {
											"type": [
												"boolean",
												"null"
											]
										},
										"comparedImageCount": {
											"type": "integer",
											"minimum": 0
										},
										"missingExpectationCount": {
											"type": "integer",
											"minimum": 0
										},
										"ambiguousExpectationCount": {
											"type": "integer",
											"minimum": 0
										}
									}
								}
							}
						}
					}
				}
			}
		}
	}
}
