UpWolf Pro vývojáře

Endpointy

Pět GET endpointů, všechny read-only, všechny vracejí JSON. Každá ukázka níž je skutečná odpověď našeho sandbox feedu — curl u ní jde spustit tak, jak je.

Konvence

Platí pro všechny endpointy. Většina vznikla z otázek implementátorů a jedna z nich (význam 404) je důvod, proč se kontrakt dá nasazovat po částech.

Transport a kódování

Jen HTTPS, odpovědi v JSON a UTF-8, Content-Type: application/json. Holé HTTP a neveřejné adresy odmítneme ještě před odesláním požadavku.

Autentizace

Jeden bearer token na konzumenta: Authorization: Bearer <token>. Na své straně ho držte jako hash, nechte ho rotovatelný a řekněte nám, jak dlouhé je okno rotace. Když váš stack neumí číst hlavičky, vezmeme token i v URL — jen nám to řekněte.

Dva tvary cest

Výchozí je cesta {base}/offers/123456. Když váš hosting nepředává PATH_INFO za PHP souborem (na sdíleném hostingu běžné), použijte variantu s parametrem: {base}?upwolf_route=offers/123456. Lomítko uvnitř route zůstává nezakódované, identifikátor se kóduje zvlášť. Vyberte si jednu variantu, řekněte nám ji a držte ji na všech endpointech.

Na poli `version` stojí celá detekce změn

Neprůhledný řetězec, který se změní pokaždé, když se změní obsah nabídky — hash obsahu i časové razítko poslední revize fungují stejně dobře. Nikdy ho nerozebíráme, jen ho porovnáváme s verzí, ze které vznikla kreativa. Když se nemění nikdy, nepřegenerujeme kreativu u upravených nabídek; když se mění při každém dotazu, přegenerováváme pořád všechno.

Chyby a význam 404

Chyba je HTTP status a k tomu {"error": {"code": "…", "message": "…"}}. **404 je platná odpověď**, ne selhání: u nabídky znamená „není ve feedu", u celého endpointu „zatím neumíme". Právě díky tomu jde kontrakt nasazovat po částech — my si zjistíme, co existuje, a zbytek degradujeme. Cokoliv 5xx opakujeme třikrát s exponenciálním backoffem.

Časové limity, se kterými pracujeme

Čtení na pozadí má timeout 20 sekund. Jedna cesta je výrazně přísnější: když kampaň přijde pushem, máme na celou odpověď půl sekundy, takže detail nabídky tahá s rozpočtem 300 ms a jediným pokusem. Feed, který odpoví do 300 ms, dostane cílení hned napoprvé; pomalejší funguje taky, jen u toho jednoho volání spadne na výchozí hodnoty.

Stránkování a `updated_since`

Stránkované endpointy vracejí next_page (jakýkoliv skalár, vracíme ho zpátky beze změny; null = konec). Dvě věci potřebujeme mít popsané: jestli je hranice updated_since inkluzivní nebo exkluzivní a jestli je řazení stabilní. Watermark si posouváme na čas začátku průchodu, takže při exkluzivní hranici a nestabilním řazení položky mezi stránkami tiše propadávají.

Identita a idempotence

external_id identifikuje nabídku a nikdy se nesmí použít pro jinou. (source_type, external_key) identifikuje kandidáta na kampaň. Opakování je vždycky bezpečné: stejná dvojice nikdy nezaloží druhou kampaň, jen aktualizuje tu existující. Proto musí být zrušení výslovné status: "cancelled", ne zmizení z výpisu — zmizení se nedá odlišit od vypadnutí z okna updated_since.

Zavřené nabídky zůstávají čitelné

Když se nabídka zavře, přepněte status na closed, ale detail nechte dál odpovídat. Kampaň, která už běžela, potřebuje data nabídky i poté, co nabídka zmizela — kvůli výsledným metrikám, archivu kreativy a reportu, který vidí klient.

Cachování (volitelné, ale oceníme)

Posílej na výpisech ETag nebo Last-Modified a odpovídej 304, když se nic nezměnilo. Snapshot pulujeme v řádu minut; u velkého katalogu je tohle nejlevnější věc, kterou můžete pro vlastní servery udělat.

Reference

GET offers povinné

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.

shell
curl -s "https://upwolf.com/dev/sandbox/feed/offers" \
  -H "Authorization: Bearer sandbox-demo-token"
json
{
    "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."
            }
        }
    ]
}

Pole po poli: OffersSnapshot · JSON Schema

GET offers/{external_id} povinné

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.

shell
curl -s "https://upwolf.com/dev/sandbox/feed/offers/DEMO-1001" \
  -H "Authorization: Bearer sandbox-demo-token"
json
{
    "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"
}

Pole po poli: Offer · JSON Schema

GET offers?updated_since={iso8601}&page={n} doporučené

Stejná 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.

Bez tohohle endpointu: Kreativa se po úpravě nabídky nikdy neobnoví.
Parametr Typ Povinný Poznámka
updated_since string (ISO 8601) ano Dolní hranice; popiš, jestli je inkluzivní.
page scalar ne Hodnota `next_page` z předchozí odpovědi.
shell
curl -s "https://upwolf.com/dev/sandbox/feed/offers?updated_since=2026-09-01T00:00:00Z" \
  -H "Authorization: Bearer sandbox-demo-token"
json
{
    "items": [
        {
            "external_id": "DEMO-1001",
            "version": "v2026-09-01T10:00:00Z",
            "status": "published",
            "updated_at": "2026-09-01T10:00:00+02:00"
        },
        {
            "external_id": "DEMO-1002",
            "version": "8f14e45fceea167a",
            "status": "published",
            "updated_at": "2026-09-03T08:30:00+02:00"
        },
        {
            "external_id": "DEMO-1003",
            "version": "v2026-08-20T16:00:00Z",
            "status": "closed",
            "updated_at": "2026-08-20T16:00:00+02:00"
        }
    ],
    "next_page": null
}

Pole po poli: OfferChanges · JSON Schema

GET campaign-candidates?updated_since={iso8601}&page={n} doporučené

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.

Bez tohohle endpointu: Žádná kampaň nevznikne sama — každá musí přijít pushem na naše API nebo vzniknout ručně.
Parametr Typ Povinný Poznámka
updated_since string (ISO 8601) ne Při prvním průchodu se nepošle — tehdy chceme všechno.
page scalar ne Hodnota `next_page` z předchozí odpovědi.
shell
curl -s "https://upwolf.com/dev/sandbox/feed/campaign-candidates?updated_since=2026-09-01T00:00:00Z" \
  -H "Authorization: Bearer sandbox-demo-token"
json
{
    "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
}

Pole po poli: CampaignCandidates · JSON Schema

GET offers/{external_id}/applications-stats?from={iso8601} doporučené

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ň.

Bez tohohle endpointu: 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.
Parametr Typ Povinný Poznámka
from string (ISO 8601) ano Počítej reakce od tohoto okamžiku.
shell
curl -s "https://upwolf.com/dev/sandbox/feed/offers/DEMO-1001/applications-stats?from=2026-09-01T00:00:00Z" \
  -H "Authorization: Bearer sandbox-demo-token"
json
{
    "external_id": "DEMO-1001",
    "from": "2026-09-01T00:00:00+02:00",
    "count": 4,
    "total_count": 11
}

Pole po poli: ApplicationsStats · JSON Schema

Chybové odpovědi

Každá odpověď mimo 2xx by měla nést tohle tělo. Zprávu ukazujeme našim provozním lidem, takže ji piště pro člověka, který váš systém nezná.

json
{
    "error": {
        "code": "not_found",
        "message": "Nabídka 123456 ve feedu není."
    }
}