# Pizza Fashion Partner API · API documentation https://api.pizzafashion.bg/v1 Language selection affects documentation only. JSON keys are unchanged; dish names and some restaurant messages remain Bulgarian. Examples are illustrative: replace IDs, codes, revisions and amounts with actual catalogue and /quote values. This page sends no orders. ## Quick start This is a server-to-server integration. You download the menu; you submit orders to us. There is no endpoint to modify our products. - Obtain a separate API key/password from Pizza Fashion. The key is the Basic username; the password is the Basic password. - Download every /catalogue page and read /availability. - Build items + choice and call /quote. Show the final price to the customer. - After confirmation, submit /orders with the same items/choice, quote_token and a permanent external_order_id. - Save order_number. Poll only your own submitted orders through /orderstatus. Use HTTPS with certificate verification, Accept: application/vnd.api+json, and Content-Type: application/json for POST. Maximum JSON body: 128 KiB. Do not automatically follow redirects with credentials. Every operation, including the catalogue, requires authentication. Success is HTTP 200 with data under meta.data. Controller errors use meta.error; gateway/routing errors may use other JSON envelopes or text. Check HTTP status first. ### Authentication · keep values server-side ```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' ``` ### Successful response envelope (abbreviated) ```json { "meta": { "data": { "currency": "EUR", "page": 1, "has_more": true } } } ``` ## Products: download and synchronization `GET /catalogue?page=1` The only query parameter is page: integer1–10000, default1. Up to50products are returned. Continue until has_more=false. Category/combo metadata may repeat on every page: merge by id/code. Display cards only for listed=true. Retain listed=false records because choices may reference them. Available=false prevents ordering; server rechecks availability and expired combos. Sync approximately once per minute within your request limit. Replace local catalogue data only after a complete successful cycle. Hide products absent from a complete new catalogue. Keep the last successful copy on failed/partial sync. A size’s price is variants[].minor, not a surcharge over base price. Use variants[].addons for size-specific extra prices. A flavour/package choice in choices selects a different product_id, with its own recipe and price. ### Download the next page ```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' ``` ## Zones, receipt and payment `GET /availability` Response contains zones, collection location and ordering_enabled for the authenticated partner. This flag does not mean every zone is open now. Opening hours use Europe/Sofia; /quote enforces exact minimums, late periods, closures and fees. Delivery: fulfilment=delivery + a returned zone_id. Collection: fulfilment=pickup + location_id=main. Never switch zone/receipt method to bypass minimums or opening hours. This version accepts payment=cash only: cash on delivery/collection. No online payment or processing fee is taken through this API. POS on receipt is not a separate supported API method and requires separate agreement. Promo codes, FASHION, personal vouchers, external prices and paid=true are not accepted. Combo discounts are already reflected in prices. No customer directory, Club accounts, points or other channels’ orders are exposed. ### Zones and receipt options ```sh curl --fail-with-body --user "$PF_API_KEY:$PF_API_PASSWORD" \ --header 'Accept: application/vnd.api+json' \ 'https://api.pizzafashion.bg/v1/availability' ``` ## Pricing and product options `POST /quote` Send items and choice. Response contains quote.total_minor, quote_token and expires_at. A quote creates no order, reserves no stock and charges nothing. It expires after about 15 minutes; obtain and reconfirm a fresh quote when price/configuration/rules change. Normal product: type=product, catalogue product_id and size. Removed is a list of removable ingredient IDs; addons is a list of allowed extra codes; base is one pizza-base code. Initialize crust from crust_included and change it only when crust_selectable=true. Combo: copy combos[].default_selection including code/revision and ordered items. Change product_id only within slot.product_ids; preserve size and count. Do not add nested quantity. Selected modifications may increase the final amount. Half-and-half: only when half.enabled=true and both pizzas are half_eligible with a family variant. Send exactly two halves with product_id, size=family and removed; put the shared base on the outer selection. Extras and crust changes per half are unsupported. Use /quote for the final price. Retain the original items and choice. Never substitute quote.items[].configuration for request.selection: the response contains read-only fields rejected in requests. ### 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' ``` ### Pizza selection with options (illustrative IDs/codes) ```json { "type": "product", "product_id": 123, "size": "family", "removed": [ 17 ], "crust": 1, "addons": [ "garlic_sauce" ], "base": "tomato" } ``` ### Combo selection (copy the catalogue 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": [] } ] } ``` ### Half-and-half selection ```json { "type": "half", "halves": [ { "product_id": 123, "size": "family", "removed": [] }, { "product_id": 125, "size": "family", "removed": [ 17 ] } ], "base": "tomato" } ``` ### Quote response (abbreviated) ```json { "meta": { "data": { "quote": { "currency": "EUR", "total_minor": 790, "delivery_minor": 0, "schedule_ready": true }, "quote_token": "", "expires_at": "2026-10-18T12:15:00Z" } } } ``` ## Orders: submit to the restaurant `POST /orders` This is the inbound flow: your platform creates an order in our administration. Send external_order_id, original items/choice, customer, expected_total_minor from the quote and quote_token. Never send price, source, user_id, status or card data. External_order_id is your permanent idempotency key. Create and persist it before the first HTTP request. Identical content with the same ID returns the same PF reference and replayed=true. Changed content with the same ID returns409. Array order matters; persist the request body. After a timeout, never generate a new ID. Query /orderstatus and retry the original request with the same ID/content if needed.404 does not prove an in-flight submission failed. An already saved identical request may resume even with an expired token; this does not authorize a new order from an expired quote. Name and complete phone are required. Provide the customer’s actual email when available; restaurant configuration may require it. Delivery needs street, number and necessary details. Do not substitute partner contacts. This creates neither a registered account nor marketing consent. Source is determined by authenticated partner identity. OneBite orders are automatically marked OneBite in administration and Telegram. HTTP 200 confirms persistence; it does not separately guarantee Telegram/email/SMS delivery. ### 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' ``` ### Successfully recorded order ```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 functions, no automatic submission ```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. ``` ## Status and retry handling `POST /orderstatus` Send external_order_id only. Returns status only for this partner’s order, without customer contacts. V1 has no other-channel lookup, remote state change, cancellation or webhook endpoint. Contact the restaurant for changes. Poll active orders every 30–60 seconds within the shared limit. On 429 respect Retry-After and add jitter; on 503 use bounded backoff with the same ID. Alert your operator after repeated failures. ### 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`: Recorded in the restaurant system; staff continue processing. - `preparing`: Being prepared. - `sent`: Dispatched; for pickup this means ready for collection. - `delivered`: Delivered / completed. - `cancelled`: Cancelled. - `payment_review`: Requires manual review; contact the restaurant. ## Complete attribute reference ### Request headers · HTTP headers - `Authorization` · string · Required: HTTP Basic: API key is username, separate password is password. Send only to the HTTPS API host. Example value: `"Basic "`. - `Accept` · string · Required: Explicitly request the JSON API response format. Example value: `"application/vnd.api+json"`. - `Content-Type` · string · post: For POST send a UTF-8 JSON object, not an HTML form or form-data. Example value: `"application/json"`. ### Catalogue pagination · GET /catalogue query - `page` · integer 1–10000 · Optional: Integer1–10000, default1. Example: /catalogue?page=2. Other query parameters are unsupported. Example value: `1`. ### Catalogue response · meta.data - `schema` · int · Response only: Payload schema version; currently 1. Example value: `1`. - `currency` · EUR · Response only: Currency of all prices. Example value: `"EUR"`. - `timezone` · string · Response only: Timezone used for restaurant opening hours. Example value: `"Europe/Sofia"`. - `categories` · Category[] · Response only: Categories; resolve them using each product’s category_ids. Example value: `[]`. - `products` · Product[] · Response only: Up to 50 products per page, including option children. Example value: `[]`. - `page` · int · Response only: Current page number, starting at 1. Example value: `1`. - `page_size` · int · Response only: Page size: 50. Example value: `50`. - `total` · int · Response only: Total product records, including unlisted option children. Example value: `144`. - `has_more` · bool · Response only: When true, request the next page. Example value: `true`. - `combos` · Combo[] · Response only: Combo offers; merge repeated page metadata by code. Example value: `[]`. - `pizza_bases` · Base[] · Response only: Global pizza bases; use products[].bases for product eligibility. Example value: `[]`. - `half` · HalfSettings object · Response only: Half-and-half configuration. Example value: `{}`. - `generated_at` · RFC3339 string · Response only: Generation timestamp, RFC3339. Example value: `"2026-10-18T12:00:00+00:00"`. ### Categories · categories[] - `id` · int · Response only: Stable category identifier. Example value: `7`. - `name` · string · Response only: Public category name. Example value: `"Пици"`. - `icon` · string · Response only: Icon code; presentation metadata. Example value: `"pizza"`. - `count` · int · Response only: Menu product count for the category. Example value: `24`. ### Product attributes · products[] - `id` · int · Response only: Stable ID; send it as selection.product_id. Example value: `123`. - `name` · string · Response only: Display name; changing documentation language does not translate dish names. Example value: `"Пица Чикън"`. - `description` · string · Response only: Plain text description without HTML. Example value: `"Пилешко месо, моцарела"`. - `category_ids` · integer[] · Response only: Category IDs containing this product. Example value: `[7]`. - `image` · url? · Response only: Absolute HTTPS product image URL, or null. Example value: `"https://pizzafashion.bg/images/example.webp"`. - `thumbnail` · url? · Response only: Smaller catalogue image URL, or null. Example value: `"https://pizzafashion.bg/images/example-thumb.webp"`. - `image_illustrative` · bool · Response only: Whether the image is illustrative. Example value: `false`. - `listed` · bool · Response only: False: flavour/package option child; retain it but do not display a separate card. Example value: `true`. - `minor` · minor? · Response only: Base displayed price in EUR cents. Use the selected variant price. Example value: `790`. - `currency` · EUR · Response only: Currency EUR. Example value: `"EUR"`. - `available` · bool · Response only: When false, disable ordering this product. Example value: `true`. - `available_at` · str? · Response only: Scheduled restock label/timestamp or null; does not override current available. Example value: `null`. - `availability_notice` · string · Response only: Availability notice, when configured. Example value: `""`. - `variants` · Variant[] · Response only: Available sizes and their prices, ingredients and options. Example value: `[]`. - `addons` · Addon[] · Response only: Allowed extras; selected-size prices are in variants[].addons. Example value: `[]`. - `bases` · Base[] · Response only: Allowed bases for this pizza; empty when inapplicable. Example value: `[]`. - `choices` · ChoiceGroup[] · Response only: Flavour/package choices referencing a different product_id. Example value: `[]`. - `half_eligible` · bool · Response only: Eligible for half-and-half when enabled and a family variant exists. Example value: `true`. ### Sizes / variants · products[].variants[] - `key` · standard | medium | family · Response only: Key for selection.size; use only a returned variant. Example value: `"medium"`. - `label` · string · Response only: Human-readable size label. Example value: `"Средна"`. - `minor` · minor? · Response only: Full price for this size, not a size surcharge. Example value: `790`. - `weight_g` · int? · Response only: Weight in grams, or null when unspecified. Example value: `550`. - `ingredients` · Ingredient[] · Response only: Ingredients; only entries with removable=1 may be removed. Example value: `[]`. - `allergens` · integer[1–14][] · Response only: Numeric codes 1–14; ingredient removal never guarantees allergen-free food. Example value: `[1,7]`. - `crust_selectable` · bool · Response only: Whether crust selection is permitted. Example value: `true`. - `crust_included` · bool · Response only: Recipe’s included crust; use as the initial crust selection. Example value: `false`. - `crust_extra_minor` · minor? · Response only: Surcharge for adding crust; null does not mean free. Example value: `250`. - `addons` · Addon[] · Response only: Extras priced for this exact size. Example value: `[]`. ### Ingredients · variants[].ingredients[] - `id` · int · Response only: ID for selection.removed[] when removal is allowed. Example value: `17`. - `name` · string · Response only: Ingredient name. Example value: `"Лук"`. - `removable` · boolean | 0 | 1 · Response only: 1/true permits removal; 0/false prohibits it. Example value: `1`. ### Sauces and extras · products[].addons[] / variants[].addons[] - `code` · string · Response only: Send this code in selection.addons[], without duplicates. Example value: `"garlic_sauce"`. - `name` · string · Response only: Extra name for the selected size. Example value: `"Чеснов сос"`. - `group` · sauce | topping · Response only: Extra group: sauce or pizza topping. Example value: `"sauce"`. - `enabled` · boolean | 0 | 1 · Response only: Definition enabled flag; catalogue includes permitted extras. Example value: `1`. - `minor` · integer · EUR cents · Response only: Price in EUR cents; variant-level value is size-specific. Example value: `100`. - `allergens` · integer[1–14][] · Response only: Allergen codes of this extra. Example value: `[7]`. - `reviewed` · boolean | 0 | 1 · Response only: Whether allergen data has been reviewed. Example value: `1`. - `family_minor` · integer · EUR cents · Response only: Optional family-size price metadata. Example value: `200`. - `weight_g` · int · Response only: Extra weight in grams when known. Example value: `50`. - `family_weight_g` · int · Response only: Optional family-size weight. Example value: `100`. - `family_name` · string · Response only: Optional family-size display label. Example value: `"Добавка — 100 г"`. ### Pizza bases · products[].bases[] / pizza_bases[] - `code` · string · Response only: Code for selection.base; this is not a paid extra. Example value: `"tomato"`. - `name` · string · Response only: Display label. Example value: `"Доматено пюре"`. - `enabled` · boolean | 0 | 1 · Response only: Definition flag; may be absent in normalized quote bases. Example value: `1`. - `default` · boolean | 0 | 1 · Response only: Default base, used when base is omitted. Example value: `1`. - `minor` · integer · EUR cents · Response only: Bases are included; value is 0. Example value: `0`. - `allergens` · integer[1–14][] · Response only: Base allergen codes. Example value: `[]`. - `reviewed` · boolean | 0 | 1 · Response only: Whether allergen data is reviewed. Example value: `1`. ### Flavour / package choices · products[].choices[] - `label` · string · Response only: Choice group heading. Example value: `"Избери вкус"`. - `show_price` · bool · Response only: Whether to display the price beside the option. Example value: `true`. - `options` · ChoiceOption[] · Response only: Alternatives, each using a separate product_id. Example value: `[]`. ### Choice option · choices[].options[] - `product_id` · int · Response only: After choosing, resolve this product and its own variants/extras. Example value: `124`. - `label` · string · Response only: Option label. Example value: `"Без захар"`. - `minor` · integer · EUR cents · Response only: Standalone price of the target product. Example value: `200`. - `current` · bool · Response only: Whether this option represents the current product. Example value: `false`. ### Combo offers · combos[] - `code` · string · Response only: Stable code for selection.combo_code. Example value: `"combo-example"`. - `revision` · string · Response only: Copy exactly into combo_revision; never invent a revision. Example value: `""`. - `name` · string · Response only: Public name. Example value: `"Комбо предложение"`. - `description` · string · Response only: Short description. Example value: `"Пица и напитка"`. - `image` · url? · Response only: HTTPS image URL or null. Example value: `null`. - `minor` · integer · EUR cents · Response only: Default configuration price; options may add a surcharge. Example value: `1490`. - `available` · bool · Response only: Current availability; server rechecks it on submission. Example value: `true`. - `schedule` · string · Response only: Human-readable schedule, not authoritative ordering permission. Example value: `"Всеки ден · през работното време"`. - `slots` · ComboSlot[] · Response only: Ordered slots; preserve their count and order. Example value: `[]`. - `default_selection` · Selection object · Response only: Ready-to-use request selection; change only permitted options. Example value: `{}`. ### Combo slots · combos[].slots[] - `product_ids` · integer[] · Response only: Allowed products for this slot; do not substitute other IDs. Example value: `[123,124]`. - `size` · standard | medium | family · Response only: Fixed slot size; cannot be changed. Example value: `"medium"`. ### Half-and-half settings · half - `enabled` · bool · Response only: When false, do not offer the half-and-half builder. Example value: `true`. - `size` · family · Response only: Half-and-half is available only in family size. Example value: `"family"`. - `price_mode` · higher | average | fixed | unset · Response only: Pricing mode; always obtain the final amount from /quote. Example value: `"higher"`. - `fixed_minor` · minor? · Response only: Fixed price for fixed mode; null does not mean zero. Example value: `null`. ### Receipt and opening hours · meta.data - `currency` · EUR · Response only: Currency of monetary values. Example value: `"EUR"`. - `timezone` · string · Response only: Local timezone used for schedules. Example value: `"Europe/Sofia"`. - `payment_methods` · string[] · Response only: This version returns cash only. Example value: `["cash"]`. - `ordering_enabled` · bool · Response only: Global/partner submission switch; does not guarantee a zone is open now. Example value: `true`. - `zones` · Zone[] · Response only: Delivery zones. Numeric database fields may be numbers or numeric strings. Example value: `[]`. - `pickup` · Pickup object · Response only: Restaurant collection information. Example value: `{}`. - `notice` · string · Response only: Reminder to use /quote for authoritative rules. Example value: `"Use POST /quote for authoritative current eligibility, fees and minimums."`. ### Delivery zones · zones[] - `id` · integer | numeric string · Response only: ID for choice.zone_id; send it as an integer. Example value: `1`. - `name` · string · Response only: Delivery area name. Example value: `"Велинград"`. - `enabled` · integer | numeric string · Response only: 1 means enabled; 0 means disabled. Example value: `1`. - `days_mask` · integer | numeric string · Response only: Weekday bitmask: Monday=1, Tuesday=2, … Sunday=64; 127=every day. Example value: `127`. - `acceptance_opens` · string · Response only: Ordinary opening time, HH:MM, Europe/Sofia. Example value: `"09:00"`. - `acceptance_cutoff` · string · Response only: Ordinary cutoff; late/holiday rules are enforced by /quote. Example value: `"22:00"`. - `delivery_fee_minor` · numeric? · Response only: Ordinary delivery fee or null; use /quote for the exact fee. Example value: `0`. - `minimum_order_minor` · numeric? · Response only: Ordinary minimum or null when not configured. Example value: `0`. - `free_delivery_over_minor` · numeric? · Response only: Free-delivery threshold or null if none. Example value: `null`. ### Restaurant collection · pickup - `location_id` · main · Response only: Use main in choice.location_id. Example value: `"main"`. - `name` · string · Response only: Restaurant name. Example value: `"Pizza Fashion"`. - `address` · string · Response only: Collection address. Example value: `"Велинград, адрес на обекта"`. - `opens` · string · Response only: Ordinary opening time. Example value: `"09:00"`. - `cutoff` · string · Response only: Ordinary cutoff time. Example value: `"22:00"`. - `days` · (integer | numeric string)[] · Response only: ISO weekdays: 1=Monday, 7=Sunday; values may be strings. Example value: `[1,2,3,4,5,6,7]`. ### Import an order into Pizza Fashion · POST /orders - `external_order_id` · string 1–80 · Required: Your permanent unique ID: 1–80 ASCII letters/digits and _ . : -; starts alphanumeric. Reuse on retries. Example value: `"ONEBITE-2026-10001"`. - `items` · Line[] · Required: 1–40 lines; identical to the matching signed quote request. Example value: `[]`. - `choice` · ReceiptChoice object · Required: Receipt/payment choice; identical to the matching /quote request. Example value: `{}`. - `customer` · Customer object · Required: Fulfilment contacts for this order; no Club account is created. Example value: `{}`. - `expected_total_minor` · integer ≥ 1 · Required: Exact quote.total_minor as a JSON integer; not a price override. Example value: `790`. - `quote_token` · string · Required: Unchanged /quote token; first submission must be within 15 minutes. Example value: `""`. ### Authoritative quote request · POST /quote - `items` · Line[] · Required: 1–40 configured lines; never send item prices. Example value: `[]`. - `choice` · ReceiptChoice object · Required: Receipt method and cash payment. Example value: `{}`. ### Order line · items[] - `selection` · Selection object · Required: Product, half-and-half or combo configuration. Example value: `{}`. - `quantity` · integer 1–20 · Optional: Quantity 1–20; defaults to 1. Example value: `1`. ### Product configuration · items[].selection - `type` · product | half | combo · Optional: Selection kind; defaults to product. Set explicitly for half and combo. Example value: `"product"`. - `product_id` · integer ≥ 1 · product: Catalogue ID; for a normal product or nested half/combo slot. Example value: `123`. - `size` · standard | medium | family · product: Returned variants[].key. Preserve combo slot size; half-and-half uses family. Example value: `"medium"`. - `removed` · integer[] · Optional: Up to 100 distinct removable ingredient IDs; defaults to []. Example value: `[17]`. - `crust` · boolean | 0 | 1 · Optional: Crust selection: 0/1 or false/true. Initialize from crust_included; change only when selectable. Example value: `0`. - `addons` · string[] · Optional: Up to 40 distinct codes allowed for the product/size; not on the outer half-and-half selection. Example value: `["garlic_sauce"]`. - `base` · string · Optional: One product.bases code; omission uses default. For half-and-half put it on the outer selection. Example value: `"tomato"`. - `halves` · Selection[] · half: Exactly two halves with product_id, size=family and removed. Each half follows included crust; extra selection is unsupported here. Example value: `[]`. - `combo_code` · string · combo: Exact combos[].code. Example value: `"combo-example"`. - `combo_revision` · string · combo: Send the returned revision to detect a changed definition/price. Example value: `""`. - `items` · Selection[] · combo: Ordered default_selection.items, one per slot. No nested quantity field. Example value: `[]`. ### Delivery, collection and payment · choice - `fulfilment` · delivery | pickup · Required: Send delivery or pickup explicitly. Example value: `"delivery"`. - `zone_id` · integer ≥ 1 · delivery: Returned /availability.zones ID for delivery. Example value: `1`. - `location_id` · main · pickup: Use main for pickup; never change fulfilment to bypass delivery rules. Example value: `"main"`. - `payment` · cash · Required: Cash on delivery/collection only; no online charge or payment-processing fee. Example value: `"cash"`. ### Customer fulfilment contacts · customer - `name` · str100 · Required: Real customer name, up to 100 characters, plain text. Example value: `"Име на клиента"`. - `phone` · string ≤ 80 · Required: Complete valid phone, preferably E.164. Bulgarian mobile: 10 domestic digits with leading0 or +359 and 9digits; incomplete numbers are rejected. Example value: `"0888123456"`. - `email` · email string ≤ 190 · email: Valid email; required when configured by the restaurant. Send the real address when available. Example value: `"customer@example.com"`. - `address` · str240 · delivery: For delivery: street, number and needed details, up to 240 characters. Unused for pickup. Example value: `"Улица Примерна 10, вход А"`. - `note` · str500 · Optional: Note up to 500 characters; restaurant can disable it. No HTML or line breaks. Example value: `"Звънец 3"`. ### Own-order status request · POST /orderstatus - `external_order_id` · string 1–80 · Required: The same ID this partner used when submitting the order. Example value: `"ONEBITE-2026-10001"`. ### Order / status response · meta.data - `external_order_id` · string 1–80 · Response only: Your order ID. Example value: `"ONEBITE-2026-10001"`. - `order_number` · string · Response only: Reference generated by Pizza Fashion; do not use it as your external ID. Example value: `"PF-A2000"`. - `status` · accepted | preparing | sent | delivered | cancelled | payment_review · Response only: Machine-readable state; meanings are listed separately. Example value: `"accepted"`. - `status_label` · string · Response only: Restaurant label; may remain Bulgarian regardless of documentation language. Example value: `"Приета"`. - `currency` · EUR · Response only: Order currency. Example value: `"EUR"`. - `total_minor` · integer · EUR cents · Response only: Saved final amount including delivery. Example value: `790`. - `payment` · cash · Response only: Payment method is cash. Example value: `"cash"`. - `paid` · bool · Response only: Recorded payment flag. False is normal for a newly created cash order. Example value: `false`. - `created_at` · RFC3339 string · Response only: UTC creation timestamp. Example value: `"2026-10-18T12:00:00Z"`. - `updated_at` · RFC3339 string · Response only: UTC last-update timestamp. Example value: `"2026-10-18T12:05:00Z"`. - `replayed` · bool · Response only: Only on /orders: true means an identical replay, not another order. Example value: `false`. ### Quote response · meta.data - `quote` · Quote object · Response only: Authoritative pricing breakdown; do not POST this whole object as an order. Example value: `{}`. - `quote_token` · string · Response only: Retain and send with the original items/choice to /orders. Example value: `""`. - `expires_at` · RFC3339 string · Response only: First-submission deadline, approximately 15 minutes. Example value: `"2026-10-18T12:15:00+00:00"`. ### Totals and receipt rules · quote - `schema` · int · Response only: Pricing payload schema version. Example value: `1`. - `test_only` · bool · Response only: True prevents operational partner submission. Example value: `false`. - `currency` · EUR · Response only: All amounts are EUR cents. Example value: `"EUR"`. - `choice` · QuoteChoice object · Response only: Normalized display choice; keep the original request choice for /orders. Example value: `{}`. - `location` · QuoteLocation object · Response only: Delivery zone or collection location. Example value: `{}`. - `items` · QuotedLine[] · Response only: Configured lines and pricing breakdowns. Example value: `[]`. - `count` · int · Response only: Sum of quantities, not number of lines. Example value: `2`. - `subtotal_minor` · integer · EUR cents · Response only: Product subtotal before order-level discounts and delivery. Example value: `1580`. - `delivery_minor` · integer · EUR cents · Response only: Authoritative receipt/delivery fee. Example value: `0`. - `total_minor` · integer · EUR cents · Response only: Final display amount; use as expected_total_minor. Example value: `1580`. - `minimum_minor` · minor? · Response only: Applicable minimum-order amount. Example value: `0`. - `minimum_exclusive` · bool · Response only: True means the amount must be strictly above the minimum. Example value: `false`. - `minimum_includes_delivery` · bool · Response only: Whether the minimum includes delivery. Example value: `false`. - `period` · regular | late | pickup · Response only: Applied receipt period. Example value: `"pickup"`. - `schedule_ready` · bool · Response only: Whether current rules permit operational submission. Example value: `true`. - `warnings` · string[] · Response only: Warnings intended for display. Example value: `[]`. - `discount_minor` · integer · EUR cents · Response only: Order promotion discount; partner API accepts no promo code, normally 0. Example value: `0`. - `discount_code` · string · Response only: Promotion code; empty for this API. Example value: `""`. - `online_discount_minor` · integer · EUR cents · Response only: Online payment discount; 0 for cash. Example value: `0`. - `online_discount_basis_points` · int · Response only: Online-discount basis points; 0 for cash. Example value: `0`. - `delivery_net_minor` · integer · EUR cents · Response only: Delivery amount excluding included VAT. Example value: `0`. - `delivery_tax_minor` · integer · EUR cents · Response only: Included VAT on delivery. Example value: `0`. - `net_minor` · integer · EUR cents · Response only: Total net amount; do not add it again to total. Example value: `1317`. - `tax_minor` · integer · EUR cents · Response only: Total included VAT, not an additional charge. Example value: `263`. - `vat_percent` · int · Response only: VAT percentage from restaurant configuration, not a fixed API policy. Example value: `20`. ### Normalized quote choice · quote.choice - `fulfilment` · delivery | pickup · Response only: Validated receipt method. Example value: `"pickup"`. - `zone_id` · int · Response only: Delivery zone; 0 for pickup. Example value: `0`. - `location_id` · string · Response only: Main for pickup; empty string for delivery. Example value: `"main"`. - `payment` · cash · Response only: Validated cash payment method. Example value: `"cash"`. ### Quoted receipt location · quote.location - `id` · integer | "main" · Response only: Numeric zone ID or main for pickup. Example value: `"main"`. - `name` · string · Response only: Location/zone name. Example value: `"Pizza Fashion"`. - `address` · string · Response only: Collection address for pickup; empty for a delivery zone. Example value: `"Велинград, адрес на обекта"`. - `opens` · string · Response only: Ordinary opening time. Example value: `"09:00"`. - `cutoff` · string · Response only: Ordinary cutoff. Example value: `"22:00"`. ### Priced line · quote.items[] - `key` · string · Response only: Server line key; not a product_id and not a request field. Example value: `""`. - `name` · string · Response only: Configured display name. Example value: `"Пица Чикън — Средна"`. - `quantity` · int · Response only: Quantity. Example value: `1`. - `unit_minor` · integer · EUR cents · Response only: Price per configured unit. Example value: `790`. - `subtotal_minor` · integer · EUR cents · Response only: Unit price × quantity before order-level discounts. Example value: `790`. - `configuration` · Configuration object · Response only: Full calculated configuration, read-only. Example value: `{}`. - `discount_minor` · integer · EUR cents · Response only: Allocated line discount. Example value: `0`. - `gross_minor` · integer · EUR cents · Response only: Discounted line amount including VAT. Example value: `790`. - `net_minor` · integer · EUR cents · Response only: Line amount excluding included VAT. Example value: `658`. - `tax_minor` · integer · EUR cents · Response only: Included line VAT. Example value: `132`. ### Calculated product / combo · quote.items[].configuration - `schema` · int · Response only: Configuration schema version. Example value: `1`. - `type` · product | half | combo · Response only: Configured line kind. Example value: `"product"`. - `name` · string · Response only: Display name. Example value: `"Пица Чикън — Средна"`. - `unit_minor` · integer · EUR cents · Response only: Final configured unit price. Example value: `790`. - `currency` · EUR · Response only: Currency EUR. Example value: `"EUR"`. - `weight_g` · int? · Response only: Total weight when known. Example value: `550`. - `parts` · ConfiguredPart[] · Response only: One product, two halves or combo parts. Example value: `[]`. - `allergens` · integer[1–14][] · Response only: Combined allergen codes. Example value: `[1,7]`. - `base` · Base object · Response only: Normalized selected base when applicable. Example value: `{}`. - `combo_code` · string · Response only: Combo code. Example value: `"combo-example"`. - `combo_revision` · string · Response only: Applied combo revision. Example value: `""`. - `regular_minor` · integer · EUR cents · Response only: Equivalent standalone total. Example value: `1800`. - `combo_savings_minor` · integer · EUR cents · Response only: Savings from the combo price. Example value: `270`. - `extras_minor` · integer · EUR cents · Response only: Selected option surcharges. Example value: `0`. - `selection` · Selection object · Response only: Normalized combo selection; retain the original request for token matching. Example value: `{}`. ### Calculated product part · configuration.parts[] - `product_id` · int · Response only: Product ID. Example value: `123`. - `name` · string · Response only: Product/part name. Example value: `"Пица Чикън"`. - `size` · standard | medium | family · Response only: Selected size. Example value: `"medium"`. - `recipe_revision` · int · Response only: Recipe revision, read-only. Example value: `3`. - `weight_g` · int? · Response only: Weight in grams or null. Example value: `550`. - `portion` · string · Response only: Configured serving label, when present. Example value: `""`. - `ingredients` · Ingredient[] · Response only: Recipe ingredient list. Example value: `[]`. - `omitted` · OmittedIngredient[] · Response only: Removed ingredients. Example value: `[]`. - `allergens` · integer[1–14][] · Response only: Allergen codes; removing an ingredient does not automatically remove them. Example value: `[1,7]`. - `allergens_reviewed` · bool · Response only: Whether allergen data is reviewed. Example value: `true`. - `composition_complete` · bool · Response only: Whether recorded composition is complete. Example value: `true`. - `crust` · boolean | 0 | 1 · Response only: Effective crust selection. Example value: `0`. - `crust_included` · bool · Response only: Whether the recipe includes crust. Example value: `false`. - `crust_extra_minor` · integer · EUR cents · Response only: Actual applied crust surcharge. Example value: `0`. - `base_minor` · integer · EUR cents · Response only: Base product/size price. Example value: `790`. - `unit_minor` · integer · EUR cents · Response only: Part unit price. Do not independently sum half prices for a half-and-half order. Example value: `790`. - `half_eligible` · bool · Response only: Half-and-half eligibility. Example value: `true`. - `addons` · Addon[] · Response only: Selected product/combo-part extras; absent on half parts. Example value: `[]`. - `addons_extra_minor` · integer · EUR cents · Response only: Total extra surcharge. Example value: `0`. - `size_extra_minor` · integer · EUR cents · Response only: Informational size surcharge, already included in the price. Example value: `0`. ### Removed ingredient · configuration.parts[].omitted[] - `id` · int · Response only: Removed ingredient ID. Example value: `17`. - `name` · string · Response only: Removed ingredient name. Example value: `"Лук"`. ### API controller error · meta.error - `status` · int · Response only: HTTP error status. Example value: `422`. - `message` · string · Response only: Explanation; some business messages are Bulgarian. Branch on status, not exact text. Example value: `"Invalid request"`. ## Response and error codes Handle HTTP status before parsing JSON. Do not assume every infrastructure error contains meta.error. Do not endlessly retry invalid-data or authentication errors. ### Controller error envelope ```json { "meta": { "error": { "status": 409, "message": "Total changed. Request a fresh quote." } } } ``` - `200`: Success, including an identical /orders replay. - `400`: Unsupported query parameters / gateway request. - `401`: Missing, invalid, paused or rotated credentials. - `403`: HTTPS required; HTTP API requests are not redirected with credentials. - `404`: No own order for this ID, or unknown route. - `409`: Changed/expired quote, conflicting external_order_id or busy request. Reconcile status. - `413 / 415`: Body exceeds 128 KiB / Content-Type: application/json required. - `422`: Invalid fields/options/contact/minimum, closed period or unavailable product. Correct the request. - `429`: Rate limit reached. Respect Retry-After. - `503`: Disabled submission/API/partner or temporary unavailability. Bounded retries with the same ID. ## Access, limits and compatibility Credentials are for your server only. Keep them in a secret store or environment variables. Never place them in URLs, frontend JavaScript, mobile apps, logs or a public API tester. Redact Authorization and customer data. Default partner limit is 120 requests/minute; the restaurant controls individual/global caps. The lower cap applies, plus an independent 120/minute source-IP cap. Avoid aggressive concurrent full synchronizations. New partners start paused, without issued credentials or order submission. Pizza Fashion generates separate credentials for each partner. Pausing blocks subsequent requests; rotation invalidates old credentials while preserving identity and replay protection. API remains v1. The original https://pizzafashion.bg/api/index.php/v1/pizzafashion/partner base remains compatible. Ignore unknown additive response fields; do not send undocumented request fields. Arrange a controlled integration rehearsal with Pizza Fashion before going live. These examples contain no working credentials and never send orders automatically.