{"openapi":"3.1.0","info":{"title":"Goose PWA API","version":"1.9.4","description":"Programmatic access to your Goose PWA account — PWAs, domains, pixels, push and analytics. Built for scripts and AI agents.\n\n## Authentication\nAll requests use your account API token as a Bearer credential:\n\n```\nAuthorization: Bearer <YOUR_API_TOKEN>\n```\n\nGet your token on the [Developers page](https://goose-pwa.com/developers). Treat it like a password — it grants full access to your account.\n\n## Conventions\n- Base URL: `http://127.0.0.1:14000`\n- JSON in/out. Success payloads include `\"success\": true`.\n- Errors: `{ \"success\": false, \"error\": \"<code>\" }` with a 4xx/5xx status.\n- A negative balance puts the account in read-only mode: write calls return `402 negative_balance`.\n- List endpoints (`/pwas`, `/pixels`, `/push`, `/domains`) return your **complete** set — no paging, no `limit`. The `/logs/*` endpoints are the exception: they page with `limit` (default 50, max 200) and `offset`, and report `total_count` + `has_more`.\n\n## Time periods\nEvery `period` query parameter accepts exactly these values:\n\n`today` · `yesterday` · `this_week` · `last_7_days` · `last_14_days` · `last_30_days` · `this_month` · `last_month` · `last_3_months` · `all_time` · `custom`\n\n`custom` requires `from` and `to` (`YYYY-MM-DD`, inclusive). **Any other value silently falls back to `today`** — a misspelled period is not an error, it just looks like a very quiet day.\n\nDays are bounded in the **account's timezone** (`timezone` in `GET /user/profile`, set in the cabinet profile; UTC unless set). Every stats response carries `period.timezone`; `period.tz_fallback: true` means the window starts before hourly history exists (`period.hourly_since`) and was served as UTC days instead.\n\n## Names that collide\nA few words are reused for different things. Worth reading once:\n\n- **`category`** appears four times with three meanings. On a PWA it is the **vertical** (`gambling`, `nutra`, …). On `design.<lang>` it is the free-text **label shown under the app name** on the store page, per language — leave it blank and the vertical fills it in. On a template it is the vertical again (gallery filter). On a push campaign it is `action` vs `interval`, unrelated to any of the above.\n- **`user_id`** is three different namespaces: your account (`/user/profile`), a **visitor** who landed on your PWA (everything under `/logs/*`), and a **team member** (the `user_id` filter on the analytics breakdown).\n- **`creator_id` vs `buyer_id`**: `creator_id` is the owning tenant and for team-owned objects it is the **team id, not a person**; `buyer_id` is the individual it is attributed to. For personal objects they match.\n- **`name` vs `app_name`**: `name` is your internal label in the cabinet; `design.<lang>.app_name` is what visitors read on the store page.\n- **The funnel has two vocabularies for the same two events**: `lead` = `registration` = `regs`, and `sale` = `deposit` = `deps`. Postbacks and pixel event maps say lead/sale; analytics and the visitor timeline say registration/deposit.\n- **Three things sound like tracking**: `tracker` decides where a visitor is **sent** (offer URLs per geo); `analytics.*` are ad-platform tags fired **in the browser**; a **Pixel** object delivers conversions **server-side** to the ad network. Configuring one does nothing for the other two.\n- **`domains` on a PWA are hostnames**, not `domain_id` values.\n- **`archived` is a boolean, not a status.** A PWA has a `status` AND an `archived` flag, independently.\n- **`comments`** on a PWA are store **reviews** (the item schema is `Review`).\n\n## Rate limits\nPer token, in fixed one-minute windows: **300** requests total, of which at most **40** may be writes (`POST`/`PUT`/`DELETE`), and at most **15** may be `POST /v1/app/domains` (each one provisions real infrastructure — a Cloudflare zone, an nginx vhost and a TLS certificate).\n\nResponses carry `X-RateLimit-Limit` and `X-RateLimit-Remaining`. Over the limit you get `429 rate_limited` with `Retry-After: 60` — wait it out and retry; the window is not a rolling penalty.\n\n## PWA lifecycle\n`status`: `draft` (created, not deployed) → `processing` (deploying) → `active` (live). `paused` = taken offline (e.g. after unarchiving), `error` = deploy failed. `archived` is a separate boolean flag — archive/unarchive never deletes anything.\n\n## Workflows\n**Launch a PWA from scratch:**\n1. `GET /v1/app/templates` — pick a `template_id` (optional; skip to design from a blank).\n2. `POST /v1/app/pwas` with `{ name, template_id }` → returns `pwa_id`, status `draft`.\n3. `PUT /v1/app/pwas/{pwa_id}` — set `tracker.geo_offers` (offer URL per country), `additional` (per-device behaviour), `cloaking`, and any `design` tweaks.\n4. Get a domain: `GET /v1/app/domains/check` to validate, then `POST /v1/app/domains` (`own` / `system` / `buy`); for many own domains at once use `POST /v1/app/domains/bulk-connect`.\n5. `PUT /v1/app/pwas/{pwa_id}` with `{ \"domains\": [\"your-domain.com\"] }`.\n6. `POST /v1/app/pwas/{pwa_id}/activate` → status `processing`. Poll `GET /v1/app/pwas/{pwa_id}` until `status` is `active` (typically 1–3 minutes).\n\n**Clone a working PWA:** `POST /v1/app/pwas/{pwa_id}/copy` → attach fresh domains via `PUT` → `activate`.\n\n**Replace a dead domain:** connect a fresh domain (step 4 above), then `PUT /v1/app/pwas/{pwa_id}` with the updated `domains` array — an `active` PWA redeploys automatically.\n\n**Rotate a banned pixel:** `PUT /v1/app/pixels/{pixel_id}` with the new `pixel_code` + `access_token` — conversion delivery switches immediately, no PWA redeploy needed.\n\n**Pull stats into your own tooling:** `GET /v1/app/dashboard/breakdown?dimension=country&period=last_30_days` (dimensions: `pwa`, `country`, `domain`, `source`, `os`, `sub`, `user`). For row-level export rather than aggregates, page through `GET /v1/app/logs/users`.\n\n**Forward conversions to your own tracker:** `PUT /v1/app/pwas/{pwa_id}/postbacks` with one rule per event (`install` / `lead` / `sale` / `upsale`). Replace-all, live immediately, no redeploy. Then verify delivery in `GET /v1/app/logs/outgoing?type=s2s`.\n\n**Debug a missing conversion:** check `GET /v1/app/logs/postbacks` (did the advertiser tell us?) → `GET /v1/app/logs/outgoing` (did we forward it, and what did the network answer?) → `GET /v1/app/logs/outgoing/{id}` for the exact payload.\n\n## Landings (your own pages)\nThe Landings section (open to every account) hosts pages you upload as a zip: `index.html` at the top level, assets alongside, relative links keep working, no PHP. Upload with `POST /v1/app/tracker/landings/upload` (multipart `file`) and replace the files with `POST /v1/app/tracker/landings/{landing_id}/upload`. Two uses: **whitepage** — `cloaking.whitepage_source: \"landing\"` + `whitepage_landing_id` serves the page to bots, moderators and wrong-geo visitors instead of the generic one; **prelanding** — `installation_flow.preland_landing_id` shows the page to real visitors before the install page, and its `{offer}` links (required: the response reports `offer_macro`) become the link to the install page. A landing in use cannot be archived or deleted (`409 landing_in_use`); the platform may disable a landing (`blocked`, with `blocked_reason`) — it is then served nowhere and the PWAs fall back to the generic whitepage.\n\n## Built-in tracker\nCampaigns, offers and traffic sources live under `/v1/app/tracker/*` — same token, same team scope, and the per-member matrix on the Team page decides what a member may do (a missing grant is a `403` with the `permission` it lacks). A campaign link is `https://<campaign domain>/<alias>?<your params>`; every stream's landing / offer URL may carry `{subid}` (our click id), `{external_id}`, `{sub_id_1}`… so the advertiser can hand the click back. Advertisers report conversions to `https://<campaign domain>/pb/<postback_key>?subid={subid}&status=<status>&payout=<amount>` (`GET /v1/app/tracker/settings` → `postback_key`; `status_map` there translates their words into registration / deposit / sale / rejected). Read them back in `GET /v1/app/tracker/conversions`, and see the tracker's numbers next to the PWA funnel everywhere else: `tracker_*` keys on `/v1/app/dashboard/breakdown` (plus the `campaign` and `offer` dimensions), a `tracker` object on `/v1/app/logs/users` rows whose visitor came through a campaign, and `source: \"tracker\"` rows in `/v1/app/logs/postbacks` and `/v1/app/logs/outgoing`. Sending traffic to your own PWA through the tracker is just a landing URL with `?sub1={subid}` on it.\n\n## MCP (AI agents)\nPrefer a ready-made agent tool set over raw HTTP? We expose a Model Context Protocol server at `https://goose-pwa.com/mcp` (Streamable HTTP). Connect from Claude Code:\n\n```\nclaude mcp add --transport http goose-pwa https://goose-pwa.com/mcp \\\n  --header \"Authorization: Bearer <YOUR_API_TOKEN>\"\n```\n\nIt wraps these same endpoints as curated tools (`create_pwa`, `update_pwa`, `activate_pwa`, `add_domain`, `get_stats`, …) and runs as a public client, so it returns the same trimmed shapes.","contact":{"name":"Goose PWA","url":"https://t.me/goose_pwa_manager"}},"servers":[{"url":"http://127.0.0.1:14000","description":"Production"}],"security":[{"bearerAuth":[]}],"tags":[{"name":"Account","description":"Your profile, balance and plan."},{"name":"Analytics","description":"Traffic and conversion metrics."},{"name":"PWAs","description":"Your PWA applications."},{"name":"Pixels","description":"Advertiser conversion pixels."},{"name":"Domains","description":"Domains connected to your PWAs. Domains and PWAs are many-to-many: a PWA can have any number of domains, and one domain can serve several PWAs at once (co-tenancy — traffic is split evenly between the co-tenant PWAs and each visitor stickily keeps the same one)."},{"name":"Push","description":"Push notification campaigns."},{"name":"Landings","description":"Pages you upload as a zip — a PWA's whitepage or prelanding (and the tracker's stream landings)."},{"name":"Postbacks","description":"Server-to-server postbacks: where WE forward your conversions. One rule per event per PWA, applied live (no redeploy). For conversions coming the other way — advertiser → us — see Logs."},{"name":"Logs","description":"Raw event data: visitors, their journeys, incoming advertiser postbacks and outgoing pixel/postback fires. The only paged section (`limit`/`offset`) — built for exporting into your own reporting."},{"name":"Team","description":"Your team members."},{"name":"Tracker","description":"The built-in tracker (TDS): campaigns with streams and filters, offers, traffic sources, the inbound conversion log, postback settings and the audit trail. Period stats (`?period=`) ride on every list."}],"paths":{"/v1/app/user/profile":{"get":{"tags":["Account"],"summary":"Get your account profile","description":"Identity, plan/tariff and balance. `balance` is the spendable wallet — your own, or the team owner's shared one if you're in a team. Check it before paid actions (e.g. installs).","operationId":"getProfile","responses":{"200":{"description":"Account profile","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"},"user":{"$ref":"#/components/schemas/Profile"}}}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"429":{"$ref":"#/components/responses/TooManyRequests"}}}},"/v1/app/user/api-token/rotate":{"post":{"tags":["Account"],"summary":"Rotate your API token","description":"Issues a new token and invalidates the current one **immediately**. There is no grace period and no way back: every script, MCP client and signed-in cabinet session still holding the old value starts getting `401`. Capture `api_token` from the response — it is shown once here and nowhere else.\n\nUse it when a token leaks. Authenticate the call with the token you are replacing.","operationId":"rotateApiToken","responses":{"200":{"description":"New token issued","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"},"api_token":{"type":"string","description":"The new token. Store it now — it is never shown again."},"rotated_at":{"type":"string","format":"date-time"}}}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"429":{"$ref":"#/components/responses/TooManyRequests"}}}},"/v1/app/dashboard/breakdown":{"get":{"tags":["Analytics"],"summary":"Analytics breakdown","description":"The single stats endpoint — full-funnel metrics (clicks → installs → registrations → deposits → earnings) grouped by ONE dimension, exactly what the cabinet's Analytics section shows. Scoped to the PWAs you can see. Responses are cached ~2 minutes server-side.","operationId":"getAnalyticsBreakdown","parameters":[{"name":"dimension","in":"query","required":true,"description":"Group rows by: `pwa`, `country`, `domain`, `source`, `os`, `sub` (with `sub_key`), `user` (team member), or — tracker only — `campaign` / `offer` (rows are your campaigns / offers, PWA metrics 0).","schema":{"type":"string","enum":["pwa","country","domain","source","os","sub","user","campaign","offer"]}},{"name":"sub_key","in":"query","description":"For `dimension=sub` — which sub position to group by (1–9).","schema":{"type":"integer","minimum":1,"maximum":9}},{"name":"period","in":"query","description":"One of `today`, `yesterday`, `this_week`, `last_7_days`, `last_14_days`, `last_30_days`, `this_month`, `last_month`, `last_3_months`, `all_time`, `custom`. Unknown values fall back to `today`.","schema":{"type":"string","enum":["today","yesterday","this_week","last_7_days","last_14_days","last_30_days","this_month","last_month","last_3_months","all_time","custom"]}},{"name":"from","in":"query","schema":{"type":"string","format":"date"}},{"name":"to","in":"query","schema":{"type":"string","format":"date"}},{"name":"pwa_id","in":"query","description":"Limit to a single PWA.","schema":{"type":"string"}},{"name":"country","in":"query","description":"ISO-2 country code.","schema":{"type":"string"}},{"name":"user_id","in":"query","description":"Limit to one team member's PWAs.","schema":{"type":"string"}},{"name":"tag","in":"query","description":"Limit to PWAs carrying this tag.","schema":{"type":"string"}}],"responses":{"200":{"description":"Grouped metrics","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"},"dimension":{"type":"string"},"tracker_enabled":{"type":"boolean","description":"True when the account has tracker numbers — the `tracker_*` keys mean something."},"rows":{"type":"array","items":{"$ref":"#/components/schemas/BreakdownRow"}},"period":{"type":"object","properties":{"from":{"type":"string","format":"date","nullable":true},"to":{"type":"string","format":"date","nullable":true}}}}}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"429":{"$ref":"#/components/responses/TooManyRequests"}}}},"/v1/app/pwas":{"get":{"tags":["PWAs"],"summary":"List your PWAs","description":"Pass `period` to include per-PWA install/registration/deposit counts.","operationId":"listPwas","parameters":[{"name":"period","in":"query","description":"Stats window per row (omit to skip stats). One of `today`, `yesterday`, `this_week`, `last_7_days`, `last_14_days`, `last_30_days`, `this_month`, `last_month`, `last_3_months`, `all_time`, `custom`. Unknown values fall back to `today`.","schema":{"type":"string","enum":["today","yesterday","this_week","last_7_days","last_14_days","last_30_days","this_month","last_month","last_3_months","all_time","custom"]}},{"name":"from","in":"query","schema":{"type":"string","format":"date"}},{"name":"to","in":"query","schema":{"type":"string","format":"date"}},{"name":"archived","in":"query","description":"`true` → archived only, `false` → active only, omit → both.","schema":{"type":"boolean"}}],"responses":{"200":{"description":"List of PWAs","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"},"pwas":{"type":"array","items":{"$ref":"#/components/schemas/PwaListItem"}},"total_count":{"type":"integer"}}}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"429":{"$ref":"#/components/responses/TooManyRequests"}}},"post":{"tags":["PWAs"],"summary":"Create a PWA","description":"Creates a `draft` PWA, optionally from a template (`GET /templates`). Then: configure via `PUT /pwas/{pwa_id}`, attach domains, and launch with `POST /pwas/{pwa_id}/activate` (full walkthrough in the API description above).","operationId":"createPwa","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/PwaCreate"}}}},"responses":{"200":{"description":"Created","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"},"pwa_id":{"type":"string"}}}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"402":{"$ref":"#/components/responses/PaymentRequired"},"429":{"$ref":"#/components/responses/TooManyRequests"}}}},"/v1/app/pwas/{pwa_id}":{"parameters":[{"name":"pwa_id","in":"path","required":true,"schema":{"type":"string"}}],"get":{"tags":["PWAs"],"summary":"Get a PWA","description":"Full PWA object — design (per language), tracker geo-offers, cloaking, push and analytics config.","operationId":"getPwa","responses":{"200":{"description":"PWA","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"},"pwa":{"$ref":"#/components/schemas/Pwa"}}}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/TooManyRequests"}}},"put":{"tags":["PWAs"],"summary":"Update a PWA","description":"Partial update — send only the fields you change.\n\n**Domains.** `domains` replaces the full list. A PWA can hold any number of domains, and a domain can be shared by several PWAs at once (co-tenancy) — attaching an occupied domain is legal and does not detach it from other PWAs, but it is never implicit: the call is refused with `409 domain_in_use` listing the current tenants (`co_tenants`) until you re-send it with `allow_cotenancy: true`. Newly-added domains must be valid FQDNs already connected to your account (`400 invalid_domains` / `403 domain_not_owned`), must not be used by an active Split (`409 domain_bound_to_split`), and the PWA must be `active` or `draft` to take a new domain (`409 pwa_not_active`). Removing a domain is always allowed.","operationId":"updatePwa","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/PwaUpdate"}}}},"responses":{"200":{"description":"Updated","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"},"pwa_id":{"type":"string"},"updated_fields":{"type":"array","items":{"type":"string"}}}}}}},"400":{"description":"Invalid input — e.g. `invalid_domains` (not valid FQDNs; offending names in `domains`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"examples":{"invalid_domains":{"value":{"success":false,"error":"invalid_domains","domains":["not a domain"]}}}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"402":{"$ref":"#/components/responses/PaymentRequired"},"403":{"description":"`domain_not_owned` — connect the domain to your account first (offending names in `domains`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"examples":{"not_owned":{"value":{"success":false,"error":"domain_not_owned","domains":["example.com"]}}}}}},"404":{"$ref":"#/components/responses/NotFound"},"409":{"description":"`pwa_not_active` — the PWA is mid-deploy/failed/paused and can't take a new domain; `domain_bound_to_split` — the domain is routed by a Split; `domain_in_use` — another PWA already serves the domain, re-send with `allow_cotenancy: true` to share it; `domain_busy` — another save is attaching the same domain right now, retry.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"examples":{"bound_to_split":{"value":{"success":false,"error":"domain_bound_to_split","domains":["example.com"]}},"not_active":{"value":{"success":false,"error":"pwa_not_active"}},"in_use":{"value":{"success":false,"error":"domain_in_use","domains":["example.com"],"co_tenants":[{"domain":"example.com","pwa_id":"pwa_123","name":"Chicken Road","status":"active"}]}}}}}},"429":{"$ref":"#/components/responses/TooManyRequests"}}}},"/v1/app/pwas/{pwa_id}/activate":{"parameters":[{"name":"pwa_id","in":"path","required":true,"schema":{"type":"string"}}],"post":{"tags":["PWAs"],"summary":"Activate (deploy) a PWA","description":"Launches the PWA: builds and deploys it to every domain in its `domains` array. Returns immediately with status `processing` — poll `GET /pwas/{pwa_id}` until `status` becomes `active` (typically 1–3 min). Also the way to bring a `paused` PWA back online. Requires at least one attached domain. Every language of `design` must be filled in first — `app_name`, `description`, at least one screenshot and `icon_url` — because the store page serves exactly the visitor's language with no fallback to another one.","operationId":"activatePwa","responses":{"200":{"description":"Deploy queued","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"},"status":{"type":"string","examples":["processing"]}}}}}},"400":{"description":"`archived` — unarchive first. `missing_design` — the PWA has no content yet. `incomplete_languages` — at least one language is not filled in; the response carries `languages` (the codes) and `gaps` (`[{ lang, missing }]`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"402":{"$ref":"#/components/responses/PaymentRequired"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/TooManyRequests"}}}},"/v1/app/pwas/{pwa_id}/archive":{"parameters":[{"name":"pwa_id","in":"path","required":true,"schema":{"type":"string"}}],"post":{"tags":["PWAs"],"summary":"Archive / unarchive a PWA","description":"`{ \"archived\": true }` archives (an `active` PWA is also paused); `{ \"archived\": false }` unarchives — the PWA comes back as `paused`, call `activate` to redeploy it.","operationId":"archivePwa","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["archived"],"properties":{"archived":{"type":"boolean"}}}}}},"responses":{"200":{"description":"Done","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"}}}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/TooManyRequests"}}}},"/v1/app/pwas/{pwa_id}/copy":{"parameters":[{"name":"pwa_id","in":"path","required":true,"schema":{"type":"string"}}],"post":{"tags":["PWAs"],"summary":"Clone a PWA","description":"Full copy of the PWA (design, tracker, behaviour, push wiring) as a new `draft` with no domains. Attach domains and `activate` to launch the clone.","operationId":"copyPwa","requestBody":{"required":false,"content":{"application/json":{"schema":{"type":"object","properties":{"name":{"type":"string","description":"Name for the clone (default: source name + \" (Copy)\")."}}}}}},"responses":{"200":{"description":"Cloned","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"},"pwa_id":{"type":"string"},"name":{"type":"string"}}}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"402":{"$ref":"#/components/responses/PaymentRequired"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/TooManyRequests"}}}},"/v1/app/templates":{"get":{"tags":["PWAs"],"summary":"List PWA templates","description":"Ready-made store designs to start a PWA from — pass a `template_id` to `POST /pwas`.","operationId":"listTemplates","parameters":[{"name":"search","in":"query","schema":{"type":"string"}},{"name":"category","in":"query","schema":{"type":"string"}},{"name":"scope","in":"query","description":"Which bucket to list: `global` = platform-curated, visible to everyone; `team` = your own team's private ones. Omit for both.","schema":{"type":"string","enum":["global","team"]}},{"name":"page","in":"query","schema":{"type":"integer","default":1}},{"name":"page_size","in":"query","schema":{"type":"integer","default":20,"maximum":100}}],"responses":{"200":{"description":"Templates","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"},"templates":{"type":"array","items":{"$ref":"#/components/schemas/Template"}},"page":{"type":"integer"},"page_size":{"type":"integer"},"total":{"type":"integer"},"total_pages":{"type":"integer"}}}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"429":{"$ref":"#/components/responses/TooManyRequests"}}}},"/v1/app/pwas/{pwa_id}/postbacks":{"get":{"tags":["Postbacks"],"summary":"Get a PWA's S2S postbacks","description":"The server-to-server postback rules configured on this PWA — at most one per event.","operationId":"getPwaPostbacks","parameters":[{"name":"pwa_id","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Configured rules","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"},"postbacks":{"type":"array","items":{"$ref":"#/components/schemas/PostbackRule"}}}}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/TooManyRequests"}}},"put":{"tags":["Postbacks"],"summary":"Set a PWA's S2S postbacks","description":"**Replace-all.** Send the complete set of rules you want on this PWA — any event missing from the array is deleted. To clear everything, send `{ \"postbacks\": [] }`.\n\nOne rule per event (`install`, `lead`, `sale`, `upsale`); a second rule for the same event overwrites the first. URLs must be absolute `http(s)://` and may carry the usual macros (`{click_id}`, `{pwa_id}`, `{payout}`, …). Takes effect immediately — no PWA redeploy, the delivery service reads these live.","operationId":"updatePwaPostbacks","parameters":[{"name":"pwa_id","in":"path","required":true,"schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["postbacks"],"properties":{"postbacks":{"type":"array","items":{"$ref":"#/components/schemas/PostbackRule"}}}}}}},"responses":{"200":{"description":"Saved rules","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"},"pwa_id":{"type":"string"},"postbacks":{"type":"array","items":{"$ref":"#/components/schemas/PostbackRule"}}}}}}},"400":{"description":"`postbacks` missing, or a URL is not absolute http(s).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"examples":{"badUrl":{"value":{"success":false,"error":"invalid_url"}}}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/TooManyRequests"}}}},"/v1/app/pixels":{"get":{"tags":["Pixels"],"summary":"List pixels","operationId":"listPixels","parameters":[{"name":"archived","in":"query","description":"`true` | `false` | `all`.","schema":{"type":"string"}}],"responses":{"200":{"description":"Pixels","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"},"pixels":{"type":"array","items":{"$ref":"#/components/schemas/Pixel"}}}}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"429":{"$ref":"#/components/responses/TooManyRequests"}}},"post":{"tags":["Pixels"],"summary":"Create a pixel","operationId":"createPixel","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/PixelCreate"}}}},"responses":{"200":{"description":"Created","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"},"_id":{"type":"string"},"pixel_code":{"type":"string"}}}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"402":{"$ref":"#/components/responses/PaymentRequired"},"429":{"$ref":"#/components/responses/TooManyRequests"}}}},"/v1/app/pixels/{pixel_id}":{"parameters":[{"name":"pixel_id","in":"path","required":true,"schema":{"type":"string"}}],"get":{"tags":["Pixels"],"summary":"Get a pixel","operationId":"getPixel","responses":{"200":{"description":"Pixel","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"},"pixel":{"$ref":"#/components/schemas/Pixel"}}}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/TooManyRequests"}}},"put":{"tags":["Pixels"],"summary":"Update a pixel","operationId":"updatePixel","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/PixelUpdate"}}}},"responses":{"200":{"description":"Updated","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"},"_id":{"type":"string"}}}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"402":{"$ref":"#/components/responses/PaymentRequired"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/TooManyRequests"}}}},"/v1/app/pixels/{pixel_id}/archive":{"parameters":[{"name":"pixel_id","in":"path","required":true,"schema":{"type":"string"}}],"post":{"tags":["Pixels"],"summary":"Archive / unarchive a pixel","operationId":"archivePixel","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["archived"],"properties":{"archived":{"type":"boolean"}}}}}},"responses":{"200":{"description":"Done","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"},"_id":{"type":"string"},"archived":{"type":"boolean"}}}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/TooManyRequests"}}}},"/v1/app/domains":{"get":{"tags":["Domains"],"summary":"List domains","operationId":"listDomains","parameters":[{"name":"archived","in":"query","schema":{"type":"boolean"}},{"name":"period","in":"query","description":"Include per-domain stats for this window. One of `today`, `yesterday`, `this_week`, `last_7_days`, `last_14_days`, `last_30_days`, `this_month`, `last_month`, `last_3_months`, `all_time`, `custom`. Unknown values fall back to `today`.","schema":{"type":"string","enum":["today","yesterday","this_week","last_7_days","last_14_days","last_30_days","this_month","last_month","last_3_months","all_time","custom"]}},{"name":"from","in":"query","schema":{"type":"string","format":"date"}},{"name":"to","in":"query","schema":{"type":"string","format":"date"}}],"responses":{"200":{"description":"Domains","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"},"domains":{"type":"array","items":{"$ref":"#/components/schemas/Domain"}},"total_count":{"type":"integer"}}}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"429":{"$ref":"#/components/responses/TooManyRequests"}}},"post":{"tags":["Domains"],"summary":"Add a domain","description":"Three modes via `type`:\n- `own` — connect a domain you own (returns the nameservers to set at your registrar).\n- `system` — a free platform subdomain (`subdomain` + `base`).\n- `buy` — purchase a domain through us (`name`).","operationId":"createDomain","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/DomainCreate"}}}},"responses":{"200":{"description":"Connected / provisioning","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"},"domain_id":{"type":"string"},"name":{"type":"string"},"status":{"type":"string"},"name_servers":{"type":"array","items":{"type":"string"}},"provisioning_eta_seconds":{"type":"integer"}}}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"402":{"$ref":"#/components/responses/PaymentRequired"},"429":{"$ref":"#/components/responses/TooManyRequests"}}}},"/v1/app/domains/check":{"get":{"tags":["Domains"],"summary":"Check domain availability","description":"Validate before connecting/buying. For `type=system` pass `subdomain` + `base`; for `own`/`buy` pass `name`.","operationId":"checkDomain","parameters":[{"name":"type","in":"query","required":true,"schema":{"type":"string","enum":["own","system","buy"]}},{"name":"name","in":"query","description":"FQDN for `own`/`buy`.","schema":{"type":"string"}},{"name":"subdomain","in":"query","description":"For `system`.","schema":{"type":"string"}},{"name":"base","in":"query","description":"For `system` — pick one from `GET /domains/system`.","schema":{"type":"string"}}],"responses":{"200":{"description":"Verdict","content":{"application/json":{"schema":{"type":"object","properties":{"available":{"type":"boolean"},"reason":{"type":"string","description":"When unavailable: `invalid`, `taken`, `base_unavailable`, `domain_unavailable`.","nullable":true},"message":{"type":"string","nullable":true}}}}}},"400":{"description":"`invalid_type` / `missing_params`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"429":{"$ref":"#/components/responses/TooManyRequests"}}}},"/v1/app/domains/system":{"get":{"tags":["Domains"],"summary":"List system domain bases","description":"Bases you can hang a free subdomain on (`POST /domains` with `type=system`): platform bases + your own active domains.","operationId":"listSystemBases","responses":{"200":{"description":"Bases","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"},"bases":{"type":"array","items":{"type":"object","properties":{"base":{"type":"string"},"available":{"type":"boolean"},"owner":{"type":"string","enum":["platform","user"]}}}}}}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"429":{"$ref":"#/components/responses/TooManyRequests"}}}},"/v1/app/domains/bulk-connect":{"post":{"tags":["Domains"],"summary":"Bulk-connect own domains","description":"Connect up to 50 of your own domains in one call. Each returned row carries the nameservers to set at your registrar; domains go `pending_ns` until delegation is detected.","operationId":"bulkConnectDomains","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["domains"],"properties":{"domains":{"type":"array","maxItems":50,"items":{"type":"string"}}}}}}},"responses":{"200":{"description":"Per-domain results","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"},"connected":{"type":"integer"},"total":{"type":"integer"},"results":{"type":"array","items":{"type":"object","properties":{"domain":{"type":"string"},"success":{"type":"boolean"},"domain_id":{"type":"string","nullable":true},"status":{"type":"string","nullable":true},"nameservers":{"type":"array","nullable":true,"items":{"type":"string"}},"error":{"type":"string","nullable":true,"description":"`invalid_format` | `domain_busy` | `domain_taken` | `cloudflare_zone_failed`."}}}}}}}}},"400":{"description":"`no_valid_domains` / `too_many`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"402":{"$ref":"#/components/responses/PaymentRequired"},"429":{"$ref":"#/components/responses/TooManyRequests"}}}},"/v1/app/push":{"get":{"tags":["Push"],"summary":"List push campaigns","operationId":"listPush","parameters":[{"name":"category","in":"query","description":"`action` | `interval`.","schema":{"type":"string"}},{"name":"group_id","in":"query","schema":{"type":"string"}},{"name":"status","in":"query","schema":{"type":"string"}},{"name":"search","in":"query","schema":{"type":"string"}},{"name":"archived","in":"query","schema":{"type":"boolean"}}],"responses":{"200":{"description":"Campaigns + groups","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"},"items":{"type":"array","items":{"$ref":"#/components/schemas/Push"}},"groups":{"type":"array","items":{"type":"object","properties":{"group_id":{"type":"string"},"name":{"type":"string"},"push_count":{"type":"integer"},"connected_pwas":{"type":"array","items":{"type":"string"}}}}}}}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"429":{"$ref":"#/components/responses/TooManyRequests"}}},"post":{"tags":["Push"],"summary":"Create a push campaign","description":"`action` campaigns fire on `triggers`; `interval` campaigns fire on a `schedule`.","operationId":"createPush","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/PushCreate"}}}},"responses":{"200":{"description":"Created","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"},"push_id":{"type":"string"}}}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"402":{"$ref":"#/components/responses/PaymentRequired"},"429":{"$ref":"#/components/responses/TooManyRequests"}}}},"/v1/app/push/{push_id}":{"parameters":[{"name":"push_id","in":"path","required":true,"schema":{"type":"string"}}],"get":{"tags":["Push"],"summary":"Get a push campaign","operationId":"getPush","responses":{"200":{"description":"Campaign","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"},"push":{"$ref":"#/components/schemas/Push"}}}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/TooManyRequests"}}},"put":{"tags":["Push"],"summary":"Update a push campaign","operationId":"updatePush","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/PushUpdate"}}}},"responses":{"200":{"description":"Updated","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"},"push_id":{"type":"string"}}}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"402":{"$ref":"#/components/responses/PaymentRequired"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/TooManyRequests"}}}},"/v1/app/push/{push_id}/activate":{"parameters":[{"name":"push_id","in":"path","required":true,"schema":{"type":"string"}}],"post":{"tags":["Push"],"summary":"Activate a push campaign","description":"Starts sending: `draft`/`paused` → `active`.","operationId":"activatePush","responses":{"200":{"description":"Activated","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"},"status":{"type":"string","examples":["active"]}}}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/TooManyRequests"}}}},"/v1/app/push/{push_id}/archive":{"parameters":[{"name":"push_id","in":"path","required":true,"schema":{"type":"string"}}],"post":{"tags":["Push"],"summary":"Archive / unarchive a push campaign","operationId":"archivePush","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["archived"],"properties":{"archived":{"type":"boolean"}}}}}},"responses":{"200":{"description":"Done","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"},"archived":{"type":"boolean"}}}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/TooManyRequests"}}}},"/v1/app/logs/users":{"get":{"tags":["Logs"],"summary":"List visitors","description":"Raw per-visitor rows behind the funnel — one row per visitor, with the conversion flags they reached. This is the export surface: page through it with `limit`/`offset` to pull traffic into your own BI. Scoped to the PWAs you can see; ordered newest first.","operationId":"listVisitors","parameters":[{"name":"period","in":"query","description":"One of `today`, `yesterday`, `this_week`, `last_7_days`, `last_14_days`, `last_30_days`, `this_month`, `last_month`, `last_3_months`, `all_time`, `custom`. Unknown values fall back to `today`.","schema":{"type":"string","enum":["today","yesterday","this_week","last_7_days","last_14_days","last_30_days","this_month","last_month","last_3_months","all_time","custom"]}},{"name":"from","in":"query","schema":{"type":"string","format":"date"}},{"name":"to","in":"query","schema":{"type":"string","format":"date"}},{"name":"limit","in":"query","description":"Rows per page. Default 50, max 200.","schema":{"type":"integer","minimum":1,"maximum":200,"default":50}},{"name":"offset","in":"query","schema":{"type":"integer","minimum":0,"default":0}},{"name":"pwa_id","in":"query","schema":{"type":"string"}},{"name":"country","in":"query","description":"ISO-2, or a CSV of them (`BR,MX,US`).","schema":{"type":"string"}},{"name":"device","in":"query","description":"OS family: `Android`, `iOS`, `Windows`, `macOS`, `Linux`.","schema":{"type":"string"}},{"name":"event","in":"query","description":"Keep only visitors who reached this event.","schema":{"type":"string","enum":["install","lead","sale"]}},{"name":"search","in":"query","description":"Substring match on visitor id or IP.","schema":{"type":"string"}}],"responses":{"200":{"description":"Visitor rows","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"},"logs":{"type":"array","items":{"$ref":"#/components/schemas/VisitorRow"}},"total_count":{"type":"integer"},"has_more":{"type":"boolean"}}}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"429":{"$ref":"#/components/responses/TooManyRequests"}}}},"/v1/app/logs/users/{user_id}/flow":{"get":{"tags":["Logs"],"summary":"Get one visitor's journey","description":"Everything known about a single visitor: device, geo, the click→install→open→registration→deposit timeline with elapsed times, the sub/custom params their traffic carried, ad-platform click ids, and which pixels were present. `user_id` is the `id` from the visitor list.","operationId":"getVisitorFlow","parameters":[{"name":"user_id","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Visitor journey","content":{"application/json":{"schema":{"$ref":"#/components/schemas/VisitorFlow"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/TooManyRequests"}}}},"/v1/app/logs/postbacks":{"get":{"tags":["Logs"],"summary":"List incoming postbacks","description":"Conversion postbacks your advertiser sent US (registrations, deposits) — the audit trail for \"the advertiser says they fired it\".","operationId":"listIncomingPostbacks","parameters":[{"name":"period","in":"query","schema":{"type":"string","enum":["today","yesterday","this_week","last_7_days","last_14_days","last_30_days","this_month","last_month","last_3_months","all_time","custom"]}},{"name":"from","in":"query","schema":{"type":"string","format":"date"}},{"name":"to","in":"query","schema":{"type":"string","format":"date"}},{"name":"limit","in":"query","schema":{"type":"integer","minimum":1,"maximum":200,"default":50}},{"name":"offset","in":"query","schema":{"type":"integer","minimum":0,"default":0}},{"name":"pwa_id","in":"query","schema":{"type":"string"}},{"name":"event","in":"query","schema":{"type":"string"}},{"name":"search","in":"query","description":"Substring match on visitor id or IP.","schema":{"type":"string"}}],"responses":{"200":{"description":"Incoming postback rows","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"},"logs":{"type":"array","items":{"$ref":"#/components/schemas/IncomingPostbackRow"}},"total_count":{"type":"integer"},"has_more":{"type":"boolean"}}}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"429":{"$ref":"#/components/responses/TooManyRequests"}}}},"/v1/app/logs/outgoing":{"get":{"tags":["Logs"],"summary":"List outgoing events","description":"What WE sent out on your behalf: S2S postbacks to your tracker (`type=s2s`) and conversion fires to ad networks (`type=pixel`), with the provider's status for each. This is where you check whether a conversion actually reached Meta/TikTok/Snapchat. Note a `200` from a network means \"accepted\", not \"attributed\".","operationId":"listOutgoingEvents","parameters":[{"name":"period","in":"query","schema":{"type":"string","enum":["today","yesterday","this_week","last_7_days","last_14_days","last_30_days","this_month","last_month","last_3_months","all_time","custom"]}},{"name":"from","in":"query","schema":{"type":"string","format":"date"}},{"name":"to","in":"query","schema":{"type":"string","format":"date"}},{"name":"limit","in":"query","schema":{"type":"integer","minimum":1,"maximum":200,"default":50}},{"name":"offset","in":"query","schema":{"type":"integer","minimum":0,"default":0}},{"name":"pwa_id","in":"query","schema":{"type":"string"}},{"name":"type","in":"query","description":"`s2s` = postbacks to your tracker, `pixel` = ad-network fires. Omit for both.","schema":{"type":"string","enum":["s2s","pixel"]}},{"name":"network","in":"query","description":"Ad network — implies `type=pixel`.","schema":{"type":"string","enum":["meta","google","tiktok","snapchat","mintegral","bigo","kwai"]}}],"responses":{"200":{"description":"Outgoing event rows","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"},"logs":{"type":"array","items":{"$ref":"#/components/schemas/OutgoingEventRow"}},"total_count":{"type":"integer","description":"Capped at 50000 — a larger result set reports 50000."},"has_more":{"type":"boolean"}}}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"429":{"$ref":"#/components/responses/TooManyRequests"}}}},"/v1/app/logs/outgoing/{id}":{"get":{"tags":["Logs"],"summary":"Get one outgoing event","description":"The full request payload we sent and the provider's raw response — the detail you need to debug a rejected conversion. Auth headers and tokens are redacted.","operationId":"getOutgoingEvent","parameters":[{"name":"id","in":"path","required":true,"description":"Row `id` from the outgoing list.","schema":{"type":"string"}}],"responses":{"200":{"description":"Event detail","content":{"application/json":{"schema":{"$ref":"#/components/schemas/OutgoingEventDetail"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/TooManyRequests"}}}},"/v1/app/team/members":{"get":{"tags":["Team"],"summary":"List team members","description":"Members of your team with per-member stats + totals.","operationId":"listTeamMembers","parameters":[{"name":"from","in":"query","schema":{"type":"string","format":"date"}},{"name":"to","in":"query","schema":{"type":"string","format":"date"}},{"name":"role","in":"query","schema":{"type":"string"}}],"responses":{"200":{"description":"Members","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"},"members":{"type":"array","items":{"$ref":"#/components/schemas/TeamMember"}},"totals":{"$ref":"#/components/schemas/TeamTotals"}}}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"429":{"$ref":"#/components/responses/TooManyRequests"}}}},"/v1/app/tracker/landings":{"get":{"tags":["Landings"],"summary":"List landings","description":"Your hosted landings (and, with the tracker, external ones), newest first. `offer_macro` says whether `index.html` carries `{offer}` — required for a prelanding.","operationId":"listLandings","parameters":[{"name":"archived","in":"query","description":"`false` / omit = live, `true` = archived, `all` = both.","schema":{"type":"string","enum":["false","true","all"]}}],"responses":{"200":{"description":"landings","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"},"landings":{"type":"array","items":{"$ref":"#/components/schemas/Landing"}}}}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/TooManyRequests"}}}},"/v1/app/tracker/landings/upload":{"post":{"tags":["Landings"],"summary":"Upload a landing (zip)","description":"Multipart: `file` = the zip (`index.html` at the top level — one wrapping folder is stripped — assets alongside; executables, installers, archives and server-side scripts are refused, PHP only for allow-listed accounts), `name`, optional `group`, `notes`, `status` (`active` | `paused`). Limits per host (defaults): zip 20 MB, 50 MB unpacked, 500 files, 50 landings per account. Put `{offer}` on the CTA link if the landing will be a prelanding.","operationId":"uploadLanding","requestBody":{"required":true,"content":{"multipart/form-data":{"schema":{"type":"object","required":["file"],"properties":{"file":{"type":"string","format":"binary"},"name":{"type":"string"},"group":{"type":"string"},"notes":{"type":"string"},"status":{"type":"string","enum":["active","paused"]}}}}}},"responses":{"200":{"description":"Created","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"},"landing_id":{"type":"string"},"landing":{"$ref":"#/components/schemas/Landing"}}}}}},"400":{"description":"`bad_zip`, `empty_zip`, `no_index`, `too_many_files`, `too_big`, `file_too_big`, `file_type_not_allowed` (`message` names the file)."},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"description":"`limit_reached`, `php_not_allowed`, or the member lacks `landings:create`."},"413":{"description":"The zip is over the size limit."},"429":{"$ref":"#/components/responses/TooManyRequests"}}}},"/v1/app/tracker/landings/{landing_id}/upload":{"post":{"tags":["Landings"],"summary":"Replace a landing's files","description":"Same multipart as the upload; the folder is swapped atomically and the edge cache of every PWA using the landing is dropped — no redeploy. `403 landing_blocked` while the platform has it disabled.","operationId":"replaceLandingFiles","parameters":[{"name":"landing_id","in":"path","required":true,"schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"multipart/form-data":{"schema":{"type":"object","required":["file"],"properties":{"file":{"type":"string","format":"binary"},"name":{"type":"string"},"group":{"type":"string"},"notes":{"type":"string"}}}}}},"responses":{"200":{"description":"Replaced","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"},"landing":{"$ref":"#/components/schemas/Landing"}}}}}},"400":{"description":"Validation error (see the upload)."},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"description":"`landing_blocked` or a missing `landings:update` grant."},"404":{"description":"Not found."}}}},"/v1/app/tracker/landings/{landing_id}":{"get":{"tags":["Landings"],"summary":"Get a landing","operationId":"getLanding","parameters":[{"name":"landing_id","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"landing","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"},"landing":{"$ref":"#/components/schemas/Landing"}}}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"description":"Not found."}}},"put":{"tags":["Landings"],"summary":"Update a landing","description":"Name, group, status, notes. The files change through `/upload`.","operationId":"updateLanding","parameters":[{"name":"landing_id","in":"path","required":true,"schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/LandingWrite"}}}},"responses":{"200":{"description":"Updated","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"},"landing":{"$ref":"#/components/schemas/Landing"}}}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"description":"Not found."}}},"delete":{"tags":["Landings"],"summary":"Delete a landing","description":"Removes the row and the files. `409 landing_in_use` while a live PWA uses it as whitepage or prelanding (`pwas` lists them).","operationId":"deleteLanding","parameters":[{"name":"landing_id","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Deleted"},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"description":"Not found."},"409":{"description":"`landing_in_use`."}}}},"/v1/app/tracker/landings/{landing_id}/archive":{"post":{"tags":["Landings"],"summary":"Archive / restore a landing","description":"`{ archived: true | false }`. `409 landing_in_use` when archiving a landing a live PWA uses.","operationId":"archiveLanding","parameters":[{"name":"landing_id","in":"path","required":true,"schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["archived"],"properties":{"archived":{"type":"boolean"}}}}}},"responses":{"200":{"description":"Done"},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"description":"Not found."},"409":{"description":"`landing_in_use`."}}}},"/v1/app/tracker/landings/{landing_id}/files":{"get":{"tags":["Landings"],"summary":"List a landing's files","description":"The folder's files (first 500) with sizes.","operationId":"listLandingFiles","parameters":[{"name":"landing_id","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"files","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"},"landing_id":{"type":"string"},"files":{"type":"array","items":{"type":"object","properties":{"path":{"type":"string"},"bytes":{"type":"integer"}}}}}}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"description":"Not found."}}}},"/v1/app/tracker/campaigns":{"get":{"tags":["Tracker"],"summary":"List campaigns","description":"Your campaigns within your tracker visibility. With `limit` the list is paged and filtered on the server — live campaigns first (active, paused, drafts), newest change first: rows carry `streams_count` / `offers_count` instead of the streams, and the response adds `total`, `counts` (live / archived) and `facets` (groups / domains / source_ids for filter dropdowns). Without `limit` the complete list with streams is returned while the account holds at most 500 campaigns; beyond that the list is always paged (200 per page) — read one campaign for its streams, or pass `fields=full` for everything. `fields=min` returns `{campaign_id, name, alias, group, source_id, domain, status, archived}` for every campaign; `offer_id` returns the campaigns that route to that offer, streams included. Add `period` to get `stats` per row ({clicks, uniques, bots, regs, deps, sales, rejected, revenue, cost, epc, roi}) from the tracker's daily rollup. `can` says what you may create / update / delete.","operationId":"listTrackerCampaigns","parameters":[{"name":"archived","in":"query","description":"`false` / omit = live, `true` = archived, `all` = both.","schema":{"type":"string","enum":["false","true","all"]}},{"name":"limit","in":"query","description":"Page size, 1–200. Switches server-side paging on.","schema":{"type":"integer","minimum":1,"maximum":200}},{"name":"offset","in":"query","schema":{"type":"integer","minimum":0}},{"name":"q","in":"query","description":"Search in name, link path, group, domain and campaign id (case-insensitive).","schema":{"type":"string"}},{"name":"status","in":"query","schema":{"type":"string","enum":["active","paused","draft"]}},{"name":"group","in":"query","schema":{"type":"string"}},{"name":"source_id","in":"query","schema":{"type":"string"}},{"name":"domain","in":"query","schema":{"type":"string"}},{"name":"offer_id","in":"query","description":"Only the campaigns with a stream that routes to this offer (streams included).","schema":{"type":"string"}},{"name":"fields","in":"query","description":"`min` = the eight identifying fields for every campaign; `full` = the complete list with streams whatever its size.","schema":{"type":"string","enum":["min","full"]}},{"name":"period","in":"query","description":"One of `today`, `yesterday`, `this_week`, `last_7_days`, `last_14_days`, `last_30_days`, `this_month`, `last_month`, `last_3_months`, `all_time`, `custom`. With it every row carries `stats` for the window.","schema":{"type":"string","enum":["today","yesterday","this_week","last_7_days","last_14_days","last_30_days","this_month","last_month","last_3_months","all_time","custom"]}},{"name":"from","in":"query","schema":{"type":"string","format":"date"}},{"name":"to","in":"query","schema":{"type":"string","format":"date"}}],"responses":{"200":{"description":"campaigns","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"},"campaigns":{"type":"array","items":{"$ref":"#/components/schemas/TrackerCampaign"}},"can":{"type":"object","properties":{"create":{"type":"boolean"},"update":{"type":"boolean"},"delete":{"type":"boolean"}}},"period":{"type":"object","nullable":true,"properties":{"period":{"type":"string"},"from":{"type":"string","format":"date"},"to":{"type":"string","format":"date"},"label":{"type":"string"}}}}}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/TooManyRequests"}}},"post":{"tags":["Tracker"],"summary":"Create a campaign","description":"Creates a campaign. Cap: 6000 per account (`limit_reached`); up to 5000 streams per campaign and 300 offers per stream. The link path `alias` must be unique per account (`409 alias_taken`, omit it to get a free one); `domain` must be one of your connected domains (`403 domain_not_owned`) and not serve a PWA or split (`409 domain_used_by_pwa`) — a domain is either a PWA's or the tracker's, never both.","operationId":"createTrackerCampaign","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/TrackerCampaignWrite"}}}},"responses":{"200":{"description":"Created","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"},"campaign_id":{"type":"string"},"campaign":{"$ref":"#/components/schemas/TrackerCampaign"}}}}}},"400":{"description":"Validation error (`error` names the field)."},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/TooManyRequests"}}}},"/v1/app/tracker/campaigns/{campaign_id}":{"get":{"tags":["Tracker"],"summary":"Get a campaign","operationId":"getTrackerCampaign","parameters":[{"name":"campaign_id","in":"path","required":true,"schema":{"type":"string"}},{"name":"period","in":"query","description":"One of `today`, `yesterday`, `this_week`, `last_7_days`, `last_14_days`, `last_30_days`, `this_month`, `last_month`, `last_3_months`, `all_time`, `custom`. With it every row carries `stats` for the window.","schema":{"type":"string","enum":["today","yesterday","this_week","last_7_days","last_14_days","last_30_days","this_month","last_month","last_3_months","all_time","custom"]}},{"name":"from","in":"query","schema":{"type":"string","format":"date"}},{"name":"to","in":"query","schema":{"type":"string","format":"date"}}],"responses":{"200":{"description":"a campaign","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"},"campaign":{"$ref":"#/components/schemas/TrackerCampaign"}}}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/TooManyRequests"}}},"put":{"tags":["Tracker"],"summary":"Update a campaign","description":"Partial update — only the keys you send change.","operationId":"updateTrackerCampaign","parameters":[{"name":"campaign_id","in":"path","required":true,"schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/TrackerCampaignWrite"}}}},"responses":{"200":{"description":"Updated","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"},"campaign":{"$ref":"#/components/schemas/TrackerCampaign"}}}}}},"400":{"description":"Validation error."},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/TooManyRequests"}}},"delete":{"tags":["Tracker"],"summary":"Delete a campaign","description":"Hard delete. Prefer archiving (`/archive`) — it keeps the stats readable.","operationId":"deleteTrackerCampaign","parameters":[{"name":"campaign_id","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Deleted","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"}}}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/TooManyRequests"}}}},"/v1/app/tracker/campaigns/{campaign_id}/archive":{"post":{"tags":["Tracker"],"summary":"Archive / restore a campaign","operationId":"archiveTrackerCampaign","parameters":[{"name":"campaign_id","in":"path","required":true,"schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"archived":{"type":"boolean"}},"required":["archived"]}}}},"responses":{"200":{"description":"Done","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"},"campaign":{"$ref":"#/components/schemas/TrackerCampaign"}}}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/TooManyRequests"}}}},"/v1/app/tracker/campaigns/{campaign_id}/duplicate":{"post":{"tags":["Tracker"],"summary":"Duplicate a campaign","description":"Copies the campaign with its streams as a paused draft under a free alias.","operationId":"duplicateTrackerCampaign","parameters":[{"name":"campaign_id","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"The copy","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"},"campaign_id":{"type":"string"},"campaign":{"$ref":"#/components/schemas/TrackerCampaign"}}}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/TooManyRequests"}}}},"/v1/app/tracker/offers":{"get":{"tags":["Tracker"],"summary":"List offers","description":"Your offers, newest first, within your tracker visibility. Without `limit` the whole list is returned. With `limit` the list is paged and filtered on the server: the response adds `total`, `facets` (advertisers / groups / geos for filter dropdowns) and `counts` (live / archived). `ids` returns exactly those offers; `fields=min` returns `{offer_id, name, archived, status}` only. Add `period` to get `stats` per returned row ({clicks, uniques, bots, regs, deps, sales, rejected, revenue, revenue_expected, cost, epc, roi}) from the tracker's daily rollup. `can` says what you may create / update / delete.","operationId":"listTrackerOffers","parameters":[{"name":"archived","in":"query","description":"`false` / omit = live, `true` = archived, `all` = both.","schema":{"type":"string","enum":["false","true","all"]}},{"name":"limit","in":"query","description":"Page size, 1–200. Switches server-side paging on.","schema":{"type":"integer","minimum":1,"maximum":200}},{"name":"offset","in":"query","schema":{"type":"integer","minimum":0}},{"name":"q","in":"query","description":"Search in name, advertiser, group, URL and offer id (case-insensitive).","schema":{"type":"string"}},{"name":"advertiser","in":"query","schema":{"type":"string"}},{"name":"group","in":"query","schema":{"type":"string"}},{"name":"geo","in":"query","description":"ISO-2 country the offer lists.","schema":{"type":"string"}},{"name":"status","in":"query","schema":{"type":"string","enum":["active","paused"]}},{"name":"ids","in":"query","description":"Comma-separated offer ids (max 500): return exactly these.","schema":{"type":"string"}},{"name":"fields","in":"query","description":"`min` = id, name, archived, status only.","schema":{"type":"string","enum":["min"]}},{"name":"period","in":"query","description":"One of `today`, `yesterday`, `this_week`, `last_7_days`, `last_14_days`, `last_30_days`, `this_month`, `last_month`, `last_3_months`, `all_time`, `custom`. With it every row carries `stats` for the window.","schema":{"type":"string","enum":["today","yesterday","this_week","last_7_days","last_14_days","last_30_days","this_month","last_month","last_3_months","all_time","custom"]}},{"name":"from","in":"query","schema":{"type":"string","format":"date"}},{"name":"to","in":"query","schema":{"type":"string","format":"date"}}],"responses":{"200":{"description":"offers","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"},"offers":{"type":"array","items":{"$ref":"#/components/schemas/TrackerOffer"}},"can":{"type":"object","properties":{"create":{"type":"boolean"},"update":{"type":"boolean"},"delete":{"type":"boolean"}}},"period":{"type":"object","nullable":true,"properties":{"period":{"type":"string"},"from":{"type":"string","format":"date"},"to":{"type":"string","format":"date"},"label":{"type":"string"}}}}}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/TooManyRequests"}}},"post":{"tags":["Tracker"],"summary":"Create an offer","description":"Creates an offer. Cap: 10000 per account (`limit_reached`).","operationId":"createTrackerOffer","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/TrackerOfferWrite"}}}},"responses":{"200":{"description":"Created","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"},"offer_id":{"type":"string"},"offer":{"$ref":"#/components/schemas/TrackerOffer"}}}}}},"400":{"description":"Validation error (`error` names the field)."},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/TooManyRequests"}}}},"/v1/app/tracker/offers/{offer_id}":{"get":{"tags":["Tracker"],"summary":"Get an offer","operationId":"getTrackerOffer","parameters":[{"name":"offer_id","in":"path","required":true,"schema":{"type":"string"}},{"name":"period","in":"query","description":"One of `today`, `yesterday`, `this_week`, `last_7_days`, `last_14_days`, `last_30_days`, `this_month`, `last_month`, `last_3_months`, `all_time`, `custom`. With it every row carries `stats` for the window.","schema":{"type":"string","enum":["today","yesterday","this_week","last_7_days","last_14_days","last_30_days","this_month","last_month","last_3_months","all_time","custom"]}},{"name":"from","in":"query","schema":{"type":"string","format":"date"}},{"name":"to","in":"query","schema":{"type":"string","format":"date"}}],"responses":{"200":{"description":"an offer","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"},"offer":{"$ref":"#/components/schemas/TrackerOffer"}}}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/TooManyRequests"}}},"put":{"tags":["Tracker"],"summary":"Update an offer","description":"Partial update — only the keys you send change.","operationId":"updateTrackerOffer","parameters":[{"name":"offer_id","in":"path","required":true,"schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/TrackerOfferWrite"}}}},"responses":{"200":{"description":"Updated","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"},"offer":{"$ref":"#/components/schemas/TrackerOffer"}}}}}},"400":{"description":"Validation error."},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/TooManyRequests"}}},"delete":{"tags":["Tracker"],"summary":"Delete an offer","description":"Hard delete. Prefer archiving (`/archive`) — it keeps the stats readable.","operationId":"deleteTrackerOffer","parameters":[{"name":"offer_id","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Deleted","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"}}}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/TooManyRequests"}}}},"/v1/app/tracker/offers/{offer_id}/stats":{"get":{"tags":["Tracker"],"summary":"Offer stats","description":"The offer's numbers for the period from the tracker's daily rollup — totals (`stats`), per campaign (`by_campaign`) and per day (`daily`). Same metric shape as `stats` on the list: clicks, uniques, bots, regs, deps, sales, rejected, revenue (confirmed), revenue_expected (leads × payout), cost, epc, roi. Default period: last 14 days. `refresh=1` rolls today's events up first.","operationId":"getTrackerOfferStats","parameters":[{"name":"offer_id","in":"path","required":true,"schema":{"type":"string"}},{"name":"period","in":"query","schema":{"type":"string","enum":["today","yesterday","this_week","last_7_days","last_14_days","last_30_days","this_month","last_month","last_3_months","all_time","custom"]}},{"name":"from","in":"query","schema":{"type":"string","format":"date"}},{"name":"to","in":"query","schema":{"type":"string","format":"date"}},{"name":"refresh","in":"query","description":"`1` = roll today's events up before reading.","schema":{"type":"string","enum":["1"]}}],"responses":{"200":{"description":"offer stats","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"},"offer_id":{"type":"string"},"period":{"type":"object","properties":{"period":{"type":"string"},"from":{"type":"string","format":"date"},"to":{"type":"string","format":"date"},"label":{"type":"string"}}},"stats":{"$ref":"#/components/schemas/TrackerStats"},"by_campaign":{"type":"array","items":{"type":"object","properties":{"campaign_id":{"type":"string"},"name":{"type":"string"},"stats":{"$ref":"#/components/schemas/TrackerStats"}}}},"daily":{"type":"array","items":{"type":"object","properties":{"date":{"type":"string","format":"date"},"stats":{"$ref":"#/components/schemas/TrackerStats"}}}}}}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/TooManyRequests"}}}},"/v1/app/tracker/offers/{offer_id}/archive":{"post":{"tags":["Tracker"],"summary":"Archive / restore an offer","operationId":"archiveTrackerOffer","parameters":[{"name":"offer_id","in":"path","required":true,"schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"archived":{"type":"boolean"}},"required":["archived"]}}}},"responses":{"200":{"description":"Done","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"},"offer":{"$ref":"#/components/schemas/TrackerOffer"}}}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/TooManyRequests"}}}},"/v1/app/tracker/sources":{"get":{"tags":["Tracker"],"summary":"List sources","description":"Your sources, newest first, within your tracker visibility. Add `period` to get `stats` per row ({clicks, uniques, bots, regs, deps, sales, rejected, revenue, cost, epc, roi}) from the tracker's daily rollup. `can` says what you may create / update / delete.","operationId":"listTrackerSources","parameters":[{"name":"archived","in":"query","description":"`false` / omit = live, `true` = archived, `all` = both.","schema":{"type":"string","enum":["false","true","all"]}},{"name":"period","in":"query","description":"One of `today`, `yesterday`, `this_week`, `last_7_days`, `last_14_days`, `last_30_days`, `this_month`, `last_month`, `last_3_months`, `all_time`, `custom`. With it every row carries `stats` for the window.","schema":{"type":"string","enum":["today","yesterday","this_week","last_7_days","last_14_days","last_30_days","this_month","last_month","last_3_months","all_time","custom"]}},{"name":"from","in":"query","schema":{"type":"string","format":"date"}},{"name":"to","in":"query","schema":{"type":"string","format":"date"}}],"responses":{"200":{"description":"sources","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"},"sources":{"type":"array","items":{"$ref":"#/components/schemas/TrackerSource"}},"can":{"type":"object","properties":{"create":{"type":"boolean"},"update":{"type":"boolean"},"delete":{"type":"boolean"}}},"period":{"type":"object","nullable":true,"properties":{"period":{"type":"string"},"from":{"type":"string","format":"date"},"to":{"type":"string","format":"date"},"label":{"type":"string"}}}}}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/TooManyRequests"}}},"post":{"tags":["Tracker"],"summary":"Create a traffic source","description":"Creates a traffic source. Cap: 500 per account (`limit_reached`).","operationId":"createTrackerSource","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/TrackerSourceWrite"}}}},"responses":{"200":{"description":"Created","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"},"source_id":{"type":"string"},"source":{"$ref":"#/components/schemas/TrackerSource"}}}}}},"400":{"description":"Validation error (`error` names the field)."},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/TooManyRequests"}}}},"/v1/app/tracker/sources/{source_id}":{"get":{"tags":["Tracker"],"summary":"Get a traffic source","operationId":"getTrackerSource","parameters":[{"name":"source_id","in":"path","required":true,"schema":{"type":"string"}},{"name":"period","in":"query","description":"One of `today`, `yesterday`, `this_week`, `last_7_days`, `last_14_days`, `last_30_days`, `this_month`, `last_month`, `last_3_months`, `all_time`, `custom`. With it every row carries `stats` for the window.","schema":{"type":"string","enum":["today","yesterday","this_week","last_7_days","last_14_days","last_30_days","this_month","last_month","last_3_months","all_time","custom"]}},{"name":"from","in":"query","schema":{"type":"string","format":"date"}},{"name":"to","in":"query","schema":{"type":"string","format":"date"}}],"responses":{"200":{"description":"a traffic source","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"},"source":{"$ref":"#/components/schemas/TrackerSource"}}}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/TooManyRequests"}}},"put":{"tags":["Tracker"],"summary":"Update a traffic source","description":"Partial update — only the keys you send change.","operationId":"updateTrackerSource","parameters":[{"name":"source_id","in":"path","required":true,"schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/TrackerSourceWrite"}}}},"responses":{"200":{"description":"Updated","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"},"source":{"$ref":"#/components/schemas/TrackerSource"}}}}}},"400":{"description":"Validation error."},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/TooManyRequests"}}},"delete":{"tags":["Tracker"],"summary":"Delete a traffic source","description":"Hard delete. Prefer archiving (`/archive`) — it keeps the stats readable.","operationId":"deleteTrackerSource","parameters":[{"name":"source_id","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Deleted","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"}}}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/TooManyRequests"}}}},"/v1/app/tracker/sources/{source_id}/archive":{"post":{"tags":["Tracker"],"summary":"Archive / restore a traffic source","operationId":"archiveTrackerSource","parameters":[{"name":"source_id","in":"path","required":true,"schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"archived":{"type":"boolean"}},"required":["archived"]}}}},"responses":{"200":{"description":"Done","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"},"source":{"$ref":"#/components/schemas/TrackerSource"}}}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/TooManyRequests"}}}},"/v1/app/tracker/conversions":{"get":{"tags":["Tracker"],"summary":"List conversions","description":"The inbound event log of the tracker — every advertiser postback that matched one of your clicks, newest first, straight from the event store. `basis` picks which timestamp puts a row into the period. `show=all` adds the attempts that are not counted: duplicates and postbacks without a click (flagged `duplicate` / `matched`). `totals` sum the counted rows. Up to 5000 rows per call (`truncated` says if the window had more).","operationId":"listTrackerConversions","parameters":[{"name":"period","in":"query","description":"Default `last_14_days`.","schema":{"type":"string","enum":["today","yesterday","this_week","last_7_days","last_14_days","last_30_days","this_month","last_month","last_3_months","all_time","custom"]}},{"name":"from","in":"query","schema":{"type":"string","format":"date"}},{"name":"to","in":"query","schema":{"type":"string","format":"date"}},{"name":"basis","in":"query","schema":{"type":"string","enum":["event","sale","received"],"default":"event"}},{"name":"status","in":"query","schema":{"type":"string","enum":["registration","deposit","sale","rejected","unknown"]}},{"name":"campaign_id","in":"query","schema":{"type":"string"}},{"name":"offer_id","in":"query","schema":{"type":"string"}},{"name":"source_id","in":"query","schema":{"type":"string"}},{"name":"country","in":"query","description":"ISO-2.","schema":{"type":"string"}},{"name":"q","in":"query","description":"Exact match on the click id (subid), `external_id` or the advertiser's event id.","schema":{"type":"string"}},{"name":"show","in":"query","description":"`all` includes duplicates and unmatched postbacks.","schema":{"type":"string","enum":["all"]}},{"name":"limit","in":"query","schema":{"type":"integer","minimum":1,"maximum":5000,"default":2000}}],"responses":{"200":{"description":"Conversions","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"},"conversions":{"type":"array","items":{"$ref":"#/components/schemas/TrackerConversion"}},"totals":{"type":"object","properties":{"regs":{"type":"integer"},"deps":{"type":"integer"},"sales":{"type":"integer"},"rejected":{"type":"integer"},"revenue":{"type":"number"}}},"truncated":{"type":"boolean"},"period":{"type":"object","properties":{"period":{"type":"string"},"from":{"type":"string","format":"date"},"to":{"type":"string","format":"date"},"label":{"type":"string"},"basis":{"type":"string"}}}}}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/TooManyRequests"}}}},"/v1/app/tracker/conversions/{conversion_id}":{"get":{"tags":["Tracker"],"summary":"Get a conversion","description":"One conversion with its click, the history of every postback the advertiser sent for that click, and the outbound deliveries it triggered.","operationId":"getTrackerConversion","parameters":[{"name":"conversion_id","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Conversion","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"},"conversion":{"$ref":"#/components/schemas/TrackerConversion"},"click":{"type":"object","nullable":true,"description":"The click row: geo, device, sub ids, destination."},"history":{"type":"array","items":{"$ref":"#/components/schemas/TrackerConversion"}},"outbound":{"type":"array","items":{"type":"object","properties":{"attempt_id":{"type":"string"},"rule_scope":{"type":"string"},"url":{"type":"string"},"method":{"type":"string"},"attempt":{"type":"integer"},"http_code":{"type":"integer"},"ok":{"type":"integer"},"dry_run":{"type":"integer"},"error":{"type":"string"},"sent_at":{"type":"string"}}}}}}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/TooManyRequests"}}}},"/v1/app/tracker/settings":{"get":{"tags":["Tracker"],"summary":"Get tracker settings","description":"Your inbound postback key and path, the status map (the advertiser's words → registration / deposit / sale / rejected), global outbound postback rules and the traffback pool. Sections you may not read come back blank; `can` says what you hold.","operationId":"getTrackerSettings","responses":{"200":{"description":"Settings","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"},"settings":{"$ref":"#/components/schemas/TrackerSettings"}}}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/TooManyRequests"}}},"put":{"tags":["Tracker"],"summary":"Update tracker settings","description":"Partial update of `status_map`, `outbound` (needs `postbacks:update`), `traffback_pool`, `traffback_url` (needs `traffback:update`). One advertiser word may map to one type only; outbound rules need an http(s) `url` and at least one status (max 20).","operationId":"updateTrackerSettings","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"status_map":{"type":"object","additionalProperties":{"type":"array","items":{"type":"string"}}},"outbound":{"type":"array","items":{"$ref":"#/components/schemas/TrackerOutboundRule"}},"traffback_pool":{"type":"array","items":{"type":"object","properties":{"offer_id":{"type":"string"},"share":{"type":"number"}}}},"traffback_url":{"type":"string"}}}}}},"responses":{"200":{"description":"Settings","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"},"settings":{"$ref":"#/components/schemas/TrackerSettings"}}}}}},"400":{"description":"Validation error."},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/TooManyRequests"}}}},"/v1/app/tracker/settings/rotate-key":{"post":{"tags":["Tracker"],"summary":"Rotate the postback key","description":"Mints a new inbound postback key; the old `/pb/<key>` URL stops working immediately — update it at your advertisers.","operationId":"rotateTrackerPostbackKey","responses":{"200":{"description":"Settings with the new key","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"},"settings":{"$ref":"#/components/schemas/TrackerSettings"}}}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/TooManyRequests"}}}},"/v1/app/tracker/audit":{"get":{"tags":["Tracker"],"summary":"Audit trail","description":"Who changed what in the tracker (offers, sources, campaigns, settings), newest first. `before` is a cursor: entries strictly older than that instant.","operationId":"listTrackerAudit","parameters":[{"name":"resource_type","in":"query","schema":{"type":"string","enum":["offer","source","campaign","settings"]}},{"name":"action","in":"query","schema":{"type":"string","enum":["create","update","archive","restore","delete","rotate_key"]}},{"name":"resource_id","in":"query","schema":{"type":"string"}},{"name":"before","in":"query","schema":{"type":"string","format":"date-time"}},{"name":"limit","in":"query","schema":{"type":"integer","minimum":1,"maximum":200,"default":100}}],"responses":{"200":{"description":"Entries","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"},"entries":{"type":"array","items":{"$ref":"#/components/schemas/TrackerAuditEntry"}}}}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/TooManyRequests"}}}}},"components":{"securitySchemes":{"bearerAuth":{"type":"http","scheme":"bearer","description":"Your account API token. Get it on the Developers page."}},"responses":{"Unauthorized":{"description":"Missing or invalid API token.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"examples":{"unauthorized":{"value":{"success":false,"error":"unauthorized"}}}}}},"Forbidden":{"description":"Your tracker access lacks this grant (set per member on the Team page).","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"},"error":{"type":"string","example":"forbidden"},"permission":{"type":"string","example":"campaigns:update"},"message":{"type":"string"}}}}}},"PaymentRequired":{"description":"Account is read-only (negative balance). Top up to write.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"examples":{"negative":{"value":{"success":false,"error":"negative_balance"}}}}}},"NotFound":{"description":"Resource not found or not visible to you.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"TooManyRequests":{"description":"Rate limit exceeded — retry after the window closes (see the Rate limits section).","headers":{"Retry-After":{"description":"Seconds to wait before retrying (always 60).","schema":{"type":"integer"}},"X-RateLimit-Limit":{"description":"Requests allowed per minute for this token.","schema":{"type":"integer"}},"X-RateLimit-Remaining":{"description":"Requests left in the current window.","schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"examples":{"limited":{"value":{"success":false,"error":"rate_limited","message":"Too many requests. Slow down and retry shortly."}}}}}}},"schemas":{"Error":{"type":"object","properties":{"success":{"type":"boolean"},"error":{"type":"string","description":"Machine-readable error code."},"message":{"type":"string"}},"required":["success","error"]},"Team":{"type":"object","nullable":true,"properties":{"team_id":{"type":"string"},"name":{"type":"string"},"role":{"$ref":"#/components/schemas/TeamRole"}}},"Profile":{"type":"object","properties":{"user_id":{"type":"string","description":"YOUR account id. Note the collision: a `user_id` under /logs/* is a VISITOR (someone who saw your PWA), and the `user_id` filter on the analytics breakdown is a TEAM MEMBER. Three different namespaces, one field name."},"username":{"type":"string","nullable":true},"avatar_url":{"type":"string","nullable":true},"email":{"type":"string","nullable":true},"telegram_username":{"type":"string","nullable":true},"tariff":{"type":"string","enum":["pay_per_install"]},"is_trial":{"type":"boolean"},"created_at":{"type":"string","format":"date-time","nullable":true},"next_payment_at":{"type":"string","format":"date-time","nullable":true},"balance":{"type":"number","description":"Spendable balance — your own, or the team owner's shared wallet."},"team":{"$ref":"#/components/schemas/Team"}}},"BreakdownRow":{"type":"object","description":"Full-funnel metrics summed for one dimension value.","properties":{"tracker_clicks":{"type":"integer","description":"Built-in tracker — clicks on campaign links. Never summed into `clicks`."},"tracker_uniques":{"type":"integer"},"tracker_regs":{"type":"integer","description":"Tracker registrations (advertiser postbacks)."},"tracker_deps":{"type":"integer","description":"Tracker deposits."},"tracker_sales":{"type":"integer"},"tracker_revenue":{"type":"number","description":"Payout of counted tracker conversions, USD."},"tracker_cost":{"type":"number","description":"Sum of the campaigns' `cost` parameter over clicks."},"key":{"type":"string","description":"Dimension value id (pwa_id, ISO-2 country, domain, …)."},"label":{"type":"string","description":"Display label for the dimension value."},"icon":{"type":"string","nullable":true,"description":"PWA icon URL (`dimension=pwa` only)."},"clicks":{"type":"integer"},"uniques":{"type":"integer"},"installs":{"type":"integer"},"apk_installs":{"type":"integer"},"ios_installs":{"type":"integer"},"opens":{"type":"integer"},"first_opens":{"type":"integer"},"registrations":{"type":"integer"},"deposits":{"type":"integer"},"earnings":{"type":"number"},"push_subs":{"type":"integer"},"upsales":{"type":"integer"}}},"PwaCategory":{"type":"string","enum":["gambling","nutra","dating","crypto","betting"],"description":"The PWA's **vertical** — an internal classification of what you promote. This is NOT the text a visitor sees: that is `design.<lang>.category`, a free-text label per language (see DesignLocale). The two are linked one way — when a language's label is left blank, the vertical supplies the default (`gambling` → \"Gambling\").\n\n**Not validated on write:** any other string is accepted and stored, but only these five are recognised, and an unrecognised one silently defaults that label to \"Gambling\"."},"PwaStatus":{"type":"string","enum":["draft","processing","active","paused","error"],"description":"`draft` created, never deployed · `processing` deploying · `active` live · `paused` taken offline · `error` deploy failed. Archiving is the separate `archived` boolean, NOT a status."},"TeamRole":{"type":"string","enum":["owner","teamlead","buyer","finance"],"nullable":true},"VisibilityScope":{"type":"string","enum":["own","assigned","team"],"nullable":true,"description":"What this member can see: only their own objects, those of the teamlead they report to, or the whole team's."},"PwaStats":{"type":"object","description":"Per-PWA counters. The funnel has TWO vocabularies across this API and they mean the same events: `lead` = `registration` = `regs`, and `sale` = `deposit` = `deps`. Postback rules and pixel event maps use lead/sale; analytics rows and the visitor timeline use registration/deposit.","properties":{"installs":{"type":"integer"},"regs":{"type":"integer","description":"Registrations — the same event postbacks call `lead`."},"deps":{"type":"integer","description":"Deposits — the same event postbacks call `sale`."}}},"PwaListItem":{"type":"object","properties":{"pwa_id":{"type":"string"},"name":{"type":"string"},"category":{"$ref":"#/components/schemas/PwaCategory"},"icon_url":{"type":"string","nullable":true},"domains":{"type":"array","items":{"type":"string"},"description":"All domains serving this PWA (any count; a domain may also serve other PWAs)."},"countries":{"type":"array","items":{"type":"string"}},"tags":{"type":"array","items":{"type":"string"}},"cloaking_enabled":{"type":"boolean"},"status":{"$ref":"#/components/schemas/PwaStatus"},"archived":{"type":"boolean"},"creator_id":{"type":"string","nullable":true,"description":"Owner scope. **For a team-owned object this is the TEAM id, not a person** — the individual is `buyer_id`. Treat it as \"which wallet/tenant this belongs to\"."},"buyer_id":{"type":"string","nullable":true,"description":"The individual account this object is attributed to (the media buyer). For personal, non-team objects it equals `creator_id`."},"created_at":{"type":"string","nullable":true},"created_by":{"type":"string","nullable":true,"description":"Display NAME of whoever created it — not an id."},"stats":{"$ref":"#/components/schemas/PwaStats"}}},"Pwa":{"type":"object","properties":{"pwa_id":{"type":"string"},"name":{"type":"string","description":"Your INTERNAL label for this PWA, visible only to you in the cabinet. The name visitors actually see on the store page is `design.<lang>.app_name`."},"status":{"$ref":"#/components/schemas/PwaStatus"},"category":{"$ref":"#/components/schemas/PwaCategory"},"domains":{"type":"array","items":{"type":"string"},"description":"Hostnames serving this PWA — plain strings like `datgame.cfd`, NOT `domain_id` values. A domain may also serve other PWAs (co-tenancy). Any count."},"previous_domains":{"type":"array","items":{"type":"string"}},"countries":{"type":"array","items":{"type":"string"}},"tags":{"type":"array","items":{"type":"string"}},"languages":{"type":"array","items":{"type":"string","pattern":"^[a-z]{2}$"},"description":"Lowercase 2-letter language codes. Anything else is ignored by the content checks and never rendered."},"archived":{"type":"boolean","description":"Independent of `status` — a PWA is archived OR not, on top of whatever status it has. There is no `status: \"archived\"`."},"cloaking":{"$ref":"#/components/schemas/Cloaking"},"tracker":{"$ref":"#/components/schemas/Tracker"},"push":{"$ref":"#/components/schemas/PushConfig"},"additional":{"$ref":"#/components/schemas/TrafficBehavior"},"analytics":{"type":"object","description":"Ad-platform pixel ids fired IN THE VISITOR'S BROWSER from the PWA page (client-side tags). Not the same as a Pixel object, which delivers conversions server-side (CAPI) and is configured under /pixels; and not the same as `tracker`, which is where the visitor gets redirected. `yandex_cursor_id` is a Yandex Metrica counter id despite the name.","properties":{"meta_pixel":{"type":"string","nullable":true},"tiktok_pixel":{"type":"string","nullable":true},"snapchat_pixel":{"type":"string","nullable":true},"yandex_cursor_id":{"type":"string","nullable":true}}},"installation_flow":{"$ref":"#/components/schemas/InstallationFlow"},"design":{"type":"object","description":"Store listing per language — keys are language codes (`en`, `ru`, …), one DesignLocale each.","additionalProperties":{"$ref":"#/components/schemas/DesignLocale"}},"comments":{"type":"object","description":"Store REVIEWS, per language — keys are language codes, values are arrays of reviews. The field is named `comments` for historical reasons; the object shape is `Review`.","additionalProperties":{"type":"array","items":{"$ref":"#/components/schemas/Review"}}},"creator_id":{"type":"string","nullable":true},"buyer_id":{"type":"string","nullable":true},"created_at":{"type":"string","nullable":true},"updated_at":{"type":"string","nullable":true}}},"PwaCreate":{"type":"object","required":["name"],"properties":{"name":{"type":"string"},"template_id":{"type":"string","description":"Clone a template's design."},"pwa_id":{"type":"string","description":"Optional client-supplied id."}}},"PwaUpdate":{"type":"object","description":"Any subset of these fields.","properties":{"name":{"type":"string"},"category":{"$ref":"#/components/schemas/PwaCategory"},"domains":{"type":"array","items":{"type":"string"},"description":"Replaces the full domain list. Any count; sharing a domain with other PWAs is allowed (see the update endpoint description for attach rules)."},"allow_cotenancy":{"type":"boolean","description":"Confirms that a newly-added domain may join PWAs that already serve it (traffic is split between co-tenants). Without it such an attach returns 409 `domain_in_use`."},"tags":{"type":"array","items":{"type":"string"}},"languages":{"type":"array","items":{"type":"string","pattern":"^[a-z]{2}$"},"description":"Lowercase 2-letter language codes. Anything else is ignored by the content checks and never rendered."},"design":{"type":"object","description":"Per-language store listing; each language key is merged field-by-field.","additionalProperties":{"$ref":"#/components/schemas/DesignLocale"}},"comments":{"type":"object","description":"Per-language review arrays.","additionalProperties":{"type":"array","items":{"$ref":"#/components/schemas/Review"}}},"tracker":{"$ref":"#/components/schemas/Tracker"},"cloaking":{"$ref":"#/components/schemas/Cloaking"},"push":{"$ref":"#/components/schemas/PushConfig"},"additional":{"$ref":"#/components/schemas/TrafficBehavior"},"analytics":{"type":"object","properties":{"meta_pixel":{"type":"string"},"tiktok_pixel":{"type":"string"},"snapchat_pixel":{"type":"string"},"yandex_cursor_id":{"type":"string"}}},"installation_flow":{"$ref":"#/components/schemas/InstallationFlow"}}},"TrafficBehavior":{"type":"object","description":"Per-device traffic behaviour (the `additional` object) — what iOS / desktop / Android visitors get. Android is the primary install flow; iOS can't install PWAs natively, and desktop traffic is usually moderators — hence separate actions per platform.","properties":{"ios_action":{"type":"string","enum":["show_install_popup_onclick","show_install_popup_immediately","redirect_to_offer","redirect_to_native_app","show_whitepage"],"description":"iOS visitors: show the install popup on tap / immediately, send straight to the offer URL, open a native App Store app (see `native_app_url`), or show the whitepage."},"pc_action":{"type":"string","enum":["open_whitepage","redirect_to_offer","show_install_popup"],"description":"Desktop visitors: `open_whitepage` (default — safest, desktop is mostly ad-network moderators), send to the offer, or let them see the PWA."},"android_popup":{"type":"string","enum":["show_install_popup_immediately","show_install_popup_onclick","no_popup"],"description":"When Android visitors see the install popup; `no_popup` skips it and routes to the offer."},"use_backurl":{"type":"boolean","description":"Browser Back button leads to the offer instead of leaving the page."},"use_green_theme":{"type":"boolean","description":"Alternative green store theme."},"native_app_url":{"type":"string","description":"App Store URL used by `ios_action=redirect_to_native_app`."}}},"InstallationFlow":{"type":"object","description":"How the Android install actually happens.","properties":{"primary_action":{"type":"string","enum":["pwa","apk"],"description":"`pwa` — browser install prompt; `apk` — download an APK."},"fallback_action":{"type":"string","enum":["redirect","apk"],"description":"When the primary path is unavailable: `redirect` to the offer, or fall back to the APK."},"idle_action":{"type":"string","enum":["none","redirect"],"description":"Idle visitor: `none` (default) — nothing; `redirect` — send a visitor who taps nothing to the offer after `idle_timeout_seconds`."},"idle_timeout_seconds":{"type":"integer","minimum":5,"maximum":600,"description":"Seconds of inactivity before `idle_action` fires. Ignored when `idle_action` is `none`."},"preland_landing_id":{"type":"string","nullable":true,"description":"Prelanding: id (`lnd_…`) of one of your hosted landings (GET /v1/app/tracker/landings; hosted, no PHP, `offer_macro: true`) shown to real visitors before the install page. Its `{offer}` link leads to the install page on the PWA's domain. Empty = off."}}},"PushConfig":{"type":"object","description":"Which push campaigns this PWA's subscribers get.","properties":{"timing":{"type":"string","enum":["on_install","on_open","on_event"],"description":"When to ask for the push permission."},"request_permission":{"type":"boolean","description":"Ask for push permission at all."},"use_default":{"type":"boolean","description":"Include the platform's default campaigns."},"connected_push":{"type":"array","items":{"type":"string"},"description":"Individual campaign `push_id`s connected to this PWA."},"connected_groups":{"type":"array","items":{"type":"string"},"description":"Campaign group ids connected to this PWA."}}},"Tracker":{"type":"object","description":"Where the traffic goes — offer URL per country. IMPORTANT: updates replace the WHOLE tracker object, so always send the complete `geo_offers` list, not a diff.","properties":{"geo_offers":{"type":"array","items":{"type":"object","properties":{"country":{"type":"string","description":"ISO-2 code, or `default` for the catch-all row."},"url":{"type":"string","description":"Offer/tracker URL. Supports macros like {click_id}."}}}}}},"Cloaking":{"type":"object","description":"Whitepage shown to moderators/bots instead of the real PWA.","properties":{"is_active":{"type":"boolean"},"whitepage_source":{"type":"string","enum":["tsl_whitepage","pwa_whitepage","landing","404","google_play","redirect"],"description":"`tsl_whitepage` — hosted generic page; `pwa_whitepage` — built from the PWA's own listing; `404` — plain Not Found; `google_play` — clone a real Google Play listing (set `whitepage_url`); `redirect` — send cloaked visitors to `whitepage_url` (302)."},"whitepage_url":{"type":"string","nullable":true,"description":"Google Play listing URL for `google_play`, or the redirect target for `redirect` (required for both)."},"whitepage_landing_id":{"type":"string","nullable":true,"description":"For `landing`: id (`lnd_…`) of one of your hosted landings (GET /v1/app/tracker/landings)."}}},"DesignLocale":{"type":"object","description":"Store listing for ONE language. All fields optional on update — only sent fields change.","properties":{"app_name":{"type":"string"},"developer":{"type":"string"},"developer_address":{"type":"string"},"category":{"type":"string","description":"Free-text label shown under the app name on the store page, in THIS language (\"Casino\", \"Games\", …). A different thing from the PWA's `category` (its vertical, one enum for the whole PWA) — leave this blank and the vertical fills it in."},"promotion":{"type":"string","description":"Promo banner text."},"verify_icon":{"type":"string","enum":["none","google","instagram","twitter","tiktok"],"description":"Verification badge next to the developer name."},"age_rating":{"type":"string","examples":["18"]},"icon_url":{"type":"string"},"screenshots":{"type":"array","items":{"type":"string"}},"description":{"type":"string"},"tags":{"type":"array","items":{"type":"string"}},"rating":{"type":"number","examples":[4.8]},"downloads":{"type":"number","description":"Displayed install count."},"reviews_count":{"type":"number"},"star_ratings":{"type":"object","description":"Ratings-bar distribution, percentages.","properties":{"one_star":{"type":"number"},"two_stars":{"type":"number"},"three_stars":{"type":"number"},"four_stars":{"type":"number"},"five_stars":{"type":"number"}}},"version":{"type":"string"},"changelog":{"type":"string"},"last_update":{"type":"string","description":"Free text; `{date}` macro = today."},"date_published":{"type":"string","description":"Free text; `{date-1y}` macro = a year ago."},"app_size":{"type":"string","examples":["12.5"]},"advanced":{"type":"object","description":"Store-page section toggles.","properties":{"header_section":{"type":"boolean"},"contains_ads":{"type":"boolean"},"contains_purchases":{"type":"boolean"},"share_button":{"type":"boolean"},"wishlist_button":{"type":"boolean"},"similar_games":{"type":"boolean"},"bottom_section":{"type":"boolean"},"menu":{"type":"boolean"},"editors_choice":{"type":"boolean"},"app_support":{"type":"boolean"}}}}},"Review":{"type":"object","description":"One store review.","properties":{"author":{"type":"string"},"rating":{"type":"number","description":"1–5."},"description":{"type":"string","description":"Review text."},"developer_response":{"type":"string","nullable":true},"likes":{"type":"integer"},"avatar_url":{"type":"string","nullable":true}}},"Template":{"type":"object","description":"Ready-made PWA design — pass its `template_id` to `POST /pwas`.","properties":{"template_id":{"type":"string"},"name":{"type":"string"},"category":{"type":"string","description":"The template's vertical (same vocabulary as a PWA's `category`) — used to filter the gallery. Not a store label."},"tags":{"type":"array","items":{"type":"string"}},"preview_image_url":{"type":"string","nullable":true},"scope":{"type":"string","enum":["global","team"],"description":"Bucket it belongs to — see the `scope` query parameter."},"use_count":{"type":"integer"},"created_at":{"type":"string","nullable":true}}},"EventsMatching":{"type":"object","description":"Which conversion each app event maps to.","properties":{"install":{"type":"string","nullable":true},"open":{"type":"string","nullable":true},"push_subscription":{"type":"string","nullable":true},"lead":{"type":"string","nullable":true},"sale":{"type":"string","nullable":true}}},"Pixel":{"type":"object","properties":{"_id":{"type":"string"},"name":{"type":"string"},"network":{"$ref":"#/components/schemas/PixelNetwork"},"pixel_code":{"type":"string"},"access_token":{"type":"string","nullable":true},"status":{"type":"string","enum":["active","paused","archived"],"description":"`archived` mirrors the `archived` flag."},"archived":{"type":"boolean"},"tags":{"type":"array","items":{"type":"string"}},"events_matching":{"$ref":"#/components/schemas/EventsMatching"},"creator_id":{"type":"string","nullable":true},"buyer_id":{"type":"string","nullable":true},"created_at":{"type":"string","nullable":true},"updated_at":{"type":"string","nullable":true},"created_by":{"type":"string","nullable":true}}},"PixelNetwork":{"type":"string","enum":["meta","google","tiktok","snapchat","mintegral"],"description":"Ad network the conversions are sent to. Historical rows may also carry `bigo` or `kwai` — the delivery engine still fires those, but they cannot be created through this API."},"PixelCreate":{"type":"object","required":["network","pixel_code"],"properties":{"network":{"$ref":"#/components/schemas/PixelNetwork"},"pixel_code":{"type":"string"},"name":{"type":"string"},"access_token":{"type":"string"},"events_matching":{"$ref":"#/components/schemas/EventsMatching"}}},"PixelUpdate":{"type":"object","properties":{"name":{"type":"string"},"pixel_code":{"type":"string"},"access_token":{"type":"string"},"events_matching":{"$ref":"#/components/schemas/EventsMatching"}}},"Domain":{"type":"object","properties":{"domain_id":{"type":"string"},"name":{"type":"string"},"type":{"type":"string","enum":["own","system","purchased","migrate"],"description":"How the domain got here. **Note the asymmetry:** you POST `type=buy`, but a purchased domain reads back as `purchased`. `migrate` is set when a domain is moved between PWAs."},"status":{"type":"string","enum":["pending","pending_ns","processing","active","error","timeout","archived"],"description":"`pending_ns` = waiting for nameserver delegation at your registrar."},"archived":{"type":"boolean"},"added":{"type":"string","nullable":true},"expires":{"type":"string","nullable":true},"nameservers":{"type":"string","nullable":true,"description":"Comma-separated nameserver list (own domains awaiting/using NS delegation)."},"tags":{"type":"array","items":{"type":"string"}},"creator_id":{"type":"string","nullable":true},"buyer_id":{"type":"string","nullable":true},"created_by":{"type":"string","nullable":true,"description":"Display name of who added the domain."},"usage":{"type":"object","description":"Team-wide occupancy — which PWAs currently serve on this domain (a domain can back several PWAs at once).","properties":{"mine_pwa_ids":{"type":"array","items":{"type":"string"},"description":"Your PWAs attached to this domain."},"members":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"name":{"type":"string"}}},"description":"Teammates who also have PWAs on this domain (names only)."}}},"stats":{"type":"object","nullable":true,"properties":{"uniques":{"type":"integer"},"installs":{"type":"integer"},"regs":{"type":"integer"},"deps":{"type":"integer"},"earnings":{"type":"number"}}}}},"DomainCreate":{"type":"object","required":["type"],"properties":{"type":{"type":"string","enum":["own","system","buy"]},"name":{"type":"string","description":"FQDN — for `own` (connect) and `buy` (purchase)."},"subdomain":{"type":"string","description":"For `system`."},"base":{"type":"string","description":"For `system` — the base domain to attach the subdomain to."}}},"Push":{"type":"object","properties":{"push_id":{"type":"string"},"name":{"type":"string"},"category":{"type":"string","enum":["action","interval"],"description":"What makes this campaign fire — `action` on a trigger event, `interval` on a schedule. Unrelated to a PWA's `category`, which is a vertical; the word is reused."},"target_action":{"type":"string","nullable":true},"country":{"type":"string","nullable":true},"group_id":{"type":"string","nullable":true},"status":{"type":"string"},"archived":{"type":"boolean"},"tags":{"type":"array","items":{"type":"string"}},"triggers":{"type":"object","additionalProperties":true,"description":"Event triggers (action campaigns)."},"schedule":{"type":"object","additionalProperties":true,"description":"Send schedule (interval campaigns)."},"stats":{"type":"object","properties":{"sent":{"type":"integer"},"received":{"type":"integer"},"opened":{"type":"integer"},"open_rate":{"type":"number"}}},"created_by":{"type":"string","nullable":true}}},"PushCreate":{"type":"object","required":["name","category"],"properties":{"name":{"type":"string"},"category":{"type":"string","enum":["action","interval"]},"group_id":{"type":"string"},"triggers":{"type":"array","items":{"type":"object","additionalProperties":true},"description":"For `action` campaigns."},"schedule":{"type":"array","items":{"type":"object","additionalProperties":true},"description":"For `interval` campaigns."},"content":{"type":"object","additionalProperties":true,"description":"Notification content (title/body/icon/url)."},"targeting":{"type":"object","properties":{"countries":{"type":"array","items":{"type":"string"}},"target_action":{"type":"string"},"sub_filters":{"type":"array","items":{"type":"object","additionalProperties":true}},"sub_logic":{"type":"string","enum":["and","or"],"description":"How multiple `sub_filters` combine. Default `and`."}}}}},"PushUpdate":{"type":"object","properties":{"name":{"type":"string"},"tags":{"type":"array","items":{"type":"string"}},"triggers":{"type":"array","items":{"type":"object","additionalProperties":true}},"schedule":{"type":"array","items":{"type":"object","additionalProperties":true}},"content":{"type":"object","additionalProperties":true,"description":"Notification content."},"targeting":{"type":"object","properties":{"countries":{"type":"array","items":{"type":"string"}},"target_action":{"type":"string"},"sub_filters":{"type":"array","items":{"type":"object","additionalProperties":true}},"sub_logic":{"type":"string","enum":["and","or"],"description":"How multiple `sub_filters` combine. Default `and`."}}}}},"TeamTotals":{"type":"object","properties":{"installs":{"type":"integer"},"registrations":{"type":"integer"},"deposits":{"type":"integer"},"earnings":{"type":"number"},"domains_bought":{"type":"integer"},"platform_spend":{"type":"number"}}},"TeamMember":{"type":"object","properties":{"user_id":{"type":"string"},"username":{"type":"string","nullable":true},"avatar_url":{"type":"string","nullable":true},"role":{"$ref":"#/components/schemas/TeamRole"},"is_online":{"type":"boolean"},"telegram_username":{"type":"string","nullable":true},"object_visibility":{"$ref":"#/components/schemas/VisibilityScope"},"stats_visibility":{"$ref":"#/components/schemas/VisibilityScope"},"pwa_count":{"type":"integer"},"stats":{"$ref":"#/components/schemas/TeamTotals"}}},"PostbackRule":{"type":"object","description":"One S2S postback rule. At most one per event per PWA.","required":["event","url"],"properties":{"event":{"type":"string","enum":["install","lead","sale","upsale"],"description":"`lead` = registration, `sale` = deposit."},"method":{"type":"string","enum":["GET","POST"],"default":"GET"},"url":{"type":"string","description":"Absolute http(s) URL. Macros are substituted at fire time (`{click_id}`, `{pwa_id}`, `{payout}`, `{country}`, `{sub1}`…)."}}},"VisitorRow":{"type":"object","description":"One visitor and the conversion flags they reached.","properties":{"tracker":{"type":"object","nullable":true,"description":"The tracker click that brought this visitor — set when a landing parameter carried a tracker click id (24 hex), null otherwise.","properties":{"click_id":{"type":"string"},"campaign_id":{"type":"string"},"campaign":{"type":"string"},"stream_id":{"type":"string"},"offer_id":{"type":"string"},"offer":{"type":"string"},"source_id":{"type":"string"},"source":{"type":"string"},"clicked_at":{"type":"string","description":"UTC, `YYYY-MM-DD HH:MM:SS`."}}},"id":{"type":"string","nullable":true,"description":"Visitor id — pass to /logs/users/{user_id}/flow."},"user_id":{"type":"string","nullable":true,"description":"Same value as `id`. This is a VISITOR — a device that landed on your PWA — NOT your account and not a team member. Different namespace from the `user_id` in /user/profile."},"date":{"type":"string","nullable":true,"description":"Last activity, formatted for display."},"pwa_id":{"type":"string","nullable":true},"pwa_name":{"type":"string"},"country":{"type":"string","nullable":true,"description":"ISO-2."},"os":{"type":"string"},"browser":{"type":"string"},"user_agent":{"type":"string"},"ip":{"type":"string"},"device":{"type":"string"},"domain":{"type":"string","description":"Hostname the visitor landed on."},"url":{"type":"string"},"installed":{"type":"boolean"},"opened":{"type":"boolean"},"registered":{"type":"boolean","description":"Reached the `lead` event."},"deposited":{"type":"boolean","description":"Reached the `sale` event."},"sale_amount":{"type":"number","nullable":true}}},"VisitorEvent":{"type":"object","properties":{"type":{"type":"string","enum":["click","install","open","registration","deposit"]},"date":{"type":"string","format":"date-time"},"elapsed":{"type":"string","nullable":true,"description":"Time since the click, e.g. `0s`, `4m`, `2h`."},"amount":{"type":"number","description":"Payout — on `deposit` only."}}},"VisitorFlow":{"type":"object","properties":{"success":{"type":"boolean"},"user_id":{"type":"string"},"pwa_id":{"type":"string","nullable":true},"pwa_name":{"type":"string"},"country":{"type":"string","nullable":true},"device":{"type":"string"},"os":{"type":"string"},"os_version":{"type":"string"},"browser":{"type":"string"},"browser_version":{"type":"string"},"language":{"type":"string"},"timezone":{"type":"string"},"ip":{"type":"string"},"first_seen":{"type":"string","format":"date-time","nullable":true},"last_active":{"type":"string","format":"date-time","nullable":true},"payout":{"type":"number","nullable":true},"statuses":{"type":"object","properties":{"install":{"type":"boolean"},"open":{"type":"boolean"},"lead":{"type":"boolean"},"sale":{"type":"boolean"},"push":{"type":"boolean"}}},"attribution":{"type":"object","additionalProperties":true,"description":"Campaign fields parsed out of the landing URL (campaign_name/id, adset_name/id, ad_id, placement, …). Only populated keys are present."},"params":{"type":"object","additionalProperties":true,"description":"Query params the traffic carried — your subs AND any custom keys, verbatim. Only populated keys are present."},"pixels":{"type":"object","additionalProperties":true,"description":"Ad-platform browser ids present on this visitor, keyed by network (meta, google, tiktok, snapchat, …)."},"click_ids":{"type":"object","additionalProperties":true,"description":"Ad-platform click ids (fbclid, gclid, ttclid, sclid, …)."},"events":{"type":"array","items":{"$ref":"#/components/schemas/VisitorEvent"},"description":"Chronological timeline."}}},"IncomingPostbackRow":{"type":"object","description":"A conversion postback the advertiser sent to us.","properties":{"source":{"type":"string","enum":["pwa","tracker"],"description":"`tracker` rows are the advertiser's postbacks to the built-in tracker: `user_id` is the click id, `pwa_name` the campaign, `status_text` OK | duplicate | no click."},"campaign":{"type":"string","nullable":true},"campaign_id":{"type":"string","nullable":true},"offer":{"type":"string","nullable":true},"id":{"type":"string"},"date":{"type":"string","nullable":true},"event":{"type":"string"},"user_id":{"type":"string"},"method":{"type":"string"},"path":{"type":"string"},"pwa_id":{"type":"string"},"pwa_name":{"type":"string"},"ip":{"type":"string"},"status_code":{"type":"integer"},"status_text":{"type":"string"}}},"OutgoingEventRow":{"type":"object","description":"One conversion we forwarded — to your tracker (`postback`) or to an ad network (`pixel`).","properties":{"source":{"type":"string","enum":["pwa","tracker"],"description":"`tracker` rows are the tracker's own postback deliveries (`type` s2s): `user_id` is the click id, `status_text` dry-run | OK | failed | <error>."},"campaign":{"type":"string","nullable":true},"campaign_id":{"type":"string","nullable":true},"attempt":{"type":"integer","nullable":true},"rule_scope":{"type":"string","nullable":true,"description":"`campaign` | `global` | `source` — which rule fired."},"id":{"type":"string","description":"Pass to /logs/outgoing/{id} for the full payload."},"date":{"type":"string","nullable":true},"type":{"type":"string","enum":["postback","pixel"]},"is_test":{"type":"boolean","description":"Fired by the pixel Test button, not by real traffic."},"network":{"type":"string","nullable":true,"description":"Pixel rows only."},"event":{"type":"string"},"user_id":{"type":"string"},"pixel_id":{"type":"string","nullable":true},"status_code":{"type":"integer","nullable":true,"description":"Provider HTTP status. 200 means accepted, NOT attributed."},"status_text":{"type":"string"},"duration":{"type":"integer","nullable":true,"description":"Round-trip in ms."},"method":{"type":"string"},"url":{"type":"string"},"pwa_id":{"type":"string"},"pwa_name":{"type":"string"}}},"OutgoingEventDetail":{"type":"object","properties":{"success":{"type":"boolean"},"id":{"type":"string"},"type":{"type":"string","enum":["postback","pixel"]},"network":{"type":"string","nullable":true},"event":{"type":"string"},"user_id":{"type":"string"},"pixel_id":{"type":"string","nullable":true},"status_code":{"type":"integer","nullable":true},"date":{"type":"string","nullable":true},"method":{"type":"string"},"url":{"type":"string"},"request":{"type":"object","additionalProperties":true,"description":"The payload we sent (body + headers). Authorization / token headers are redacted."},"response":{"description":"The provider's raw response — shape varies by network."}}},"TrackerStats":{"type":"object","description":"Period sums from the tracker's daily rollup (five-minute lag).","properties":{"clicks":{"type":"integer"},"uniques":{"type":"integer"},"bots":{"type":"integer"},"regs":{"type":"integer"},"deps":{"type":"integer"},"sales":{"type":"integer"},"rejected":{"type":"integer"},"revenue":{"type":"number"},"cost":{"type":"number"},"epc":{"type":"number"},"roi":{"type":"number","nullable":true,"description":"% — null without cost."}}},"TrackerOfferWrite":{"type":"object","properties":{"name":{"type":"string"},"advertiser":{"type":"string"},"group":{"type":"string"},"geo":{"type":"array","items":{"type":"string"},"description":"ISO-2 codes."},"url":{"type":"string","description":"Offer URL; may carry `{subid}`, `{external_id}`, `{sub_id_1}`… `{country}`, `{cost}`."},"offer_type":{"type":"string","enum":["redirect","preload","action"]},"preload_url":{"type":"string"},"payout_type":{"type":"string","enum":["CPA","CPC"]},"payout":{"type":"number"},"currency":{"type":"string"},"payout_from_param":{"type":"boolean"},"status":{"type":"string","enum":["active","paused"]},"notes":{"type":"string"}}},"LandingWrite":{"type":"object","properties":{"name":{"type":"string"},"group":{"type":"string"},"status":{"type":"string","enum":["active","paused"]},"notes":{"type":"string"}}},"Landing":{"allOf":[{"$ref":"#/components/schemas/LandingWrite"},{"type":"object","properties":{"landing_id":{"type":"string"},"landing_type":{"type":"string","enum":["local","external"],"description":"`local` = files we host (the only kind a PWA can use); `external` = a URL (tracker streams only)."},"url":{"type":"string"},"index_file":{"type":"string","description":"`index.html` / `index.htm` / `index.php`."},"php":{"type":"boolean","description":"The folder holds PHP — cannot be a PWA whitepage or prelanding."},"offer_macro":{"type":"boolean","description":"`index.html` carries `{offer}` — required for a prelanding."},"files_count":{"type":"integer"},"bytes":{"type":"integer"},"archived":{"type":"boolean"},"blocked":{"type":"boolean","description":"Disabled by the platform: served nowhere, files cannot be replaced."},"blocked_reason":{"type":"string"},"created_by":{"type":"string"},"created_at":{"type":"string","format":"date-time"},"updated_at":{"type":"string","format":"date-time"}}}]},"TrackerOffer":{"allOf":[{"$ref":"#/components/schemas/TrackerOfferWrite"},{"type":"object","properties":{"offer_id":{"type":"string"},"archived":{"type":"boolean"},"created_by":{"type":"string"},"created_at":{"type":"string","format":"date-time"},"updated_at":{"type":"string","format":"date-time"},"stats":{"$ref":"#/components/schemas/TrackerStats"}}}]},"TrackerSourceWrite":{"type":"object","properties":{"name":{"type":"string"},"template":{"type":"string","description":"Facebook, Google, TikTok… or your own."},"traffic_loss":{"type":"number","description":"0–99 %."},"postback_url":{"type":"string","description":"Where the source wants conversions; macros `{external_id}`, `{status}`, `{payout}`, `{subid}`."},"params":{"type":"array","maxItems":60,"description":"Replaced whole on update.","items":{"type":"object","properties":{"param":{"type":"string"},"name":{"type":"string"},"placeholder":{"type":"string"},"fixed":{"type":"boolean"}}}},"notes":{"type":"string"}}},"TrackerSource":{"allOf":[{"$ref":"#/components/schemas/TrackerSourceWrite"},{"type":"object","properties":{"source_id":{"type":"string"},"archived":{"type":"boolean"},"created_by":{"type":"string"},"created_at":{"type":"string","format":"date-time"},"updated_at":{"type":"string","format":"date-time"},"stats":{"$ref":"#/components/schemas/TrackerStats"}}}]},"TrackerStream":{"type":"object","description":"One routing rule of a campaign. Streams are tried in order: `forced` first, then `regular` by weight / position, `default` when nothing matched.","properties":{"id":{"type":"string","description":"`stm_…`; omit on create."},"type":{"type":"string","enum":["forced","regular","default"]},"name":{"type":"string"},"enabled":{"type":"boolean"},"weight":{"type":"number"},"position":{"type":"integer"},"schema":{"type":"string","enum":["offers","landing","redirect","action"]},"offers":{"type":"array","items":{"type":"object","properties":{"offer_id":{"type":"string"},"share":{"type":"number"}}}},"landing":{"type":"string","description":"Landing / prelander URL (`schema=landing`) — a PWA link with `?sub1={subid}` sends the click to your PWA."},"redirect_url":{"type":"string"},"action":{"type":"object","properties":{"kind":{"type":"string","enum":["to_campaign","status404","show_text","show_html","do_nothing"]},"target":{"type":"string","description":"campaign_id for `to_campaign`."}}},"filters":{"type":"array","items":{"type":"object","properties":{"name":{"type":"string","enum":["country","keyword","os","device","bot","proxy"]},"mode":{"type":"string","enum":["accept","reject"]},"values":{"type":"array","items":{"type":"string"}}}}},"notes":{"type":"string"}}},"TrackerCampaignWrite":{"type":"object","properties":{"name":{"type":"string"},"alias":{"type":"string","description":"Link path, `[a-z0-9-]`, unique per account. Omit to get a free one from the name."},"domain":{"type":"string","description":"One of your connected domains; a domain serves either PWAs or the tracker."},"status":{"type":"string","enum":["active","paused","draft"]},"source_id":{"type":"string","nullable":true},"rotation":{"type":"string","enum":["weight","position"]},"cost_model":{"type":"string","enum":["CPC","CPA","CPM"]},"cost":{"type":"number"},"currency":{"type":"string"},"uniqueness":{"type":"string","enum":["ip_ua","ip","param"]},"cookie_ttl":{"type":"integer","description":"Days a visitor stays bound to the stream they got."},"bind_visitor":{"type":"boolean"},"streams":{"type":"array","items":{"$ref":"#/components/schemas/TrackerStream"}},"params":{"type":"array","description":"Sub-id mapping: which URL parameter lands in which click column (`slot`).","items":{"type":"object","properties":{"name":{"type":"string"},"parameter":{"type":"string"},"placeholder":{"type":"string"},"slot":{"type":"string"}}}},"postbacks":{"type":"array","description":"Campaign-level outbound rules.","items":{"type":"object","properties":{"id":{"type":"string"},"event":{"type":"string","enum":["reg","dep","sale","rejected"]},"method":{"type":"string"},"url":{"type":"string"}}}},"notes":{"type":"string"}}},"TrackerCampaign":{"allOf":[{"$ref":"#/components/schemas/TrackerCampaignWrite"},{"type":"object","properties":{"campaign_id":{"type":"string"},"link":{"type":"string","description":"`https://<domain>/<alias>` — the tracking link."},"offer_ids":{"type":"array","items":{"type":"string"}},"archived":{"type":"boolean"},"created_by":{"type":"string"},"created_at":{"type":"string","format":"date-time"},"updated_at":{"type":"string","format":"date-time"},"stats":{"$ref":"#/components/schemas/TrackerStats"},"stream_stats":{"type":"object","additionalProperties":{"$ref":"#/components/schemas/TrackerStats"},"description":"`GET /{campaign_id}?period=` only."}}}]},"TrackerConversion":{"type":"object","properties":{"conversion_id":{"type":"string"},"click_id":{"type":"string","description":"Our subid."},"status":{"type":"string","enum":["registration","deposit","sale","rejected","unknown"]},"raw_status":{"type":"string","description":"What the advertiser sent."},"payout":{"type":"number"},"currency":{"type":"string"},"advertiser_event_id":{"type":"string"},"external_id":{"type":"string"},"campaign_id":{"type":"string"},"campaign":{"type":"string"},"stream_id":{"type":"string"},"offer_id":{"type":"string"},"offer":{"type":"string"},"source_id":{"type":"string"},"country":{"type":"string"},"sub_ids":{"type":"array","items":{"type":"string"}},"matched":{"type":"integer","description":"1 = the click was found."},"duplicate":{"type":"integer","description":"1 = this (click, status) was already counted."},"event_time":{"type":"string"},"sale_time":{"type":"string","nullable":true},"received_at":{"type":"string"}}},"TrackerOutboundRule":{"type":"object","properties":{"id":{"type":"string"},"name":{"type":"string"},"url":{"type":"string","description":"Macros: `{subid}`, `{status}`, `{payout}`, `{external_id}`, `{sub_id_1}`…"},"method":{"type":"string","enum":["GET","POST"]},"statuses":{"type":"array","items":{"type":"string","enum":["registration","deposit","sale","rejected"]}},"enabled":{"type":"boolean"}}},"TrackerSettings":{"type":"object","properties":{"postback_key":{"type":"string"},"postback_path":{"type":"string","description":"`/pb/<key>` — append to any of your campaign domains. `/<key>/postback` works too (Keitaro-style)."},"status_map":{"type":"object","additionalProperties":{"type":"array","items":{"type":"string"}},"description":"type → the advertiser's words for it."},"outbound":{"type":"array","items":{"$ref":"#/components/schemas/TrackerOutboundRule"}},"traffback_pool":{"type":"array","items":{"type":"object","properties":{"offer_id":{"type":"string"},"share":{"type":"number"}}}},"traffback_url":{"type":"string"},"updated_by":{"type":"string"},"updated_at":{"type":"string","format":"date-time"},"can":{"type":"object","properties":{"postbacks_read":{"type":"boolean"},"postbacks_update":{"type":"boolean"},"traffback_read":{"type":"boolean"},"traffback_update":{"type":"boolean"},"audit_read":{"type":"boolean"}}}}},"TrackerAuditEntry":{"type":"object","properties":{"id":{"type":"string"},"actor_id":{"type":"string"},"actor_name":{"type":"string"},"action":{"type":"string"},"resource_type":{"type":"string"},"resource_id":{"type":"string"},"resource_name":{"type":"string"},"before":{"type":"object","nullable":true},"after":{"type":"object","nullable":true},"created_at":{"type":"string","format":"date-time"}}}}}}