Api Key

Overview







  1. Ensure a domain exists: POST /v1/domainsPATCH /v1/domains/:id/verify (until verified: true).
  2. Create campaign with the minimal example — keep "deny_always": false (or omit it).
  3. Confirm with GET /v1/campaigns/:id or list (in_review must be false for production).



Anti-patterns (do not)



  • Do not set filters.deny_always: true unless the user explicitly wants review/test mode.
  • Do not copy the full filters object 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.params values as a plain string — always string[] (e.g. "utm_source": ["google"]).
  • Do not put http(s)://, www, or / in domain name.
  • Do not treat PUT update as partial PATCH — send a full CampaignBody.
  • Do not use https://api.example.com — use https://api.cloakup.me (or CLOAKUP_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 true

filters.deny_always

in_review

filters.deny_always (CampaignFull has no top-level in_review)

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

params

Record<string, string[]>

Param name → list of allowed values in the URL

rules

Record<string, string>

Per-param match rule: equals, contains, starts_with, ends_with

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 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:



  1. Valid API key (Authorization: Bearer ck_...)
  2. Rate limit: 120 requests/minute per key
  3. Plan must have API keys enabled (plan.api_keys_enabled)
  4. Active cloakup subscription
  5. Route-specific scope (see each endpoint)



Scopes



Scope

Description

campaigns:list

List campaigns

campaigns:create

Create campaign

campaigns:show

Get campaign by ID

campaigns:update

Update campaign

campaigns:delete

Delete campaign

campaigns:clone

Clone campaign

campaigns:toggle

Toggle campaign active status

domains:list

List domains

domains:create

Register domain

domains:verify

Verify domain DNS/certificate

domains:delete

Delete domain



Error response



All application errors return JSON:



{
  "message": "string",
  "action": "string",
  "code": "string",
  "data": {}
}



HTTP

Meaning

401

Missing/invalid/expired/revoked API key

403

Missing scope, API keys disabled on plan, or inactive subscription

404

Resource not found (or not owned by user)

422

Validation error (data contains field errors)

429

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 422 if 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

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



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

Wikipedia — 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; region also lowercase)

Full list / reference

IETF language tag · ISO 639-1 codes

Examples

"pt-br", "en", "es"

Invalid

"pt-BR" (case), "portuguese", "pt_BR" (underscore)



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



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. Nested allow / 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

active

true

filters.facebook, google, tiktok, kwai, taboola, pinterest, yandex, mgid, outbrain, sms, revcontent, adskeeper, newsbreak

false

filters.adspy, bots, proxy, cloakup_ai

true

filters.deny_always

false

filters.state

{ "allow": true, "states": [] }

filters.city

{ "allow": true, "cities": [] }

filters.network_type

{ "allow": true, "types": [] }

filters.device

{ "allow": true, "devices": [] }

filters.geolocation.allow

true

filters.referer.allow

true

filters.query.allow

true

filters.query.remove_params

false

filters.domain.allow, browser.allow, browser_language.allow, isp.allow, os.allow, user_agent.allow

true



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 share

Within each segmentation group, share values must sum to exactly 100. Group key = sorted segmentation.country join, or "fallback" when country list empty/missing.

Fallback offer

At least one offer must have no country segmentation (empty/missing segmentation.country) — that is the fallback.

mode: "basic"

Exactly one ad-network flag true, and filters.device.devices must have ≥ 1 entry.

mode: "advanced"

Zero or one ad network; empty device.devices allowed.



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 filters blob unless you need every override.


Business rules: see Pages / offers business rules (share sums, fallback offer, basic mode).



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

cloakup_list_campaigns

GET

/v1/campaigns

campaigns:list

cloakup_create_campaign

POST

/v1/campaigns

campaigns:create

cloakup_get_campaign

GET

/v1/campaigns/:id

campaigns:show

cloakup_update_campaign

PUT

/v1/campaigns/:id

campaigns:update

cloakup_delete_campaign

DELETE

/v1/campaigns/:id

campaigns:delete

cloakup_clone_campaign

POST

/v1/campaigns/:id/clone

campaigns:clone

cloakup_toggle_campaign

PUT

/v1/campaigns/:id/toggle-status

campaigns:toggle

cloakup_list_domains

GET

/v1/domains

domains:list

cloakup_create_domain

POST

/v1/domains

domains:create

cloakup_verify_domain

PATCH

/v1/domains/:id/verify

domains:verify

cloakup_delete_domain

DELETE

/v1/domains/:id

domains:delete



Environment variables (for clients)



Variable

Description

CLOAKUP_API_URL

Base URL without trailing slash (e.g. https://api.cloakup.me)

CLOAKUP_API_KEY

Full key ck_...



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

Was this article helpful?

Share your feedback

Cancel

Thank you!