clx

Sign in

clx management API

Everything the clx.cx app does goes through these endpoints: connect a Cloudflare account, install the clx-edge worker into it, add sites and take their counter snippet, make short links with rules and QR codes, read reports. An agent setting clx up for a person starts at clx for AI agents.

The same contract, machine-readable: openapi.yaml · openapi.json (OpenAPI 3.1.0). This page as markdown: /api.md, or Accept: text/markdown.

Conventions

You and your API keys

GET /v1/me

The caller — user, plan, limits, current use

Sign-up, e-mail confirmation and password reset are not part of /v1: they live under /auth with the page session (docs/spec.md §9). Until email_confirmed, POST /v1/accounts answers email_unconfirmed.

Answers:

  • 200 OK — user: object, plan: one of free, api, limits: object, use: object, via: one of session, key

DELETE /v1/me

Delete the account (page session only, with the password)

Every connected Cloudflare account is disconnected first (clx-edge, its database and routes removed where the token still can — what could not be is listed in left, the working tokens to revoke in revoke_tokens); then the totals go at once, and the user with the keys, sites and links.

Body (JSON):

  • password (required): string

Takes an Idempotency-Key header — required with an API key.

Answers:

  • 200 Deleted — ok: boolean, left: array of string, revoke_tokens: array of string

GET /v1/keys

API keys (page session only)

Answers:

  • 200 OK — keys: array of Key

POST /v1/keys

Issue an API key (page session only; 1 on the free plan, 5 on api — limit_reached over it); the key is in this answer only

Body (JSON):

  • scopes (required): array of one of accounts, sites, links, reports
  • allow_accounts: array of string, ^[0-9a-f]{32}$
  • allow_ips: array of string

Takes an Idempotency-Key header — required with an API key.

Answers:

  • 201 Issued — key is shown once
  • 409 key_already_issued — a replay of the same Idempotency-Key

DELETE /v1/keys/{id}

Revoke an API key (page session only)

Parameters:

  • id (path, required): string

Takes an Idempotency-Key header — required with an API key.

Answers:

  • 200 Revoked

Cloudflare accounts

GET /v1/accounts

Connected Cloudflare accounts (scope accounts or sites)

Answers:

  • 200 OK — accounts: array of Account

POST /v1/accounts

Connect a Cloudflare account with a bootstrap token (scope accounts)

The bootstrap token (Account Settings — Read + Account API Tokens — Edit, which the API names Write; one day) is used within this request and deleted; it is never stored. The account then goes on to the install of clx-edge (§4) — poll GET for its state: installing, then ready once the script, its database and cron are in place (seconds; the worker's own confirmation may follow minutes later, or never — then error.warnings says not_confirmed), or connected with an error.

Body (JSON):

  • cf_account_id (required): string, ^[0-9a-f]{32}$
  • bootstrap_token (required): string

Takes an Idempotency-Key header — required with an API key.

Answers:

GET /v1/accounts/{id}

One connected account (scope accounts or sites), with its latest operation and link host

Parameters:

  • id (path, required): string

Answers:

DELETE /v1/accounts/{id}

Disconnect (scope accounts) — delete clx-edge and its database, then the record

The answer names the working token to revoke in Cloudflare (it cannot delete itself), and in left whatever clx could not delete — the token is revoked, a right is missing, or the worker was changed outside clx — for the user to remove by hand.

Parameters:

  • id (path, required): string

Takes an Idempotency-Key header — required with an API key.

Answers:

  • 200 Disconnected — ok: boolean, revoke_token: string | null, left: array of string

GET /v1/accounts/{id}/zones

The account's zones (scope accounts or sites) — to pick a site's or link host's zone from

Read live from Cloudflare with the working token, sorted by name; at most 1,000, truncated past that. A host is still checked against its zone when it is added.

Parameters:

  • id (path, required): string

Answers:

  • 200 OK — zones: array of object, truncated: boolean

POST /v1/accounts/{id}/install

Install clx-edge again (scope accounts) — after a failed install, or to reinstall

The recorded database is kept; the worker gets a new key (the previous one is accepted for a day). A clx-edge worker or database clx did not create is never touched (name_taken), nor a worker changed outside clx (resource_drift). Poll GET for the state.

Parameters:

  • id (path, required): string

Takes an Idempotency-Key header — required with an API key.

Answers:

POST /v1/accounts/{id}/token

Renew the working token with a new bootstrap token (scope accounts)

Parameters:

  • id (path, required): string

Body (JSON):

  • bootstrap_token (required): string

Takes an Idempotency-Key header — required with an API key.

Answers:

  • 200 Renewed

Sites

GET /v1/sites

Sites (scope sites); account_id narrows to one account

Parameters:

  • account_id (query): string

Answers:

  • 200 OK — sites: array of Site

POST /v1/sites

Add a site (scope sites) — the zone is found in the account, the route made, the config synced

The account must be ready. The answer carries the snippet at once; config turns synced once the worker has the site (seconds), and the counter answers from then on.

Body (JSON):

  • account_id (required): string
  • host (required): string — example.com or a subdomain of a zone in that account
  • excluded_paths: array of string, ^/, up to 50

Takes an Idempotency-Key header — required with an API key.

Answers:

  • 202 Added — site: Site

GET /v1/sites/{id}

One site (scope sites)

Parameters:

  • id (path, required): string

Answers:

  • 200 OK — site: Site

PATCH /v1/sites/{id}

Change the excluded paths (scope sites)

Parameters:

  • id (path, required): string

Body (JSON):

  • excluded_paths: array of string, ^/, up to 50

Takes an Idempotency-Key header — required with an API key.

Answers:

  • 200 Changed — site: Site

DELETE /v1/sites/{id}

Remove the site's routes (scope sites); the site stays as deleted, its totals with it

Parameters:

  • id (path, required): string

Takes an Idempotency-Key header — required with an API key.

Answers:

  • 200 Deleted — site: Site

POST /v1/sites/{id}/rotate

A new path, names and snippet (scope sites); the old path is counted for 30 more days

Parameters:

  • id (path, required): string

Takes an Idempotency-Key header — required with an API key.

Answers:

  • 200 Rotated — site: Site

POST /v1/links

Add a short link (scope links) on the account's link host

A click is a 302 to the first matching rule's URL, else url; it is counted as a view of the link, with source qr when the address carries ?q (the QR code's) — or as a bot when the client is one (curl, a script, a crawler: it is redirected all the same). config turns synced once the worker has the link (seconds).

Body (JSON):

  • account_id (required): string
  • code: string, ^[A-Za-z0-9_-]{3,32}$ — a random 6 characters when not given
  • url (required): string (uri) — https:// only, up to 2048 characters, not on the link host
  • rules: array of Rule, up to 10

Takes an Idempotency-Key header — required with an API key.

Answers:

  • 201 Added — link: Link

Reports

GET /v1/sites/{id}/report

Totals and series of the site (scope reports); with breakdowns=1 also the top pages, sources, countries, devices, browsers, OS and bots

Parameters:

  • id (path, required): string
  • period (query): one of today, 7d, 30d
  • breakdowns (query): one of 1 — read the breakdowns from the account's database (cached 5 minutes)

Answers:

Objects

Key

  • id: string
  • prefix: string
  • scopes: array of one of accounts, sites, links, reports
  • allow_accounts: array of string | null
  • allow_ips: array of string | null
  • created_at: string (date-time)
  • last_used: string (date) | null

Account

  • id: string
  • cf_account_id: string
  • name: string
  • state: one of pending, bootstrap_lost, connected, installing, ready, permission_error, revoked, resource_drift, no_connection — pending — the connect is running, or stopped on a passing failure (5xx other than token_rights_mismatch, token_check_failed, permission_catalog, which end it) and waits for the same request again (same Idempotency-Key); bootstrap_lost — a connect left pending for an hour, or one that died before clx stored its working token: revoke the token its error names and connect again; connected — token ready, clx-edge not installed; installing — the install runs; ready — installed and serving, go on (the worker confirms itself on its first cron: meanwhile operation shows the install at step selfcheck, for up to 15 minutes, and if no confirmation comes error.warnings says not_confirmed — nothing to wait for either way); no_connection — installed, but no push from the worker for 26 hours (sites can still be added; its next push makes it ready again)
  • token: object | null
    • name: string — clx-<operation id>
    • expires_at: string (date-time)
  • error: object | null — code and details, or warnings such as bootstrap_not_deleted
  • edge: object | null — The installed clx-edge, from its upload on (the worker's own confirmation is not needed)
    • bundle: string — sha256 of the bundle
    • up_to_date: boolean
    • schema: integer
    • installed_at: string (date-time)
  • push: object | null — The worker's own report from its last push of totals (hourly)
    • at: string (date-time)
    • bundle: string — sha256 of the bundle the worker runs
    • schema: integer | null
    • queue: integer — queue parts and final days waiting to be sent
    • error: string | null — the worker's last error
    • dropped: object — items dropped unsent, by reason: budget, invalid, unknown_target, too_old, busy, expired
  • advice: object | null — When to move this Cloudflare account to Workers Paid, from its measured use over the last 7 complete UTC days; recomputed daily, null until first computed
    • level: one of ok, watch, upgrade_soon, over — the worst metric: over — a limit was reached (or the database is at backpressure, 400 MB); upgrade_soon — busiest day ≥ 80% or the limit within 7 days; watch — ≥ 60% or within 30 days
    • metric: one of requests, writes, reads, size, or null
    • metrics: object — requests, writes, reads — per day for the whole account, against 100,000 / 100,000 / 5,000,000; size — clx-edge now, against 500 MB
    • unavailable: array of one of analytics, size — what could not be read — not guessed
    • suggest: one of shorter_hourly_retention — size alone is the problem
    • at: string (date-time)
  • operation: object | null — The latest operation on the account (GET /v1/accounts/{id} only)
    • kind: one of connect, renew, install, update, disconnect
    • state: one of running, done, failed
    • step: string
    • error: object | null
    • updated_at: string (date-time)
  • link_host: LinkHost | null — The account's link host (GET /v1/accounts/{id} only)
  • created_at: string (date-time)

LinkHost

  • host: string
  • state: one of route_pending, active, route_conflict — route_pending — the route <host>/* is still to be made (the cron retries); route_conflict — another worker holds it, see error
  • config: one of pending, synced
  • error: object | null

Rule

matches when the visitor's country is in countries (if given) and their device in devices (if given)

  • countries: array of string, ^[A-Z]{2}$
  • devices: array of one of mobile, desktop
  • url (required): string (uri) — https:// only, up to 2048 characters, not on the link host

Site

  • id: string
  • account_id: string
  • host: string
  • state: one of route_pending, active, route_conflict, deleted — route_pending — the route is still to be made (the cron retries); route_conflict — another worker holds the pattern, see error
  • path: string — the per-site path the counter answers under, e.g. /lumora or /kavi/tesomu
  • snippet: object | null — Embed one of the two at build time; stable until rotate
    • inline: string — <script>…</script>, under 600 bytes
    • script_tag: string — <script src="/<path>/<s>.js"> with defer (the order of the attributes varies by site)
  • excluded_paths: array of string
  • config: one of pending, synced — whether the worker has this version of the site yet
  • retiring: array of object — old paths still counted after a rotation
  • error: object | null
  • created_at: string (date-time)

Top

the top 10 by count; "(other)" holds what went over the detail caps

array of object, up to 10 — each with key, n

Report

  • period: one of today, 7d, 30d
  • from: string (date) — first UTC day
  • to: string (date) — last UTC day — today
  • as_of: string (date-time) | null — today is counted up to this moment. A site: the end of the latest closed hour the worker sent (hours arrive up to an hour late), null before its first push. A link: the moment of the live read of the account's database
  • totals: object
    • views: integer
    • bots: integer
    • visitors: integer — the sum of daily unique visitors
  • series: array of object — today — one point per hour up to as_of; 7d/30d — one per day (with visitors)
  • incomplete: boolean — today's hours were refused over the account's write budget; daily totals stay exact
  • breakdowns: object | null — only with breakdowns=1; read from the account's own database, null when unavailable
  • unavailable: one of not_installed, cloudflare, rate_limited — why breakdowns (and a link's today) are not there: the worker is not installed or the token is gone; Cloudflare failed or took over 5 s; over 30 reads of the account databases a minute for this clx user (each cached 5 minutes)