FOUNDATION · FOR DEVELOPERS AND AI AGENTS

Built to be read, measured and wired in.

Every Praxis site publishes plain, read-only copies of its content for AI agents. Your own code can send events, join a person's browsers when they sign in, follow each visitor's consent, and run on any page. Your data leaves in open formats. And every change an AI makes passes through one governed gate. Plain statements, each checked against the product's code.

Every figure on this page is as of October 2026.
On this pageWhat it does
  1. What it does
  2. What AI agents can read
  3. Events from your code
  4. Identity
  5. The consent signal for your tags
  6. Data out
  7. Your team's directory
  8. How it works
  9. What keeps it safe
  10. Common questions

What it does

What AI agents can read

  • /llms.txt lists published entries, up to 500, each with its Markdown copy, and leaves out what you hide from search.
  • Those pages and posts have Markdown copies at /content/pages/<slug>.md or /content/posts/<slug>.md. /content/index.md lists them, and /sitemap.md maps the site in Markdown.
  • /content/jsonld.json holds validated schema.org data for those entries. /content/openapi.json describes these read-only files in the OpenAPI format. /content/mcp.json describes the site's resources for agents that speak the Model Context Protocol. /content/validate.json compares what was exported with the site's own facts.
  • All of it is read-only, and published content only. A members-only article's body is cut from these files.
  • A members-only page's body is cut from these files too. Coming soon.
  • Which agents may read, and whether they may train on what they read, is the site's own bot policy, changed behind a card.

Events from your code

  • window.praxis.track(type, props) sends an event from your own code.
  • The type starts with a lowercase letter and continues with 2 to 63 lowercase letters, digits or underscores. The props are a plain object; anything else is sent as an empty one. A call whose type breaks the rule is ignored.
  • Wherever the measurement script runs, it is defined, and it checks the visitor's consent on every call. When analytics is not allowed, it does nothing, quietly; it never throws into your code.
  • A visitor who has refused analytics, or whose browser sends Global Privacy Control, gets no measurement script at all on a page without your own page scripts, so call it guarded, as below.
  • A type that arrives without a definition waits on Insights › Events & Capture › "Needs review" until someone defines it.
  • Each page has an event budget. Past it, events are not sent, the browser console says "Praxis analytics: this page reached its event budget; further events are not sent.", and the drops are counted on "Capture health".
  • window.praxis.on("capture", handler) lets your code see every event as the page sends it, so your own tag manager or consent tool can mirror it. A handler that throws never breaks capture.
// Count a quote request. Quietly does nothing until the visitor allows analytics.
window.praxis?.track?.("quote_requested", { service: "roof_repair", value: 1200 });

// See every event that leaves the page, for your own tag manager.
window.praxis?.on?.("capture", (event) => {
  // event.type and event.props, exactly as they were sent
});

Identity

  • When your own sign-in succeeds, a call from your page to /api/dxp/identity/link with the account id joins this browser to a person. A call to /api/dxp/identity/detach takes it off again, and the next page load gives the browser a fresh anonymous identity.
  • The visitor id itself lives in a cookie that page code cannot read. The call sends your account id, never the visitor id, and the server decides which browser is speaking.
  • Whether the account id is kept follows the site's counting policy and the visitor's saved consent, decided on the server, and a browser that sends Global Privacy Control cannot save that consent. Wherever the measurement script runs, it adds praxis.identify and praxis.logout, which make these two calls.
  • On the person's record in Privacy & Data › Visitor Data, the "Identity" tab shows every browser joined to them, with "Take browser off this person".
// After your own sign-in succeeds
fetch("/api/dxp/identity/link", { method: "POST", headers: { "content-type": "application/json" }, body: JSON.stringify({ accountId: "account-id-from-your-system" }) });

// When the person signs out
fetch("/api/dxp/identity/detach", { method: "POST" });
  • Your site gets its own identity secret, stored encrypted, the first time one of its pages signs the link for a visitor signed in to the site. From then on, the link refuses a call without that signature. Until then, it trusts the account id your page sends, so call it only from a signed-in session that your own server vouches for.

Your scripts and CSS hooks

Page scripts.

The Composer's "Scripts & events" drawer runs your own JavaScript on a page at "Page load", "Click", "Submit", "Becomes visible", "Scroll depth", "Toggle open" or "Hover", with an optional event to capture. "Click", "Submit", "Becomes visible" and "Hover" need a CSS target. It is carried by the "Script & event binding" section, and it publishes with the page.

What runs, and what waits.

Your function runs as first-party page code. The event it names goes through the same consent check as every other event.

The head.

On Privacy & Data › Consent › "Head & CSP", "Head entries" hold raw HTML or a tag manager template, each loading "Before consent" or waiting for consent of the purpose you choose. A change there says "This changes executable code or metadata in the public site's <head> and its CSP." and waits for approval.

Security policy and CSS hooks.

One Content Security Policy for the site, in "Report only" or "Enforce", with a fresh nonce on every script the platform puts on the page, your page scripts and head entries included. Each section takes an "HTML anchor" and up to eight "Additional CSS classes", every section is marked with data-praxis-section and data-praxis-key, and the theme arrives as CSS variables at the page's root. A site-wide stylesheet can ride a head entry.

Data out

  • Exports. Privacy & Data › Exports › "Export a range" writes events, decisions, goals, audience transitions, audiences, profiles, consent and daily rollups as gzipped NDJSON, with a manifest. "Run history" keeps every run, with its downloads and a zip of the whole run, and "Warehouse ingestion" gives recipes for DuckDB, BigQuery and Snowflake.
  • Where they land. In the platform's own export storage, under your site's own folder.
  • On a schedule. Daily, on chosen weekdays, or monthly. Scheduled exports are available on request.
  • One person's data. "Export this visitor's data" on the person's record.
  • Everything else that downloads. Form submissions as CSV, up to 1,000 rows; orders, up to 5,000; the copilot's "Activity" as CSV or JSON; your account's Security log as CSV, the newest 10,000 rows; and EmDash's Site transfer, the site as one archive.

Your team's directory

  • SCIM. Mint a token on your account's Security tab, shown once, and your directory can add team members and switch them on or off.

How it works

One first-party script.

Every page carries one small script of the site's own. It checks consent before every event it sends, and posts to the site's own address; what arrives is stored in your site's own database.

Consent before everything.

The consent runtime is installed in the head before any entry of yours, and every API it offers replays the current state to a newcomer, then each change.

Files made from your content.

The agent files are derived from the site's own published content, read-only, with a validation report beside them.

What keeps it safe

Consent checked on every event.

No Praxis capture call from your code sends anything the visitor's consent does not allow.

A cookie scripts cannot read.

The visitor id stays in a cookie that scripts cannot read.

Read-only doors.

The agent files read your published content; personal tokens read published content and drafts within their scopes and your role.

The head behind a card.

Every change to your head entries or the site's security policy waits for approval.

A nonce per response.

Each script the platform puts on the page carries a nonce made for that response alone.

AI changes through one gate.

Every AI action on your site goes through one governed gate.

Common questions

Can my code read my content?

Yes. The site publishes its content as files, described in the OpenAPI format at /content/openapi.json, and personal tokens read published content and drafts within their scopes and your role.

What can AI agents do on my site?

Agents read your published content. Changes go through the governed copilot, and publishing, deleting and settings changes wait for a person's approval.

Does praxis.track work before the visitor consents?

It is there for a visitor who has not decided yet, and it sends nothing until analytics is allowed. For a visitor who has refused, it is there only on pages that carry your own page scripts, so call it guarded.

Can I use Google Tag Manager?

Yes. Add it as a head entry, using the tag manager template, and let it wait for the consent it needs. Your tags also receive praxis_consent_change in the dataLayer.

Can I bring my own consent manager?

Yes. Load it as a head entry, and let it tell the page when it is ready; the native notice stays as the fallback.

Can I get my data into BigQuery or Snowflake?

Yes. Export a range as gzipped NDJSON, and follow the warehouse recipe for DuckDB, BigQuery or Snowflake.

Is there a staging environment?

A site's staging copy is made from a backup, and a card promotes it. Coming soon. Changes made in the Composer wait on a draft until they are published, and putting an experiment live needs your approval. An edit to a live experiment's version waits for your approval too.

Can I host Praxis myself?

Praxis is a hosted platform: we run it for you, on Cloudflare.