# Exponata API Docs > Public Integration API documentation for Exponata This document contains the full content of all documentation pages for AI consumption. --- ## Authentication **URL:** https://docs.expo.wie.dev/docs/authentication **Description:** Bearer token authentication and error handling for the public integration API. # Authentication Every request must include the integration token in the `Authorization` header. ```http Authorization: Bearer ## Response format Successful responses return JSON. Validation and authentication failures use a shared error envelope. ```json { "error": { "code": "validation_failed", "message": "Request validation failed", "issues": [ { "path": "limit", "message": "Limit must be between 1 and 100", "code": "invalid_value" } ] } } ``` | Status | Code | When it happens | | --- | --- | --- | | `400` | `validation_failed` | Query parameters, path parameters, or request body fields are invalid. | | `401` | `unauthorized` | The bearer token is missing or incorrect. | | `404` | `not_found` | The requested store, protocol, exposition, or related resource does not exist. | | `503` | `not_configured` | Public integration access is not configured on the server. | ## Token handling - Keep the token server-side. Do not expose it in browser clients. - Rotate the token if it is shared outside trusted integration infrastructure. - Use HTTPS only; production requests should target `https://expo.wie.dev`. --- ## Exponata Public Integration API **URL:** https://docs.expo.wie.dev/docs **Description:** Integrate external systems with Exponata stores, expositions, expectations, protocols, photos, and fill-rate analytics. # Exponata Public Integration API Use the public integration API to keep your external store catalogue in sync with Exponata and to read protocol, photo, and fill-rate results back into your own systems. ## Base URL ```txt https://expo.wie.dev ``` All public integration routes are versioned under `/v1`. ## Recommended integration flow ## Common conventions | Convention | Meaning | | --- | --- | | `externalId` | Your system's stable store identifier. Use it in public paths instead of Exponata internal IDs. | | `from` / `to` | Required date-time range filters on analytics and photo endpoints. The lower bound is inclusive; the upper bound is exclusive. | | `limit` / `cursor` | Cursor pagination for list endpoints. Reuse `nextCursor` until it returns `null`. | | Fill-rate values | Percentages from `0` to `100`, rounded to one decimal place where aggregation is required. | | Expectations | Used to calculate deltas and `belowExpectation` flags. Missing or ambiguous expectation matches are reported explicitly. | ## Machine-readable schema --- ## OpenAPI schema **URL:** https://docs.expo.wie.dev/docs/openapi **Description:** Download and consume the OpenAPI 3.1 schema. # OpenAPI schema The canonical OpenAPI 3.1 schema is published with this documentation site at `/openapi.json`. ```bash curl https://docs.expo.wie.dev/openapi.json ``` ## Server ```txt https://expo.wie.dev ``` --- ## Stores **URL:** https://docs.expo.wie.dev/docs/api-reference/stores **Description:** Create, update, retrieve, and page through stores using your external store IDs. { "openapi": "3.2.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 } } } } } } } } } } } --- ## Contacts **URL:** https://docs.expo.wie.dev/docs/api-reference/contacts **Description:** Replace and read the operational contacts attached to a store. { "openapi": "3.2.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 } } } } } } } } } } } --- ## Expectations **URL:** https://docs.expo.wie.dev/docs/api-reference/expectations **Description:** Define expected fill-rate targets by weekday, protocol time, display type, and product category. { "openapi": "3.2.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 } } } } } } } } } } } --- ## Expositions **URL:** https://docs.expo.wie.dev/docs/api-reference/expositions **Description:** Manage the displays, shelves, or product areas that Exponata monitors in each store. { "openapi": "3.2.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 } } } } } } } } } } } --- ## Protocols **URL:** https://docs.expo.wie.dev/docs/api-reference/protocols **Description:** Read scheduled protocol runs, completion status, image counts, and average fill-rate results. { "openapi": "3.2.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 } } } } } } } } } } } --- ## Photos **URL:** https://docs.expo.wie.dev/docs/api-reference/photos **Description:** List evaluated photos and related protocol metadata for a store and time range. { "openapi": "3.2.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 } } } } } } } } } } } --- ## Fill rates **URL:** https://docs.expo.wie.dev/docs/api-reference/fill-rates **Description:** Aggregate measured fill rates and compare them with configured expectations. { "openapi": "3.2.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 } } } } } } } } } } } --- ## Links - [GitHub](https://github.com/asailabs/exponata) - [Support](mailto:support@wie.dev)