# Build a REST analytics report

> Build a weekly website report with Databuddy's REST API: scoped authentication, website selection, timezone handling, empty results and partial-query failures.


import { Callout, Card, Cards } from "@/components/docs";

Use the Query API to read last week's summary and top pages for one website. This walkthrough keeps your API key on the server, selects the destination explicitly, and checks each query result before producing a report.

Reading analytics does not install a tracker or send events. First verify that the website already has the data you expect using the [installation guide](/docs/getting-started).

## 1. Create a read-only key

Create a key in **Organization settings → API keys** with `read:data` access to the website you want to report on. Use website-specific access when the integration only needs one site. You do not need `track:events` for this reporting task.

Store the key in your server or local environment as `DATABUDDY_API_KEY`. A browser's public Client ID is not an API key. Do not put the key in `NEXT_PUBLIC_` variables, client components, a shared notebook, or a committed example file. See [authentication and scopes](/docs/api/authentication).

## 2. Find the website ID

List websites accessible to the key:

```sh
curl --fail --silent --show-error \
  -H "x-api-key: $DATABUDDY_API_KEY" \
  https://api.databuddy.cc/v1/query/websites
```

The response has `success`, a `websites` array and `total`. Match the intended site by its name, domain and organization, then copy its returned `id` into `DATABUDDY_WEBSITE_ID`. Do not silently pick the first site: keys can have access to several websites.

An empty list does not mean the organization has no traffic. Check the key's organization, website access and read permission. If the request is rejected, fix authentication before querying reports.

## 3. Discover the current query contract

Query-type discovery is public:

```sh
curl --fail --silent --show-error \
  'https://api.databuddy.cc/v1/query/types?include_meta=true'
```

Check that `summary_metrics` and `top_pages` are present. Each entry under `configs` describes supported filters and optional metadata, including `meta.output_fields`. Use these fields to interpret the response rather than assuming every query returns the same metric names.

For these queries, the summary exposes `unique_visitors`, while top pages exposes `name` for the path and `visitors` for its visitor count. Top-page visitor counts overlap when someone visits several pages, so summing those rows does not produce the site's unique-visitor total.

## 4. Query a complete calendar week

Set `DATABUDDY_TIMEZONE` to the timezone you use when reviewing the website, such as `UTC` or `Europe/Berlin`. It does not automatically inherit the dashboard's selected timezone.

This request uses `last_week`: the previous Sunday through Saturday in the requested timezone. It avoids comparing a partial current day with a completed week. The `overview` and `pages` IDs let your code match returned results even if their order changes.

```sh
curl --fail --silent --show-error \
  -X POST \
  -H "x-api-key: $DATABUDDY_API_KEY" \
  -H 'Content-Type: application/json' \
  --data '{
    "parameters": [
      { "name": "summary_metrics", "id": "overview" },
      { "name": "top_pages", "id": "pages" }
    ],
    "preset": "last_week",
    "limit": 10,
    "page": 1
  }' \
  "https://api.databuddy.cc/v1/query?website_id=$DATABUDDY_WEBSITE_ID&timezone=$DATABUDDY_TIMEZONE"
```

`limit: 10` returns a top-page shortlist, not a complete export. For a complete page inventory, use the query reference's pagination controls and verify how many rows you actually fetched. If you need a fixed historical period, use `startDate` and `endDate` instead of `preset` and retain those dates with the report.

## 5. Run the checked JavaScript example

[Download query-analytics.mjs](/examples/query-analytics.mjs). It uses built-in `fetch`, needs no dependencies, and runs with Bun or Node.js 22+. With your key, website ID and timezone already in the environment:

```sh
bun query-analytics.mjs
# Or: node query-analytics.mjs
```

The script requests the same two reports and prints JSON only after both succeed. It validates the timezone before making a request, uses a 20-second timeout, checks HTTP status and the response envelope, and requires one successful result for each requested ID. Missing, duplicated or failed results cause a nonzero exit instead of a misleading complete report.

The example intentionally stops on errors rather than retrying automatically. An automated scheduler should preserve failure details and apply bounded retries appropriate to the error. For `429`, respect the server's cooldown or `Retry-After` guidance and add backoff; do not spin in a tight loop. See [rate limits](/docs/api/rate-limits).

## Interpret success, failure and empty data

HTTP success and a top-level `success: true` do not prove every query succeeded. For a single request, inspect each item in `data`:

```json
{
  "success": true,
  "data": [
    { "parameter": "overview", "success": true, "data": [] },
    { "parameter": "pages", "success": false, "data": [], "error": "..." }
  ]
}
```

This synthetic example is incomplete: the pages query failed. Do not label its empty array as zero pageviews. The downloadable script rejects the report.

A successful result with an empty array is different. The script preserves it with `empty: true`; it does not invent zero-valued metrics. Depending on the query, an empty period can also return a row containing zero counts. Inspect the returned fields and compare the intended website, dates and timezone before interpreting either case.

For an unexpectedly empty result, check that collection was enabled during that period, the website ID is correct, and any filters match data that exists. Remove optional filters one at a time. An empty response alone does not diagnose a tracking failure.

## Verify before scheduling

Run the report against a separate test website with a known small journey. Compare the same period and timezone in the dashboard, then check both results independently. Reconcile differences in limits, filters and metric definitions rather than summing per-page visitors or treating a request counter as a traffic total.

If you add parameters, discover their supported filters first. A filter accepted by one query can fail another query in the same request. For a batch of requests, inspect each object in `results` and then its nested parameter results; the downloadable example intentionally handles a single request only.

Authentication or authorization errors need a valid key and resource access. A `402 FEATURE_UNAVAILABLE` response requires reviewing the requested feature and plan, not adding broader scopes. Invalid parameters need a corrected request. See [API error handling](/docs/api/errors) and keep request IDs with your operational logs when the API supplies them.

## Continue with the reference

<Cards>
  <Card title="Query API reference" href="/docs/api/query">
    Date ranges, filters, pagination and batch-response structure.
  </Card>
  <Card title="API authentication" href="/docs/api/authentication">
    Choose the minimum scope and website access for the integration.
  </Card>
  <Card title="Query API explorer" href="/api">
    Discover available query types and their reporting fields.
  </Card>
</Cards>
