Skip to content
Docs

Tracking guide

Caps, rules and day-parting

Targeting rules and lists, traffic controls, caps with soft and hard behaviour, day-parting and scheduled actions.

These controls decide whether a click or conversion is accepted, rerouted or limited. They run on the edge for clicks and in ingest for conversions, from the same configuration snapshot, so a change applies within seconds.

Rule sets

A rule set attaches to an offer (also landing page, SmartLink entry, payout entry, cap, fraud rule, or the whole tenant for traffic controls). It has:

FieldMeaning
event_scopeclick, impression, conversion, event or all.
matchall (every rule must match) or any.
rules[]variable, operator, values[].
actionallow, deny, route_to, fail_traffic, mark.
fail_traffic_targetWhere denied traffic goes: offer, offer_url, offer_group or smartlink plus routing_value; with is_pay_affiliate and is_pass_through options and reason_categories (traffic_filter, already_converted, offer_expired, geotargeting, caps, other) that become the click's invalid reason.
partner_ids, excluded_partner_idsNarrow to or exempt partners.
priority, date_valid_from/to, statusEvaluation order (higher first) and validity.

Order of evaluation at click time: tenant traffic controls, then the offer's rule sets, then landing-page rules. The first deciding rule set wins.

Variables and operators

GET /rules/catalog returns the live catalogue; POST /rules/validate checks a rule set without saving.

  • Variables: geography (country, region, city, postal_code, dma), network (isp, carrier, connection_type, asn, ip, ip_range), device and software (device_type, device_brand, device_model, os, os_version, browser, browser_version, browser_language, user_agent), traffic (referer, source, sub1 to sub10, creative_id, tracking_domain, link_style), app (app_name, app_id, app_version, sdk_version), ids (aid, gaid, idfa), conversion-side (txn_id, user_id, email, sale_amount, payout, revenue, goal, status, coupon_code, ctit_seconds), time (hour_of_day, day_of_week, date_range in the tenant time zone), flags (is_proxy, is_vpn, is_datacenter, is_bot, fraud_score, cookie_consent) and entity ids.
  • Operators: equals, not_equals, in, not_in, contains, not_contains, starts_with, ends_with, regex, gt, gte, lt, lte, between, empty, not_empty, cidr, not_cidr, day_of_week, hour_range, list_ref (in a list), not_in_list. Short aliases (eq, neq, starts, ends, exists, missing, in_list) are accepted.

Lists

Lists are reusable value sets referenced by list_ref: ip, isp, subid, domain, referrer, suppression, email_suppression, asn, device_id, custom. Fill them with values, upload a file (POST /lists/{id}/upload), or point source_url at a file and set sync_interval_minutes; a worker refreshes URL-backed lists (DT_LIST_SYNC_EVERY, default 1 minute poll) and POST /lists/{id}/sync forces a refresh. Fraud has its own deny lists, see fraud.

Conversion rules and holds

Rule sets with event_scope: conversion run when a conversion arrives, before caps and pricing: first the tenant's (Traffic ▸ Conversion rules, scoped to all traffic, partners, offers or countries like traffic controls), then the offer's (Offer ▸ Targeting ▸ Conversion rules), by priority. Actions:

ActionEffect
denyThe conversion is stored rejected with status source rule:<rule set id>.
holdThe conversion is stored extended (reason hold) for hold_minutes (1 to 129600, 90 days) and then becomes hold_then (approved or rejected). Only conversions that would otherwise be approved or pending are held.
allowStop evaluating conversion rules.
markRecord the rule set on the conversion (fraud context) and continue.

Variables are the catalogue's conversion-side ones (sale_amount, payout, revenue, goal, ctit_seconds, conv_ip, txn_id, coupon_code, email, user_id, status) plus the click's geo, device, flags and sub IDs; GET /rules/conversion-catalog lists them. Templates on the page: hold every conversion for 7 days, hold sales over 500 for 3 days, reject installs under 10 seconds after the click, reject conversions from proxies.

Held conversions (Money ▸ Held conversions, GET /conversion-holds) lists every hold with a countdown. The workers release due holds every 10 seconds (DT_HOLDS_EVERY) through the conversion status API with expected_status: extended, so a hold never overrides a decision: approving, rejecting or reversing a held conversion yourself cancels its hold, also when your decision and the timer cross. Release now (POST /conversion-holds/{id}/release, needs the approve-conversions permission) applies the rule's decision at once. During an ingest outage holds stay due and are released when it is back (after 20 failed attempts a hold is marked failed and waits for a manual release). The conversion inspector in the logs shows the hold and its countdown. Bookkeeping rows the timer keeps for decisions on conversions that no rule held are deleted after a day (DT_HOLDS_PLACEHOLDER_TTL).

Caps

A cap limits an entity over a period.

FieldValues
entity_type, entity_idoffer, offer_group, advertiser, partner, smartlink, goal, landing_page, tenant.
metricclicks, unique_clicks, gross_clicks, impressions, conversions, approved_conversions, pending_conversions, installs, events, payout, pending_payout, revenue, pending_revenue, gross_revenue, net_revenue, sale_amount, budget.
per_minute, hourly, daily, weekly, monthly, lifetimeThe limits (decimal strings). Buckets follow the cap's timezone (default: the tenant's caps time zone); weeks run Monday to Sunday.
partner_ids, partner_cap_modeScope to partners; each gives every partner its own counter, group shares one.
goal_id, landing_page_ids, geoNarrow what is counted.
is_soft_capA soft cap lets in-flight conversions through and marks them pending with reason cap_reached; a hard cap rejects.
on_reachedredirect_blank, fallback_link, fail_traffic, pause_entity, block, notify.
alert_thresholds, notify_channelsFor example [50, 80, 100] percent.
visibilitypublic lets partners see their remaining cap.

Behaviour to know:

  • Click-metric caps are enforced at click time and a hard cap reached marks the click invalid with reason caps. If the cap counter cannot be read at that moment (the cache did not answer within its budget), a hard click cap never lets the click through: the visitor gets the cap's fallback and the click is recorded invalid with reason caps_degraded (it still appears in reports as an invalid click). Soft caps only notify, so they let it through. Conversion and money caps are enforced in ingest when the conversion is created.
  • Counters live in Redis; a nightly job (DT_CAP_RECONCILE_HOUR, default 03:00 UTC) reconciles them with ClickHouse and records the differences (GET /caps/reconciliations). GET /caps/status and GET /caps/report show utilisation.
  • If Redis is unreachable, or has lost its counters, soft caps let traffic through (they only notify). Hard caps never over-admit: a click under a hard click cap — on the offer or on any other entity (partner, smartlink, landing page, advertiser, offer group) — gets the offer's fallback and is recorded invalid with reason caps_degraded; a conversion under a hard conversion or money cap is queued and counted once the counters are readable again.
  • Several windows are checked together. A conversion that falls under, say, a daily and a weekly cap is admitted only if every window has room, and then all of them count it in one step. If one hard window is full, none of them is counted.
  • Rejected conversions give their place back. When a conversion ends rejected or cancelled (cap rejection, scrub, a status change or a reversal) every window it took is released; if it is later approved again it is counted again. Payout and revenue caps move by the change in amount.
  • Money caps count the final price. A payout cap counts the amount the partner is actually paid, after overrides, tiered commissions, partner-tier commissions, scrub and customer-value bonuses; a revenue cap counts the revenue actually booked. Conversion-count caps are checked first, money caps after pricing, and a conversion either fits every window or takes none.
  • Each conversion counts once. A conversion's admission can run more than once — a slow reply that is retried, a conversion queued during a Redis hiccup and replayed, a retry after an ingest crash — but every window remembers which conversions it holds, so it is counted once and a full cap is never filled by repeats.
  • Arrival order decides. For offers with hard conversion or money caps, tiered payouts or customer-value ladders, conversions are decided in the order they arrived, also when one of them had to be queued — but a later conversion waits only when its decision can depend on the queued one. A conversion-count cap that has room for every conversion queued ahead (or none even without them) decides at once; only a window the queued conversions could fill makes the later one wait behind them (answered queued, reason ordered_behind_deferral). The same holds for conversion-count tiers (it waits only when the queued ones could move the counter across a tier boundary). Payout / revenue caps, revenue tiers and customer-value ladders wait when a queued conversion could move the same window (same partner, period and — for ladders — user), because its amount is not known yet. Everything else is decided at once.
  • The first copy wins. When an advertiser's retry arrives while the first copy of the same transaction is still queued, the retry waits for it: the first copy creates the conversion and the retry answers duplicate with its id.
  • Nobody waits more than a minute. If a conversion would wait longer than DT_INGEST_ORDER_HOLD_MS (60 s) behind queued ones (a long Redis outage), it is decided with the counters as they are; such conversions carry order_hold_expired (event annotation and the postback log reason).
  • Approved-conversion caps count approved conversions only. A conversion that arrives pending (must-approve, an advertiser status, a hold, a fraud flag) takes no place in an approved_conversions cap; it takes one when it is approved, and gives it back if it is later rejected, cancelled or moved out of approved.
  • Today unique_clicks caps are not counted at click time (duplicate detection runs after cap evaluation).

Day-parting

A schedule opens an offer, or a partner on an offer, only inside weekday and time windows, evaluated in a time zone with daylight-saving handled correctly.

json
{
  "name": "Business hours EU",
  "offer_id": "01J...",
  "timezone": "Europe/Berlin",
  "windows": [{ "days": [1, 2, 3, 4, 5], "start": "08:00", "end": "20:00" }],
  "outside_action": "fail_traffic",
  "fail_traffic_target": { "type": "offer_url", "routing_value": "https://example.com/closed" }
}

Rules on hour_of_day, day_of_week and date_range, payout-override hours and weekdays, and entry validity dates use the same clock: the offer schedule's time zone if the offer has one, otherwise the tenant's reporting time zone.

days are 0 (Sunday) to 6. A window whose end is earlier than its start wraps past midnight. A partner-specific schedule replaces the offer-level one for that partner. A schedule with no windows is always closed. outside_action is block or fail_traffic.

Scheduled actions

POST /scheduled-actions queues a change for later: set_status on an offer, partner, advertiser, SmartLink, cap, payout entry or partner-offer setting; set_cap; set_payout; set_access. They run once at run_at (polled every 15 seconds by default, DT_SCHEDULED_ACTIONS_EVERY), record a result and can be cancelled with POST /scheduled-actions/{id}/cancel. Use them for "pause this offer on Sunday night" or "raise the payout for the weekend". For repeating or metric-driven changes see automation rules in the automation reference.

Automation rules on a metric threshold use the report metrics (for example cr, epc, avg_fraud_score) plus invalid_click_rate, postback_failure_rate, postback_attempts and cap_utilization_pct. Set compare_to: previous_period to compare with the period before (epc_change_pct is accepted as a shorthand for epc compared with the previous period). A rule with a metric outside that list is refused when saved; an older rule that the reports refuse is paused and the reason is shown in its run history.

Per-partner overrides

Everything about a partner on an offer (access, payout overrides, caps, custom scrub, visibility) lives in the partner-offer setting, set with PUT /partners/{id}/offers/{offerId}.