E-shop → objednávka → výdejka → faktura
Tenhle recept popisuje, jak napojit e-shop na ProfiBrew: synchronizaci
katalogu a partnerů, založení objednávky z e-shopové sady a její doběhnutí
až k zaplacené faktuře — přesně tou cestou, kterou by klikal člověk v
aplikaci, jen přes API.
1. Cíl
Po dokončení receptu umí vaše integrace:
- udržovat katalog položek a partnerů v ProfiBrew v souladu s e-shopem
(obousměrné mapování přes
external_ref),
- založit objednávku z e-shopové objednávky se správnými množstvími a
jednotkami,
- nechat ji projít workflow (potvrzení → výdejka → faktura → úhrada)
pomocí akcí z katalogu, stejně jako by to udělal obchodník v UI,
- zjistit dostupnost zboží před přijetím objednávky.
2. Klíč, sandbox a scopes
Než začnete integrovat proti ostrým datům, založte si v /settings/api
testovací klíč (pb_test_…) — běží nad stínovým DEMO tenantem, takže
si můžete recept vyzkoušet bez rizika pro produkční data. Testovací a
ostrý klíč fungují na obou hostech rodiny Profi stejně, jen s hlavičkou
X-Profi-Sandbox: 1 navíc a livemode: false v odpovědích.
Zvolte předdefinovanou sadu scopes E-shop:
core:read, core:write, stock:read, sales:read, sales:write, events:read
Tahle sada NEobsahuje finance:write — přesto stačí na fakturaci, protože
POST /invoices a actions/create_invoice přijímají i sales:write
(faktura vzniklá z objednávky patří pod Obchod). Vyžadovaný scope u
každého volání je v hlavičce odpovědi OpenAPI dokumentu (x-scopes) a v
tabulkách níže.
Ukládejte klíč do proměnné prostředí, nikdy ne do URL:
export K=pb_test_…
3. Základní URL
https://www.profibrew.com/api/v1
(zákazníci na značce ProfiEkonom používají https://app.profifirma.cz/api/v1
— stejný klíč funguje na obou hostech).
4. Krok 0 — kdo jsem a v čem tenant měří
curl -s -H "Authorization: Bearer $K" \
https://www.profibrew.com/api/v1/me | jq '{tenant, scopes, modules, measure_profile}'
curl -s -H "Authorization: Bearer $K" \
https://www.profibrew.com/api/v1/units | jq '.data[] | {id, code, dimension, decimals}'
GET /v1/units vrací číselník jednotek dostupných tomuto tenantovi
(systémové podle jeho měrné soustavy + vlastní). Každé množství v API se
na jednu z nich odkazuje přes unit_id nebo unit_code — API množství
NIKDY nepřevádí, takže znát nabídku tenanta předem se vyplatí.
5. Krok 1 — synchronizace katalogu
Pro každou položku e-shopu nejdřív zkuste najít existující záznam podle
vaší reference:
curl -s -H "Authorization: Bearer $K" \
"https://www.profibrew.com/api/v1/items/by-external-ref/shoptet/10234"
404 not_found → položka ještě neexistuje, založte ji s external_ref a
?on_conflict=return (souběžný druhý pokus se stejnou referencí tak
nespadne na 409, ale vrátí existující záznam):
curl -s -X POST -H "Authorization: Bearer $K" \
-H "Idempotency-Key: 8f14e45f-ceea-467e-bd3f-b7a1f1a2a3b4" \
-H "Content-Type: application/json" \
-d '{
"name": "Ležák 12° 0,5 l",
"item_type": "purchased_sale_item",
"unit": { "unit_code": "ks" },
"sale_price": "34.90",
"external_ref": { "system": "shoptet", "id": "10234" }
}' \
"https://www.profibrew.com/api/v1/items?on_conflict=return" | jq '{id, unit, external_ref}'
Na existující položku pak posílejte jen PATCH se změněnými poli (tělo je
striktní — neznámé pole vrátí 422 unknown_field, nikdy se tiše
neignoruje):
curl -s -X PATCH -H "Authorization: Bearer $K" \
-H "Content-Type: application/json" \
-H "If-Match: $ETAG" \
-d '{"sale_price": "36.90"}' \
"https://www.profibrew.com/api/v1/items/$ITEM_ID"
If-Match (ETag z předchozího GET, hlavička ETag: W/"…") je nepovinný —
bez něj platí last-write-wins jako v UI; s ním chyba nesouhlasu vrátí
412 stale_version a integrace ví, že si má záznam znovu načíst.
Ceny pro konkrétního partnera a množství (kaskáda partner → ceník →
sleva → základní cena, stejná jako v UI) zjistíte přes:
curl -s -H "Authorization: Bearer $K" \
"https://www.profibrew.com/api/v1/items/$ITEM_ID/prices?partner_id=$PARTNER_ID&quantity=6" \
| jq '{unit_price, currency, price_source}'
quantity je vždy v jednotce položky — endpoint jednotku pro dotaz
nepřijímá (jednotka je odvozená z položky samotné).
6. Krok 2 — synchronizace partnerů
Stejný vzor jako u položek — nejdřív by-external-ref, pak POST s
?on_conflict=return:
curl -s -X POST -H "Authorization: Bearer $K" \
-H "Idempotency-Key: 3d3f0b3a-8b0b-4e9a-9a3a-2d3f0b3a8b0b" \
-H "Content-Type: application/json" \
-d '{
"name": "Hospoda U Zeleného stromu",
"is_customer": true,
"ico": "12345678",
"email": "objednavky@hospoda.cz",
"external_ref": { "system": "shoptet", "id": "cust-9981" }
}' \
"https://www.profibrew.com/api/v1/partners?on_conflict=return" | jq '{id, external_ref}'
Pokud jde partner dohledávat i podle IČO (bez vaší reference), počítejte s
409 duplicate_ico, když v ProfiBrew už existuje partner se stejným IČO
pod jiným external_ref — odpověď nese existingName. V tom případě
dohledejte partnera přes GET /v1/partners?search=<ičo> a napárujte
external_ref sami (PATCH partnera).
7. Krok 3 — dostupnost zboží
Než objednávku přijmete, ověřte sklad:
curl -s -H "Authorization: Bearer $K" \
"https://www.profibrew.com/api/v1/stock-levels?item_id=$ITEM_ID&warehouse_id=$WAREHOUSE_ID" \
| jq '.data[] | {quantity, available_quantity, unit}'
available_quantity = quantity − reserved_quantity (nikdy záporně).
Množství je vždy v jednotce položky, stejně jako všude v API.
8. Krok 4 — založení objednávky
curl -s -X POST -H "Authorization: Bearer $K" \
-H "Idempotency-Key: 3fa85f64-5717-4562-b3fc-2c963f66afa6" \
-H "Content-Type: application/json" \
-d '{
"partner_id": "'"$PARTNER_ID"'",
"warehouse_id": "'"$WAREHOUSE_ID"'",
"external_ref": { "system": "shoptet", "id": "ORD-2044" },
"lines": [
{ "item_id": "'"$ITEM_ID"'", "quantity": "6", "unit": { "unit_id": "'"$UNIT_ID"'" } }
]
}' \
"https://www.profibrew.com/api/v1/orders?on_conflict=return" | jq '{id, number, status, allowed_actions}'
Poznámky:
unit na řádku musí být jednotka POLOŽKY — jiná vrátí
422 unit_mismatch s expected_unit v těle chyby. API množství nikdy
nepřevádí (§3.4 specu S107); pošlete quantity rovnou v jednotce, kterou
vrátila položka v kroku 1.
unit_price na řádku můžete vynechat — dopočítá se stejnou cenovou
kaskádou jako v kroku 1.
warehouse_id je povinné, jen když má účet víc než jednu aktivní
provozovnu (jinak 422 shop_required — provozovna objednávky se odvozuje
ze skladu).
- Objednávka vzniká jako KONCEPT (
status: "draft") s origin: "api" —
v UI se u ní zobrazí odznak „přes API (název klíče)".
9. Krok 5 — průchod workflow přes akce
Stavové přechody NEJDOU přímo přes PATCH status — jen přes
POST …/actions/{key}, s klíči z téhož katalogu, který pohání tlačítka
v UI. Aktuální doklad vždy nese allowed_actions — na ně se spolehněte
místo natvrdo zapsané posloupnosti:
curl -s -X POST -H "Authorization: Bearer $K" \
-H "Idempotency-Key: $(uuidgen)" \
"https://www.profibrew.com/api/v1/orders/$ORDER_ID/actions/confirm" \
| jq '{status, allowed_actions}'
Vyskladnění (vytvoří výdejku v konceptu, objednávka zůstává ve stejném
stavu — potvrzení výdejky je samostatný krok):
curl -s -X POST -H "Authorization: Bearer $K" \
-H "Idempotency-Key: $(uuidgen)" \
"https://www.profibrew.com/api/v1/orders/$ORDER_ID/actions/create_stock_issue" \
| jq '.created'
# → { "object": "stock_issue", "id": "…" }
STOCK_ISSUE_ID=… # z odpovědi výše
curl -s -X POST -H "Authorization: Bearer $K" \
-H "Idempotency-Key: $(uuidgen)" \
"https://www.profibrew.com/api/v1/stock-issues/$STOCK_ISSUE_ID/actions/confirm" \
| jq '{status}'
Zásoba se odepíše až tímhle potvrzením (CLAUDE.md: žádný pohyb bez
potvrzeného dokladu) — do té doby je výdejka jen koncept.
Fakturace (přepne objednávku na invoiced):
curl -s -X POST -H "Authorization: Bearer $K" \
-H "Idempotency-Key: $(uuidgen)" \
"https://www.profibrew.com/api/v1/orders/$ORDER_ID/actions/create_invoice" \
| jq '.created'
# → { "object": "invoice", "id": "…" }
INVOICE_ID=…
curl -s -X POST -H "Authorization: Bearer $K" \
-H "Idempotency-Key: $(uuidgen)" \
"https://www.profibrew.com/api/v1/invoices/$INVOICE_ID/actions/issue" \
| jq '{status, number, allowed_actions}'
(Ekvivalentní zkratka bez objednávkové akce: POST /v1/invoices s tělem
{ "order_id": "…" } — vytvoří i vystaví ve dvou krocích stejně.)
Úhrada je MIMO katalog workflow (platební engine S89), ale připojuje se
stejně přes actions/:
curl -s -X POST -H "Authorization: Bearer $K" \
-H "Idempotency-Key: $(uuidgen)" \
-H "Content-Type: application/json" \
-d '{"paid_at": "2026-09-15"}' \
"https://www.profibrew.com/api/v1/invoices/$INVOICE_ID/actions/mark_paid" \
| jq '{status, amount_paid, amount_remaining}'
Bez account_id se použije výchozí účet tenanta pro platební metodu
faktury — pokud žádný není nastavený, vrátí se 422 account_required a
je potřeba poslat account_id. Dostupné účty vypíšete přes:
curl -s -H "Authorization: Bearer $K" \
"https://www.profibrew.com/api/v1/finance-accounts" \
| jq '.data[] | {id, type, name, currency}'
10. Inkrementální synchronizace
Pro pravidelné dotahování změn (ceny, sklad, stavy objednávek) používejte
updated_since + kurzor, ne plný re-scan:
curl -s -H "Authorization: Bearer $K" \
"https://www.profibrew.com/api/v1/orders?updated_since=2026-09-14T00:00:00Z&limit=100" \
| jq '{next_cursor, has_more}'
Když je has_more: true, zavolejte znovu se stejným updated_since a
cursor=<next_cursor>. Uložte si poslední zpracovaný updated_at
(případně přímo next_cursor) jako checkpoint pro příští běh.
11. Chyby, na které v tomhle receptu narazíte
| Kód | Kdy | Co s tím |
|---|
401 unauthenticated | chybí/špatný klíč | zkontrolujte hlavičku Authorization: Bearer … |
403 insufficient_scope | sada nemá potřebný scope (required_scopes v těle) | přidejte scope klíči v /settings/api, nebo přepněte na sadu Vlastní |
403 module_not_enabled | tenant nemá modul v předplatném | řešení je na straně zákazníka (upgrade) |
404 not_found | špatné id, nebo cizí tenant | ověřte, že id patří stejnému livemode (test/live) |
409 external_ref_conflict | external_ref už je použitý | zavolejte znovu s ?on_conflict=return, nebo použijte existing_id z těla chyby / hlavičku Location |
409 duplicate_ico | partner se stejným IČO existuje | dohledejte partnera přes GET /partners?search=, napárujte referenci |
409 invalid_transition | akce mimo allowed_actions | přečtěte allowed_actions z těla chyby — někdo mezitím doklad posunul jinam |
412 stale_version | If-Match nesedí s aktuální verzí | načtěte záznam znovu, zopakujte PATCH s novým ETagem |
422 unit_mismatch | jednotka řádku ≠ jednotka položky | pošlete quantity v jednotce z GET /items/{id} |
422 quantity_scale | víc desetinných míst, než dovoluje jednotka | zaokrouhlete na decimals jednotky sami — server nezaokrouhluje mlčky |
422 shop_required | víc aktivních provozoven, chybí warehouse_id | pošlete warehouse_id |
422 account_required | mark_paid bez výchozího účtu | vypište GET /finance-accounts a pošlete account_id |
429 rate_limited | překročen limit tarifu | počkejte podle Retry-After, sledujte hlavičky RateLimit-* |
12. Checklist před nasazením do produkce
Updated 2026-09-15
Účetní software — vydané a přijaté faktury, peněžní deník
Tenhle recept popisuje, jak napojit účetní/ekonomický systém na ProfiBrew
jako čtenáře dokladů (vydané faktury, přijaté faktury, peněžní deník) a
zapisovatele úhrad — tedy typický tok pro externí účtárnu nebo účetní
kancelář, která si doklady stahuje a zpětně hlásí, co je zaplacené.
1. Cíl
Po dokončení receptu umí vaše integrace:
- pravidelně stahovat nové a změněné vydané faktury, přijaté faktury a
doklady peněžního deníku (
updated_since + kurzor),
- založit přijatou fakturu ručně (dorazila poštou, ne skenem),
- zapsat úhradu dokladu s konkrétním účtem,
- naimportovat naskenovanou nebo ISDOC fakturu přes přílohu a digitální
podatelnu (nebo zkratkou pro malé soubory rovnou).
2. Klíč, sandbox a scopes
Založte si testovací klíč (pb_test_…) a sadu Účetnictví:
core:read, core:write, finance:read, finance:write, sales:read, events:read
sales:read je v sadě proto, že vydané faktury (S82) patří u tenantů
s modulem Obchod pod něj — bez tohoto scope by GET /invoices u takového
tenanta vrátil 403 insufficient_scope, i když má klíč finance:read.
core:write je potřeba na nahrávání přílohy a podatelnu v kroku 3
(POST /attachments, /attachments/finalize, /inbox/submissions) —
bez něj tahle tři volání vrátí 403 insufficient_scope, i když zbytek
receptu běží jen na finance:*.
export K=pb_test_…
Základní URL: https://www.profibrew.com/api/v1 (nebo
https://app.profifirma.cz/api/v1 pro účetní kanceláře pracující se
zákazníky na značce ProfiEkonom — stejný klíč funguje na obou).
3. Krok 1 — stahování vydaných faktur
curl -s -H "Authorization: Bearer $K" \
"https://www.profibrew.com/api/v1/invoices?updated_since=2026-09-01T00:00:00Z&limit=100" \
| jq '{next_cursor, has_more, count: (.data | length)}'
Seznam NENESE řádky faktur (kvůli velikosti stránky) — pro položky
zavolejte detail:
curl -s -H "Authorization: Bearer $K" \
"https://www.profibrew.com/api/v1/invoices/$INVOICE_ID" \
| jq '{number, status, totals, amount_paid, amount_remaining, lines}'
amount_remaining je vždy dopočítané read-only pole
(totals.incl_vat − amount_paid) — nepočítejte si ho znovu sami, ušetříte
si zaokrouhlovací rozdíly.
Pokračujte stránkováním, dokud has_more nespadne na false:
curl -s -H "Authorization: Bearer $K" \
"https://www.profibrew.com/api/v1/invoices?updated_since=2026-09-01T00:00:00Z&cursor=$NEXT_CURSOR&limit=100"
Uložte si poslední zpracovaný updated_at jako checkpoint dalšího běhu —
kurzor sám o sobě mezi jednotlivými spuštěními neuchovávejte (je platný
jen pro danou stránkovací sekvenci updated_since).
4. Krok 2 — stahování přijatých faktur
curl -s -H "Authorization: Bearer $K" \
"https://www.profibrew.com/api/v1/received-invoices?updated_since=2026-09-01T00:00:00Z&limit=100" \
| jq '.data[] | {id, supplier_invoice_number, status, totals, paid_amount}'
Na rozdíl od vydané faktury zůstává přijatá faktura editovatelná i po
confirm — zamyká ji až export do účetnictví nebo storno (Finance 3.0
R3). Pokud vaše integrace přijatou fakturu i zakládá ručně (doklad, který
nedorazil skenem):
curl -s -X POST -H "Authorization: Bearer $K" \
-H "Idempotency-Key: 6b2f6a2e-1c3d-4e5f-8a9b-0c1d2e3f4a5b" \
-H "Content-Type: application/json" \
-d '{
"supplier_invoice_number": "2026-441",
"partner_id": "'"$PARTNER_ID"'",
"issue_date": "2026-09-10",
"due_date": "2026-09-24",
"external_ref": { "system": "pohoda", "id": "PF-441" },
"lines": [
{ "description": "Nákup obalového materiálu", "amount": "4200.00", "vat_rate_pct": "21" }
]
}' \
"https://www.profibrew.com/api/v1/received-invoices?on_conflict=return" | jq '{id, status}'
Řádky přijaté faktury NENESOU množství ani jednotku — je to čistě účetní
rozpad (description, amount bez DPH, vat_rate_pct, volitelná
category_id), ne skladový pohyb.
Potvrzení (přidělí interní číslo, vyžaduje aspoň jeden řádek):
curl -s -X POST -H "Authorization: Bearer $K" \
-H "Idempotency-Key: $(uuidgen)" \
"https://www.profibrew.com/api/v1/received-invoices/$RECEIVED_INVOICE_ID/actions/confirm" \
| jq '{status, number, allowed_actions}'
5. Krok 3 — import ze skenu / ISDOC
Import naskenované nebo ISDOC faktury jde jinou cestou než ruční
POST /received-invoices výše — přes přílohu a digitální podatelnu.
5.1 Nahrání souboru podepsanou URL
Vyžádejte upload ticket. Přílohu zakládejte BEZ entity_type/entity_id
— soubor zatím nepatří k žádné konkrétní faktuře, jen čeká v podatelně:
curl -s -X POST -H "Authorization: Bearer $K" \
-H "Content-Type: application/json" \
-d '{
"file_name": "faktura-2026-441.isdoc",
"mime_type": "application/xml",
"file_size": 18432
}' \
"https://www.profibrew.com/api/v1/attachments" \
| jq '{path, upload_url, upload_method, upload_headers, expires_at}'
Odpověď nese path (opaque, pošlete ho beze změny do finalize níže),
upload_url a upload_headers — id přílohy v tomhle kroku ještě
neexistuje. Nahrajte bajty PUTem přesně podle vrácených hlaviček (žádná
další autentizace — token v URL opravňuje přesně tuhle cestu jednou):
curl -s -X PUT "$UPLOAD_URL" \
-H "Content-Type: application/xml" \
-H "x-upsert: false" \
--data-binary @faktura-2026-441.isdoc
Založte záznam přílohy (entity_type/entity_id musí sedět s tím, co
jste poslali do prvního volání — tady opět oba vynechané):
curl -s -X POST -H "Authorization: Bearer $K" \
-H "Idempotency-Key: $(uuidgen)" \
-H "Content-Type: application/json" \
-d '{
"path": "'"$PATH"'",
"file_name": "faktura-2026-441.isdoc",
"mime_type": "application/xml"
}' \
"https://www.profibrew.com/api/v1/attachments/finalize" \
| jq '{id, purpose, created_at}'
Teprve teď máte id přílohy (ATTACHMENT_ID níže). Skutečná velikost se
ověřuje z úložiště až tady — neshoda nebo překročení kvóty plánu selže na
tomhle volání, ne na prvním. GET /v1/attachments/{id}/content kdykoli
vrátí 302 na aktuální podepsanou URL k bajtům.
5.2 Přímý import jako ISDOC
curl -s -X POST -H "Authorization: Bearer $K" \
-H "Idempotency-Key: $(uuidgen)" \
-H "Content-Type: application/json" \
-d '{
"attachment_id": "'"$ATTACHMENT_ID"'",
"file_name": "faktura-2026-441.isdoc"
}' \
"https://www.profibrew.com/api/v1/received-invoices/isdoc" \
| jq '{received_invoice_id, supplier_name, document_number, pdf_attached}'
file_name musí končit .isdoc/.isdocx — podle přípony se řídí
parsování, nezávisle na deklarovaném MIME typu. Duplicitní dokument
(napárovaný přes ISDOC UUID) vrátí 409 duplicate; jiný důvod odmítnutí
vrátí 422 s kódem jako code (unsupported_document_type,
invalid_xml, no_invoice_in_archive, file_too_large,
no_finance_module, …).
5.3 Nebo přes obecnou podatelnu
Stejnou přílohu můžete místo přímého importu poslat do obecné digitální
podatelny — ISDOC se tam rozpozná a naimportuje automaticky, ostatní typy
čekají v agendě Soubory na ruční roztřídění (nebo jdou rovnou do AI
podatelny, podle nastavení tenanta):
curl -s -X POST -H "Authorization: Bearer $K" \
-H "Content-Type: application/json" \
-d '{"attachment_id": "'"$ATTACHMENT_ID"'"}' \
"https://www.profibrew.com/api/v1/inbox/submissions" \
| jq '{outcome, attachment_id, received_invoice_id, note, duplicate}'
outcome říká, co se stalo: isdoc_imported (vznikla přijatá faktura),
isdoc_rejected (vypadalo to na ISDOC, ale import se nepovedl — důvod v
note), files (čeká na ruční roztřídění), scan (šlo rovnou do AI
podatelny). Ve zdrojových metadatech výsledného dokladu se ukáže „přes
API", stejně jako u e-mailové podatelny.
5.4 Zkratka pro malé soubory
Soubor do 4 MB nemusíte nahrávat podepsanou URL — pošlete ho rovnou jako
multipart přímo do podatelny a přeskočte krok 5.1:
curl -s -X POST -H "Authorization: Bearer $K" \
-F "file=@faktura-2026-441.isdoc;type=application/xml" \
"https://www.profibrew.com/api/v1/inbox/submissions" \
| jq '{outcome, received_invoice_id}'
Nad 4 MB endpoint vrátí 413 payload_too_large — nad tuhle hranici jde
soubor jedině cestou z 5.1 (POST /attachments → PUT → finalize).
Idempotency-Key je bezpečný i na multipart nahrání — server pro
otisk požadavku počítá ze syrových bajtů těla, ne z dekódovaného textu,
takže binární obsah přílohy se u shodného opakovaného volání nezkreslí a
POST /inbox/submissions s multipart tělem lze retryovat úplně stejně
jako kterékoliv jiné zapisující volání.
6. Krok 4 — zápis úhrady
Když potřebujete konkrétní účet (tenant nemá nastavený výchozí pro
platební metodu dokladu, nebo chcete platbu přiřadit jinam), vypište
dostupné účty:
curl -s -H "Authorization: Bearer $K" \
"https://www.profibrew.com/api/v1/finance-accounts" \
| jq '.data[] | {id, type, name, currency, is_active}'
Zůstatky se přes API nevystavují — endpoint slouží jen k dohledání id
pro account_id níže.
curl -s -X POST -H "Authorization: Bearer $K" \
-H "Idempotency-Key: $(uuidgen)" \
-H "Content-Type: application/json" \
-d '{"paid_at": "2026-09-15", "account_id": "'"$ACCOUNT_ID"'"}' \
"https://www.profibrew.com/api/v1/invoices/$INVOICE_ID/actions/mark_paid" \
| jq '{status, paid_at: .paid_at, amount_paid}'
Stejná akce existuje i pro přijaté faktury
(POST /received-invoices/{id}/actions/mark_paid) a pro doklady peněžního
deníku (níže). Bez amount se použije celý zbývající zůstatek; bez
account_id výchozí účet tenanta pro platební metodu dokladu — pokud
žádný není nastavený, vrátí se 422 account_required.
Storno úhrady (vrátí doklad do stavu před zaplacením):
curl -s -X POST -H "Authorization: Bearer $K" \
-H "Idempotency-Key: $(uuidgen)" \
"https://www.profibrew.com/api/v1/invoices/$INVOICE_ID/actions/revert_payment"
7. Krok 5 — peněžní deník (Zjednodušené / Interní doklady)
curl -s -H "Authorization: Bearer $K" \
"https://www.profibrew.com/api/v1/cashflows?updated_since=2026-09-01T00:00:00Z&type=expense" \
| jq '.data[] | {id, number, type, status, amount, paid_amount}'
Nový doklad (ve výchozím stavu rovnou potvrzený — status může vynutit
draft/cancelled):
curl -s -X POST -H "Authorization: Bearer $K" \
-H "Idempotency-Key: 1a2b3c4d-5e6f-7081-92a3-b4c5d6e7f809" \
-H "Content-Type: application/json" \
-d '{
"cashflow_type": "expense",
"amount": "1500.00",
"date": "2026-09-15",
"description": "Parkovné a drobný nákup",
"payment_method": "cash"
}' \
"https://www.profibrew.com/api/v1/cashflows" | jq '{id, number, status}'
Peněžní deník NEMÁ external_ref (sloupec v DB je legacy jednorázový
import, API ho nevystavuje ani nezapisuje), ani PATCH, ani řádky — jen
hlavičku a dvě akce (mark_paid, cancel), odvozené přímo ze statusu,
ne z katalogu workflow.
curl -s -X POST -H "Authorization: Bearer $K" \
-H "Idempotency-Key: $(uuidgen)" \
"https://www.profibrew.com/api/v1/cashflows/$CASHFLOW_ID/actions/cancel"
8. Chyby, na které v tomhle receptu narazíte
| Kód | Kdy | Co s tím |
|---|
401 unauthenticated | chybí/špatný klíč | zkontrolujte hlavičku Authorization |
403 insufficient_scope | chybí sales:read u tenanta s Obchodem | doplňte scope, viz krok 2 výše |
403 module_not_enabled | tenant nemá modul Finance/Obchod v předplatném | mimo dosah integrace |
404 not_found | špatné id, nebo cizí livemode | ověřte test/live |
409 invalid_transition (EXPORTED) | přijatá faktura byla už exportovaná do účetnictví | úpravy po exportu API nedovoluje — stejné jako v UI |
409 invalid_transition (ostatní) | akce mimo allowed_actions (např. mark_paid na draft) | přečtěte allowed_actions v těle chyby |
409 duplicate | POST /received-invoices/isdoc na dokument se stejným ISDOC UUID | bezpečné volat opakovaně, faktura z prvního importu už existuje |
412 stale_version | If-Match na PATCH přijaté faktury nesedí | znovu načtěte a zopakujte s aktuálním ETagem |
413 payload_too_large | multipart POST /inbox/submissions nad 4 MB | nahrajte přes POST /attachments → PUT → finalize, pak pošlete { attachment_id } |
422 account_required | mark_paid bez výchozího účtu a bez account_id | vypište GET /finance-accounts a pošlete account_id, nebo nechte zákazníka nastavit výchozí účet |
422 no_lines | confirm na přijaté faktuře bez řádků | přidejte aspoň jeden řádek přes POST …/lines |
422 <reason> | ISDOC/ISDOCX se nepovedlo naimportovat (unsupported_document_type, invalid_xml, no_invoice_in_archive, file_too_large, no_finance_module, …) | code/detail v těle chyby popisuje důvod |
422 validation_failed / unknown_field | tělo neodpovídá schématu | zkontrolujte OpenAPI dokument — tělo zápisu je striktní |
429 rate_limited | limit tarifu | zpomalte podle Retry-After |
9. Checklist před nasazením do produkce
Updated 2026-09-15
Skladový systém (WMS) — příjemky, výdejky, stav skladu, převody
Tenhle recept popisuje, jak napojit externí skladový systém (čtečky
čárových kódů, WMS terminál) na sklad ProfiBrew: zaúčtování příjmu zboží,
výdej s výběrem konkrétní šarže, průběžnou synchronizaci stavu skladu a
převod mezi sklady.
1. Cíl
Po dokončení receptu umí vaše integrace:
- zaúčtovat příjem zboží jako koncept + řádky → potvrzení (zásoba vzniká
AŽ potvrzením, nikdy dřív),
- zaúčtovat výdej, volitelně s ručním výběrem konkrétní FIFO vrstvy
(šarže),
- udržovat lokální kopii stavu skladu synchronizovanou přes
updated_since,
- provést převod zboží mezi dvěma sklady tenanta.
2. Klíč, sandbox a scopes
Testovací klíč (pb_test_…), sada Sklad / WMS:
core:read, stock:read, stock:write, sales:read, events:read
export K=pb_test_…
Základní URL: https://www.profibrew.com/api/v1.
Než začnete, zjistěte id skladů a jednotek:
curl -s -H "Authorization: Bearer $K" \
"https://www.profibrew.com/api/v1/warehouses" | jq '.data[] | {id, code, name, categories}'
curl -s -H "Authorization: Bearer $K" \
"https://www.profibrew.com/api/v1/units" | jq '.data[] | {id, code, dimension}'
3. Krok 1 — příjemka (koncept → řádky → potvrzení)
Zásoba nikdy nevzniká bez potvrzené příjemky (CLAUDE.md, S104) — dokud
příjemku nepotvrdíte, je to jen koncept a na sklad se nic nepromítne, ani
kdyby měla řádky.
Založte koncept, případně rovnou s řádky:
curl -s -X POST -H "Authorization: Bearer $K" \
-H "Idempotency-Key: 2c3d4e5f-6a7b-8c9d-0e1f-2a3b4c5d6e7f" \
-H "Content-Type: application/json" \
-d '{
"warehouse_id": "'"$WAREHOUSE_ID"'",
"partner_id": "'"$SUPPLIER_ID"'",
"movement_purpose": "purchase",
"date": "2026-09-15",
"external_ref": { "system": "wms", "id": "GR-8831" },
"lines": [
{
"item_id": "'"$ITEM_ID"'",
"quantity": "500",
"unit": { "unit_code": "kg" },
"unit_price": "28.50",
"lot_number": "2026-K37",
"expiry_date": "2027-03-01"
}
]
}' \
"https://www.profibrew.com/api/v1/goods-receipts?on_conflict=return" | jq '{id, number, status, allowed_actions}'
Nebo přidejte řádky později, po vzniku konceptu:
curl -s -X POST -H "Authorization: Bearer $K" \
-H "Idempotency-Key: $(uuidgen)" \
-H "Content-Type: application/json" \
-d '{
"item_id": "'"$ITEM_ID"'",
"quantity": "500",
"unit": { "unit_code": "kg" },
"unit_price": "28.50",
"lot_number": "2026-K37"
}' \
"https://www.profibrew.com/api/v1/goods-receipts/$RECEIPT_ID/lines"
unit na řádku musí být jednotka POLOŽKY (jinak 422 unit_mismatch) —
API množství nikdy nepřevádí. lot_number na řádku příjemky založí NOVOU
šarži s tímhle označením; nechte prázdné, pokud šarže tenanta nezajímají.
Potvrzení — teprve teď se zásoba objeví na skladu:
curl -s -X POST -H "Authorization: Bearer $K" \
-H "Idempotency-Key: $(uuidgen)" \
"https://www.profibrew.com/api/v1/goods-receipts/$RECEIPT_ID/actions/confirm" \
| jq '{status, allowed_actions}'
Katalog akcí příjemky má jen confirm (z draft) a cancel (z
confirmed) — koncept se v API nedá smazat ani zrušit (žádné DELETE na
celý doklad, na rozdíl od řádků objednávky). Nepotřebný koncept nechte
prostě ležet, nebo ho potvrďte a hned stornujte.
4. Krok 2 — výdej s výběrem šarže
Nejdřív zjistěte dostupné vrstvy (zůstatek per příjmový řádek):
curl -s -H "Authorization: Bearer $K" \
"https://www.profibrew.com/api/v1/stock-levels/lots?item_id=$ITEM_ID&warehouse_id=$WAREHOUSE_ID" \
| jq '.data[] | {id, lot_number, quantity, unit, expiry_date}'
id v odpovědi JE receipt_line_id — pošlete ho zpátky jako lot_id na
řádku výdejky, abyste vydali přesně tuhle vrstvu místo výchozího FIFO:
curl -s -X POST -H "Authorization: Bearer $K" \
-H "Idempotency-Key: 9a8b7c6d-5e4f-3a2b-1c0d-9e8f7a6b5c4d" \
-H "Content-Type: application/json" \
-d '{
"warehouse_id": "'"$WAREHOUSE_ID"'",
"movement_purpose": "sale",
"lines": [
{
"item_id": "'"$ITEM_ID"'",
"quantity": "120",
"unit": { "unit_code": "kg" },
"lot_id": "'"$LOT_ID"'"
}
]
}' \
"https://www.profibrew.com/api/v1/stock-issues" | jq '{id, number, status}'
Vynechte lot_id, když je vám FIFO vrstva jedno — potvrzení pak vybere
nejstarší dostupnou vrstvu automaticky, stejně jako v UI.
curl -s -X POST -H "Authorization: Bearer $K" \
-H "Idempotency-Key: $(uuidgen)" \
"https://www.profibrew.com/api/v1/stock-issues/$ISSUE_ID/actions/confirm"
Stejně jako u příjemky: confirm jen z draft, cancel jen z
confirmed — a stejně nejde koncept smazat.
5. Krok 3 — synchronizace stavu skladu
curl -s -H "Authorization: Bearer $K" \
"https://www.profibrew.com/api/v1/stock-levels?warehouse_id=$WAREHOUSE_ID&updated_since=2026-09-14T00:00:00Z" \
| jq '.data[] | {item: .item.code, quantity, available_quantity, unit}'
Řádek existuje pro každou kombinaci položka × sklad, která KDY měla
zásobu — i nulovou. quantity je vždy v jednotce položky (nikdy
nepřevedená), available_quantity = quantity − reserved_quantity
(nikdy záporně).
6. Krok 4 — převod mezi sklady
Převod je výdejka s movement_purpose: "transfer" a vyplněným
target_warehouse_id:
curl -s -X POST -H "Authorization: Bearer $K" \
-H "Idempotency-Key: 4d5e6f70-8192-a3b4-c5d6-e7f809102a3b" \
-H "Content-Type: application/json" \
-d '{
"warehouse_id": "'"$WAREHOUSE_FROM"'",
"target_warehouse_id": "'"$WAREHOUSE_TO"'",
"movement_purpose": "transfer",
"lines": [
{ "item_id": "'"$ITEM_ID"'", "quantity": "50", "unit": { "unit_code": "kg" } }
]
}' \
"https://www.profibrew.com/api/v1/stock-issues" | jq '{id}'
curl -s -X POST -H "Authorization: Bearer $K" \
-H "Idempotency-Key: $(uuidgen)" \
"https://www.profibrew.com/api/v1/stock-issues/$TRANSFER_ID/actions/confirm"
Potvrzením se zásoba odepíše ve zdrojovém skladu A ZÁROVEŇ vznikne
automatická příjemka v konceptu na CÍLOVÉM skladu (najdete ji přes
GET /goods-receipts?warehouse_id=$WAREHOUSE_TO&status=draft) — tu je
potřeba potvrdit zvlášť, aby se zásoba objevila i v cíli:
curl -s -H "Authorization: Bearer $K" \
"https://www.profibrew.com/api/v1/goods-receipts?warehouse_id=$WAREHOUSE_TO&status=draft" \
| jq '.data[0].id'
curl -s -X POST -H "Authorization: Bearer $K" \
-H "Idempotency-Key: $(uuidgen)" \
"https://www.profibrew.com/api/v1/goods-receipts/$INCOMING_RECEIPT_ID/actions/confirm"
Pokud cílový sklad položku vůbec neumí přijmout (např. neshoduje se
kategorie skladu), vrátí potvrzení výdejky 409 transfer_blocked — ověřte
kategorie skladů předem přes GET /warehouses.
7. Sdílená externí reference napříč příjemkami a výdejkami
external_ref u příjemek a výdejek sdílí JEDEN sloupec na úrovni tenanta
— tatáž hodnota {system, id} proto nesmí patřit současně příjemce i
výdejce. Pošlete-li external_ref, který už používá doklad OPAČNÉ
rodiny, dostanete 409 external_ref_conflict s hlavičkou Location ukazující na
SKUTEČNOU rodinu nalezeného dokladu (/goods-receipts/… nebo
/stock-issues/…) — čtěte Location, ne jen cestu, kterou jste volali.
8. Chyby, na které v tomhle receptu narazíte
| Kód | Kdy | Co s tím |
|---|
401 unauthenticated | chybí/špatný klíč | zkontrolujte hlavičku Authorization |
403 insufficient_scope | chybí stock:write | doplňte scope, viz krok 2 výše |
404 not_found | id patří opačné rodině (příjemka vs. výdejka), nebo jinému livemode | GET /goods-receipts/{id} na id výdejky vrátí 404, ne doklad |
409 external_ref_conflict | external_ref používá doklad opačné rodiny | čtěte hlavičku Location odpovědi, viz bod 7 |
409 invalid_transition | confirm/cancel mimo aktuální stav | jen draft → confirm, confirmed → cancel |
409 inventory_locked | sklad je zamčený běžící inventurou | počkejte na dokončení inventury |
409 excise_reported | pohyb spadá do už podaného hlášení spotřební daně | úprava mimo dosah API, řeší se v aplikaci |
409 transfer_blocked | cílový sklad převodu položku nepřijme | ověřte kategorie skladů přes GET /warehouses |
409 receipt_has_allocations | storno příjemky, jejíž řádky už byly vydány | nejdřív stornujte navazující výdej |
422 validation_failed (invalid_reference) | item_id/warehouse_id/partner_id/lot_id neexistuje pro tenanta | ověřte id přes odpovídající GET |
422 unit_mismatch | jednotka řádku ≠ jednotka položky | pošlete quantity v jednotce z GET /items/{id} |
422 quantity_scale | víc desetinných míst, než dovoluje jednotka | zaokrouhlete na decimals |
429 rate_limited | limit tarifu | zpomalte podle Retry-After |
9. Checklist před nasazením do produkce
Updated 2026-09-15