Campaign filters
Allowed values, query params, devices, OS, browser, ISP, IPs, and filter enums
Create/update returns 422 if a value does not match the format or is not in the API allowlist. Do not guess country names or free-form locale strings.
Allowed values
| Field | Type | Max items | Format |
|---|---|---|---|
filters.geolocation.countries | string[] | 256 | ISO 3166-1 alpha-2, uppercase |
filters.browser_language.languages | string[] | 255 | IETF language tag, all lowercase (xx or xx-yy) |
filters.state.states | string[] | 100 | Place name (see below) |
filters.city.cities | string[] | 100 | Place name (see below) |
pages.offers[].segmentation.country | string[] | 256 | Same as filters.geolocation.countries |
filters.device.devices | string[] | — | desktop, smartphone, tablet, unknown |
filters.os.os | string[] | — | Lowercase detector name (see OS) |
filters.browser.browsers | string[] | — | Lowercase detector name (see Browser) |
filters.isp.isps | string[] | — | Substring of the GeoIP ISP name |
filters.blacklist / filters.whitelist | string[] | — | Exact IP string |
Country codes
Used in filters.geolocation.countries and pages.offers[].segmentation.country.
| Rule | Value |
|---|---|
| Format | Exactly 2 uppercase letters (ISO 3166-1 alpha-2) |
| Examples | "BR", "US", "PT" |
| Invalid | "br", "Brazil", "BRA", "brasil" |
Browser language codes
Used in filters.browser_language.languages.
| Rule | Value |
|---|---|
| Format | Lowercase IETF language tag: xx or xx-yy (hyphen, not underscore) |
| Examples | "pt-br", "en", "es" |
| Invalid | "pt-BR", "portuguese", "pt_BR" |
State and city names
Used in filters.state.states and filters.city.cities.
| Rule | Value |
|---|---|
| Length | 1–100 characters (trimmed) |
| Allowed chars | Unicode letters, spaces, ', -, . |
| Must start with | A letter |
| Examples | "São Paulo", "California", "St. Louis" |
| Invalid | "123", "", "City@Name" |
Free-text GeoIP place names — not ISO codes.
URL query params (filters.query)
Cloakup matches the query string after ? in the visitor URL against filters.query.
You always send two objects with the same keys:
| Object | What it stores |
|---|---|
params | Param name → values to match (always a string[], never a plain string) |
rules | Param name → how to compare (equals, contains, starts_with, ends_with) |
"asiudhasudhasd": "equals" does not set the value. It only says: compare the URL param named asiudhasudhasd with exact match. The value lives in params as an array: "asiudhasudhasd": ["123u1basj"].
Visitor URL:
https://track.example.com/slug?asiudhasudhasd=123u1basjMatching config:
"query": {
"allow": true,
"remove_params": false,
"condition": "some",
"params": {
"asiudhasudhasd": ["123u1basj"]
},
"rules": {
"asiudhasudhasd": "equals"
}
}Every key in params needs the same key in rules. Empty params: {} and rules: {} means no URL param filtering.
| Field | Type | Description |
|---|---|---|
params | Record<string, string[]> | Param name → list of values in the URL |
rules | Record<string, string> | Per-param comparison. Not the value itself |
condition | "some" | "every" | some = at least one param must match; every = all must match |
allow | boolean | true = allow when params match; false = block when params match |
remove_params | boolean | Strip matched params from the URL before redirect |
Match rules
The value in rules is the operator. The value in params is what you compare against.
| Rule | Meaning | params value | Matches | Does not match |
|---|---|---|---|---|
equals | Exact value | ["google"] | ?utm_source=google | ?utm_source=Google, ?utm_source=googleads |
contains | Substring anywhere | ["google"] | ?utm_source=googleads | ?utm_source=facebook |
starts_with | Prefix | ["Cjw"] | ?gclid=CjwKEAjabc | ?gclid=xxCjw |
ends_with | Suffix | ["-br"] | ?utm_campaign=promo-br | ?utm_campaign=br-promo |
Wildcard (*)
"*" is a special value in params (not a rule, not a glob). If that key is present in the URL, the checker accepts any value and skips equals / contains / starts_with / ends_with.
| URL | Config | Result |
|---|---|---|
?fbclid=IwAR123abc | "fbclid": ["*"] | Match — key exists |
?fbclid= | "fbclid": ["*"] | Match — key exists, even empty |
?utm_source=google | "fbclid": ["*"] | No match — fbclid missing |
?utm_source=googleads | "utm_source": ["goo*"] | No match — "goo*" is a literal string, not a pattern |
You still send a rules entry for that key (use "equals"). The operator is ignored when "*" is in the values list.
"params": { "fbclid": ["*"] },
"rules": { "fbclid": "equals" }Do not mix "*" with other values on the same key (["*", "google"]). "*" already matches any value, so the extra entries never run.
Examples
URL: ?asiudhasudhasd=123u1basj
The visitor must send this exact token. Common for a secret query key you append to the ad URL.
"query": {
"allow": true,
"remove_params": false,
"condition": "some",
"params": {
"asiudhasudhasd": ["123u1basj"]
},
"rules": {
"asiudhasudhasd": "equals"
}
}URL: ?utm_source=google
"query": {
"allow": true,
"remove_params": false,
"condition": "some",
"params": {
"utm_source": ["google"]
},
"rules": {
"utm_source": "equals"
}
}URL: ?utm_source=google&asiudhasudhasd=123u1basj&utm_campaign=black-friday
condition: "some" — at least one of these params must match.
"query": {
"allow": true,
"remove_params": false,
"condition": "some",
"params": {
"utm_source": ["google"],
"asiudhasudhasd": ["123u1basj"],
"utm_campaign": ["black-friday"]
},
"rules": {
"utm_source": "equals",
"asiudhasudhasd": "equals",
"utm_campaign": "equals"
}
}Same keys as above, but the visitor must match all of them.
URL that matches: ?utm_source=google&asiudhasudhasd=123u1basj
URL that does not match: ?utm_source=google (missing token)
"query": {
"allow": true,
"remove_params": false,
"condition": "every",
"params": {
"utm_source": ["google"],
"asiudhasudhasd": ["123u1basj"]
},
"rules": {
"utm_source": "equals",
"asiudhasudhasd": "equals"
}
}URL: ?utm_source=google or ?utm_source=facebook
Values are always an array. Do not send "utm_source": "google".
"query": {
"allow": true,
"remove_params": false,
"condition": "some",
"params": {
"utm_source": ["google", "facebook"]
},
"rules": {
"utm_source": "equals"
}
}URL: ?utm_content=summer-promo-br matches because the value contains promo.
"query": {
"allow": true,
"remove_params": false,
"condition": "some",
"params": {
"utm_content": ["promo"]
},
"rules": {
"utm_content": "contains"
}
}URL: ?gclid=CjwKEAjabc123 matches because the value starts with Cjw.
"query": {
"allow": true,
"remove_params": false,
"condition": "some",
"params": {
"gclid": ["Cjw"]
},
"rules": {
"gclid": "starts_with"
}
}URL: ?utm_campaign=sales-br matches because the value ends with -br.
"query": {
"allow": true,
"remove_params": false,
"condition": "some",
"params": {
"utm_campaign": ["-br"]
},
"rules": {
"utm_campaign": "ends_with"
}
}URL that matches: ?fbclid=IwAR123abc (any value)
URL that matches: ?fbclid= (empty value still has the key)
URL that does not match: ?utm_source=google (fbclid missing)
"query": {
"allow": true,
"remove_params": false,
"condition": "some",
"params": {
"fbclid": ["*"]
},
"rules": {
"fbclid": "equals"
}
}URL that matches: ?asiudhasudhasd=anything
URL that does not match: no asiudhasudhasd in the query
Use this when you only care that the secret param name is present, not the value.
"query": {
"allow": true,
"remove_params": false,
"condition": "some",
"params": {
"asiudhasudhasd": ["*"]
},
"rules": {
"asiudhasudhasd": "equals"
}
}Visitor must have fbclid (any value) and utm_source=google.
URL that matches: ?fbclid=IwAR123&utm_source=google
URL that does not match: ?utm_source=google (missing fbclid)
URL that does not match: ?fbclid=IwAR123&utm_source=facebook
"query": {
"allow": true,
"remove_params": false,
"condition": "every",
"params": {
"fbclid": ["*"],
"utm_source": ["google"]
},
"rules": {
"fbclid": "equals",
"utm_source": "equals"
}
}Match utm_source=google, then remove it from the URL before redirect so the destination does not see it.
"query": {
"allow": true,
"remove_params": true,
"condition": "some",
"params": {
"utm_source": ["google"]
},
"rules": {
"utm_source": "equals"
}
}Send "utm_source": ["google"], never "utm_source": "google". A plain string returns 422.
Referer (filters.referer)
filters.referer.domains is a list of hostnames. Send the hostname only (no https://, no path). www. is stripped at match time.
* in a referer domain matches that subdomain slot. Use *.facebook.com to allow every subdomain of facebook.com.
| Config | Matches | Does not match |
|---|---|---|
"facebook.com" | https://facebook.com/foo, https://www.facebook.com (www. is stripped) | https://m.facebook.com |
"*.facebook.com" | https://m.facebook.com, https://l.facebook.com | https://facebook.com (no subdomain) |
*.facebook.com does not match the apex host facebook.com. Add both entries if you need apex + subdomains.
block_null: true blocks visitors with no referer (direct access).
"referer": {
"allow": true,
"block_null": true,
"domains": ["facebook.com", "*.facebook.com", "instagram.com"]
}Allow traffic that came from facebook.com only (not m.facebook.com).
"referer": {
"allow": true,
"block_null": true,
"domains": ["facebook.com"]
}Allow m.facebook.com, l.facebook.com, lm.facebook.com, and any other subdomain.
Does not match facebook.com itself. Add both if you need apex + subdomains.
"referer": {
"allow": true,
"block_null": true,
"domains": ["facebook.com", "*.facebook.com"]
} "referer": {
"allow": true,
"block_null": true,
"domains": [
"facebook.com",
"*.facebook.com",
"instagram.com",
"*.instagram.com",
"google.com"
]
}Device (filters.device)
Empty devices: [] means no device filtering.
allow: true = only listed devices pass. allow: false = listed devices are blocked.
| Value | What it matches |
|---|---|
smartphone | Phones. Phablet is treated as smartphone |
tablet | Tablets |
desktop | Desktops and laptops |
unknown | Detector could not classify the device |
"device": {
"allow": true,
"devices": ["smartphone", "tablet"]
}Desktop visitors are blocked.
"device": {
"allow": true,
"devices": ["smartphone"]
} "device": {
"allow": false,
"devices": ["desktop"]
} "device": {
"allow": true,
"devices": ["smartphone", "tablet", "desktop"]
}OS (filters.os)
Empty os: [] means no OS filtering. Names are lowercase as the device detector reports them.
| Value | Matches |
|---|---|
android | Android |
ios | iOS |
windows | Windows |
mac | macOS |
gnu/linux | Generic Linux desktop. Send this, not "linux" |
ubuntu | Ubuntu |
unknown | Detector could not classify the OS |
other | Any OS not in windows, mac, gnu/linux, android, ios, ubuntu |
"os": {
"allow": true,
"os": ["android", "ios"]
} "os": {
"allow": true,
"os": ["android", "ios"]
} "os": {
"allow": true,
"os": ["windows", "mac", "gnu/linux", "ubuntu"]
}Include "other" to also match uncommon OS names (Chrome OS, Tizen, and so on).
"os": {
"allow": true,
"os": ["android", "ios", "windows", "mac", "other"]
}Browser (filters.browser)
Empty browsers: [] means no browser filtering. Match is case-insensitive exact on the detector name.
Common values: chrome, chrome mobile, chrome mobile ios, chrome webview, safari, mobile safari, firefox, microsoft edge, opera, samsung browser, miui browser, android browser, internet explorer, facebook, instagram app, whatsapp, baidu spark.
"browser": {
"allow": true,
"browsers": ["chrome", "chrome mobile", "safari", "mobile safari"]
} "browser": {
"allow": true,
"browsers": [
"chrome",
"chrome mobile",
"chrome mobile ios",
"safari",
"mobile safari"
]
} "browser": {
"allow": false,
"browsers": ["facebook", "instagram app", "whatsapp"]
} "browser": {
"allow": true,
"browsers": ["firefox"]
}ISP (filters.isp)
Empty isps: [] means no ISP filtering. Match is a case-insensitive substring of the GeoIP ISP string — "claro" matches Claro S.A..
Use names as they appear in GeoIP (carriers, not brand slogans).
"isp": {
"allow": true,
"isps": ["Claro", "Vivo", "Oi", "TIM"]
} "isp": {
"allow": true,
"isps": ["Claro", "Vivo", "Oi", "TIM"]
} "isp": {
"allow": false,
"isps": ["Amazon", "Google", "DigitalOcean", "OVH", "Hetzner"]
} "isp": {
"allow": true,
"isps": ["Comcast"]
}IP lists (filters.blacklist / filters.whitelist)
Both are arrays of exact IP strings (no CIDR). Empty [] means no IP list.
| Field | Effect |
|---|---|
blacklist | That IP is blocked |
whitelist | That IP always passes, including when other filters would deny. GET /v1/campaigns/:id returns is_test_mode: true if your IP is in this list |
"blacklist": ["203.0.113.10", "198.51.100.22"],
"whitelist": ["192.0.2.80"] "blacklist": ["203.0.113.10", "198.51.100.22"]Add your IP to whitelist so you always see the offer while testing.
"whitelist": ["192.0.2.80"]192.0.2.80 still passes because whitelist wins.
"blacklist": ["203.0.113.10"],
"whitelist": ["192.0.2.80"]Required filter keys
On create/update, these top-level keys under filters must be present:
geolocation, referer, query, domain, browser, browser_language, isp, os, user_agent, blacklist
Optional keys (defaults apply): ad-network flags, deny_always, bots, proxy, adspy, cloakup_ai, state, city, network_type, device, whitelist.
Full JSON Schema: Schemas → CampaignFilters.