# Pizza Fashion Partner API · Документация на API https://api.pizzafashion.bg/v1 Езикът се отнася за документацията. JSON ключовете не се превеждат; имената на ястията и някои съобщения от ресторанта остават на български. Примерите са илюстративни: заменете ID, кодове, ревизии и суми с реалните от каталога и /quote. Тази страница не изпраща поръчки. ## Бърз старт Интеграцията е сървър към сървър. Каталогът се изтегля от вас; поръчките се изпращат към нас. Няма endpoint за промяна на нашите продукти. - Получете отделни API ключ и парола от Pizza Fashion. Ключът е Basic username, паролата е Basic password. - Изтеглете всички страници на /catalogue и прочетете /availability. - Създайте items + choice и извикайте /quote. Покажете крайната цена на клиента. - След потвърждение изпратете /orders със същите items/choice, quote_token и постоянен external_order_id. - Запазете order_number. Следете само своите поръчки чрез /orderstatus. Използвайте HTTPS с проверка на сертификата, Accept: application/vnd.api+json и Content-Type: application/json за POST. Максимален JSON body: 128 KiB. Не следвайте автоматично redirect с данните за достъп. Всички заявки, включително каталогът, изискват удостоверяване. Успехът е HTTP 200 с данни в meta.data. Грешките от нашия контролер са в meta.error; gateway/маршрутизацията може да върнат друг JSON формат или текст. Първо проверявайте HTTP статуса. ### Удостоверяване · пазете стойностите на сървъра ```sh export PF_API_KEY='' export PF_API_PASSWORD='' curl --fail-with-body --user "$PF_API_KEY:$PF_API_PASSWORD" \ --header 'Accept: application/vnd.api+json' \ 'https://api.pizzafashion.bg/v1/catalogue?page=1' ``` ### Формат на успешен отговор (съкратен) ```json { "meta": { "data": { "currency": "EUR", "page": 1, "has_more": true } } } ``` ## Продукти: изтегляне и синхронизация `GET /catalogue?page=1` Единственият query параметър е page: цяло число 1–10000, по подразбиране 1. Връщат се до 50 продукта. Четете до has_more=false. Категориите и комботата може да се повтарят на всяка страница: обединявайте по id/code. Показвайте карти само за listed=true. Запазете listed=false записи, защото choices може да сочи към тях. available=false означава, че продуктът не може да се поръча; наличностите и изтеклите комбота се проверяват отново на сървъра. Синхронизирайте приблизително веднъж в минута в рамките на лимита. Заменяйте локалния каталог само след успешен пълен цикъл. Продукти, които вече липсват от пълния каталог, се скриват. При непълен/неуспешен цикъл пазете последното успешно копие. Цената на размера е variants[].minor, а не разлика спрямо основната цена. Цената на добавките се взема от variants[].addons за избрания размер. Избор на вкус/разфасовка в choices означава друг product_id, със собствена рецепта и цена. ### Изтегляне на следваща страница ```sh curl --fail-with-body --user "$PF_API_KEY:$PF_API_PASSWORD" \ --header 'Accept: application/vnd.api+json' \ 'https://api.pizzafashion.bg/v1/catalogue?page=2' ``` ## Зони, получаване и плащане `GET /availability` Отговорът съдържа зони, обект за вземане и ordering_enabled за текущия партньор. Този флаг не означава, че всяка зона е отворена сега. Работните часове са Europe/Sofia; точните минимуми, късни режими, почивни дни и такси се налагат от /quote. За доставка: fulfilment=delivery + zone_id от отговора. За вземане: fulfilment=pickup + location_id=main. Не сменяйте зоната/начина, за да заобиколите минимум или работно време. Тази версия приема само payment=cash — в брой при доставка или вземане. Не се плаща онлайн през API и няма такса за обработка. POS при получаване не е отделен поддържан API метод и изисква отделно договаряне. Не се приемат промокодове, FASHION, лични ваучери, външни цени или paid=true. Комбо намаленията вече са в цените. Не се предоставя достъп до клиентската база, Club профили, точки или поръчки от други канали. ### Зони и начини на получаване ```sh curl --fail-with-body --user "$PF_API_KEY:$PF_API_PASSWORD" \ --header 'Accept: application/vnd.api+json' \ 'https://api.pizzafashion.bg/v1/availability' ``` ## Цена и продуктови опции `POST /quote` Подайте items и choice. Отговорът съдържа quote.total_minor, quote_token и expires_at. Офертата не създава поръчка, не резервира наличност и не таксува. Изтича след около 15 минути; при промяна на цена/състав/условия поискайте нова и потвърдете с клиента. Обикновен продукт: type=product, product_id и size от каталога. removed е масив от разрешени за премахване ingredient.id; addons е масив от кодове за добавки; base е един код за основа. Инициализирайте crust от crust_included и го променяйте само при crust_selectable=true. Комбо: копирайте combos[].default_selection с code/revision и подредените items. Сменяйте product_id само в рамките на slot.product_ids, запазете размера и броя. Не добавяйте quantity в комбо позициите. Модификациите може да увеличат крайната цена. 50/50: само ако half.enabled=true и двете пици са half_eligible с family вариант. Изпратете точно две halves с product_id, size=family и removed; общият base е на външния selection. Добавки и смяна на борд за половините не се конфигурират тук. Крайната цена идва от /quote. Запазете оригиналните items и choice. Не използвайте quote.items[].configuration вместо request.selection: отговорът съдържа полета само за четене, които API отхвърля във входяща заявка. ### quote-request.json → /quote ```text { "items": [ { "selection": { "type": "product", "product_id": 123, "size": "medium", "removed": [], "crust": 0, "addons": [] }, "quantity": 1 } ], "choice": { "fulfilment": "pickup", "location_id": "main", "payment": "cash" } } curl --fail-with-body --user "$PF_API_KEY:$PF_API_PASSWORD" \ --header 'Accept: application/vnd.api+json' \ --header 'Content-Type: application/json' \ --data-binary @quote-request.json \ 'https://api.pizzafashion.bg/v1/quote' ``` ### selection за пица с опции (ID/кодовете са примерни) ```json { "type": "product", "product_id": 123, "size": "family", "removed": [ 17 ], "crust": 1, "addons": [ "garlic_sauce" ], "base": "tomato" } ``` ### selection за комбо (копирайте default_selection от каталога) ```json { "type": "combo", "combo_code": "combo-example", "combo_revision": "", "items": [ { "product_id": 123, "size": "medium", "removed": [], "crust": 0, "addons": [] }, { "product_id": 124, "size": "standard", "removed": [], "crust": 0, "addons": [] } ] } ``` ### selection за 50/50 ```json { "type": "half", "halves": [ { "product_id": 123, "size": "family", "removed": [] }, { "product_id": 125, "size": "family", "removed": [ 17 ] } ], "base": "tomato" } ``` ### Отговор от /quote (съкратен) ```json { "meta": { "data": { "quote": { "currency": "EUR", "total_minor": 790, "delivery_minor": 0, "schedule_ready": true }, "quote_token": "", "expires_at": "2026-10-18T12:15:00Z" } } } ``` ## Поръчки: изпращане към ресторанта `POST /orders` Това е обратното наливане: вашата платформа създава поръчка в нашата администрация. Изпратете external_order_id, оригиналните items/choice, customer, expected_total_minor от офертата и quote_token. Не се изпращат price, source, user_id, status или данни за карта. external_order_id е постоянният ви ключ срещу дублиране. Създайте и съхранете номера преди първата HTTP заявка. Един и същ номер с идентични данни връща същия PF номер и replayed=true. Същият номер с променени данни връща409. Редът на масивите е значим; запазете тялото на заявката. След timeout не създавайте нов номер. Проверете /orderstatus и повторете първоначалната заявка със същия ID и данни при нужда.404 сам по себе си не доказва провал на заявка, която още се обработва. При вече записана заявка точният повторен опит може да се възобнови и с изтекъл токен; това не разрешава нова поръчка с изтекла оферта. Име и пълен телефон са задължителни. Изпратете истинския имейл на клиента, когато е наличен; ресторантът може да го изисква. При доставка са нужни улица, номер и уточнения. Не подменяйте данните с адрес/телефон на партньора. Това не създава регистрация или маркетингово съгласие. Източникът се определя от удостоверения партньор. OneBite поръчките автоматично се маркират като OneBite в администрацията и Telegram. HTTP 200 потвърждава записването; не е отделна гаранция за доставено Telegram/имейл/SMS известие. ### order-request.json → /orders ```text { "external_order_id": "ONEBITE-2026-10001", "items": [ { "selection": { "type": "product", "product_id": 123, "size": "medium", "removed": [], "crust": 0, "addons": [] }, "quantity": 1 } ], "choice": { "fulfilment": "pickup", "location_id": "main", "payment": "cash" }, "customer": { "name": "Customer name", "phone": "0888123456", "email": "customer@example.com", "note": "Doorbell 3" }, "expected_total_minor": 790, "quote_token": "" } curl --fail-with-body --user "$PF_API_KEY:$PF_API_PASSWORD" \ --header 'Accept: application/vnd.api+json' \ --header 'Content-Type: application/json' \ --data-binary @order-request.json \ 'https://api.pizzafashion.bg/v1/orders' ``` ### Успешно записана поръчка ```json { "meta": { "data": { "external_order_id": "ONEBITE-2026-10001", "order_number": "PF-A2000", "status": "accepted", "status_label": "Приета", "currency": "EUR", "total_minor": 790, "payment": "cash", "paid": false, "created_at": "2026-10-18T12:00:00Z", "updated_at": "2026-10-18T12:00:00Z", "replayed": false } } } ``` ### Node.js · backend функции, без автоматично изпращане ```javascript const base = 'https://api.pizzafashion.bg/v1'; const auth = Buffer.from( process.env.PF_API_KEY + ':' + process.env.PF_API_PASSWORD ).toString('base64'); async function pf(path, body) { const response = await fetch(base + path, { method: body === undefined ? 'GET' : 'POST', redirect: 'error', signal: AbortSignal.timeout(20_000), headers: { Authorization: 'Basic ' + auth, Accept: 'application/vnd.api+json', ...(body === undefined ? {} : {'Content-Type':'application/json'}) }, ...(body === undefined ? {} : {body:JSON.stringify(body)}) }); const raw = await response.text(); let payload; try { payload = JSON.parse(raw); } catch { payload = null; } if (!response.ok) { // Do not log Authorization, raw contact data or the entire body. const error = new Error(payload?.meta?.error?.message || 'API request failed'); error.status = response.status; error.retryAfter = response.headers.get('retry-after'); throw error; } if (!payload?.meta?.data) throw new Error('Unexpected response envelope'); return payload.meta.data; } async function readCompleteCatalogue() { const products = new Map(), combos = new Map(); for (let page = 1; ; page++) { const data = await pf('/catalogue?page=' + page); for (const product of data.products) products.set(product.id, product); for (const combo of data.combos) combos.set(combo.code, combo); if (!data.has_more) return {products:[...products.values()], combos:[...combos.values()]}; if (page >= 10000) throw new Error('Pagination limit'); } } async function priceSelection(items, choice) { const result = await pf('/quote', {items, choice}); // Persist original items/choice; show result.quote.total_minor to customer. return result; } async function sendConfirmedOrder(savedRequest) { // Persist external_order_id and this exact payload BEFORE calling. // Call only after the customer confirms the quoted total. return pf('/orders', savedRequest); } async function checkOwnOrder(externalOrderId) { return pf('/orderstatus', {external_order_id:externalOrderId}); } // After an ambiguous timeout: checkOwnOrder(savedRequest.external_order_id). // If needed retry sendConfirmedOrder(savedRequest), preserving ID and content. // This sample defines functions only. It creates no order automatically. ``` ## Статус и повторни опити `POST /orderstatus` Изпратете само external_order_id. Връща се статус единствено за поръчка на вашия партньор, без контактните данни на клиента. Няма endpoint за чужди поръчки, промяна на статус, отказ или webhook във v1. За промяна се свържете с ресторанта. Проверявайте активните поръчки през 30–60 секунди в общия лимит. При 429 спазете Retry-After и добавете случайно забавяне; при 503 ползвайте ограничен backoff със същия ID. При повтарящи се грешки уведомете ваш оператор. ### status-request.json → /orderstatus ```text { "external_order_id": "ONEBITE-2026-10001" } curl --fail-with-body --user "$PF_API_KEY:$PF_API_PASSWORD" \ --header 'Accept: application/vnd.api+json' \ --header 'Content-Type: application/json' \ --data-binary @status-request.json \ 'https://api.pizzafashion.bg/v1/orderstatus' ``` - `accepted`: Записана и приета в системата; служителите продължават обработката. - `preparing`: Приготвя се. - `sent`: Изпратена; при pickup означава готова за вземане. - `delivered`: Доставена / приключена. - `cancelled`: Отказана. - `payment_review`: Изисква ръчна проверка; свържете се с ресторанта. ## Пълен списък на атрибутите ### Заглавки на заявките · HTTP headers - `Authorization` · string · Задължително: HTTP Basic: API ключът е username, отделната парола е password. Само към HTTPS адреса на API. Примерна стойност: `"Basic "`. - `Accept` · string · Задължително: Изрично поискайте JSON API отговор. Примерна стойност: `"application/vnd.api+json"`. - `Content-Type` · string · post: За POST тялото трябва да е UTF-8 JSON обект. Не изпращайте HTML form или form-data. Примерна стойност: `"application/json"`. ### Страници на каталога · GET /catalogue query - `page` · integer 1–10000 · Незадължително: Цяло число 1–10000, по подразбиране 1. Пример: /catalogue?page=2. Други query параметри не се поддържат. Примерна стойност: `1`. ### Отговор на каталога · meta.data - `schema` · int · Само в отговора: Версия на структурата; в момента 1. Примерна стойност: `1`. - `currency` · EUR · Само в отговора: Валутата на всички цени. Примерна стойност: `"EUR"`. - `timezone` · string · Само в отговора: Часова зона за работното време. Примерна стойност: `"Europe/Sofia"`. - `categories` · Category[] · Само в отговора: Категории; свържете чрез category_ids на продукта. Примерна стойност: `[]`. - `products` · Product[] · Само в отговора: До 50 продукта на страница, включително деца за избор. Примерна стойност: `[]`. - `page` · int · Само в отговора: Номер на текущата страница; започва от 1. Примерна стойност: `1`. - `page_size` · int · Само в отговора: Размер на страницата: 50. Примерна стойност: `50`. - `total` · int · Само в отговора: Общ брой продукти, не само видимите карти. Примерна стойност: `144`. - `has_more` · bool · Само в отговора: При true извикайте следващата страница. Примерна стойност: `true`. - `combos` · Combo[] · Само в отговора: Комбо предложения; обединявайте страниците по code. Примерна стойност: `[]`. - `pizza_bases` · Base[] · Само в отговора: Общ списък основи; използвайте конкретния products[].bases за продукта. Примерна стойност: `[]`. - `half` · HalfSettings object · Само в отговора: Настройки за пица 50/50. Примерна стойност: `{}`. - `generated_at` · RFC3339 string · Само в отговора: Час на генериране, RFC3339. Примерна стойност: `"2026-10-18T12:00:00+00:00"`. ### Категории · categories[] - `id` · int · Само в отговора: Постоянен идентификатор на категорията. Примерна стойност: `7`. - `name` · string · Само в отговора: Публично име на категорията. Примерна стойност: `"Пици"`. - `icon` · string · Само в отговора: Код на иконка; визуален метаданен атрибут. Примерна стойност: `"pizza"`. - `count` · int · Само в отговора: Брой продукти в категорията според менюто. Примерна стойност: `24`. ### Атрибути на продукт · products[] - `id` · int · Само в отговора: Стабилен ID; изпратете го като selection.product_id. Примерна стойност: `123`. - `name` · string · Само в отговора: Име за показване; API не превежда имената на ястията. Примерна стойност: `"Пица Чикън"`. - `description` · string · Само в отговора: Обикновен текст без HTML. Примерна стойност: `"Пилешко месо, моцарела"`. - `category_ids` · integer[] · Само в отговора: Категориите, към които принадлежи продуктът. Примерна стойност: `[7]`. - `image` · url? · Само в отговора: Абсолютен HTTPS адрес на снимката или null. Примерна стойност: `"https://pizzafashion.bg/images/example.webp"`. - `thumbnail` · url? · Само в отговора: По-малка снимка за каталог или null. Примерна стойност: `"https://pizzafashion.bg/images/example-thumb.webp"`. - `image_illustrative` · bool · Само в отговора: Дали снимката е илюстративна. Примерна стойност: `false`. - `listed` · bool · Само в отговора: false: дете за вкус/разфасовка; пазете го, но не показвайте отделна карта. Примерна стойност: `true`. - `minor` · minor? · Само в отговора: Базова показвана цена в евроцентове. Проверявайте конкретния размер. Примерна стойност: `790`. - `currency` · EUR · Само в отговора: Валута EUR. Примерна стойност: `"EUR"`. - `available` · bool · Само в отговора: При false забранете поръчването на този продукт. Примерна стойност: `true`. - `available_at` · str? · Само в отговора: Планирано връщане в наличност или null; не заменя текущото available. Примерна стойност: `null`. - `availability_notice` · string · Само в отговора: Текст за наличността, ако е зададен. Примерна стойност: `""`. - `variants` · Variant[] · Само в отговора: Размерите и съответните цени, съставки и опции. Примерна стойност: `[]`. - `addons` · Addon[] · Само в отговора: Допустими добавки. Цената за избрания размер е във variants[].addons. Примерна стойност: `[]`. - `bases` · Base[] · Само в отговора: Допустими основи за тази пица; празно за неприложими продукти. Примерна стойност: `[]`. - `choices` · ChoiceGroup[] · Само в отговора: Избори за вкус/разфасовка, водещи към друг product_id. Примерна стойност: `[]`. - `half_eligible` · bool · Само в отговора: Може да участва в 50/50 при включена функция и размер family. Примерна стойност: `true`. ### Размери · products[].variants[] - `key` · standard | medium | family · Само в отговора: Ключ за selection.size; използвайте само върнат размер. Примерна стойност: `"medium"`. - `label` · string · Само в отговора: Човешко име на размера. Примерна стойност: `"Средна"`. - `minor` · minor? · Само в отговора: Пълна цена на този размер, не доплащане спрямо друг размер. Примерна стойност: `790`. - `weight_g` · int? · Само в отговора: Грамаж или null, когато не е зададен. Примерна стойност: `550`. - `ingredients` · Ingredient[] · Само в отговора: Съставки; премахвайте само тези с removable=1. Примерна стойност: `[]`. - `allergens` · integer[1–14][] · Само в отговора: Числови кодове 1–14; премахване на съставка не гарантира липса на алерген. Примерна стойност: `[1,7]`. - `crust_selectable` · bool · Само в отговора: Разрешен ли е изборът на борд. Примерна стойност: `true`. - `crust_included` · bool · Само в отговора: Борд по рецепта; използвайте като начална стойност на crust. Примерна стойност: `false`. - `crust_extra_minor` · minor? · Само в отговора: Доплащане при добавяне на борд; null не означава безплатно. Примерна стойност: `250`. - `addons` · Addon[] · Само в отговора: Добавки с изчислена цена за този размер. Примерна стойност: `[]`. ### Съставки · variants[].ingredients[] - `id` · int · Само в отговора: ID за selection.removed[], ако премахването е разрешено. Примерна стойност: `17`. - `name` · string · Само в отговора: Име на съставката. Примерна стойност: `"Лук"`. - `removable` · boolean | 0 | 1 · Само в отговора: 1/true: може да се премахне; 0/false: не може. Примерна стойност: `1`. ### Сосове и добавки · products[].addons[] / variants[].addons[] - `code` · string · Само в отговора: Изпратете този код в selection.addons[], без повторения. Примерна стойност: `"garlic_sauce"`. - `name` · string · Само в отговора: Име на добавката за избрания размер. Примерна стойност: `"Чеснов сос"`. - `group` · sauce | topping · Само в отговора: Група: сос или добавка за пица. Примерна стойност: `"sauce"`. - `enabled` · boolean | 0 | 1 · Само в отговора: Флаг за включена дефиниция; каталогът връща разрешените добавки. Примерна стойност: `1`. - `minor` · integer · EUR cents · Само в отговора: Цена в евроцентове; във variant е за конкретния размер. Примерна стойност: `100`. - `allergens` · integer[1–14][] · Само в отговора: Алергените на добавката. Примерна стойност: `[7]`. - `reviewed` · boolean | 0 | 1 · Само в отговора: Потвърдени ли са данните за алергени. Примерна стойност: `1`. - `family_minor` · integer · EUR cents · Само в отговора: Допълнителна метаинформация за семейния размер, ако е налична. Примерна стойност: `200`. - `weight_g` · int · Само в отговора: Грамаж на добавката, ако е известен. Примерна стойност: `50`. - `family_weight_g` · int · Само в отговора: Грамаж за семейния размер, ако е наличен. Примерна стойност: `100`. - `family_name` · string · Само в отговора: Етикет за семейния размер, ако е наличен. Примерна стойност: `"Добавка — 100 г"`. ### Основи за пица · products[].bases[] / pizza_bases[] - `code` · string · Само в отговора: Код за selection.base; не е платена добавка. Примерна стойност: `"tomato"`. - `name` · string · Само в отговора: Име за показване. Примерна стойност: `"Доматено пюре"`. - `enabled` · boolean | 0 | 1 · Само в отговора: Флаг на дефиницията; липсва в някои нормализирани отговори за цена. Примерна стойност: `1`. - `default` · boolean | 0 | 1 · Само в отговора: Основата по подразбиране; избира се при пропуснат base. Примерна стойност: `1`. - `minor` · integer · EUR cents · Само в отговора: Основите са включени; стойността е 0. Примерна стойност: `0`. - `allergens` · integer[1–14][] · Само в отговора: Алергенни кодове за основата. Примерна стойност: `[]`. - `reviewed` · boolean | 0 | 1 · Само в отговора: Потвърдени ли са алергенните данни. Примерна стойност: `1`. ### Вкус и разфасовка · products[].choices[] - `label` · string · Само в отговора: Заглавие на групата за избор. Примерна стойност: `"Избери вкус"`. - `show_price` · bool · Само в отговора: Дали интерфейсът да показва цената до избора. Примерна стойност: `true`. - `options` · ChoiceOption[] · Само в отговора: Варианти, всеки с отделен product_id. Примерна стойност: `[]`. ### Опция за вкус/разфасовка · choices[].options[] - `product_id` · int · Само в отговора: След избора заредете този продукт и неговите размери/добавки. Примерна стойност: `124`. - `label` · string · Само в отговора: Текст на избора. Примерна стойност: `"Без захар"`. - `minor` · integer · EUR cents · Само в отговора: Самостоятелна цена на целевия продукт. Примерна стойност: `200`. - `current` · bool · Само в отговора: Тази опция ли е текущият продукт. Примерна стойност: `false`. ### Комбо предложения · combos[] - `code` · string · Само в отговора: Постоянен код за selection.combo_code. Примерна стойност: `"combo-example"`. - `revision` · string · Само в отговора: Копирайте точно в combo_revision; не генерирайте своя стойност. Примерна стойност: `""`. - `name` · string · Само в отговора: Публично име. Примерна стойност: `"Комбо предложение"`. - `description` · string · Само в отговора: Кратко описание. Примерна стойност: `"Пица и напитка"`. - `image` · url? · Само в отговора: HTTPS снимка или null. Примерна стойност: `null`. - `minor` · integer · EUR cents · Само в отговора: Цена на конфигурацията по подразбиране; опции може да добавят доплащане. Примерна стойност: `1490`. - `available` · bool · Само в отговора: Текуща достъпност; сървърът проверява отново при поръчка. Примерна стойност: `true`. - `schedule` · string · Само в отговора: Човешко описание, не машинно разрешение за поръчка. Примерна стойност: `"Всеки ден · през работното време"`. - `slots` · ComboSlot[] · Само в отговора: Подредени позиции; запазете броя и реда. Примерна стойност: `[]`. - `default_selection` · Selection object · Само в отговора: Готов selection за заявка; променяйте само разрешените опции. Примерна стойност: `{}`. ### Позиции в комбо · combos[].slots[] - `product_ids` · integer[] · Само в отговора: Допустими продукти за тази позиция; не използвайте други ID. Примерна стойност: `[123,124]`. - `size` · standard | medium | family · Само в отговора: Задължителен размер за позицията; не може да се сменя. Примерна стойност: `"medium"`. ### Настройки 50/50 · half - `enabled` · bool · Само в отговора: Ако false, не предлагайте билдера 50/50. Примерна стойност: `true`. - `size` · family · Само в отговора: 50/50 е само семейна пица. Примерна стойност: `"family"`. - `price_mode` · higher | average | fixed | unset · Само в отговора: Метод за цена; винаги вземайте окончателната сума от /quote. Примерна стойност: `"higher"`. - `fixed_minor` · minor? · Само в отговора: Фиксирана цена при mode=fixed; null не означава 0. Примерна стойност: `null`. ### Получаване и работно време · meta.data - `currency` · EUR · Само в отговора: Валута на сумите. Примерна стойност: `"EUR"`. - `timezone` · string · Само в отговора: Местна часова зона за графиците. Примерна стойност: `"Europe/Sofia"`. - `payment_methods` · string[] · Само в отговора: За тази версия се връща само cash. Примерна стойност: `["cash"]`. - `ordering_enabled` · bool · Само в отговора: Общ и партньорски прием; не гарантира, че дадена зона е отворена. Примерна стойност: `true`. - `zones` · Zone[] · Само в отговора: Зони за доставка. Числата може да са JSON числа или цифрови низове. Примерна стойност: `[]`. - `pickup` · Pickup object · Само в отговора: Информация за вземане от ресторанта. Примерна стойност: `{}`. - `notice` · string · Само в отговора: Напомняне да се ползва /quote за окончателните правила. Примерна стойност: `"Use POST /quote for authoritative current eligibility, fees and minimums."`. ### Зони за доставка · zones[] - `id` · integer | numeric string · Само в отговора: ID за choice.zone_id; изпращайте като цяло число. Примерна стойност: `1`. - `name` · string · Само в отговора: Име на обслужваното място. Примерна стойност: `"Велинград"`. - `enabled` · integer | numeric string · Само в отговора: 1: включена зона; 0: изключена. Примерна стойност: `1`. - `days_mask` · integer | numeric string · Само в отговора: Битова маска: понеделник=1, вторник=2, … неделя=64; 127=всеки ден. Примерна стойност: `127`. - `acceptance_opens` · string · Само в отговора: Обичаен начален час, HH:MM, Europe/Sofia. Примерна стойност: `"09:00"`. - `acceptance_cutoff` · string · Само в отговора: Обичаен краен час; късни правила/почивни дни се проверяват чрез /quote. Примерна стойност: `"22:00"`. - `delivery_fee_minor` · numeric? · Само в отговора: Обичайна такса или null; точната такса идва от /quote. Примерна стойност: `0`. - `minimum_order_minor` · numeric? · Само в отговора: Обичаен минимум или null, ако не е конфигуриран. Примерна стойност: `0`. - `free_delivery_over_minor` · numeric? · Само в отговора: Праг за безплатна доставка или null, ако няма такъв. Примерна стойност: `null`. ### Вземане от място · pickup - `location_id` · main · Само в отговора: Използвайте main в choice.location_id. Примерна стойност: `"main"`. - `name` · string · Само в отговора: Име на обекта. Примерна стойност: `"Pizza Fashion"`. - `address` · string · Само в отговора: Адрес за получаване. Примерна стойност: `"Велинград, адрес на обекта"`. - `opens` · string · Само в отговора: Обичаен начален час. Примерна стойност: `"09:00"`. - `cutoff` · string · Само в отговора: Обичаен краен час. Примерна стойност: `"22:00"`. - `days` · (integer | numeric string)[] · Само в отговора: ISO дни: 1=понеделник, 7=неделя; стойностите може да са низове. Примерна стойност: `[1,2,3,4,5,6,7]`. ### Наливане на поръчка към Pizza Fashion · POST /orders - `external_order_id` · string 1–80 · Задължително: Ваш постоянен уникален номер, 1–80 ASCII букви/цифри и _ . : -; започва с буква/цифра. Пазете при повторен опит. Примерна стойност: `"ONEBITE-2026-10001"`. - `items` · Line[] · Задължително: 1–40 позиции; същите като при подписаната оферта. Примерна стойност: `[]`. - `choice` · ReceiptChoice object · Задължително: Начин на получаване и плащане; същият като в /quote. Примерна стойност: `{}`. - `customer` · Customer object · Задължително: Данни за изпълнение на тази поръчка; не създават Club профил. Примерна стойност: `{}`. - `expected_total_minor` · integer ≥ 1 · Задължително: Точно quote.total_minor като JSON integer; не е ръчно задаване на цена. Примерна стойност: `790`. - `quote_token` · string · Задължително: Непроменен токен от /quote; първото изпращане трябва да е до 15 минути. Примерна стойност: `""`. ### Заявка за точна цена · POST /quote - `items` · Line[] · Задължително: 1–40 позиции с избори; цени не се изпращат. Примерна стойност: `[]`. - `choice` · ReceiptChoice object · Задължително: Начин на получаване и cash плащане. Примерна стойност: `{}`. ### Позиция в поръчката · items[] - `selection` · Selection object · Задължително: Избор на продукт, 50/50 или комбо. Примерна стойност: `{}`. - `quantity` · integer 1–20 · Незадължително: Количество 1–20; по подразбиране 1. Примерна стойност: `1`. ### Конфигуриране на продукт · items[].selection - `type` · product | half | combo · Незадължително: Вид избор; по подразбиране product. За 50/50 и комбо го задайте изрично. Примерна стойност: `"product"`. - `product_id` · integer ≥ 1 · product: ID от каталога; за нормален продукт или за вложена половина/комбо позиция. Примерна стойност: `123`. - `size` · standard | medium | family · product: Размер от variants[].key. В комбо запазете размера на позицията; за 50/50 е family. Примерна стойност: `"medium"`. - `removed` · integer[] · Незадължително: До 100 уникални ID от ingredients с removable=1; по подразбиране []. Примерна стойност: `[17]`. - `crust` · boolean | 0 | 1 · Незадължително: Избор на борд: 0/1 или false/true. Инициализирайте от crust_included; сменяйте само ако selectable. Примерна стойност: `0`. - `addons` · string[] · Незадължително: До 40 различни кода за този продукт/размер; не на общото ниво на 50/50. Примерна стойност: `["garlic_sauce"]`. - `base` · string · Незадължително: Един код от product.bases; при пропуск се взема default. За 50/50 е на общото ниво. Примерна стойност: `"tomato"`. - `halves` · Selection[] · half: Точно две половини с product_id, size=family и removed. Бордът на половините следва рецептата; добавки не се конфигурират тук. Примерна стойност: `[]`. - `combo_code` · string · combo: Точният code от combos[]. Примерна стойност: `"combo-example"`. - `combo_revision` · string · combo: Изпратете върнатата revision, за да засечете променен състав/цена. Примерна стойност: `""`. - `items` · Selection[] · combo: Подредени позиции от default_selection.items; точно една за всеки slot. Без quantity в тях. Примерна стойност: `[]`. ### Доставка, вземане и плащане · choice - `fulfilment` · delivery | pickup · Задължително: Изпращайте изрично delivery или pickup. Примерна стойност: `"delivery"`. - `zone_id` · integer ≥ 1 · delivery: ID от /availability.zones за delivery. Примерна стойност: `1`. - `location_id` · main · pickup: За pickup използвайте main; не сменяйте зона с друг начин за по-ниска цена. Примерна стойност: `"main"`. - `payment` · cash · Задължително: Само плащане в брой при доставка/вземане; няма онлайн плащане или такса за обработка. Примерна стойност: `"cash"`. ### Контакти за изпълнение · customer - `name` · str100 · Задължително: Истинско име, до 100 знака, обикновен текст. Примерна стойност: `"Име на клиента"`. - `phone` · string ≤ 80 · Задължително: Пълен валиден телефон, препоръчително E.164. Български мобилен: 10 цифри с начална 0 или +359 и 9 цифри; непълни номера се отхвърлят. Примерна стойност: `"0888123456"`. - `email` · email string ≤ 190 · email: Валиден имейл; задължителен, ако ресторантът го изисква. Изпращайте реалния адрес при наличност. Примерна стойност: `"customer@example.com"`. - `address` · str240 · delivery: При delivery: улица, номер и нужните уточнения, до 240 знака. При pickup не се използва. Примерна стойност: `"Улица Примерна 10, вход А"`. - `note` · str500 · Незадължително: Бележка до 500 знака; може да бъде изключена от ресторанта. Без HTML или нови редове. Примерна стойност: `"Звънец 3"`. ### Заявка за собствен статус · POST /orderstatus - `external_order_id` · string 1–80 · Задължително: Същият номер, с който този партньор е изпратил поръчката. Примерна стойност: `"ONEBITE-2026-10001"`. ### Отговор след поръчка / статус · meta.data - `external_order_id` · string 1–80 · Само в отговора: Вашият номер на поръчката. Примерна стойност: `"ONEBITE-2026-10001"`. - `order_number` · string · Само в отговора: Номерът, генериран от Pizza Fashion; не го изпращайте като собствен ID. Примерна стойност: `"PF-A2000"`. - `status` · accepted | preparing | sent | delivered | cancelled | payment_review · Само в отговора: Машинен статус за интеграцията; значенията са описани отделно. Примерна стойност: `"accepted"`. - `status_label` · string · Само в отговора: Етикет от ресторанта; може да е на български независимо от езика на документацията. Примерна стойност: `"Приета"`. - `currency` · EUR · Само в отговора: Валута на поръчката. Примерна стойност: `"EUR"`. - `total_minor` · integer · EUR cents · Само в отговора: Записана крайна сума, включително доставка. Примерна стойност: `790`. - `payment` · cash · Само в отговора: Начинът на плащане е cash. Примерна стойност: `"cash"`. - `paid` · bool · Само в отговора: Флаг за отчетено плащане. При нова поръчка в брой false е нормално. Примерна стойност: `false`. - `created_at` · RFC3339 string · Само в отговора: UTC време на създаване. Примерна стойност: `"2026-10-18T12:00:00Z"`. - `updated_at` · RFC3339 string · Само в отговора: UTC време на последна промяна. Примерна стойност: `"2026-10-18T12:05:00Z"`. - `replayed` · bool · Само в отговора: Само от /orders: true означава повторен отговор за същата поръчка, не нова поръчка. Примерна стойност: `false`. ### Отговор на офертата · meta.data - `quote` · Quote object · Само в отговора: Авторитетна ценова разбивка; не изпращайте целия обект като поръчка. Примерна стойност: `{}`. - `quote_token` · string · Само в отговора: Запазете и изпратете с оригиналните items/choice в /orders. Примерна стойност: `""`. - `expires_at` · RFC3339 string · Само в отговора: Краен срок за първо изпращане на офертата, около 15 минути. Примерна стойност: `"2026-10-18T12:15:00+00:00"`. ### Крайни суми и правила · quote - `schema` · int · Само в отговора: Версия на ценовата структура. Примерна стойност: `1`. - `test_only` · bool · Само в отговора: При true не може да се подаде реална партньорска поръчка. Примерна стойност: `false`. - `currency` · EUR · Само в отговора: Всички суми са в евроцентове. Примерна стойност: `"EUR"`. - `choice` · QuoteChoice object · Само в отговора: Нормализиран избор за показване; за /orders запазете оригиналната заявка choice. Примерна стойност: `{}`. - `location` · QuoteLocation object · Само в отговора: Зона за доставка или обект за вземане. Примерна стойност: `{}`. - `items` · QuotedLine[] · Само в отговора: Конфигурирани позиции и ценови разбивки. Примерна стойност: `[]`. - `count` · int · Само в отговора: Сбор на количествата, не брой редове. Примерна стойност: `2`. - `subtotal_minor` · integer · EUR cents · Само в отговора: Сума на продуктите преди общи отстъпки и доставка. Примерна стойност: `1580`. - `delivery_minor` · integer · EUR cents · Само в отговора: Авторитетна такса за избраното получаване. Примерна стойност: `0`. - `total_minor` · integer · EUR cents · Само в отговора: Крайна сума за показване и expected_total_minor. Примерна стойност: `1580`. - `minimum_minor` · minor? · Само в отговора: Приложим минимум за поръчка. Примерна стойност: `0`. - `minimum_exclusive` · bool · Само в отговора: true: сумата трябва да е строго над минимума. Примерна стойност: `false`. - `minimum_includes_delivery` · bool · Само в отговора: Дали минимумът включва доставката. Примерна стойност: `false`. - `period` · regular | late | pickup · Само в отговора: Вид приложен времеви режим. Примерна стойност: `"pickup"`. - `schedule_ready` · bool · Само в отговора: Разрешено ли е изпращане според текущите правила. Примерна стойност: `true`. - `warnings` · string[] · Само в отговора: Предупреждения за показване. Примерна стойност: `[]`. - `discount_minor` · integer · EUR cents · Само в отговора: Обща промо отстъпка; API не приема промокод и обичайно е 0. Примерна стойност: `0`. - `discount_code` · string · Само в отговора: Промокод; за този API е празен. Примерна стойност: `""`. - `online_discount_minor` · integer · EUR cents · Само в отговора: Отстъпка за онлайн плащане; при cash е 0. Примерна стойност: `0`. - `online_discount_basis_points` · int · Само в отговора: Базисни точки на онлайн отстъпка; при cash е 0. Примерна стойност: `0`. - `delivery_net_minor` · integer · EUR cents · Само в отговора: Доставка без включения ДДС. Примерна стойност: `0`. - `delivery_tax_minor` · integer · EUR cents · Само в отговора: Включен ДДС върху доставката. Примерна стойност: `0`. - `net_minor` · integer · EUR cents · Само в отговора: Обща нетна сума; не се добавя повторно към total. Примерна стойност: `1317`. - `tax_minor` · integer · EUR cents · Само в отговора: Общ включен ДДС; не е допълнителна такса. Примерна стойност: `263`. - `vat_percent` · int · Само в отговора: Приложим процент ДДС от настройките, не твърдо зададена политика. Примерна стойност: `20`. ### Нормализиран избор в отговора · quote.choice - `fulfilment` · delivery | pickup · Само в отговора: Потвърден начин на получаване. Примерна стойност: `"pickup"`. - `zone_id` · int · Само в отговора: Зона при delivery; 0 при pickup. Примерна стойност: `0`. - `location_id` · string · Само в отговора: main при pickup; празен низ при delivery. Примерна стойност: `"main"`. - `payment` · cash · Само в отговора: Потвърден cash начин на плащане. Примерна стойност: `"cash"`. ### Място в офертата · quote.location - `id` · integer | "main" · Само в отговора: ID на зона (число) или main за pickup. Примерна стойност: `"main"`. - `name` · string · Само в отговора: Име на обекта/зоната. Примерна стойност: `"Pizza Fashion"`. - `address` · string · Само в отговора: Адрес на обекта при pickup; празно за зона. Примерна стойност: `"Велинград, адрес на обекта"`. - `opens` · string · Само в отговора: Обичаен начален час. Примерна стойност: `"09:00"`. - `cutoff` · string · Само в отговора: Обичаен краен час. Примерна стойност: `"22:00"`. ### Изчислена позиция · quote.items[] - `key` · string · Само в отговора: Вътрешен ключ на ценовия ред; не е product_id и не се изпраща обратно. Примерна стойност: `""`. - `name` · string · Само в отговора: Готово име с конфигурацията. Примерна стойност: `"Пица Чикън — Средна"`. - `quantity` · int · Само в отговора: Количество. Примерна стойност: `1`. - `unit_minor` · integer · EUR cents · Само в отговора: Цена за един конфигуриран брой. Примерна стойност: `790`. - `subtotal_minor` · integer · EUR cents · Само в отговора: unit_minor × quantity преди общи отстъпки. Примерна стойност: `790`. - `configuration` · Configuration object · Само в отговора: Пълна изчислена конфигурация; само за четене. Примерна стойност: `{}`. - `discount_minor` · integer · EUR cents · Само в отговора: Разпределена отстъпка за реда. Примерна стойност: `0`. - `gross_minor` · integer · EUR cents · Само в отговора: Ред след отстъпката, с включен ДДС. Примерна стойност: `790`. - `net_minor` · integer · EUR cents · Само в отговора: Ред без включения ДДС. Примерна стойност: `658`. - `tax_minor` · integer · EUR cents · Само в отговора: Включен ДДС за реда. Примерна стойност: `132`. ### Изчислен продукт / комбо · quote.items[].configuration - `schema` · int · Само в отговора: Версия на конфигурацията. Примерна стойност: `1`. - `type` · product | half | combo · Само в отговора: Вид на конфигурираната позиция. Примерна стойност: `"product"`. - `name` · string · Само в отговора: Име за показване. Примерна стойност: `"Пица Чикън — Средна"`. - `unit_minor` · integer · EUR cents · Само в отговора: Крайна единична цена с опциите. Примерна стойност: `790`. - `currency` · EUR · Само в отговора: Валута EUR. Примерна стойност: `"EUR"`. - `weight_g` · int? · Само в отговора: Общ грамаж, ако е известен. Примерна стойност: `550`. - `parts` · ConfiguredPart[] · Само в отговора: Един продукт, две половини или позиции в комбо. Примерна стойност: `[]`. - `allergens` · integer[1–14][] · Само в отговора: Обединени алергенни кодове. Примерна стойност: `[1,7]`. - `base` · Base object · Само в отговора: Нормализирана избрана основа, когато е приложима. Примерна стойност: `{}`. - `combo_code` · string · Само в отговора: Код на комбото. Примерна стойност: `"combo-example"`. - `combo_revision` · string · Само в отговора: Приложена ревизия на комбото. Примерна стойност: `""`. - `regular_minor` · integer · EUR cents · Само в отговора: Цена на същите продукти поотделно. Примерна стойност: `1800`. - `combo_savings_minor` · integer · EUR cents · Само в отговора: Спестяване от комбо цената. Примерна стойност: `270`. - `extras_minor` · integer · EUR cents · Само в отговора: Доплащания за избраните опции. Примерна стойност: `0`. - `selection` · Selection object · Само в отговора: Нормализиран selection за комбото; оригиналната заявка остава нужна за токена. Примерна стойност: `{}`. ### Детайл на изчислен продукт · configuration.parts[] - `product_id` · int · Само в отговора: ID на продукта. Примерна стойност: `123`. - `name` · string · Само в отговора: Име на продукта/позицията. Примерна стойност: `"Пица Чикън"`. - `size` · standard | medium | family · Само в отговора: Избран размер. Примерна стойност: `"medium"`. - `recipe_revision` · int · Само в отговора: Ревизия на рецептата; само за четене. Примерна стойност: `3`. - `weight_g` · int? · Само в отговора: Грамаж или null. Примерна стойност: `550`. - `portion` · string · Само в отговора: Етикет за порцията, ако е зададен. Примерна стойност: `""`. - `ingredients` · Ingredient[] · Само в отговора: Състав по рецептата. Примерна стойност: `[]`. - `omitted` · OmittedIngredient[] · Само в отговора: Премахнати съставки. Примерна стойност: `[]`. - `allergens` · integer[1–14][] · Само в отговора: Алергенни кодове; не се премахват автоматично с компонент. Примерна стойност: `[1,7]`. - `allergens_reviewed` · bool · Само в отговора: Потвърдени ли са алергенните данни. Примерна стойност: `true`. - `composition_complete` · bool · Само в отговора: Пълен ли е записаният състав. Примерна стойност: `true`. - `crust` · boolean | 0 | 1 · Само в отговора: Действително избран борд. Примерна стойност: `0`. - `crust_included` · bool · Само в отговора: Включен ли е борд по рецепта. Примерна стойност: `false`. - `crust_extra_minor` · integer · EUR cents · Само в отговора: Действително начислено доплащане за борд. Примерна стойност: `0`. - `base_minor` · integer · EUR cents · Само в отговора: Базова цена на продукта/размера. Примерна стойност: `790`. - `unit_minor` · integer · EUR cents · Само в отговора: Единична цена на тази част. При 50/50 не я сумирайте самостоятелно. Примерна стойност: `790`. - `half_eligible` · bool · Само в отговора: Допустимост за 50/50. Примерна стойност: `true`. - `addons` · Addon[] · Само в отговора: Избрани добавки за продукт/комбо част; липсва при половини. Примерна стойност: `[]`. - `addons_extra_minor` · integer · EUR cents · Само в отговора: Сбор доплащания за добавки. Примерна стойност: `0`. - `size_extra_minor` · integer · EUR cents · Само в отговора: Информационно доплащане за размер; вече е в цената. Примерна стойност: `0`. ### Премахната съставка · configuration.parts[].omitted[] - `id` · int · Само в отговора: ID на премахнатата съставка. Примерна стойност: `17`. - `name` · string · Само в отговора: Име на премахнатата съставка. Примерна стойност: `"Лук"`. ### Грешка от API контролера · meta.error - `status` · int · Само в отговора: HTTP статус на грешката. Примерна стойност: `422`. - `message` · string · Само в отговора: Обяснение; някои бизнес грешки са на български. Следете кода, не сравнявайте текстове. Примерна стойност: `"Invalid request"`. ## Кодове за отговор и грешки Обработвайте HTTP статуса преди JSON. Не приемайте, че всички инфраструктурни грешки имат meta.error. Не повтаряйте безкрайно грешки от данните или удостоверяването. ### Грешка от API контролера ```json { "meta": { "error": { "status": 409, "message": "Total changed. Request a fresh quote." } } } ``` - `200`: Успех; включва и точен повторен отговор на /orders. - `400`: Неподдържани query параметри / gateway заявка. - `401`: Липсващ, грешен, спрян или сменен ключ/парола. - `403`: HTTPS е задължителен; HTTP API заявки не се пренасочват с ключа. - `404`: Няма ваша поръчка с този ID или маршрутът не съществува. - `409`: Променена/изтекла оферта, конфликтен external_order_id или заета заявка. Проверете статуса. - `413 / 415`: Тяло над 128 KiB / изисква се Content-Type: application/json. - `422`: Невалидни полета, опции, телефон, минимум, затворен период или липсваща наличност. Коригирайте данните. - `429`: Достигнат лимит. Спазете Retry-After. - `503`: Изключен прием/API/партньор или временна недостъпност. Ограничени повторни опити със същия ID. ## Достъп, лимити и съвместимост Данните за достъп са само за вашия сървър. Пазете ги в secret store или променливи на средата. Не ги поставяйте в URL, frontend JavaScript, мобилно приложение, логове или публичен API тестер. Изключете логването на Authorization и лични данни. По подразбиране лимитът е 120 заявки/минута; ресторантът управлява индивидуалния и общия лимит. Прилага се по-ниският, както и независим IP лимит 120/минута. Не стартирайте паралелни агресивни пълни синхронизации. Нов партньор започва спрян, без издаден ключ и без прием на поръчки. Pizza Fashion генерира ключ и парола отделно за всеки партньор. Спирането блокира следващи заявки, а смяната на ключ отменя старите данни и запазва номерата/защитата от дублиране. API версията остава v1. Оригиналният адрес https://pizzafashion.bg/api/index.php/v1/pizzafashion/partner продължава да работи. Игнорирайте непознати добавени полета в отговорите; във входящите заявки не изпращайте неописани полета. Преди реален старт договорете контролиран тест с Pizza Fashion. Примерите тук не съдържат работещ ключ и не изпращат автоматично поръчки.