clx

Sign in

clx for AI agents

You are setting up visit counting or short links for a person whose domains are on Cloudflare. clx installs a small worker, clx-edge, and a D1 database into their own Cloudflare account; visits and clicks are counted there, and clx.cx keeps only totals. This page is the whole walkthrough. Every endpoint is in the API reference.

What to ask your human for

  1. An API key clx_…. The person signs up at clx.cx/app and confirms their address. The free plan has one key, enough for one Cloudflare account, 10 sites and 200 links; the api plan (more accounts, sites and keys) is switched on by clx on request. They issue the key on the Keys page with the scopes you need: accounts, sites, links, reports for the whole setup; sites + reports for a build pipeline that only adds sites. The key is shown once.
  2. The Cloudflare account ID — 32 hex characters, in the dashboard address after dash.cloudflare.com/.
  3. A bootstrap token, made by the person in that account. clx uses it within one request to make its own working token, then deletes it. Steps for them:
    1. Open Manage account → Account API Tokens (Super Administrator role) → Create Token → Start from scratch (custom token).
    2. Any name, e.g. clx bootstrap.
    3. Permissions, scope Entire Account — search for each name alone: Account API Tokens — Edit; Account Settings — Read.
    4. Expiration: tomorrow. Create the token and hand it to you.

Do not store the bootstrap token or print it in logs; do not ask for a Global API Key — clx does not take one.

1. Connect the account

export CLX=https://clx.cx
export CLX_KEY=clx_...   # from the person

curl -s $CLX/v1/me -H "Authorization: Bearer $CLX_KEY"   # via "key"; plan "free" or "api" sets the limits

curl -s -X POST $CLX/v1/accounts \
  -H "Authorization: Bearer $CLX_KEY" \
  -H "Idempotency-Key: connect-<cf_account_id>-1" \
  -H "Content-Type: application/json" \
  -d '{"cf_account_id": "<32 hex>", "bootstrap_token": "<token>"}'

The answer is 202 with account; keep account.id. clx then installs clx-edge: poll GET /v1/accounts/{id} every 5–10 seconds while state is installing; it settles as ready (usually under a minute) or as a problem to show the person. At ready, go on: the worker serves already. It also confirms itself on its first cron, which Cloudflare may run late — until then account.operation shows the install at step selfcheck (up to 15 minutes), and a confirmation that never comes leaves the warning not_confirmed in error.warnings. Neither needs waiting for or acting on.

2. Add a site and embed the counter

The host must be in a zone of that account and proxied through Cloudflare (orange cloud). GET /v1/accounts/{id}/zones lists the zones to pick from.

curl -s -X POST $CLX/v1/sites \
  -H "Authorization: Bearer $CLX_KEY" \
  -H "Idempotency-Key: site-example.com-1" \
  -H "Content-Type: application/json" \
  -d '{"account_id": "<account.id>", "host": "example.com"}'

The answer carries site.snippet: inline (a <script> under 600 bytes) or script_tag (an external <script src="…"> with defer — use it if the site's CSP forbids inline scripts). Put one of them into every page, e.g. before </body>. The snippet is stable — a rebuild needs no call; only POST /v1/sites/{id}/rotate changes it. The counter answers once site.config is synced and site.state is active (seconds). route_conflict means another worker already holds the host's route: show error to the person.

Links live on a link host — a subdomain given wholly to clx-edge, e.g. go.example.com, with no site on it. It needs a proxied DNS record that clx does not make: the person (or you, if you have their Cloudflare access) adds AAAA go.example.com 100::, proxied. Setting the host takes the scope sites (it routes a host, as a site does); the links themselves, links. Then:

curl -s -X PUT $CLX/v1/accounts/<account.id>/link-host \
  -H "Authorization: Bearer $CLX_KEY" -H "Idempotency-Key: host-1" \
  -H "Content-Type: application/json" -d '{"host": "go.example.com"}'

curl -s -X POST $CLX/v1/links \
  -H "Authorization: Bearer $CLX_KEY" -H "Idempotency-Key: link-spring-1" \
  -H "Content-Type: application/json" \
  -d '{"account_id": "<account.id>", "url": "https://example.com/spring", "code": "spring",
       "rules": [{"countries": ["DE", "AT"], "url": "https://example.com/de/spring"},
                 {"devices": ["mobile"], "url": "https://example.com/m/spring"}]}'

link.short_url is the address to share. Rules are tried in order; the first match wins, else url. The QR code is GET /v1/links/{id}/qr.svg?size=512 with the same Authorization header — scans count with source qr.

To check a link, open it without following the redirect: curl -s -o /dev/null -w "%{http_code} %{redirect_url} " https://go.example.com/spring → 302 and the target. Such a GET is counted as a bot (curl and scripts are), not as a view — a person's click in a browser is a view; a HEAD (curl -I) is redirected but not counted. A rule by country can only be seen from that country; the rules in the link's answer are what the worker applies.

4. Reports

curl -s "$CLX/v1/sites/<site.id>/report?period=7d&breakdowns=1" -H "Authorization: Bearer $CLX_KEY"

period is today, 7d or 30d; breakdowns=1 adds the top pages, sources, countries, devices, browsers, OS and bots, read from the account's own database. Totals reach clx.cx hourly, so a new site shows nothing for the first hour — sometimes longer, when Cloudflare is slow to start the new worker's cron. as_of says how fresh today is (null before the worker's first push). Links have the same report at /v1/links/{id}/report; their today is read live from the account's database, so a click shows within minutes.

When unavailable is set, the breakdowns are missing, and a link's report also lacks the days the worker has not sent yet — its totals then cover clx.cx's closed days only, so do not report them as complete: rate_limited — more than 30 reads of the account databases a minute for this user, ask again in a minute; cloudflare — retry later; not_installed — the worker or its token is gone, check the account's state.

For a site generator

Three calls: POST /v1/accounts once per customer's Cloudflare account, POST /v1/sites per site, then embed snippet.inline into every page at build time. A key with sites + reports, limited to the customer's account, is enough after the connect.

Ground rules

Cheat sheet

base_url:      https://clx.cx
auth:          Authorization: Bearer clx_...   (1 key on free, 5 on api; scopes accounts, sites, links, reports)
idempotency:   Idempotency-Key on every POST/PUT/PATCH/DELETE; replays for 24 h
connect:       POST /v1/accounts {cf_account_id, bootstrap_token} -> 202; poll GET /v1/accounts/{id}
               while pending|installing; ready, else show error
bootstrap:     Account API Tokens Edit + Account Settings Read, Entire Account, expires tomorrow
site:          POST /v1/sites {account_id, host} -> site.snippet.inline | script_tag
link host:     AAAA <host> 100:: proxied, then PUT /v1/accounts/{id}/link-host {host}
link:          POST /v1/links {account_id, url, code?, rules?} -> link.short_url
qr:            GET /v1/links/{id}/qr.svg?size=512
report:        GET /v1/{sites|links}/{id}/report?period=today|7d|30d&breakdowns=1
limits:        120 req/min per key (429 + Retry-After); reads of the account's database for reports
               30/min per user, cached 5 min (over: 200 with unavailable "rate_limited")
errors:        {"error": {"code", "message", "details"}}
reference:     https://clx.cx/api  (https://clx.cx/api.md, https://clx.cx/openapi.json)