Hlídač zakázek
Automatické doručování nových zakázek dle vašich filtrů — kraj, industry tagy, klíčová slova, rozsah hodnot. Pull API + email digest + webhook.
Koncept
Čtyři pojmy, které potkáte ve všech endpointech:
- Tender — jedna konkrétní zakázka z portálu (NEN, VVZ, E-ZAK, TenderArena, atd.). Identifikována číselným ID.
- Filter — uložená sada kritérií (region, obor, klíčová slova, hodnota). Když nová zakázka splní kritéria, vznikne match.
- Match — nová zakázka, která padla do vašeho filtru. Reprezentuje vazbu (tender × filter) + timestamp + váš stav (sledováno / skryto / přečteno).
- Taxonomy — číselníky regionů (NUTS), oborů (industry tags) a CPV kódů, podle kterých klasifikujeme zakázky.
Rychlý start
Filtry lze nastavit dvěma způsoby — výsledek je stejný, filtr lze kdykoliv editovat libovolnou cestou.
curl -H "X-Api-Key: mrw_live_…" \ "https://veritra.io/api/v2/leads/tenders/search?qText=rekonstrukce%20mostu&limit=5"
Nahoře je ad-hoc hledání. Pokud chcete kontinuální monitoring (nové zakázky odpovídající vašim kritériím), použijte filtry + webhook/email digest — viz dále.
Hledání zakázek (ad-hoc)
Pro jednorázové vyhledání zakázek napříč všemi aktivními tendry. Nepoužívá uložený filtr, parametry jdou přímo v query stringu.
/api/v2/leads/tenders/searchQuery parametry
| Parametr | Typ | Popis |
|---|---|---|
| qText* | string | Hledaný text (full-text v title + description). |
| regions | string | NUTS leaf codes CSV (CZ010,CZ020). |
| cpvPrefixes | string | CPV prefixy CSV (45,452). |
| industryTags | string | Industry tag IDs CSV (con_buildings,it_development). |
| minValue | number | Min. odhadovaná hodnota (Kč). Tendery bez hodnoty procházejí. |
| maxValue | number | Max. odhadovaná hodnota (Kč). Tendery bez hodnoty procházejí. |
| deadlineFrom | string (YYYY-MM-DD) | Lhůta podání >= YYYY-MM-DD. |
| deadlineTo | string (YYYY-MM-DD) | Lhůta podání <= YYYY-MM-DD. |
| sort | "newest" | "deadline" | "value" | Řazení: newest (default), deadline (rostoucí), value (klesající). |
| limit | number | Počet výsledků, max 1000 (výchozí: 50). Pro >1k použij nextCursor pagination nebo /matches/export. |
| cursor | string | Keyset cursor z předchozí stránky (pagination.nextCursor). |
Příklad
curl -H "X-Api-Key: mrw_live_…" \ "https://veritra.io/api/v2/leads/tenders/search?qText=rekonstrukce®ions=CZ010,CZ020&minValue=500000&limit=10"
/api/v2/leads/tenders/:idResponse: data objekt s fieldy id, title, description, estimatedValue, deadlineAt, contractingAuthority, documents[], starred, excluded.
/api/v2/leads/tenders/:id/emailBody může obsahovat recipientEmail pro forward na jiný email. Defaultně user.email.
/api/v2/leads/documents/previewQuery: ?url=:original&kind=docx|xlsx. Allowlist hostů: NEN, E-ZAK, Tender Arena, Gemin, ProfilZadavatele.
GET /api/v2/leads/documents/preview?url=https://nen.nipez.cz/…/Vyzva.docx&kind=docx → 302 Location: https://rwx-storage…/Tendero/doc-cache-v8/<hash>.html (signed, TTL 10 min)
Catalog & autocomplete (public, žádný klíč)
Pro UI dropdowny a filter creation. Cache 1h server-side. Veřejné, žádná auth potřeba.
/api/v2/leads/zadavatele?q=praha/api/v2/leads/countries/api/v2/leads/regions?country=CZ/api/v2/leads/regions/catalog?country=CZ&locale=cs/api/v2/leads/taxonomy/industry/api/v2/leads/taxonomy/cpvCo lze filtrovat a povolené hodnoty
Filtr se skládá z polí níže. Všechna jsou volitelná kromě `name`. Prázdné pole = bez omezení v dané dimenzi.
regions string[]
NUTS regiony pro danou zemi (?country=CZ) s labels per locale.
CZČeská republikaSKSlovenskoPLPolskoDENěmeckoATRakouskoFRFrancieESŠpanělskoITItálieNLNizozemskoBEBelgiePTPortugalskoSEŠvédskoFIFinskoDKDánskoNONorskoIEIrskoGRŘeckoRORumunskoBGBulharskoHUMaďarskoHRChorvatskoSISlovinskoLTLitvaLVLotyšskoEEEstonskoLULucemburskoCYKyprMTMaltaCHŠvýcarskoISIslandMKSeverní MakedoniePro získání plného NUTS stromu konkrétní země použijte:
GET /api/v2/leads/regions/catalog?country=CZ&locale=cs GET /api/v2/leads/regions/catalog?country=DE&locale=de GET /api/v2/leads/regions/catalog?country=FR&locale=en
Příklad: NUTS-3 kódy pro CZ (kraje) — rozbalit ▸
CZ010Hlavní město PrahaCZ020Středočeský krajCZ031Jihočeský krajCZ032Plzeňský krajCZ041Karlovarský krajCZ042Ústecký krajCZ051Liberecký krajCZ052Královéhradecký krajCZ053Pardubický krajCZ063Kraj VysočinaCZ064Jihomoravský krajCZ071Olomoucký krajCZ072Zlínský krajCZ080Moravskoslezský krajindustryTags string[] Doporučeno
Multi-tag oborová klasifikace. Tender může mít víc tagů (např. 'stav_pozemni' + 'stav_remesla' u rekonstrukce). Filtr matchne tender, pokud má alespoň jeden ze zadaných tagů (JSON_OVERLAPS).
Proč preferovat industryTags před CPV: Naše industryTags vznikají kombinací LLM klasifikace názvu/popisu tendru + CPV mapování + regex hintů — odchytí relevantní zakázky i u zadavatelů co přiřazují CPV nedbale. CPV filtr je přesný, ale závisí na disciplíně zadavatele, která je v ČR slabá.
con_buildingsPozemní stavby a rekonstrukce budovcon_civilInženýrské stavby (silnice, mosty, voda)con_tradesStavební řemesla a dílčí prácecon_energy_efficiencyEnergetické úspory a OZEcon_materialsStavební materiáldes_documentationProjektová dokumentace a studiedes_supervision_ohsTechnický dozor a BOZPdes_surveyingGeodézie a pozemkové úpravyit_developmentVývoj SW a integraceit_licensingSW licence a předplatnéit_hardwareHW a infrastrukturait_cybersecurityKybernetická bezpečnostit_data_aiData, analytika, AI/MLtelecom_internetTelekomunikace, internet, mobilníprof_marketingMarketing, PR, reklamaprof_legalPrávní službyprof_accountingÚčetnictví, dotace, auditprof_hrHR, náborprof_translationPřeklady a tlumočeníprof_insurancePojištění a finančníops_cleaningÚklidové službyops_securityOstraha a recepčníops_maintenanceÚdržba a servis zařízeníops_wasteOdpadové hospodářstvíops_facilitySpráva nemovitostícat_cateringStravování, cateringcat_accommodationUbytování a konferencecat_foodPotraviny a nápojetrans_transportPřeprava cestujících a nákladutrans_vehiclesVozidla, díly, leasinghealth_pharmaLéčiva a farmahealth_devicesZdravotnické přístroje a materiálhealth_careZdravotní a sociální péčegoods_furnitureNábytek a vybavení interiérůgoods_clothingOděvy, OOPP, uniformygoods_electricalElektromateriálgoods_machineryPrůmyslové strojeenergy_fuelsPohonné hmoty a palivaenergy_power_heatElektřina a teploenergy_waterVodárenstvínat_forestryLesní hospodářstvínat_greeneryÚdržba zeleně a zahradnictvínat_agricultureZemědělstvídefense_safetyHasiči, vojsko, obranasci_labLaboratorní a měřicí vybavenísci_researchVýzkum a vývojedu_trainingVzdělávání a školenículture_mediaKultura, knihy, médiacategories string[] Méně přesné
CPV (Common Procurement Vocabulary) prefixy libovolné délky — match přes `LIKE 'prefix%'`. Tj. '45' chytne všechny stavební oddíly, '4523' jen inženýrské stavby, '45316110' jen pouliční osvětlení.
03Zemědělství, lesnictví, rybolov09Paliva a energie15Potraviny a nápoje18Oděvy, obuv, OOPP22Tiskoviny, knihy30Kancelářské stroje, IT vybavení31Elektrické stroje, kabely, svítidla32Rádio, TV, telekomunikace33Zdravotnické vybavení, léčiva34Vozidla, doprava35Bezpečnost, hasičská a vojenská technika38Laboratorní a měřicí přístroje39Nábytek, vybavení interiérů42Průmyslové stroje44Stavební materiály a konstrukce45Stavební práce48Software a licence50Opravy a údržba55Stravování, ubytování60Doprava (přeprava)64Pošta, telekomunikační služby65Veřejné služby (elektřina, voda)66Finanční a pojišťovací služby70Realitní služby a správa budov71Architektonické, projekční, inženýrské služby72IT služby (vývoj, integrace, podpora)73Výzkum a vývoj75Veřejná správa, obrana77Zemědělské, lesnické, zahradnické služby79Obchodní služby (právní, účetnictví, HR, marketing)80Vzdělávání, školení85Zdravotní a sociální péče90Odpady, životní prostředí, úklid92Kultura, sport, rekreace98Ostatní služby pro obyvatelstvoKompletní katalog (9 454 kódů) najdete na /docs/leads/cpv — searchovatelné a seskupené po oddílech.
keywords string[]
LIKE match v názvu i popisu zakázky. Case-insensitive. Mezi položkami OR. Užitečné jako pojistka, pokud industryTags/CPV ne všechno odchytí (např. specifický typ zakázky v textu).
"keywords": ["rekonstrukce", "VO", "osvětlení", "MŠ"]
minValue / maxValue number | null
Rozsah odhadované hodnoty zakázky v Kč. Lze zadat jen minValue, jen maxValue, nebo oba.
emailDigest boolean
Pokud true, dostáváte 1× denně (5:00 UTC) email se shrnutím nových matchů z tohoto filtru. Výchozí true. Vypnete editací filtru. Webhook se nastavuje zvlášť, na úrovni účtu (viz sekce níže).
name string · active boolean
`name` — max 120 znaků, povinné. `active` — pokud false, filtr neběží v cronu (žádné nové matche, digest, webhook). Lze pozastavit bez mazání.
Logika filtrování
Pole se kombinují takto:
MATCH = (zadavatel.NUTS3 ∈ expand(regions))
AND (minValue ≤ estimatedValue ≤ maxValue NEBO estimatedValue je null nebo 0)
AND (industry_or_cpv NEBO keyword_match)
# expand(regions): NUTS kódy se expandují na NUTS 3 leaves
# (CZ → 14 krajů, CZ01 → CZ010, CZ010 → CZ010).
industry_or_cpv:
pokud industryTags zadané → JSON_OVERLAPS(industryTags, tender.industryTags)
jinak categories zadané → tender.cpvCode LIKE ANY (categories + "%")
jinak → false (žádný industry filtr neaplikován)
keyword_match:
pokud keywords zadané → tender.title nebo description LIKE ANY (%kw%)
jinak → false
# Když nezadáte žádné industryTags / categories / keywords,
# vrátí se všechny tendery splňující regions a value range.industryTags má přednost před categories — pokud zadáte oboje, použije se jen industryTags (categories se ignoruje). keywords fungují nezávisle (jsou v OR větvi s industry_or_cpv).
Endpointy pro správu filtrů
Pro správu filtrů použijte management klíč (mrw_live_…). Má samostatné rate limity a nespotřebovává LEADS kredity, takže management filtrů neovlivňuje vaše denní vyzvedávání zakázek.
/api/v2/leads/filters/api/v2/leads/filters/api/v2/leads/filters/:id/api/v2/leads/filters/:id/api/v2/leads/filters/:id/api/v2/leads/filters/:id/matches/export?format=csv|xlsx|json&view=all|starred|excludedBody parametry (POST / PUT) — společné
| Parametr | Typ | Popis |
|---|---|---|
| name* | string | Název filtru (max 120 znaků) |
| regions | string[] | NUTS kódy (např. `CZ010`). Mixovat lze libovolný level. Prázdné pole = všechny regiony. |
| industryTags | string[] | Doporučená cesta. Tag IDs z naší taxonomie (např. 'stav_pozemni', 'it_vyvoj'). Multi-tag — OR mezi položkami. |
| categories | string[] | CPV prefixy libovolné délky (např. '45', '4523', '45316110'). Méně přesné než industryTags. |
| keywords | string[] | Klíčová slova — LIKE match v názvu i popisu zakázky (OR mezi položkami). |
| minValue | number | null | Min. odhadovaná hodnota (Kč). Tendery bez hodnoty procházejí. |
| maxValue | number | null | Max. odhadovaná hodnota (Kč). Tendery bez hodnoty procházejí. |
| emailDigest | boolean | Denní email digest (výchozí true) |
| active | boolean | Aktivní filtr (výchozí true) |
Webhook se na filtru už nenastavuje — viz sekci Webhook níže (account-level endpointy). Pole webhookUrl je z bezpečnostních důvodů odmítnuto s HTTP 410.
Příklady
Stavby v Praze nad 500 tis. Kč
curl -X POST -H "X-Api-Key: mrw_live_…" \
-H "Content-Type: application/json" \
-d '{
"name": "Stavby Praha 500k+",
"regions": ["CZ010"],
"industryTags": ["con_buildings", "con_trades"],
"minValue": 500000
}' \
https://veritra.io/api/v2/leads/filtersIT vývoj a SW licence (všechny kraje)
curl -X POST -H "X-Api-Key: mrw_live_…" \
-H "Content-Type: application/json" \
-d '{
"name": "IT vývoj + SW licence",
"industryTags": ["it_development", "it_licensing", "it_data_ai"],
"keywords": ["informační systém", "modul"]
}' \
https://veritra.io/api/v2/leads/filtersVeřejné osvětlení (CZ) — kombinace tagů + keywords
curl -X POST -H "X-Api-Key: mrw_live_…" \
-H "Content-Type: application/json" \
-d '{
"name": "Veřejné osvětlení (CZ)",
"industryTags": ["goods_electrical", "con_civil"],
"keywords": ["osvětlení", "VO ", "veřejné osvětlení", "lampy"],
"minValue": 200000
}' \
https://veritra.io/api/v2/leads/filtersDeaktivovat filtr
curl -X PATCH -H "X-Api-Key: mrw_live_…" \
-H "Content-Type: application/json" \
-d '{"active": false}' \
https://veritra.io/api/v2/leads/filters/<id>Doručování — webhook vs polling
Dva způsoby, jak dostat nové zakázky do svého ERP. Většina integrátorů kombinuje oba.
Webhook (push)
Tendero pošle POST na tvůj endpoint do ~2 sekund od match. Event leads.match.created s plnou payload zakázky.
Plus: real-time, žádné polling overhead, server-side gating.
Mínus: vyžaduje veřejně dostupný endpoint (HTTPS), HMAC verifikace, idempotency handling.
Polling (pull)
Tvůj ERP volá GET /api/v2/leads/matches?since=… periodicky (např. každých 5 min). Vrátí matches od daného timestampu.
Plus: žádný veřejný endpoint, jednoduchá implementace, restart-friendly.
Mínus: latence 5-10 min, rate-limit constraint (60 req/min mgmt API), zbytečné prázdné odpovědi.
Získání matchů
/api/v2/leads/matchesQuery parametry
| Parametr | Typ | Popis |
|---|---|---|
| filterId | string | Filtrovat dle konkrétního filtru |
| qText | string | Hledaný text (full-text v title + description). |
| since | string (ISO 8601 datetime) | Pouze matche od data (ISO datetime) |
| delivered | boolean | false = jen nedoručené, true = jen doručené |
| view | "starred" | "excluded" | Speciální view: starred (oblíbené) | excluded (skryté). |
| sort | "newest" | "deadline" | "value" | Řazení: newest (default), deadline (rostoucí), value (klesající). |
| limit | number | Počet výsledků, max 1000 (výchozí: 50). Pro >1k použij nextCursor pagination nebo /matches/export. |
| cursor | string | Keyset cursor z předchozí stránky (pagination.nextCursor). |
Vrácené matche jsou automaticky označeny jako delivered=true. Každý request spotřebuje 1 kredit.
Formáty matchId
matchId má dva formáty podle původu:
cm…(cuid prefix) — vrácený z pre-computed LeadMatch tabulky (cron job, který párované zakázky každý den). Stabilní napříč voláními, použitelný pro mark-as-viewed.live-12345synthetic prefix pro live-detekovaný match přes search / browse mode (qText param, view=starred/excluded). Není v LeadMatch tabulce → mark-as-viewed je no-op. tenderId je suffix po pomlčce.
Příklad
curl -H "X-Api-Key: mrw_leads_…" \ "https://veritra.io/api/v2/leads/matches?delivered=false&limit=10"
Odpověď
{
"data": [
{
"matchId": "cm…",
"filterId": "cm…",
"filterName": "Stavby Praha",
"matchedAt": "2026-04-03T06:00:00.000Z",
"viewedAt": null,
"delivered": true,
"tender": {
"id": 12345,
"title": "Rekonstrukce mostu ev.č. 123",
"estimatedValue": 12500000,
"deadlineAt": "2026-05-15T22:00:00.000Z",
"publishedAt": "2026-04-01T08:00:00.000Z",
"firstSeenAt": "2026-04-01T08:30:00.000Z",
"url": "https://nen.nipez.cz/...",
"portalType": "NEN",
"cpvCode": "45000000",
"tenderType": "OFFERS",
"contractingAuthority": {
"ico": "12345678",
"name": "Město Praha",
"region": "Praha",
"district": "Praha 1"
},
"documents": [
{ "name": "Výzva.pdf", "url": "https://nen.nipez.cz/…", "fileType": "pdf", "fileSizeBytes": 320000 }
],
"starred": false,
"excluded": false
}
}
],
"pagination": { "nextCursor": "eyJmaXJzdFNlZW5BdC…", "totalCount": 42 }
}Pro další stránku pošli pagination.nextCursor jako ?cursor= parametr.
Akce na matche
Star (oblíbit), exclude (skrýt) a view (mark as read) jsou per-tender preference uloženy v UserTenderPreference.
/api/v2/leads/matches/:matchIdResponse: stejný shape jako matches list item.
/api/v2/leads/matches/:matchId/star{ "starred": true }/api/v2/leads/matches/:matchId/exclude{ "excluded": true }/api/v2/leads/matches/:matchId/viewPro synthetic live-:id matches je no-op (žádný řádek k označení).
/api/v2/leads/preferences{
"data": {
"starred": [12345, 67890],
"excluded": [54321]
}
}Taxonomy
Statické referenční katalogy pro filter values. Locale-aware labels.
/api/v2/leads/taxonomy/industry?locale=cs{
"data": {
"locale": "cs",
"areas": [{ "id": "construction", "icon": "🏗️", "label": "Stavebnictví" }, …],
"tags": [
{ "id": "con_buildings", "area": "construction", "label": "Pozemní stavby", "cpvPrefixes": ["452"] },
…
]
}
}/api/v2/leads/taxonomy/cpv?locale=csResponse: stromová struktura oddíly → skupiny → třídy → kategorie → podkategorie.
Webhook
Webhook endpointy (CRUD, secret rotation) jsou account-level — dokumentované jednou v Account API → Webhooky. Tady je popsán jen leads-specific event payload (leads.match.created) a HMAC verifikace.
Doručování eventů
Při novém matchi pošleme event typu leads.match.created s HMAC-SHA256 podpisem v hlavičce X-Signature-256, idempotency klíčem v X-Idempotency-Key a typem v X-Event-Type. Při selhání retry s exponenciálním backoffem do ~33 hodin.
Retry policy
Pokud tvůj endpoint vrátí non-2xx (nebo neodpoví do 10s), Tendero retryuje se exponential backoff: 1min, 5min, 30min, 2h, 12h, 24h. Po 6 selháních se webhook označí jako failed a v dashboardu blikne. Idempotency-key se pro každý pokus stejný — tvůj endpoint MUSÍ deduplikovat (jinak ten samý match zpracuje 6×).
Správa endpointů přes API
Můžeš spravovat webhook endpointy bez vstupu do dashboardu. Limit 5 endpointů na účet.
/api/v2/account/webhooks/api/v2/account/webhooks/api/v2/account/webhooks/:id/api/v2/account/webhooks/:id/api/v2/account/webhooks/:id/api/v2/account/webhooks/:id/rotate-secret/api/v2/account/webhooks/:id/test/api/v2/account/webhooks/:id/deliveries/api/v2/account/webhooks/:id/deliveries/:deliveryId/replayHMAC signature verification
Server podepisuje payload HMAC-SHA256(secret, raw_body) a posílá v hlavičce X-Signature-256 ve formátu sha256=hex. Verifikuj v constant-time porovnáním, jinak je integrace zranitelná na timing attack.
// Node.js (Express)
import crypto from "node:crypto";
const WEBHOOK_SECRET = process.env.MRICKWOOD_WEBHOOK_SECRET!;
function verify(rawBody: string, sig: string | undefined): boolean {
if (!sig) return false;
const expected = "sha256=" + crypto
.createHmac("sha256", WEBHOOK_SECRET)
.update(rawBody)
.digest("hex");
// Constant-time comparison (timing-safe)
const a = Buffer.from(sig);
const b = Buffer.from(expected);
return a.length === b.length && crypto.timingSafeEqual(a, b);
}
app.post("/webhooks/veritra",
express.raw({ type: "application/json" }),
async (req, res) => {
const raw = req.body.toString("utf8");
if (!verify(raw, req.header("X-Signature-256"))) {
return res.status(401).send("Invalid signature");
}
const idempKey = req.header("X-Idempotency-Key")!;
const evt = JSON.parse(raw);
// Idempotency: store idempKey, skip if already processed
if (await alreadyProcessed(idempKey)) return res.status(200).send("ok");
await processEvent(evt);
await markProcessed(idempKey);
res.status(200).send("ok"); // Must be 2xx within 10s
},
);# Python (Flask)
import hmac, hashlib, os
from flask import Flask, request, abort
WEBHOOK_SECRET = os.environ["MRICKWOOD_WEBHOOK_SECRET"]
def verify(raw: bytes, sig: str | None) -> bool:
if not sig: return False
expected = "sha256=" + hmac.new(
WEBHOOK_SECRET.encode(), raw, hashlib.sha256
).hexdigest()
return hmac.compare_digest(sig, expected)
@app.post("/webhooks/mrickwood")
def webhook():
raw = request.get_data()
if not verify(raw, request.headers.get("X-Signature-256")):
abort(401)
# ... idempotency check + process
return "ok", 200{
"id": "evt_…",
"type": "leads.match.created",
"createdAt": "2026-04-03T06:00:00.000Z",
"data": {
"filterId": "cm…",
"filterName": "Stavby Praha",
"matchId": "cm…",
"tender": { "id": "12345", "title": "…", "estimatedValue": 12500000 }
}
}Email digest
S emailDigest: true dostanete denní email s přehledem nových zakázek. Digest se odesílá ráno (5:00 UTC) na email vašeho účtu. Lze vypnout editací filtru.
Limity
| Parametr | Typ | Popis |
|---|---|---|
| Trial | 500 req/měsíc | 1 den zdarma. Bez karty / fakturačního profilu. Default ApiKey.requestsLimit. |
| Placený plán | neomezeně | Měsíční limit je vypnut. Platí jen technický rate limit 100/h/klíč. |
| Filtry | 20 | Max počet aktivních filtrů per účet. |
| Webhook endpointy | 5 | Max počet aktivních webhook URL per účet. |
Po překročení měsíčního limitu vrátí API 429 s hlavičkou Retry-After. Limit se resetuje 1. dne v měsíci.