> Content index: https://quillly.com/docs/llms.txt
> Canonical page: https://quillly.com/docs/analytics-api

---
title: Analytics JavaScript API: qa() and data-qa attributes
description: Every qa() command and data-qa attribute the Quillly analytics tag reads: goal, event and identify, with argument rules, limits and copyable examples.
keywords: analytics JavaScript API, qa goal, data-qa-goal
published: 2026-09-25
updated: 2026-09-25
url: https://quillly.com/docs/analytics-api
word_count: 1787
---

# Analytics JavaScript API

> Every qa() command and data-qa attribute the Quillly analytics tag reads: goal, event and identify, with argument rules, limits and copyable examples.

Canonical: https://quillly.com/docs/analytics-api
Published: 2026-09-25

## Related Pages

- [Install analytics on your website](https://quillly.com/docs/analytics-install)
- [Track goals and conversions](https://quillly.com/docs/analytics-goals)
- [Plan limits and allowances](https://quillly.com/docs/limits)
- [API keys, OAuth and tool access](https://quillly.com/docs/api-keys)
- [Analytics limits, retention and excluding traffic](https://quillly.com/docs/analytics-limits)
- [Cookies or cookieless analytics](https://quillly.com/docs/analytics-privacy)
- [Visitor journeys and the Users list](https://quillly.com/docs/analytics-visitors)

This page lists every command the Quillly analytics tag accepts through `qa()` and every `data-qa-*` attribute it reads, with the rules the collector applies to each. It is the lookup table for the analytics JavaScript API, and it ends with the one server call, recording a conversion with an API key. For the steps, see [Install analytics on your website](https://quillly.com/docs/analytics-install) and [Track goals and conversions](https://quillly.com/docs/analytics-goals).

![A goal sent with qa() in JavaScript and with data-qa-goal in HTML, both recorded by Quillly as the same signup goal with its metadata](https://quillly.com/serve/v1/019c64a2-a62f-7793-aa68-2c78316d3309/images/69913b859e3014699dd1ead0c1a0141bedda6a32.webp)

## How the analytics JavaScript API loads

The analytics JavaScript API is the `qa` function and the attributes on this page. Both come with the two lines of the tag, and the Cookie tag (`/qa.js`) and the Cookieless tag (`/qa.cookieless.js`) accept exactly the same commands.

```html
<script>window.qa=window.qa||function(){(window.qa.q=window.qa.q||[]).push(arguments)}</script>
<script async src="https://quillly.com/qa.js" data-website="YOUR_WEBSITE_ID"></script>
```

- **The first line** creates `qa` at once and queues every call made before the script loads. The script sends the queue when it starts.

- **Without the first line**, a call made before the script loads fails with `qa is not defined`.

- **If the script never loads**, for example because an ad blocker stops it, queued calls are never sent. Your page keeps working.

- **`qa()` returns nothing and never throws.** A command it doesn't know, or a call that breaks a rule below, is ignored without an error.

![What happens to a qa() goal call: it waits in the queue until the tag loads, then Quillly checks it and stores it as one event](https://quillly.com/serve/v1/019c64a2-a62f-7793-aa68-2c78316d3309/images/71e674cf9abb5b0fd6c2c53ee5a14f7100e7f89b.webp)

## qa() commands

| Command | Arguments | What it records |
| --- | --- | --- |
| `qa('goal', name, metadata)` | A goal name, then an optional object | One completion of that goal |
| `qa('event', name, metadata)` | The same as `goal` | The same as `goal` |
| `qa('identify', person)` | An object with a `userId` or an `email` | Who the visitor is on your side |

There is no command to send a page view, change the page path or switch the website ID. The tag records page views by itself; see **Events the tag sends by itself** below.

### qa('goal')

Send a goal when the action succeeds, such as after a signup form returns.

```js
qa('goal', 'signup', { plan: 'pro', seats: 3 });
```

| Part | Rule | When a call breaks the rule |
| --- | --- | --- |
| Name | 1 to 64 lowercase letters, digits, `-` or `_`. Capital letters are lowercased first. | The call is dropped |
| Name starting with `qy_` | Reserved for the goals of pages Quillly serves | The call is dropped |
| Metadata | A plain object with up to 10 keys | Keys after the tenth are dropped |
| Metadata keys | 1 to 32 letters, digits or `_`. A `-` becomes `_`. | That key is dropped |
| Metadata values | Text up to 255 characters, a number, `true` or `false` | Longer text is cut; other values are dropped |
| Different names per site | Up to 500 | A new name past 500 is dropped; names already sent keep counting |

A goal appears in the **Goal** tab the first time it arrives, so there is nothing to create first. Each call counts as one event toward your [monthly allowance](https://quillly.com/docs/limits).

[Open Analytics](https://quillly.com/dashboard/analytics)

### qa('event')

`event` is another word for `goal`: the call, the rules and the result are the same.

```js
qa('event', 'signup', { plan: 'pro' });
```

### qa('identify')

Call it when a person signs up or logs in, so the **Users** list shows who they are.

```js
qa('identify', {
  userId: 'u_4821',
  email: 'maya@acme.com',
  name: 'Maya',
  plan: 'pro',
});
```

| Field | Type | Limit | What it does |
| --- | --- | --- | --- |
| `userId` | Text or a number | 128 characters | Your own ID for the person |
| `email` | Text | 254 characters | The person's email address |
| `name` | Text | 120 characters | The name the **Users** list shows |
| `revenue` | A number above 0 | 1,000,000,000 per call | Adds to the person's revenue total and marks them as a Customer |
| `isCustomer` | `true` | None | Marks the person as a Customer |
| Any other key | Text, a number, `true` or `false` | 10 keys of 1 to 32 letters, digits or `_`; text up to 200 characters | Stored with the person |

- **`userId` or `email` is required.** A call with neither sends nothing, and Quillly drops a call whose `userId` and `email` hold only spaces.

- **Later calls add to earlier ones.** A new field is added, a field you send again replaces its old value, and `revenue` adds up.

- **Keys with a `-` are dropped.** Unlike goal metadata, `identify` doesn't turn `-` into `_`.

- **Each call is one event** toward your allowance. It doesn't count as the visitor doing something, so it never changes the bounce rate.

> [!WARNING]
> Every `revenue` value adds to the person's total. Send it once for each payment, not on every page load.

## data-qa-* attributes

### On the tag's script element

| Attribute | What it does |
| --- | --- |
| `data-website` | Required. Your website's ID. Without it, the tag does nothing. |
| `data-qa-exclude` | A comma-separated list of path prefixes where the tag sends no page views, time on page or Web Vitals. Goals and `identify` still send there. |
| `data-api` | Where the tag sends events. Without it, events go to `https://quillly.com/api/qa/collect`, and you don't need to set it. |
| `data-qa-content` | Set by Quillly on the pages it serves, with the page's details. Don't add it to your own tag. |

`data-qa-exclude` matches a prefix and everything under it: `/app` covers `/app` and `/app/billing`, but not `/apps`. Matching is case-sensitive, spaces around an entry are ignored, and so is a `/` at its end. An entry of `/` on its own matches only the home page. In a single-page app, the tag checks the path again on every navigation.

```html
<script async src="https://quillly.com/qa.js" data-website="YOUR_WEBSITE_ID" data-qa-exclude="/app,/dashboard"></script>
```

### On any element

These are standard [HTML data attributes](https://developer.mozilla.org/en-US/docs/Web/HTML/Reference/Global_attributes/data-%2A), so they don't change how the element looks or works.

| Attribute | What it does |
| --- | --- |
| `data-qa-goal="name"` | A click on the element, or on anything inside it, sends the goal `name` |
| `data-qa-event="name"` | The same as `data-qa-goal` |
| `data-qa-{key}="value"` | On the same element, adds `key` to the goal's metadata with `value` as text |

```html
<a href="/pricing" data-qa-goal="pricing_click" data-qa-billing-cycle="yearly">See pricing</a>
```

- **The name follows the goal rules above.** An element whose name breaks them sends nothing.

- **Metadata comes from the same element only.** Its other `data-qa-*` attributes become metadata, so `data-qa-billing-cycle` arrives as `billing_cycle: "yearly"`.

- **The nearest element wins.** When elements with `data-qa-goal` sit inside each other, a click sends the goal of the one closest to it.

- **The goal is sent at once**, so a click on a link that leaves the page still records it.

## Events the tag sends by itself

| Event | When the tag sends it |
| --- | --- |
| Page view | When the page loads, and when the path changes in a single-page app. A change to only the query string or the `#hash` is not a new page. |
| Time on page | When the visitor hides the page, leaves it or moves to another path, after at least 2 seconds on it |
| [Web Vitals](https://web.dev/articles/vitals) (LCP, CLS and INP) | Once per page, when the visitor hides or leaves it, or moves to another path. Pages Quillly serves don't send them. |

The tag also reads `utm_source`, `utm_medium` and `utm_campaign` from the address the visitor landed on. It sends events in batches of up to 20 within a second of them happening; goals and `identify` calls go at once.

## What the collector accepts

Quillly's collector checks every request the tag sends. These are its rules, and the answer the tag gets back when a request breaks one.

| Rule | Limit | Past the limit |
| --- | --- | --- |
| Where events come from | Your website's domain and its subdomains, `quillly.com` addresses, `localhost` and `127.0.0.1` | `403`, nothing stored |
| The website | Its owner's plan must include analytics | `404`, nothing stored |
| Events in one request | 20 | `400`, nothing stored |
| Size of one request | 16 KB | `413`, nothing stored |
| Requests from one IP address | 240 a minute | `429` for up to a minute |
| Event time | From 24 hours ago to 5 minutes ahead | Set to the time Quillly receives it |
| Bots and headless browsers | Never stored | `204` |
| Monthly allowance | Used up | `204`, nothing stored until the next period or an upgrade |

A `204` answer means the request arrived, whether or not its events were stored. The tag never retries, and your page's own code never sees these answers.

## Record a conversion from your server

A payment webhook on your server can mark a person as a Customer without the browser. Send a `POST` with an account API key, the kind you create on **Connections**, in the `Authorization` header:

```bash
curl -X POST https://quillly.com/api/websites/YOUR_WEBSITE_ID/analytics/conversions \
  -H "Authorization: Bearer qly_YOUR_KEY" \
  -H "Content-Type: application/json" \
  -d '{"userId": "u_4821", "revenue": 49, "currency": "EUR"}'
```

| Field | What it is |
| --- | --- |
| `userId` | The `userId` you sent with `qa('identify')` for that person. |
| `visitorId` | Instead of `userId`, Quillly's own visitor ID. Your pages can't read it, so use `userId`. |
| `revenue` | Optional. A number from 0 to 1,000,000,000, added to the person's revenue. |
| `currency` | Optional. A three-letter code, such as `EUR`. |
| `at` | Optional. When the payment happened, as an ISO timestamp, for replaying an old webhook. It can't be in the future. |

- **The key decides who is calling.** Its owner needs Analytics on their plan and access to the website, with **Manage** on Analytics when the website is shared with them, as for setting up goals.

- **A bad key is refused.** An unknown, revoked or expired key gets `401` "Invalid or revoked API key", even from a browser that is signed in.

- **The person must be known.** A `userId` that `qa('identify')` never sent gets `404` "No visitor identified with that userId". A `visitorId` isn't checked, so a wrong one still succeeds without matching any of your visitors. A body with neither gets `400`.

- **It is not an event.** A server conversion doesn't count toward your monthly allowance and doesn't appear in a visitor's journey. It marks the person as a Customer and adds the revenue.

[API keys, OAuth and tool access](https://quillly.com/docs/api-keys) shows how to create and revoke a key.

## Common questions

### Can my server send events?

Not events: goals, `identify` calls and page views all come from the tag running in your visitors' browsers. Your server can record a conversion with an API key, as **Record a conversion from your server** shows above. To count the purchase as a goal too, call `qa('goal', 'purchase')` on the page the buyer sees after paying, such as your thank-you page.

### Why doesn't my goal show up?

Check the name against the rules above, and test from your own domain or `localhost`. New completions can take a few minutes to show in the **Goal** tab, and nothing is stored while your monthly allowance is used up.

### Do goals on excluded paths count?

Yes. `data-qa-exclude` stops page views, time on page and Web Vitals only, so a signup inside your app still sends its goal and its `identify` call.

## Related

- [Install analytics on your website](https://quillly.com/docs/analytics-install): where the tag goes, and the Cookie and Cookieless tags.

- [Track goals and conversions](https://quillly.com/docs/analytics-goals): the **Goal** tab, conversion goals and Customers.

- [Analytics limits, retention and excluding traffic](https://quillly.com/docs/analytics-limits): the monthly allowance and what counts toward it.

- [Cookies or cookieless analytics](https://quillly.com/docs/analytics-privacy): what each tag stores in the browser.

- [Visitor journeys and the Users list](https://quillly.com/docs/analytics-visitors): the people `identify` names.
