Partner API
Partner API umožňuje integračním partnerům přistupovat k privilegovaným funkcím BiznisWeb GraphQL API — pokročilé filtry, bulk operace, mutace obsahu, content pages, custom order info, tracking URLs a další operace, které nejsou dostupné přes běžný webový token.
Tento dokument je v2 a obsahuje zkušenosti z reálných integrací včetně gotcha, které nejsou v původní dokumentaci zřejmé. Pro rychlou referenci původní specifikace viz stránku Volání API.
1. Předpoklady
Před prvním voláním Partner API potřebujete:
| Údaj | Kde získat | Příklad |
|---|---|---|
| Partner ID | Od BiznisWebu po podpisu partnerské smlouvy | Demo-Integrator-2026 |
| Partner secret | Od BiznisWebu spolu s Partner ID | aB3$dEf... (řetězec, ~12–32 znaků) |
| Webový API token | Klient (majitel eshopu) vygeneruje v admin panelu › Nastavení › BiznisWeb API; pošle Vám ho | 32 znaků, např. Ab3xY9pQrSt2uVw5xYz8aB1cDeF4gH6i |
| Client domain | BiznisWeb interní identifikátor eshopu — VŽDY ve tvaru <subdoména>.flox.cz nebo <subdoména>.flox.sk |
eshop1.flox.cz |
| API endpoint domain | Veřejná doména, kde API skutečně poslouchá. Může být stejná jako client domain, nebo vlastní doména eshopu | www.eshop1.cz |
API endpoint domain a Client domain se nemusí shodovat. Jsou to dvě různé hodnoty s různými účely:
- API endpoint domain — kam posíláte HTTP request
- Client domain — vstup do SHA1 hashe (BiznisWeb interní identita eshopu)
*.flox.cz/*.flox.sk (žádná vlastní doména), jsou obě stejné. Pro eshopy s vlastní doménou (typicky CZ eshopy s .cz nebo SK eshopy s .sk) se liší. Viz sekci 3.2. Autentizace — dva tokeny
Každý partnerský request musí obsahovat oba headery:
BW-API-Key: Token <32-znakový webový token>
BW-Partner-Key: <partner-id> <40-znakový SHA1 hash>
Content-Type: application/json
2.1 Webový token REQUIRED
32-znakový řetězec vygenerovaný klientem v admin panelu eshopu. Header value musí obsahovat prefix Token (s mezerou) před tokenem:
BW-API-Key: Token Ab3xY9pQrSt2uVw5xYz8aB1cDeF4gH6i
Tyto formáty nefungují a vrátí HTTP 401:
X-Api-Key: Ab3xY...
Authorization: Bearer Ab3xY...
BW-API-Key: Ab3xY... ← chybí "Token " prefix
2.2 Partner token REQUIRED
Header value je partner ID, mezera, a 40-znakový SHA1 hash:
BW-Partner-Key: Demo-Integrator-2026 d2f7e3a8b4c1e9f0a5b2c7d8e1f4a9b6c3d5e7a8
BW-Partner-Key: Partner d2f7e3... ← prefix musí být partner ID, ne slovo "Partner"
BW-Partner-Key: d2f7e3... ← chybí partner ID prefix
BW-Partner-Key: Demo-Integrator-2026 raw_secret ← musí to být HASH, ne sám secret
2.3 Výpočet partner hashe
Hash je SHA1 (ne MD5, ne SHA256) z konkatenace tří hodnot v přesném pořadí:
partner_hash = sha1(client_domain + partner_secret + partner_id)
Výstup je přesně 40 hex znaků. Pokud vidíte 32 znaků, použili jste MD5 — to je chyba.
| Vstup | Popis | Příklad |
|---|---|---|
client_domain |
VŽDY *.flox.cz nebo *.flox.sk subdomain, ne veřejná doména eshopu |
eshop1.flox.cz |
partner_secret |
Tajný klíč od BiznisWebu — používejte raw, bez prefixů | prikladSecret*** |
partner_id |
Váš partner identifier | Demo-Integrator-2026 |
Vývojáři často omylem použijí veřejnou doménu eshopu (
www.eshop1.cz) místo client domain (eshop1.flox.cz). Hash pak nevaliduje — server vrací HTTP 412.Pravidlo: client domain je vždy
*.flox.cz/*.flox.sk subdomain, i když eshop má vlastní doménu.Konkrétní příklad (Python)
import hashlib
client_domain = "eshop1.flox.cz"
partner_secret = "<your-partner-secret>"
partner_id = "Demo-Integrator-2026"
partner_hash = hashlib.sha1(
(client_domain + partner_secret + partner_id).encode("utf-8")
).hexdigest()
print(partner_hash)
# → d2f7e3a8b4c1e9f0a5b2c7d8e1f4a9b6c3d5e7a8 (40 hex chars, ilustrační hodnota)
header_value = f"{partner_id} {partner_hash}"
# → "Demo-Integrator-2026 d2f7e3a8b4c1e9f0a5b2c7d8e1f4a9b6c3d5e7a8"
3. Endpoint URL — API endpoint domain vs Client domain
Cílová URL pro HTTP POST je vždy:
https://<api-endpoint-domain>/api/graphql
Při partner volání existují dvě oddělené hodnoty:
| Pole | K čemu | Příklad pro eshop s vlastní doménou | Příklad pro eshop bez vlastní domény |
|---|---|---|---|
| API endpoint domain | URL host kde API skutečně poslouchá | www.eshop1.cz |
eshop2.flox.sk |
| Client domain | Vstup do SHA1 hashe (interní BW identita) | eshop1.flox.cz |
eshop2.flox.sk |
Vždy posílejte request na veřejnou doménu eshopu (pokud existuje), ne na
*.flox.cz. Flox subdomain dělá HTTP 308 redirect na primary domain a klient, který redirect bezhlavě následuje, dostane HTTP 412 (protože hash je počítán proti flox.* identitě, ale server přijímající request očekává hash vůči primary doméně — nemá tyto hodnoty).4. ⚠ HTTP/1.1 — kritický požadavek
BiznisWeb partner authentication nefunguje přes HTTP/2. Každý partner request musí být poslán přes HTTP/1.1.
To je méně zřejmé, než se zdá. Moderní HTTP knihovny často automaticky vyjednávají HTTP/2 přes ALPN, pokud server podporuje (BiznisWeb server podporuje, ale partner middleware funguje jen přes HTTP/1.1).
| Klient | Default verze | Jak vynutit HTTP/1.1 |
|---|---|---|
Python requests |
HTTP/1.1 ✓ | Automaticky |
Python httpx |
HTTP/1.1 (default) nebo HTTP/2 (s http2=True) |
httpx.Client(http2=False) |
Python aiohttp |
HTTP/1.1 ✓ | Automaticky |
Node.js fetch / undici |
HTTP/1.1 | Automaticky |
Node.js node-fetch |
HTTP/1.1 | Automaticky |
Go net/http |
HTTP/2 negotiated via ALPN | tr.ForceAttemptHTTP2 = false nebo nastavte tr.TLSNextProto = map[string]func(...) http.RoundTripper{} |
curl |
HTTP/2 přes TLS (default v moderních verzích) | curl --http1.1 ... |
PHP cURL |
Závisí na verzi libcurl | CURLOPT_HTTP_VERSION = CURL_HTTP_VERSION_1_1 |
Diagnostika: pokud vidíte HTTP/2 412 v debug výstupu, klient se připojil přes h2 a partner auth neprošla. Vynuťte HTTP/1.1.
5. Časté gotchas
5.1 Snap (staging) eshopy — strip prefixu z hash inputu
BiznisWeb používá „snap" eshopy jako staging / shadow kopie produkčních webů pro testování. Jejich hostname má tvar:
snap<digits>-<production-host>.flox.cz
Například: snap1234-eshop1.flox.cz je staging kopie eshop1.flox.cz (resp. www.eshop1.cz v produkci).
Partner registrace žije na produkční identitě (
eshop1.flox.cz), ne na snap hostname. To znamená:
- HTTP endpoint:
https://snap1234-eshop1.flox.cz/api/graphql(snap host) - Hash input:
"eshop1.flox.cz"+ secret + partner_id (produkční host, BEZsnap1234-prefixu)
snap<digits>- prefix z hostname před SHA1 výpočtem, ale request posílejte na snap hostname normálně.Příklad (Python)
import hashlib
import re
snap_pattern = re.compile(r"^snap\d+-")
def hash_input_domain(client_domain: str) -> str:
"""Strip 'snap<digits>-' prefix for hash computation."""
return snap_pattern.sub("", client_domain)
# Endpoint URL — snap hostname
endpoint = "https://snap1234-eshop1.flox.cz/api/graphql"
# Hash input — production identity (snap prefix stripped)
hash_input = hash_input_domain("snap1234-eshop1.flox.cz")
# → "eshop1.flox.cz"
partner_hash = hashlib.sha1(
(hash_input + partner_secret + partner_id).encode("utf-8")
).hexdigest()
Pokud na snap eshopu vidíte HTTP 401 + generická chyba „Stalo sa niečo neočakávané" místo očekávaného 412, první věc na ověření: počítáte hash bez snap prefixu?
Pokud produkční eshop a snap kopie sdílí stejný API klíč (zejména když klient vytvořil token před existencí snapu, nebo zkopíroval credentials), jediným rozdílem mezi úpravou produkčních a snap dat je endpoint URL:
https://eshop1.flox.cz/api/graphql→ modifikuje PRODUKČNÍ eshop (živé zákazníky)https://snap1234-eshop1.flox.cz/api/graphql→ modifikuje snap (staging kopii)
Host hlavičky requestu.Vždy explicitně ověřte cíl před mutací.
5.2 HTTP 308 redirect z flox.* na vlastní doménu
Eshopy s vlastní doménou mají obvykle *.flox.cz subdomain nastavenou jako permanent redirect (HTTP 308) na primární doménu. Např.:
https://eshop1.flox.cz/api/graphql
→ HTTP 308 Permanent Redirect
→ Location: https://www.eshop1.cz/api/graphql
Pokud Váš klient následuje redirecty automaticky (defaultní chování většiny knihoven), request přejde na www.eshop1.cz. Server tam ale očekává hash vůči jeho doméně — a Váš hash byl počítán proti eshop1.flox.cz nebo vůči www.eshop1.cz (což není client domain). Výsledek: HTTP 412.
- Posílejte request přímo na primary doménu (
https://www.eshop1.cz/api/graphql) — žádný redirect, žádný problém. - Hash vždy počítejte proti client domain (
eshop1.flox.cz), bez ohledu na to, kde končí HTTP request. - Vypněte automatic redirect following (
allow_redirects=Falsev Python requests) — pokud vidíte 3xx, je to signál, že máte špatnou endpoint domain hodnotu.
5.3 IP whitelist
BiznisWeb best practices doporučují omezit každý API token na konkrétní IP adresy (klient ho nastavuje v admin panelu při vytváření tokenu). Pokud request přichází z neallowed IP:
- Webový token (
BW-API-Key) nepustí vůbec → HTTP 401 - Partner token (
BW-Partner-Key) nepustí → HTTP 412
Pro vývoj může klient ponechat whitelist prázdný (=povoleno vše). Pro produkční integrace doporučujeme whitelist mít.
5.4 Aktivace Partner Package
Klient musí mít na svém eshopu aktivovaný „Partner Package" — to mu povoluje partner-specific volání (advanced filters, content mutations, content pages atd.). Bez této aktivace i správný partner token vrátí HTTP 412 se zprávou „You can use the filter param with only valid partner token or your website must have Partner package!".
Klient si aktivaci objednává v BiznisWebu. Jako vývojář na to nemáte dosah — můžete jen rozpoznat tuto chybu a poslat klienta na podporu.
5.5 Limity frekvence volání BiznisWeb API
BiznisWeb API využívá při zpracování požadavků databázová připojení ze sdíleného connection poolu. Při příliš rychlém odesílání požadavků za sebou může dojít k vyčerpání tohoto poolu, což se projeví chybovou odpovědí namísto standardního JSON výstupu. Nejde o klasický rate limit ve smyslu pevného počtu volání za sekundu — server volné kapacity průběžně uvolňuje, proto je klíčovým parametrem délka pauzy mezi jednotlivými voláními, nikoli jejich celkový počet.
Na základě testování doporučujeme při stránkovaných (paginovaných) požadavcích dodržovat minimální pauzu 0,5 sekundy mezi každým voláním. Tato hodnota poskytuje dostatečný bezpečnostní margin a při běžných objemech dat (tisíce až desítky tisíc záznamů) umožňuje plynulé stažení bez výpadků. Při dlouhodobých stahováních přesahujících 200 po sobě jdoucích volání doporučujeme po každých 100 voláních zařadit krátkou pauzu v délce 5 sekund, která serveru umožní uvolnit databázová připojení před další dávkou požadavků.
- Minimální pauza mezi voláními: 0,5 s
- Maximální počet volání před delší pauzou: 100
- Delší pauza po každých 100 voláních: 5 s
6. HTTP error codes — diagnostika
| HTTP code | Význam | Pravděpodobná příčina | Co dělat |
|---|---|---|---|
| 200 | OK | Request úspěšný (GraphQL errors mohou být v body) | Zkontrolujte errors pole v response |
| 206 | Partial Content | Část požadovaných polí token nemůže vrátit (omezená práva) | Zkontrolujte scope tokenu v adminu |
| 308 | Permanent Redirect | Posíláte na flox.* subdomain, eshop má vlastní doménu | Použijte primary doménu v API endpoint domain (hash ponechte proti flox.*) |
| 401 | Unauthorized | Webový token (BW-API-Key) je nesprávný, expirovaný, nebo IP není ve whitelist |
Klient ať ověří / vygeneruje nový token v admin → Nastavení → BiznisWeb API |
| 403 | Forbidden | Token autentifikoval, ale nemá scope na tuto operaci | Klient ať rozšíří token permissions |
| 404 | Not Found | Špatný endpoint URL nebo špatná doména | Zkontrolujte https://<api-endpoint-domain>/api/graphql |
| 412 | Precondition Failed | Partner token (BW-Partner-Key) je neplatný, expirovaný, IP whitelist, nebo eshop nemá aktivovaný Partner Package |
Zkontrolujte hash (SHA1, ne MD5; client domain, ne endpoint domain); IP whitelist; klient ať ověří Partner Package; HTTP verze (1.1!) |
| 429 | Too Many Requests | Rate limit překročen | Respektujte Retry-After header, throttle |
| 500 | Server Error | Internal error na BW straně | Retry s backoff; pokud persistentní, kontaktujte BW support |
| 501 | Not Implemented / Maintenance | Údržba | Respektujte Retry-After, retry později |
| 509 | Bandwidth Limit / Quota Exceeded | API quota vyčerpaná | Klient ať ověří quota / upgrade plán |
7. Code samples
Všechny vzorky používají stejné demo údaje pro čitelnost (hodnoty jsou ilustrační):
domain = "www.eshop1.cz"
client_domain = "eshop1.flox.cz"
partner_id = "Demo-Integrator-2026"
partner_secret = "<your-partner-secret>"
api_key = "<your-32-char-api-token>"
7.1 Python (requests)
import hashlib
import requests
DOMAIN = "www.eshop1.cz"
CLIENT_DOMAIN = "eshop1.flox.cz"
PARTNER_ID = "Demo-Integrator-2026"
PARTNER_SECRET = ""
API_KEY = ""
def make_headers():
partner_hash = hashlib.sha1(
(CLIENT_DOMAIN + PARTNER_SECRET + PARTNER_ID).encode("utf-8")
).hexdigest()
return {
"BW-API-Key": f"Token {API_KEY}",
"BW-Partner-Key": f"{PARTNER_ID} {partner_hash}",
"Content-Type": "application/json",
}
def call(query: str, variables: dict | None = None) -> dict:
response = requests.post(
f"https://{DOMAIN}/api/graphql",
headers=make_headers(),
json={"query": query, "variables": variables or {}},
timeout=30,
# Kritické: nesledovat redirecty automaticky (zamaskovalo by domain config issue)
allow_redirects=False,
)
if response.status_code == 308:
raise RuntimeError(
f"Server redirected to {response.headers.get('Location')}. "
f"Update DOMAIN to the redirect target (but keep CLIENT_DOMAIN as flox.*)."
)
response.raise_for_status()
return response.json()
# Příklad: partner-only advanced filter na getOrderList
result = call(
"""
query Orders($params: OrderParams, $filter: OrderFilter) {
getOrderList(lang_code: "CZ", params: $params, filter: $filter) {
pageInfo { hasNextPage nextCursor }
data { order_num pur_date sum { value } }
}
}
""",
{
"params": {"limit": 30, "cursor": 0},
"filter": {"pur_date_from": "2026-01-01"},
},
)
print(result["data"]["getOrderList"]["data"])
7.2 Node.js (fetch)
import crypto from "node:crypto";
const DOMAIN = "www.eshop1.cz";
const CLIENT_DOMAIN = "eshop1.flox.cz";
const PARTNER_ID = "Demo-Integrator-2026";
const PARTNER_SECRET = "";
const API_KEY = "";
function makeHeaders() {
const partnerHash = crypto
.createHash("sha1")
.update(CLIENT_DOMAIN + PARTNER_SECRET + PARTNER_ID)
.digest("hex");
return {
"BW-API-Key": `Token ${API_KEY}`,
"BW-Partner-Key": `${PARTNER_ID} ${partnerHash}`,
"Content-Type": "application/json",
};
}
async function call(query, variables = {}) {
const response = await fetch(`https://${DOMAIN}/api/graphql`, {
method: "POST",
headers: makeHeaders(),
body: JSON.stringify({ query, variables }),
redirect: "manual", // zachytit 3xx aby domain config issues nebyly maskovány
});
if (response.status === 308) {
const location = response.headers.get("location");
throw new Error(
`Redirect to ${location}. Update DOMAIN to the redirect target.`
);
}
if (!response.ok) {
throw new Error(`HTTP ${response.status}: ${await response.text()}`);
}
return await response.json();
}
const result = await call(
`query Orders($params: OrderParams, $filter: OrderFilter) {
getOrderList(lang_code: "CZ", params: $params, filter: $filter) {
data { order_num pur_date sum { value } }
}
}`,
{ params: { limit: 30 }, filter: { pur_date_from: "2026-01-01" } }
);
console.log(result.data.getOrderList.data);
7.3 PHP (cURL)
<?php
$domain = "www.eshop1.cz";
$client_domain = "eshop1.flox.cz";
$partner_id = "Demo-Integrator-2026";
$partner_secret = "";
$api_key = "";
$partner_hash = sha1($client_domain . $partner_secret . $partner_id);
$headers = [
"BW-API-Key: Token {$api_key}",
"BW-Partner-Key: {$partner_id} {$partner_hash}",
"Content-Type: application/json",
];
$query = 'query { getOrderList(lang_code: "CZ", params: {limit: 30}, filter: {pur_date_from: "2026-01-01"}) { data { order_num } } }';
$payload = json_encode(["query" => $query, "variables" => new stdClass()]);
$ch = curl_init("https://{$domain}/api/graphql");
curl_setopt_array($ch, [
CURLOPT_POST => true,
CURLOPT_HTTPHEADER => $headers,
CURLOPT_POSTFIELDS => $payload,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1, // kritické
CURLOPT_FOLLOWLOCATION => false, // detekce 308 redirectů
CURLOPT_TIMEOUT => 30,
]);
$response = curl_exec($ch);
$http_code = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);
if ($http_code === 308) {
throw new RuntimeException("Server redirected — update \$domain to primary domain.");
}
if ($http_code !== 200) {
throw new RuntimeException("HTTP {$http_code}: {$response}");
}
$data = json_decode($response, true);
print_r($data["data"]["getOrderList"]["data"]);
7.4 curl (pro debug)
# Spočítejte hash v shellu (placeholder secret nahraďte skutečnou hodnotou)
HASH=$(printf "%s" "eshop1.flox.czDemo-Integrator-2026" | shasum -a 1 | awk '{print $1}')
curl --http1.1 \
-X POST "https://www.eshop1.cz/api/graphql" \
-H "BW-API-Key: Token " \
-H "BW-Partner-Key: Demo-Integrator-2026 $HASH" \
-H "Content-Type: application/json" \
-d '{"query":"query { getOrderList(lang_code: \"CZ\", params: {limit: 5}, filter: {pur_date_from: \"2026-01-01\"}) { data { order_num pur_date sum { formatted } } } }"}'
Pokud chcete vidět přesné HTTP headery a verzi, přidejte
-v. Pokud nevidíte HTTP/1.1 v hlavičkách, znamená to že curl prošel přes HTTP/2 — přidejte --http1.1 explicitně.8. Best practices
- Omezte duplicitní volání. Cache statická data (kategorie, stati statusů, jazyky, měny). Tato se mění zřídka.
- Omezte tokeny na konkrétní IP adresy v admin panelu.
- Pravidelně mažte nepoužívané tokeny.
- Posílejte request na primary doménu eshopu, ne na
*.flox.cz. Šetříte HTTP 308 redirect a vyhýbáte se potenciálnímu mismatch hashe. - Vypněte automatic redirect following (
allow_redirects=False/redirect: "manual"). Pokud vidíte 3xx, je to konfigurační problém. - Vynuťte HTTP/1.1. Server nepodporuje partner auth přes HTTP/2.
- Ptejte se jen na potřebná pole. GraphQL umožňuje field selection — využijte to.
- Pro paged endpoints (
getOrderList,getProductList, ...) používejte limit ≤ 30. Vyšší hodnoty mohou způsobit timeout/partial responses. - Při mutacích žádejte i změněná pole v response — ověříte si tím správnost operace:
mutation { changeOrderStatus(order_num: "12345", status_id: 4) { order_num status { id name } <!-- ← potvrzení změny --> } } - Loggujte HTTP code a partial response — ale nikdy nezapisujte raw token nebo partner secret do logů.
- Rate limiting: respektujte
Retry-Afterheader při 429 / 501.
9. Troubleshooting checklist
Když Vaše integrace padá, postupujte shora dolů:
| Symptom | Zkontrolujte |
|---|---|
| Nekonkrétní network error / DNS fail | Doména v URL je správná; máte internet; firewall na klientové / serverové IP |
| HTTP 308 | Posíláte na *.flox.cz, eshop má primary doménu — použijte ji |
| HTTP 401 | Webový token (BW-API-Key) — ověřit: 32 znaků; Token prefix v hlavičce; IP whitelist v adminu; token není expirovaný/revokovaný |
| HTTP 412 | Partner token — nejpravděpodobnější příčiny v pořadí:
|
| HTTP 429 / 509 | Rate / quota limit — throttle; respektujte Retry-After |
HTTP 200 s GraphQL errors array |
Auth prošla, ale query má syntaktickou nebo permission chybu — viz errors[].message |
| HTTP 206 Partial Content | Token nemá scope na všechna požadovaná pole — ověřit token permissions v admin |
| Random 500 errors | Backend issue na BW straně — retry s exponential backoff, kontaktujte BW support při persistentním problému |