> ## Documentation Index
> Fetch the complete documentation index at: https://docs.abtestly.com/llms.txt
> Use this file to discover all available pages before exploring further.

# How ABTestly works

> A picture of the whole system, so the rest of the docs make sense.

Four pieces do the work.

```mermaid theme={null}
%%{init: {'theme':'base', 'themeVariables': {
  'fontSize': '22px',
  'fontFamily': 'ui-sans-serif, system-ui, sans-serif',
  'primaryTextColor': '#0f172a',
  'lineColor': '#94a3b8',
  'edgeLabelBackground': '#f8fafc'
}, 'flowchart': {'nodeSpacing': 90, 'rankSpacing': 110, 'padding': 28, 'curve': 'basis'}}}%%
flowchart TB
  U["<div style='padding:14px 28px'><b style='font-size:24px'>Dashboard</b><br/><span style='font-size:16px;color:#475569'>app.abtestly.com</span></div>"]
  S["<div style='padding:14px 28px'><b style='font-size:24px'>Snippet</b><br/><span style='font-size:16px;color:#475569'>~15 KB on your site</span></div>"]
  I["<div style='padding:14px 28px'><b style='font-size:24px'>Ingest</b><br/><span style='font-size:16px;color:#475569'>api.abtestly.com</span></div>"]
  R["<div style='padding:14px 28px'><b style='font-size:24px'>Results</b><br/><span style='font-size:16px;color:#475569'>ledger + engines</span></div>"]

  U -- "&nbsp;&nbsp;publish&nbsp;&nbsp;" --> S
  S -- "&nbsp;&nbsp;exposures &amp; goals&nbsp;&nbsp;" --> I
  I -- "&nbsp;&nbsp;events&nbsp;&nbsp;" --> R
  R -- "&nbsp;&nbsp;verdict&nbsp;&nbsp;" --> U

  classDef dashboard fill:#dbeafe,stroke:#2563eb,stroke-width:2.5px,color:#0f172a;
  classDef snippet   fill:#dcfce7,stroke:#16a34a,stroke-width:2.5px,color:#0f172a;
  classDef ingest    fill:#fef3c7,stroke:#d97706,stroke-width:2.5px,color:#0f172a;
  classDef results   fill:#ede9fe,stroke:#7c3aed,stroke-width:2.5px,color:#0f172a;

  class U dashboard
  class S snippet
  class I ingest
  class R results
```

## The dashboard

The dashboard at [app.abtestly.com](https://app.abtestly.com) is where you
build tests, pick goals, invite teammates, and read results. Every save is
autosaved. Nothing goes live until you click **Start** or a scheduled start
time arrives.

Publishing writes a fresh config blob to Cloudflare KV, which the snippet
reads on the visitor's next request. The propagation window is on the order
of **sixty seconds** across the edge. Republish always works; if the publish
call fails on the network, an outbox retries it in the background until it
succeeds so you never end up with a "saved but not live" experiment.

## The snippet

The snippet is a small tag you drop into `<head>`. It fetches your site's
config from Cloudflare's edge (cached, no origin round-trip), decides which
variant a visitor should see, applies your JavaScript and CSS, and fires an
exposure event.

The runtime is around 15 KB gzipped. It has no third-party dependencies. It
can wait for consent before firing, and it respects a
[visitor opt-out URL](/install/opt-out) that a person can pass to remove
themselves from every test on your site.

The snippet also handles single-page apps. When you turn on
[SPA mode](/install/spa), a route change is treated like a new page load.
JavaScript is cleaned up, CSS is torn down, and the next route's variants
are applied. That is one of the more subtle parts of the runtime; the
[SPA lifecycle](/developer/spa-lifecycle) page walks through it in detail.

## Ingest

Events go to `api.abtestly.com`. There are only a handful of endpoints and
they are all simple:

| Endpoint               | What it carries                            |
| ---------------------- | ------------------------------------------ |
| `POST /e`              | Exposures and goals                        |
| `POST /err`            | Snippet errors, deduped per session        |
| `POST /m`              | Library telemetry                          |
| `GET  /g`              | Edge geo lookup for geo-targeted tests     |
| `GET  /preview-config` | One draft experiment for the preview modal |

Beacons use `text/plain` on purpose so that `sendBeacon` works without a CORS
preflight. Payload shapes are documented on the
[beacon protocol](/developer/beacon-protocol) page for anyone building
their own integration.

## Results

Every event ABTestly accepts is written to two places.

The first is Cloudflare Analytics Engine, which is fast and cheap to query.
It is what the dashboard reads for live-updating counters. Under very heavy
write bursts, AE can sample its own data, that is fine for a live counter,
not fine for a bill-of-materials.

The second is the **Exact Experiment Ledger**. Events go through a queue,
land in R2 as gzipped NDJSON, and get projected into a per-experiment
Durable Object. The ledger is what your final numbers come from, no
sampling, no approximation. If the ledger is a little behind while it
catches up on a burst, the results page tells you that in plain English
instead of quietly serving old numbers.

The results page turns those events into a verdict using one of three
statistics engines: frequentist, sequential, or Bayesian. You pick the
engine per experiment. Verdict text is written in plain English, and it is
honest about what it can and cannot say yet.

## Where each piece lives in the app

<CardGroup cols={2}>
  <Card title="Sites & experiments" icon="rectangle-list" href="/quickstart">
    Every site you own, and every test attached to it.
  </Card>

  <Card title="Goals, locations, audiences" icon="bullseye" href="/goals/overview">
    Reusable definitions your experiments attach to.
  </Card>

  <Card title="Settings" icon="gear" href="/settings/general">
    Per-site config: snippet, analytics adapters, SPA, global JS,
    blocked IPs, opt-out URLs.
  </Card>

  <Card title="Team & access" icon="users" href="/team/members-and-roles">
    Members, roles, and twenty per-site permission keys.
  </Card>
</CardGroup>

## What is deliberately not on this page

**Sampling and approximations.** ABTestly counts every event you send it.
The results page can trail live counters by a few seconds while the ledger
projects; that is not the same thing as dropping data.

**Any implicit "trust us" step.** The methodology behind the verdict is
[documented in full](/results/engines), including which formula is applied
to your numbers today, and locked per-experiment so an engine upgrade
cannot silently change a running test's math.

If you want the developer's version, bucket hashing, the wire format,
runtime bindings, start with the
[developer reference](/developer/window-abtestly-api).
