API endpoints
A list of available endpoints, list parameters, required fields, curl examples and error handling.
Last updated:
Base URL, response format and pagination
All endpoints are available at https://app.bakepilot.pl/api/v1/. Responses are in JSON (UTF-8), dates in ISO 8601 format, amounts in the document currency. A single resource is in the data field. Lists are paginated with the limit and offset parameters, and the response has a meta field with total (the number of all items), limit and offset. The full, always up-to-date specification that you can test live is at /api/v1/docs (Swagger), and the machine-readable OpenAPI description at /api/v1/openapi.
- Base URL: https://app.bakepilot.pl/api/v1/
- List: { data: [...], meta: { total, limit, offset } }; single resource: { data: {...} }
- Pagination: limit (default 50, maximum 200; products default 200, ingredients default 100, both up to 500) and offset (from 0)
- Interactive documentation (Swagger): https://app.bakepilot.pl/api/v1/docs
- OpenAPI specification: https://app.bakepilot.pl/api/v1/openapi
Open the interactive API documentation (Swagger) (opens in a new tab)
Example call (curl)
You pass the key in the Authorization: Bearer or X-API-Key header. The call below fetches a recipe scaled to 100 pieces - the same numbers you will see on the printout in the app.
- curl -H "Authorization: Bearer bp_live_YOUR_KEY" "https://app.bakepilot.pl/api/v1/recipes?limit=20"
- curl -H "Authorization: Bearer bp_live_YOUR_KEY" "https://app.bakepilot.pl/api/v1/recipes/RECIPE_ID?qty=100"
- curl -H "X-API-Key: bp_live_YOUR_KEY" "https://app.bakepilot.pl/api/v1/wz?status=issued&limit=50"
- curl -X POST -H "Authorization: Bearer bp_live_YOUR_KEY" -H "Content-Type: application/json" -d '{"customer_name":"Anna Kowalska","customer_phone":"600100200","pickup_date":"2026-10-01T10:00:00","items":[{"product_name":"Jagodzianka","quantity":6,"unit_price":12,"total_price":72}]}' https://app.bakepilot.pl/api/v1/orders
Orders
GET /orders returns the organization's orders, newest first (parameters limit, offset and status). GET /orders/{id} returns an order together with its items (order_items). POST /orders creates an order - by default with the Draft status (draft), and with the status field you can immediately set confirmed, in_production or ready. The customer is matched by customer_id, phone number or NIP, or created automatically. You pass the items in the items field. You can leave out an item's price - we will then calculate it from the catalog (product price, B2B price, customer discount).
- GET /orders - list (limit, offset, status)
- GET /orders/{id} - order with items
- POST /orders - creating an order, default status draft (Draft)
- Required: customer_name, pickup_date, items (at least one item)
- Item: product_name and quantity are required; product_id, variant_id, notes and configuration are optional
- Leave out unit_price and we will calculate the price from the catalog; an item outside the catalog, a tray portion and a cake from the configurator require unit_price
- total_price is optional - if you provide it, it must equal quantity × price after the item discount (otherwise 422)
- Item discount: discount_percent or discount_amount (not both at once)
- client_request_id (UUID) - a retry with the same identifier returns 200 and the same order, without a duplicate
- Additional fields: payment_method_note (gotowka = cash, przelew = bank transfer, karta = card, termin = payment on terms), crates_small, crates_medium, crates_large
- Status on creation: draft, confirmed, in_production or ready - any other value saves the order as Draft
Tip: Deposit: a deposit greater than zero immediately sets the deposit payment date (deposit_paid_at). Changing the status, deposit and notes after creation is described in the section below.
Changing an order and attachments
After an order is created, your system can change its status, enter a deposit, add a note and add a file - e.g. a cake inspiration photo from a form on your website. You cannot change items, the customer or dates through the API - you do that in the app. Both endpoints require the orders:write key permission.
- PATCH /orders/{id} - the fields status, deposit and notes_append (each optional)
- status: confirmed, in_production, ready or cancelled; the API does not set the Handed over status (delivered)
- You cannot change the status of an order that has been handed over or cancelled (422) - you can still save a deposit and a note addition for such an order; the same status as the current one changes nothing
- A deposit greater than zero sets the deposit payment date - the first saved date stays
- notes_append (up to 1000 characters) adds a line at the end of the order note
- 409 - the order changed in the meantime: fetch it again (GET) and retry the change
- POST /orders/{id}/attachments - a file as multipart/form-data: file (required), note (up to 500 characters), client_request_id (UUID, optional)
- Files: JPG, PNG, WEBP, HEIC, PDF, up to 10 MB, type checked by content; a maximum of 10 attachments per order
- A whole request through the API can be at most 4.5 MB - shrink a larger photo before sending
- A retry with the same client_request_id returns 200 and the existing attachment instead of adding a second one
- You will see the added file in the order, in the “Photos and graphics” section
Tip: Generate a client_request_id once for each new order or file and use it for retries after a dropped connection. That way a repeat will not create a duplicate.
Customers
GET /customers returns the organization's customers, newest first (limit, offset). POST /customers adds a customer. Creation is idempotent: if a customer with the same phone, e-mail or NIP already exists, the API returns their id with the field deduplicated: true instead of creating a duplicate - unless you add the parameter ?force=true.
- GET /customers - list of customers (limit, offset)
- POST /customers - adding a customer, response { id, deduplicated }
- Required: first_name, last_name
- Optional: phone, email, nip, address, customer_type (b2c or b2b), b2b_discount, marketing consents
- Deduplication by phone, e-mail or NIP; ?force=true always creates a new one
Products
GET /products returns the organization's product catalog in alphabetical order - by default only active products, the parameter active=false shows all, and search filters by name. GET /products/{id} returns a product with its variants (product_variants), label ingredients, allergens, net weight and shelf life. You don't create products through the API - you manage them in the app.
- GET /products - catalog (limit up to 500, offset, active, search)
- GET /products/{id} - product with variants, ingredients and allergens
- Creating and editing products: in the app
Recipes and ingredients (read only)
GET /recipes returns a list of recipes with their yield (“The batch yields”: yield_unit and portions_count), batch weight and costs. GET /recipes/{id} returns a recipe with its ingredients: for each item the name, the amount in grams (amount_grams), and for sub-recipes the sub_recipe_id. The qty parameter scales the recipe to a production size in the recipe's unit - in the scaled_amount_grams field you get the multiplied amounts, and in scaling the multiplier and the weight after scaling. GET /ingredients and GET /ingredients/{id} return the ingredient database with prices per kg, nutritional values and the label name.
- GET /recipes - list (limit, offset, active, search)
- GET /recipes/{id}?qty=100 - recipe scaled to 100 pieces / kg / trays
- A recipe without a set yield: scaling.factor = null and unavailable_reason
- GET /ingredients, GET /ingredients/{id} - ingredient database (limit up to 500, offset, active, search)
- Key permissions: recipes:read, ingredients:read (keys created earlier received them automatically)
Tip: Amounts in a recipe are always in grams - also for ingredients bought in pieces or liters.
Delivery notes (WZ), quotes and contracts (read only)
GET /wz returns delivery notes (WZ), newest first (the status parameter: issued, invoiced, cancelled), and GET /wz/{id} a document with its items (wz_items: product name, quantity, net and gross price, VAT rate). GET /offers and GET /offers/{id} return quotes with their items (offer_items), GET /contracts and GET /contracts/{id} contracts with payment terms and the dates they were sent and signed. You don't create documents through the API - they are created in the app.
- GET /wz, GET /wz/{id} - delivery notes (WZ) with items (wz:read permission)
- GET /offers, GET /offers/{id} - quotes with items (offers:read)
- GET /contracts, GET /contracts/{id} - contracts (contracts:read)
- Each list accepts limit, offset and status
Error handling
The API returns standard HTTP codes: 200 (success, also a repeat with the same client_request_id), 201 (created), 400 (invalid JSON), 401 (missing or wrong key), 403 (plan without API or a key without the required permission), 404 (the resource does not exist in your organization), 409 (the order changed in the meantime), 413 (file too large), 415 (unsupported file type), 422 (invalid input data or parameters, e.g. limit out of range), 429 (request limit, Retry-After header), 500 (error on our side), 503 (service temporarily unavailable). Every error has the JSON format { error: { code, message } }. In your integration rely on the code field - the message is a description for humans and its wording may change.
- 200 / 201 - success / created
- 400 - invalid JSON
- 401 - missing or wrong API key
- 403 - plan without API (code plan_required) or a key without the permission
- 404 - the resource does not exist (also someone else's identifier)
- 409 - the order changed in the meantime, fetch it and retry the change
- 413 - file larger than 10 MB
- 415 - unsupported file type (allowed: JPG, PNG, WEBP, HEIC, PDF)
- 422 - invalid input data or parameters
- 429 - the limit of 120 requests per minute was exceeded, Retry-After header
- 500 - server error, we log the details on our side
- 503 - service temporarily unavailable, retry the request in a moment (the key is still valid)