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
- 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; theapiplan (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,reportsfor the whole setup;sites+reportsfor a build pipeline that only adds sites. The key is shown once. - The Cloudflare account ID — 32 hex characters, in the dashboard address after
dash.cloudflare.com/. - 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:
- Open Manage account → Account API Tokens (Super Administrator role) → Create Token → Start from scratch (custom token).
- Any name, e.g.
clx bootstrap. - Permissions, scope Entire Account — search for each name alone:
Account API Tokens— Edit;Account Settings— Read. - 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.
connectedwith anerror— the install failed; show the error to the person, thenPOST /v1/accounts/{id}/install.permission_error,revoked,resource_drift,bootstrap_lost— stop polling and showerrorto the person: a right is missing, the working token was revoked,clx-edgewas changed outside clx, or the connect died before clx stored its token (revoke the token named there and connect again with a new bootstrap token).- A
5xxorcloudflare_unavailableanswer to the connect leaves the accountpending: send the same request again with the same Idempotency-Key (the bootstrap token must still be valid) and clx resumes where it stopped. A connect left alone for an hour turnsbootstrap_lost. Three502codes end the connect instead —token_rights_mismatch,token_check_failed,permission_catalog: nothing is left to resume; show the error to the person and connect again later with a new Idempotency-Key. invalid_bootstrap,bootstrap_permission_error— the token is wrong or lacks a right: ask for a new one with exactly the rights above.name_taken— a worker or database namedclx-edgethat clx did not make is in the account; clx will not touch it. The person decides.limit_reached,account_taken,already_connected— report to the person; do not retry.
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.
3. Short links and QR codes
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
- Send an
Idempotency-Keywith every POST, PUT, PATCH and DELETE: a new key per intended action, the same key when you retry it. A retry then never does the thing twice. 429: waitRetry-Afterseconds.409 operation_in_progress: another operation on the account is running — wait a few seconds and retry.5xxandcloudflare_unavailable: retry with backoff, with the same Idempotency-Key.- Branch on
error.code; showerror.messageto the person when you stop. Do not loop on a4xx. - clx changes nothing in the person's zones but the routes it makes for
clx-edge. Zone settings are theirs: Cloudflare Web Analytics can stay on (clx answers carry no HTML, so it does not interfere; turn it off in the dashboard if one counter is enough), and Browser Integrity Check may refuse scripted clicks on short links. - Disconnecting (
DELETE /v1/accounts/{id}) removesclx-edgeand its database; the answer names the working token for the person to revoke in Cloudflare, and anything clx could not delete inleft.
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)