{"openapi":"3.1.0","info":{"title":"Feed pro UpWolf","version":"1.0","summary":"Read-only API, které portál vystaví, aby z jeho inzerce uměl UpWolf dělat reklamní kampaně.","description":"Tohle implementuje portál, UpWolf to konzumuje. Každý endpoint smí vracet 404, dokud ho stavíte — my si zjistíme, co existuje, a zbytek degradujeme, takže se kontrakt dá nasazovat po částech.","contact":{"name":"UpWolf","url":"https://upwolf.com/dev"}},"servers":[{"url":"{feed_base}","description":"Základní adresa vašeho feedu. Varianta s cestami; varianta s parametrem je v dokumentaci.","variables":{"feed_base":{"default":"https://example.com/feed/upwolf"}}}],"security":[{"bearerAuth":[]}],"paths":{"/offers":{"get":{"operationId":"offers_snapshot","summary":"Všechno, co je právě teď živé, v jedné odpovědi.","description":"Autoritativní množina pro srovnání: co ve snapshotu není, není aktivní. Procházíme ho v řádu minut a kampaním u nabídek, které z něj vypadly, zastavujeme běh. Bez stránkování a bez parametrů — jestli je váš katalog tak velký, že je to problém, ozvěte se a domluvíme stránkování. Když sem přidáte `name` a `company.name`, ušetříte nám jedno volání detailu na nabídku a celý výpis vznikne z jednoho requestu.\n\n---\n\nStejná cesta jako snapshot, ale s parametrem `updated_since` vrací identitu a `version` místo živé množiny. Každou verzi porovnáme s tou, ze které vznikla kreativa, a kreativu označíme za neaktuální. Bez tohohle endpointu běží upravená nabídka dál s reklamou napsanou ze starého textu, dokud si toho někdo nevšimne.\n\n**Když endpoint chybí:** Kreativa se po úpravě nabídky nikdy neobnoví.","x-upwolf-level":"required","parameters":[{"name":"updated_since","in":"query","required":true,"schema":{"type":"string"},"description":"Dolní hranice; popiš, jestli je inkluzivní."},{"name":"page","in":"query","required":false,"schema":{"type":"string"},"description":"Hodnota `next_page` z předchozí odpovědi."}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"$ref":"#/components/schemas/OffersSnapshot"},"example":{"items":[{"external_id":"DEMO-1001","name":"Senior Backend Engineer","company":{"name":"Nimbus Data s.r.o."}},{"external_id":"DEMO-1002","name":"Skladník / skladnice","company":{"name":"Retail Logistics a.s."}}]}}}},"404":{"description":"Nabídka není ve feedu, nebo endpoint zatím není hotový. Obojí je platná odpověď.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/offers/{external_id}":{"get":{"operationId":"offer_detail","summary":"Všechno, z čeho se dá napsat reklama a zamířit ji.","description":"Text reklamy vzniká z `name`, `description` a `company`; cílení z `area_id` a `locations`. Na neznámou nabídku odpověz `404`. U zavřených nabídek odpovídej dál.","x-upwolf-level":"required","parameters":[{"name":"external_id","in":"path","required":true,"schema":{"type":"string"},"description":"Stabilní ID nabídky."}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Offer"},"example":{"external_id":"DEMO-1001","version":"v2026-09-01T10:00:00Z","name":"Senior Backend Engineer","description":"Hledáme backend vývojáře do týmu, který staví platební bránu pro evropské e-shopy.\n\nCo tě čeká: návrh a vývoj služeb v PHP 8 a Go, práce s PostgreSQL a Kafkou, code review a mentoring juniorů.\n\nCo čekáme: pár let na podobné pozici, zkušenost s návrhem API a chuť starat se o to, co jsi napsal, i v provozu.\n\nNabízíme: čtyři dny v kanceláři jednou za měsíc, zbytek remote, 5 týdnů dovolené a rozpočet na vzdělávání.","language_code":"cs","status":"published","company":{"name":"Nimbus Data s.r.o.","introduction":"Stavíme platební infrastrukturu pro e-commerce ve střední Evropě. 40 lidí, vlastní produkt, žádné agenturní projekty.","logo_url":"https://upwolf.com/assets/img/sandbox-logo.png","logo_approved":true},"salaries":[{"minimum":90000,"maximum":130000,"currency":"CZK","measure":"month"}],"area_id":"142","area_root_id":"180","locations":[{"city":"Praha"},{"city":"Brno"}],"updated_at":"2026-09-01T10:00:00+02:00"}}}},"404":{"description":"Nabídka není ve feedu, nebo endpoint zatím není hotový. Obojí je platná odpověď.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/campaign-candidates":{"get":{"operationId":"campaign_candidates","summary":"Které nabídky mají dostat kampaň, s jakým rozpočtem a na jak dlouho.","description":"Tohle je ta věc, díky které kampaně vznikají samy. Kandidát je vaše byznysové rozhodnutí — placená viditelnost, aktivovaný PPC produkt, cokoliv prodáváte — a nese okno i případné přebití rozpočtu. Nefiltruj podle toho, jestli je nabídka publikovaná; to si vyhodnotíme sami z detailu. Zrušení je `status: \"cancelled\"` u téže dvojice, nikdy ne zmizení. Výpis zároveň slouží jako srovnávací kolo: když se někdy ztratí push volání, nejbližší průchod to dorovná sám.\n\n**Když endpoint chybí:** Žádná kampaň nevznikne sama — každá musí přijít pushem na naše API nebo vzniknout ručně.","x-upwolf-level":"recommended","parameters":[{"name":"updated_since","in":"query","required":false,"schema":{"type":"string"},"description":"Při prvním průchodu se nepošle — tehdy chceme všechno."},{"name":"page","in":"query","required":false,"schema":{"type":"string"},"description":"Hodnota `next_page` z předchozí odpovědi."}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CampaignCandidates"},"example":{"items":[{"source_type":"visibility","external_key":"98765","offer_external_id":"DEMO-1001","product":"Extra","window":{"since":"2026-09-01T00:00:00+02:00","until":"2026-09-30T23:59:59+02:00"},"status":"active","budget_override":null,"delay_override_days":null,"first_campaign_boost":false,"updated_at":"2026-09-01T10:00:05+02:00"},{"source_type":"ppc","external_key":"98766","offer_external_id":"DEMO-1002","product":"Ppc","window":{"since":"2026-09-03T00:00:00+02:00","until":null},"status":"cancelled","budget_override":250000,"delay_override_days":0,"first_campaign_boost":true,"updated_at":"2026-09-05T11:20:00+02:00"}],"next_page":null}}}},"404":{"description":"Nabídka není ve feedu, nebo endpoint zatím není hotový. Obojí je platná odpověď.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/offers/{external_id}/applications-stats":{"get":{"operationId":"applications_stats","summary":"Kolik reakcí nabídka nasbírala od daného okamžiku.","description":"Dvě čísla rozhodují o tom, kdy přestaneme utrácet. `count` jsou kvalitní reakce — jak si kvalitu definujete, je čistě vaše věc — a podle nich se kampaň ukončí, jakmile překročí práh. `total_count` jsou všechny přihlášky; nikdy podle nich nerozhodujeme, jen díky nim nelže graf, protože „5 kvalitních\" z šesti a z šedesáti je něco úplně jiného. Číslo smí i klesnout (stažené reakce) a je to v pořádku: naše rozhodnutí se nevracejí, takže pokles nerozjede zastavenou kampaň.\n\n**Když endpoint chybí:** Kampaně dojedou celé okno a utratí celý rozpočet i u nabídek, které jsou dávno obsazené, a nikdy se neuplatní pojistka, která kampaň vůbec nezaloží u nabídky obsazující se sama.","x-upwolf-level":"recommended","parameters":[{"name":"external_id","in":"path","required":true,"schema":{"type":"string"},"description":"Stabilní ID nabídky."},{"name":"from","in":"query","required":true,"schema":{"type":"string"},"description":"Počítej reakce od tohoto okamžiku."}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApplicationsStats"},"example":{"external_id":"DEMO-1001","from":"2026-09-01T00:00:00+02:00","count":4,"total_count":11}}}},"404":{"description":"Nabídka není ve feedu, nebo endpoint zatím není hotový. Obojí je platná odpověď.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}}},"components":{"securitySchemes":{"bearerAuth":{"type":"http","scheme":"bearer"}},"schemas":{"OffersSnapshot":{"title":"Snapshot aktivních nabídek","type":"object","properties":{"items":{"type":"array","items":{"type":"object","properties":{"external_id":{"type":"string","description":"Stabilní ID nabídky. Nikdy se nepoužije pro jinou nabídku."},"name":{"type":"string","description":"Název nabídky, ať výpis vznikne bez volání detailu."},"company":{"type":"object","properties":{"name":{"type":"string","description":"Název firmy ze stejného důvodu."}},"required":[]}},"required":["external_id"]}}},"required":[]},"Offer":{"title":"Detail nabídky","type":"object","properties":{"external_id":{"type":"string","description":"Totéž ID jako ve snapshotu."},"version":{"type":"string","description":"Neprůhledný otisk obsahu. Mění se, kdykoliv se změní obsah nabídky."},"name":{"type":"string","description":"Název nabídky. Stane se z něj titulek kreativy."},"description":{"type":"string","description":"Prostý text nebo HTML. Všechno, z čeho se píše text reklamy — čím bohatší, tím lepší reklama."},"status":{"type":"string","description":"Kampaň běží jen u `published`; `closed` ji během minut zastaví.","enum":["published","pending","closed"]},"updated_at":{"type":"string","description":"Kdy se nabídka naposled změnila."},"company":{"type":"object","properties":{"name":{"type":"string","description":"Jméno inzerenta. Objeví se v kreativě a uvozuje text reklamy."},"logo_url":{"type":"string","description":"Veřejně dostupný HTTPS obrázek. Čtvercový nebo široký, aspoň 200 px."},"introduction":{"type":"string","description":"Krátké představení firmy; dá textu reklamy něco navíc k samotné nabídce."},"logo_approved":{"type":"boolean","description":"Nastavte false, když logo není ověřené; pak ho do renderované kreativy nedáme."}},"required":["name"]},"area_id":{"type":"string","description":"Vaše ID oboru nabídky. Jakýkoliv stabilní identifikátor z vaší vlastní taxonomie."},"area_root_id":{"type":"string","description":"Kořen vašeho stromu oborů pro tutéž nabídku."},"locations":{"type":"array","items":{"type":"object","properties":{"city":{"type":"string","description":"Město tak, jak ho píšete v inzerátu. Mapování na identifikátory reklamních systémů si děláme sami. Pole posílejte i prázdné — tím říkáte „remote\", ne „ještě to neumíme\"."}},"required":[]}},"language_code":{"type":"string","description":"Jazyk obsahu. Bez něj jazyk detekujeme z textu."},"salaries":{"type":"array","items":{"type":"object","properties":{"minimum":{"type":"integer","description":"Spodní hranice nabízené částky, v celých jednotkách měny."},"maximum":{"type":"integer","description":"Horní hranice, když zveřejňujete rozpětí."},"currency":{"type":"string","description":"Měna částky, např. `CZK`."},"measure":{"type":"string","description":"Období, ke kterému se částka vztahuje.","enum":["hour","day","month","year","once"]}},"required":[]}}},"required":["external_id","version","name","description","status","updated_at"]},"OfferChanges":{"title":"Změnový výpis","type":"object","properties":{"items":{"type":"array","items":{"type":"object","properties":{"external_id":{"type":"string","description":"Která nabídka se změnila."},"version":{"type":"string","description":"Její aktuální otisk obsahu."},"status":{"type":"string","description":"Ušetří volání detailu, když potřebujeme jen stav.","enum":["published","pending","closed"]},"updated_at":{"type":"string","description":"Kdy se změnila."}},"required":["external_id","version"]}},"next_page":{"type":["string","integer","null"],"description":"Vracíme ho beze změny jako `page`. `null` ukončuje průchod."}},"required":["next_page"]},"CampaignCandidates":{"title":"Kandidáti na kampaň","type":"object","properties":{"items":{"type":"array","items":{"type":"object","properties":{"source_type":{"type":"string","description":"Jaký druh produktu kandidáta vytvořil.","enum":["visibility","ppc"]},"external_key":{"type":"string","description":"ID toho nákupu u vás. Pro nás neprůhledné; číslo je v pořádku."},"offer_external_id":{"type":"string","description":"Nabídka, ke které kampaň patří."},"product":{"type":"string","description":"Klíč produktu dohodnutý s námi — vybírá rozpočet, cíl kampaně i reklamní účet."},"window":{"type":"object","properties":{"since":{"type":"string","description":"Před tímhle okamžikem kampaň běžet nesmí."},"until":{"type":["string","null"],"description":"Konec okna. U `ppc` smí být null, dopočítáme si ho z konfigurace produktu."}},"required":["since"]},"status":{"type":"string","description":"`cancelled` u téže dvojice kampaň zastaví. Zrušení nikdy nevyjadřuj vynecháním.","enum":["active","cancelled"]},"updated_at":{"type":"string","description":"Podle něj funguje stránkování přes `updated_since`."},"budget_override":{"type":["integer","null"],"description":"Rozpočet v haléřích. Přebije výchozí hodnotu produktu."},"delay_override_days":{"type":["integer","null"],"description":"Přebije, jak dlouho čekáme před startem."},"first_campaign_boost":{"type":"boolean","description":"Nový klient — start hned, bez čekání. Výchozí false."}},"required":["source_type","external_key","offer_external_id","product","status","updated_at"]}},"next_page":{"type":["string","integer","null"],"description":"Vracíme ho beze změny jako `page`. `null` ukončuje průchod."}},"required":["next_page"]},"ApplicationsStats":{"title":"Počty reakcí","type":"object","properties":{"external_id":{"type":"string","description":"Zopakované ID nabídky."},"from":{"type":"string","description":"Zopakovaná dolní hranice z dotazu. Hodí se při ladění, nic na ní nestojí."},"count":{"type":"integer","description":"Kvalitní reakce od `from`. Musí to být celé číslo — číslo jako řetězec ignorujeme."},"total_count":{"type":"integer","description":"Všechny přihlášky od `from`. Musí být větší nebo rovno `count`, jinak ho bereme jako neznámé."}},"required":["external_id","count"]},"Error":{"title":"Chyba","type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string","description":"Krátký strojově čitelný kód."},"message":{"type":"string","description":"Vysvětlení pro člověka. Skončí v našem provozním UI, takže ho piš pro člověka."}},"required":["code","message"]}},"required":[]}}}}