Api Key
Overview
- Base URL:
https://api.cloakup.me(no trailing slash). Env:CLOAKUP_API_URL. - Public API base path:
/v1 - API key format:
ck_+ 64 hex chars (e.g.ck_a1b2c3...) - Auth header:
Authorization: Bearer ck_<secret> - Content-Type:
application/json - IDs: numeric (
bigintserialized as JSON number or string depending on serializer) - Campaign filter enums: rules in Campaign filter allowed values (3 examples each + format links)
- Production safety: read Critical: do not ship campaigns in review before creating campaigns
- Campaign create: you do not need every field — see Campaign create defaults
Recommended workflow
- Ensure a domain exists:
POST /v1/domains→PATCH /v1/domains/:id/verify(untilverified: true). - Create campaign with the minimal example — keep
"deny_always": false(or omit it). - Confirm with
GET /v1/campaigns/:idor list (in_reviewmust befalsefor production).
Anti-patterns (do not)
- Do not set
filters.deny_always: trueunless the user explicitly wants review/test mode. - Do not copy the full
filtersobject from a GET response into POST/PUT. - Do not invent country codes or browser languages — follow the format links below; wrong case/name →
422. - Do not send
filters.query.paramsvalues as a plain string — alwaysstring[](e.g."utm_source": ["google"]). - Do not put
http(s)://,www, or/in domainname. - Do not treat PUT update as partial PATCH — send a full
CampaignBody. - Do not use
https://api.example.com— usehttps://api.cloakup.me(orCLOAKUP_API_URL).
Critical: do not ship campaigns in review
For AI agents and scripts: wrong defaults here burn ad spend — every visitor sees only the safe page.
Field in request | Field in list response | Where in show/create response | Effect when |
|---|---|---|---|
|
|
| Always shows the safe (white) page. No offer traffic. Use only while testing. |
Default for production: omit deny_always or set "deny_always": false.
Never copy deny_always: true from examples unless the user explicitly asks for review/test mode.
URL query params (filters.query)
Cloakup matches URL query parameters against filters.query.params. Each key maps to an array of allowed values (not a plain string).
"query": {
"allow": true,
"remove_params": false,
"condition": "some",
"params": {
"utm_source": ["google"]
},
"rules": {
"utm_source": "equals"
}
}Field | Type | Description |
|---|---|---|
|
| Param name → list of allowed values in the URL |
|
| Per-param match rule: |
|
|
|
|
|
|
|
| Strip matched params from URL before redirect |
Wildcard: use "*" as the value to accept any value for that param key:
"params": {
"utm_source": ["*"]
},
"rules": {
"utm_source": "equals"
}This only checks that utm_source exists in the URL — the actual value does not matter.
Empty params: {} means no URL param filtering.
Prerequisites (every /v1 request)
Middleware chain applied to all /v1 routes:
- Valid API key (
Authorization: Bearer ck_...) - Rate limit: 120 requests/minute per key
- Plan must have API keys enabled (
plan.api_keys_enabled) - Active cloakup subscription
- Route-specific scope (see each endpoint)
Scopes
Scope | Description |
|---|---|
| List campaigns |
| Create campaign |
| Get campaign by ID |
| Update campaign |
| Delete campaign |
| Clone campaign |
| Toggle campaign active status |
| List domains |
| Register domain |
| Verify domain DNS/certificate |
| Delete domain |
Error response
All application errors return JSON:
{
"message": "string",
"action": "string",
"code": "string",
"data": {}
}HTTP | Meaning |
|---|---|
| Missing/invalid/expired/revoked API key |
| Missing scope, API keys disabled on plan, or inactive subscription |
| Resource not found (or not owned by user) |
| Validation error ( |
| Rate limit exceeded |
Shared schemas
Error
{
"$id": "Error",
"type": "object",
"properties": {
"message": { "type": "string" },
"action": { "type": "string" },
"code": { "type": "string" },
"data": { "type": "object", "additionalProperties": true }
},
"required": ["message", "action"]
}Campaign filter allowed values
For AI agents: create/update returns
422if a value does not match the format (or is not accepted by the API allowlist). Do not guess country names or free-form locale strings.
Field | Type | Max items | Format |
|---|---|---|---|
|
| 256 | ISO 3166-1 alpha-2, uppercase |
|
| 255 | IETF language tag, all lowercase ( |
|
| 100 | Place name (see below) |
|
| 100 | Place name (see below) |
|
| 256 | Same as |
Country codes
Used in filters.geolocation.countries and pages.offers[].segmentation.country.
Rule | Value |
|---|---|
Format | Exactly 2 uppercase letters (ISO 3166-1 alpha-2) |
Full list | |
Examples |
|
Invalid |
|
Browser language codes
Used in filters.browser_language.languages.
Rule | Value |
|---|---|
Format | Lowercase IETF language tag: |
Full list / reference | |
Examples |
|
Invalid |
|
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 |
|
Invalid |
|
Free-text GeoIP place names (not ISO codes).
CampaignFilters
Used in create/update campaign body.
Agents: nested keys with defaults may be omitted or sent as
{}/ partial objects. The JSON Schema below lists top-level keys that must be present on create/update. Nestedallow/remove_params/ empty arrays often default server-side — see Campaign create defaults and the minimal POST example. Do not treat every nested property as required.
Must send (top-level under filters): geolocation, referer, query, domain, browser, browser_language, isp, os, user_agent, blacklist.
Optional (defaults apply): ad-network flags, deny_always, bots, proxy, adspy, cloakup_ai, state, city, network_type, device, whitelist.
{ "$id": "CampaignFilters", "type": "object", "required": [ "geolocation", "referer", "query", "domain", "browser", "browser_language", "isp", "os", "user_agent", "blacklist" ], "properties": { "facebook": { "type": "boolean", "default": false }, "google": { "type": "boolean", "default": false }, "tiktok": { "type": "boolean", "default": false }, "kwai": { "type": "boolean", "default": false }, "taboola": { "type": "boolean", "default": false }, "pinterest": { "type": "boolean", "default": false }, "yandex": { "type": "boolean", "default": false }, "mgid": { "type": "boolean", "default": false }, "outbrain": { "type": "boolean", "default": false }, "sms": { "type": "boolean", "default": false }, "revcontent": { "type": "boolean", "default": false }, "adskeeper": { "type": "boolean", "default": false }, "newsbreak": { "type": "boolean", "default": false }, "adspy": { "type": "boolean", "default": true }, "deny_always": { "type": "boolean", "default": false, "description": "DANGER: true = in_review mode, always shows safe page. Use false in production." }, "bots": { "type": "boolean", "default": true }, "proxy": { "type": "boolean", "default": true }, "cloakup_ai": { "type": "boolean", "default": true }, "geolocation": { "type": "object", "properties": { "allow": { "type": "boolean", "default": true }, "countries": { "type": "array", "maxItems": 256, "items": { "type": "string", "pattern": "^[A-Z]{2}$", "description": "ISO 3166-1 alpha-2 uppercase. See https://en.wikipedia.org/wiki/ISO_3166-1_alpha-2", "examples": ["BR", "US", "PT"] } } } }, "state": { "type": "object", "properties": { "allow": { "type": "boolean" }, "states": { "type": "array", "maxItems": 100, "items": { "type": "string", "minLength": 1, "maxLength": 100, "pattern": "^[\p{L}\p{M}][\p{L}\p{M}\s'.-]{0,99}$", "description": "Place name (not ISO code). Letters, spaces, apostrophe, hyphen, period.", "examples": ["São Paulo", "California", "St. Louis"] } } } }, "city": { "type": "object", "properties": { "allow": { "type": "boolean" }, "cities": { "type": "array", "maxItems": 100, "items": { "type": "string", "minLength": 1, "maxLength": 100, "pattern": "^[\p{L}\p{M}][\p{L}\p{M}\s'.-]{0,99}$", "description": "Place name (not ISO code). Letters, spaces, apostrophe, hyphen, period.", "examples": ["Campinas", "New York", "O'Fallon"] } } } }, "network_type": { "type": "object", "properties": { "allow": { "type": "boolean" }, "types": { "type": "array", "items": { "enum": ["residential", "business", "wireless", "hosting", "unknown"] } } } }, "device": { "type": "object", "properties": { "allow": { "type": "boolean" }, "devices": { "type": "array", "items": { "enum": ["desktop", "smartphone", "tablet", "unknown"] } } } }, "referer": { "type": "object", "required": ["block_null", "domains"], "properties": { "block_null": { "type": "boolean" }, "allow": { "type": "boolean", "default": true }, "domains": { "type": "array", "items": { "type": "string", "maxLength": 255 } } } }, "query": { "type": "object", "required": ["params", "condition", "rules"], "properties": { "allow": { "type": "boolean", "default": true }, "remove_params": { "type": "boolean", "default": false }, "params": { "type": "object", "description": "Record<string, string[]>. Param name → array of allowed values in URL.", "additionalProperties": { "type": "array", "items": { "type": "string" } } }, "condition": { "enum": ["some", "every"] }, "rules": { "type": "object", "additionalProperties": { "enum": ["equals", "contains", "starts_with", "ends_with"] } } } }, "domain": { "type": "object", "properties": { "allow": { "type": "boolean", "default": true }, "domains": { "type": "array", "items": { "type": "string", "maxLength": 255 } } } }, "browser": { "type": "object", "properties": { "allow": { "type": "boolean", "default": true }, "browsers": { "type": "array", "items": { "type": "string", "maxLength": 255 } } } }, "browser_language": { "type": "object", "properties": { "allow": { "type": "boolean", "default": true }, "languages": { "type": "array", "maxItems": 255, "items": { "type": "string", "pattern": "^[a-z]{2}(-[a-z]{2})?$", "description": "IETF language tag, all lowercase (xx or xx-yy). See https://en.wikipedia.org/wiki/IETF_language_tag", "examples": ["pt-br", "en", "es"] } } } }, "isp": { "type": "object", "properties": { "allow": { "type": "boolean", "default": true }, "isps": { "type": "array", "items": { "type": "string", "maxLength": 255 } } } }, "os": { "type": "object", "properties": { "allow": { "type": "boolean", "default": true }, "os": { "type": "array", "items": { "type": "string", "maxLength": 255 } } } }, "user_agent": { "type": "object", "properties": { "allow": { "type": "boolean", "default": true }, "user_agents": { "type": "array", "items": { "type": "string", "maxLength": 255 } } } }, "blacklist": { "type": "array", "items": { "type": "string", "description": "IP address" } }, "whitelist": { "type": "array", "items": { "type": "string", "description": "IP address" } } } }
Campaign create defaults
Omitted fields are filled server-side (campaign-schema validation). Do not copy the full filters object from examples — only send what you need.
Field | Default when omitted |
|---|---|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Still required (no default for the key itself): name, mode, pages, domain, alias, and in filters: geolocation, referer, query, domain, browser, browser_language, isp, os, user_agent, blacklist.
Inside required objects you may omit nested defaults (e.g. send "domain": {}, "referer": { "block_null": true, "domains": [] }).
To target a single ad network, send only that network as true (e.g. "facebook": true). All other networks default to false.
Pages / offers business rules (422 if violated)
Rule | Detail |
|---|---|
Offer | Within each segmentation group, |
Fallback offer | At least one offer must have no country segmentation (empty/missing |
| Exactly one ad-network flag |
| Zero or one ad network; empty |
CampaignBody
Create and update campaign request body. PUT uses the same full body — there is no partial update.
{
"$id": "CampaignBody",
"type": "object",
"required": ["name", "mode", "pages", "filters", "domain", "alias"],
"properties": {
"name": { "type": "string", "minLength": 3, "maxLength": 255 },
"active": { "type": "boolean", "default": true, "description": "Optional. Defaults to true." },
"mode": { "enum": ["advanced", "basic"] },
"domain": { "type": ["string", "null"], "maxLength": 128 },
"alias": { "type": ["string", "null"], "maxLength": 32 },
"pages": {
"type": "object",
"required": ["white", "offers"],
"properties": {
"white": {
"type": "object",
"required": ["type", "content"],
"properties": {
"type": { "enum": ["content", "redirect", "iframe"] },
"content": { "type": "string", "maxLength": 255 }
}
},
"offers": {
"type": "array",
"minItems": 1,
"items": {
"type": "object",
"required": ["type", "content", "share"],
"properties": {
"type": { "enum": ["content", "redirect", "iframe"] },
"content": { "type": "string", "maxLength": 255 },
"share": { "type": "number", "description": "Percent; group sums must equal 100" },
"segmentation": {
"type": "object",
"properties": {
"country": {
"type": "array",
"maxItems": 256,
"items": {
"type": "string",
"pattern": "^[A-Z]{2}$",
"description": "ISO 3166-1 alpha-2 uppercase. Same as filters.geolocation.countries."
}
}
}
}
}
}
}
}
},
"filters": { "$ref": "CampaignFilters" }
}
}CampaignFull
Single campaign response (show, create, update, clone). Review mode = filters.deny_always (list endpoint exposes the same flag as top-level in_review).
{
"$id": "CampaignFull",
"type": "object",
"properties": {
"id": { "type": "integer" },
"created_at": { "type": "string", "format": "date-time" },
"updated_at": { "type": "string", "format": "date-time" },
"name": { "type": "string" },
"active": { "type": "boolean" },
"slug": { "type": "string" },
"mode": { "enum": ["advanced", "basic"] },
"domain": { "type": ["string", "null"] },
"alias": { "type": ["string", "null"] },
"network": { "type": "string" },
"show_content_enabled": { "type": "boolean" },
"pages": { "type": "object" },
"filters": { "$ref": "CampaignFilters" },
"is_test_mode": { "type": "boolean", "description": "Only on GET show: true if client IP is in filters.whitelist" }
}
}CampaignShort
List item in campaign list response.
{
"$id": "CampaignShort",
"type": "object",
"properties": {
"id": { "type": "integer" },
"name": { "type": "string" },
"active": { "type": "boolean" },
"created_at": { "type": "string", "format": "date-time" },
"updated_at": { "type": "string", "format": "date-time" },
"mode": { "enum": ["advanced", "basic"] },
"network": { "type": "string" },
"in_review": { "type": "boolean", "description": "Read-only mirror of filters.deny_always. true = campaign blocks all offer traffic." }
}
}Domain
{
"$id": "Domain",
"type": "object",
"properties": {
"id": { "type": "integer" },
"name": { "type": "string" },
"verified": { "type": "boolean" },
"migrated": { "type": "boolean" },
"created_at": { "type": "string", "format": "date-time" }
}
}Campaigns (/v1/campaigns)
All endpoints require Authorization: Bearer ck_....
GET /v1/campaigns
List campaigns.
Scope: campaigns:list
Query:
{
"type": "object",
"properties": {
"page": { "type": "integer", "minimum": 1, "default": 1 },
"limit": { "enum": [1, 5, 10, 25, 50, 100], "default": 10 },
"active": { "type": "boolean" },
"name": { "type": "string", "maxLength": 255 },
"network": {
"enum": [
"facebook",
"google",
"tiktok",
"kwai",
"taboola",
"pinterest",
"yandex",
"mgid",
"outbrain",
"sms",
"revcontent",
"adskeeper",
"newsbreak"
]
}
}
}Response 200:
{
"type": "object",
"properties": {
"count": { "type": "integer" },
"data": {
"type": "array",
"items": { "$ref": "CampaignShort" }
}
}
}Example:
curl -H "Authorization: Bearer ck_YOUR_KEY" \
"https://api.cloakup.me/v1/campaigns?page=1&limit=10"POST /v1/campaigns
Create campaign.
Scope: campaigns:create
Request body: { "$ref": "CampaignBody" }
Response 201: { "$ref": "CampaignFull" }
Defaults: see Campaign create defaults. Omitted keys are filled server-side — do not send the full
filtersblob unless you need every override.
Business rules: see Pages / offers business rules (
sharesums, fallback offer,basicmode).
Minimal example (production-safe; only non-default values):
{
"name": "My Campaign",
"mode": "advanced",
"domain": null,
"alias": null,
"pages": {
"white": { "type": "content", "content": "safe.html" },
"offers": [{ "type": "content", "content": "offer.html", "share": 100 }]
},
"filters": {
"facebook": true,
"geolocation": { "countries": ["BR"] },
"referer": { "block_null": true, "domains": [] },
"query": { "condition": "some", "params": {}, "rules": {} },
"domain": {},
"browser": {},
"browser_language": { "languages": ["pt-br"] },
"isp": {},
"os": {},
"user_agent": {},
"blacklist": []
}
}Omitted here (defaults apply): active, all other ad networks, deny_always (= false), bots, proxy, adspy, cloakup_ai, state, city, network_type, device, and every allow / remove_params flag.
Test/review mode only — set "deny_always": true to force safe page for every visitor (in_review: true in list).
GET /v1/campaigns/:id
Get campaign by ID.
Scope: campaigns:show
Path params:
{
"type": "object",
"properties": { "id": { "type": "integer" } },
"required": ["id"]
}Response 200: { "$ref": "CampaignFull" } (includes is_test_mode based on whether the caller IP is in filters.whitelist).
PUT /v1/campaigns/:id
Update campaign. Full replace — same schema as create (CampaignBody). Send the complete body; omitted filter keys still follow create defaults, but this is not a JSON Merge Patch.
Scope: campaigns:update
Path params: same as show.
Request body: { "$ref": "CampaignBody" }
Response 200: { "$ref": "CampaignFull" }
DELETE /v1/campaigns/:id
Soft-delete campaign.
Scope: campaigns:delete
Path params: same as show.
Response 204: HTTP status is 204. The handler may still attach a JSON body { "message": "..." } (Express quirk); treat success as status 204 and ignore body if empty/unparsed.
POST /v1/campaigns/:id/clone
Clone campaign.
Scope: campaigns:clone
Path params: same as show.
Request body: none
Response 200: { "$ref": "CampaignFull" }
PUT /v1/campaigns/:id/toggle-status
Toggle campaign active flag.
Scope: campaigns:toggle
Path params: same as show.
Request body: none
Response 200: empty body (use case returns void; re-fetch with show if needed)
Domains (/v1/domains)
GET /v1/domains
List domains.
Scope: domains:list
Query:
{
"type": "object",
"properties": {
"page": { "type": "integer", "minimum": 1, "default": 1 },
"limit": { "enum": [1, 5, 10, 25, 50, 100], "default": 100 },
"verified": { "type": "boolean" },
"name": { "type": "string", "maxLength": 128 }
}
}Response 200:
{
"type": "object",
"properties": {
"count": { "type": "integer" },
"data": {
"type": "array",
"items": { "$ref": "Domain" }
}
}
}POST /v1/domains
Register domain (subdomain required, e.g. track.example.com). Rejected if name contains http, www, or /, or is not a valid subdomain hostname.
Scope: domains:create
Request body:
{ "type": "object", "required": ["name"], "properties": { "name": { "type": "string", "maxLength": 128, "description": "Subdomain hostname, e.g. track.example.com — no scheme, www, or path" } } }
Response 201:
{
"type": "object",
"properties": {
"id": { "type": "integer" },
"name": { "type": "string" },
"verified": { "type": "boolean" },
"migrated": { "type": "boolean" }
}
}PATCH /v1/domains/:id/verify
Verify domain DNS/CNAME propagation and activate certificate.
Scope: domains:verify
Path params:
{
"type": "object",
"properties": { "id": { "type": "integer" } },
"required": ["id"]
}Request body: none
Response 200:
{
"type": "object",
"properties": {
"name": { "type": "string" },
"verified": { "type": "boolean" }
}
}DELETE /v1/domains/:id
Delete domain.
Scope: domains:delete
Path params: same as verify.
Request body: none
Response 204: Same quirk as campaign delete — status 204, optional { "message": "..." } body; treat status as success.
Quick reference (AI tool mapping)
Tool name suggestion | Method | Path | Scope |
|---|---|---|---|
| GET |
|
|
| POST |
|
|
| GET |
|
|
| PUT |
|
|
| DELETE |
|
|
| POST |
|
|
| PUT |
|
|
| GET |
|
|
| POST |
|
|
| PATCH |
|
|
| DELETE |
|
|
Environment variables (for clients)
Variable | Description |
|---|---|
| Base URL without trailing slash (e.g. |
| Full key |
Example authenticated request:
GET /v1/campaigns?page=1&limit=10 HTTP/1.1 Host: api.cloakup.me Authorization: Bearer ck_0123456789abcdef... Accept: application/json
Updated on: 27/07/2026
Thank you!
