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

# Coverage: why a number is null

> Unknown is not zero: when your shop's sales numbers are null, which notice says why, and when they fill in

Your shop numbers are your real sales, not estimates, and AI agents act on them. An agent that reads `0` sales may decide a listing is dead and delete it. So when Alura doesn't know a number, the API returns `null` and adds a notice that says why. It never returns `0` for a number it doesn't know.

A `0` in a response is a real zero. A `null` is unknown. Two cases to know:

* **Revenue with no sales.** A listing that sold nothing in a covered period shows `revenue` as `{"amount": null, "currency": null, "usd": "0.00"}`. The `usd` value is the real zero; `amount` and `currency` are null only because no sale gave a currency of sale. The same `amount: null` with a `usd` value means sales in more than one currency.
* **Shop stats from the payment ledger** may hold some zeros Alura can't tell from real ones, from before a ledger-sync fix. See [Shop stats](/methodology/shop-stats#known-limits).

## A shop's sales coverage

Sales numbers come from Alura's daily sales data, built from your shop's orders after each sync. Each shop has a coverage window: the days for which that data is complete. Read it on the shop:

```bash cURL theme={null}
curl --fail-with-body -sS https://api.alura.io/v1/workspace/shops/30577120 \
  -H "Authorization: Bearer $ALURA_API_KEY"
```

```typescript TypeScript theme={null}
interface Shop {
  id: string;
  name: string | null;
  coverage: { sales_from: string; sales_through: string; refunds_through: string | null; gap_days: string[]; gap_days_total: number } | null;
  notices: { code: string; shop_ids: string[]; detail: string }[];
}

const res = await fetch('https://api.alura.io/v1/workspace/shops/30577120', { headers: { Authorization: `Bearer ${process.env.ALURA_API_KEY}` } });
if (!res.ok) throw new Error(`${res.status}: ${await res.text()}`);
const shop = (await res.json()) as Shop;

if (shop.coverage === null) {
  // Not covered yet: its sales numbers are null, never 0, until Alura's sales data reaches it.
  console.log(`${shop.name}: sales not covered yet`);
} else {
  const c = shop.coverage;
  console.log(`${shop.name}: sales ${c.sales_from} to ${c.sales_through}, refunds through ${c.refunds_through ?? 'unknown'}, ${c.gap_days_total} gap days`);
}
for (const notice of shop.notices) console.log(`notice ${notice.code}: ${notice.detail}`);
```

```python Python theme={null}
import json
import os
import urllib.request

request = urllib.request.Request(
    "https://api.alura.io/v1/workspace/shops/30577120",
    headers={"Authorization": f"Bearer {os.environ['ALURA_API_KEY']}"},
)
with urllib.request.urlopen(request) as response:
    shop = json.load(response)

c = shop["coverage"]
if c is None:
    # Not covered yet: its sales numbers are null, never 0, until Alura's sales data reaches it.
    print(f"{shop['name']}: sales not covered yet")
else:
    refunds = c["refunds_through"] or "unknown"
    print(f"{shop['name']}: sales {c['sales_from']} to {c['sales_through']}, refunds through {refunds}, {c['gap_days_total']} gap days")
for notice in shop["notices"]:
    print(f"notice {notice['code']}: {notice['detail']}")
```

```json Response theme={null}
{
  "id": "30577120",
  "name": "ClayAndName",
  "coverage": null,
  "notices": [
    {
      "code": "sales_not_covered",
      "shop_ids": ["30577120"],
      "detail": "Alura's sales data does not cover these shops yet, so their sales coverage is null (never a made-up window)."
    }
  ]
}
```

A covered shop has a window instead:

```json theme={null}
{
  "coverage": {
    "sales_from": "2021-10-01",
    "sales_through": "2026-10-16",
    "refunds_through": "2026-10-16",
    "gap_days": [],
    "gap_days_total": 0
  }
}
```

| Field | What it means |
| - | - |
| `sales_from` | The first day covered: the earlier of the shop's creation date and its first stored order. A day before it can't hold sales. |
| `sales_through` | The last complete day, in UTC. Days after it are not known yet. |
| `refunds_through` | Refunds are counted through this day only. It can be earlier than `sales_through`, or `null`. |
| `gap_days` | Days inside the window whose sales Alura knows are incomplete, oldest first, at most 100. |
| `gap_days_total` | How many gap days there are in all. |

`coverage` is `null` when the shop isn't covered yet, with a `sales_not_covered` notice. Alura builds sales data shop by shop, so a newly connected shop, or one Alura hasn't reached yet, starts uncovered. A shop also becomes uncovered for a while when Alura finds an order it can't date.

A **gap day** is a day Alura knows it can't count right: an order whose stored copy differs from Etsy's, an order with no items stored, a refund Alura can't assign to an item, or an order Etsy no longer returns. Only gap days inside the window count. Alura stores no sales for a gap day, so any total that includes one would be short.

## What is null, and the notice that says so

Each kind of number has its own rule. All of them name the shops concerned in the notice's `shop_ids`.

### Listing sales and sorts

* **A listing's `period`** (units, orders and revenue over `period` or `from`/`to`) needs the whole period covered: from `sales_from` through `sales_through`, with no gap day. If one day is missing, the whole `period` is `null`. 29 of 30 days isn't enough. `sales_not_covered` names the shop.
* **A listing's `sales`** (all its sales since `sales_from`) is `null` when the shop isn't covered, has any gap day, or Alura hasn't finished re-adding its totals after new orders arrived. `sales_not_covered` names the shop.
* **The sales sorts** (`units`, `revenue`, `orders` and `trending`) leave out the listings of shops that don't cover the period, and name those shops in `sales_not_covered`. `trending` needs the period before it covered too. See [Trending listings](/methodology/listing-trending).
* **An unknown input.** When an order line Alura stored has an unknown quantity or price, every number it would feed is `null`, never a smaller sum, and an `unknown_sales_inputs` notice names the listings.

### Sales filters

`has_sales_since` and `no_sales_since` find listings that did or didn't sell since a date. "No sale since March" is only true if every day since March is known, so the filters refuse to guess. They answer `400 sales_window_not_covered` for the first shop, in shop ID order, that can't answer. Its `reason` says why:

| `reason` | The shop |
| - | - |
| `uncovered` | isn't covered at all. `coverage_from` or `complete_through` can be `null`. |
| `before_coverage` | starts after the date. `coverage_from` says when it starts. |
| `after_complete_through` | is complete only through an earlier day, `complete_through`. |
| `gap_day` | has a gap day between the date and `complete_through`. `gap_day` is the earliest one. |

Narrow the call to other shops with `shop_id`, or pick a date inside the window. When the filters do answer, each shop is read through its own last complete day, and a `sales_through` notice states that day.

### Stats over a range

Listing stats and shop stats serve a range that is only partly covered. They total the days they know and say which they don't:

* **`partial_range`** means part of the range isn't covered or holds days Alura doesn't know. A point in `series` with no known day, or with a gap day, is `null`. Shop stats name the missing days in each notice's `missing`.
* **`sales_not_covered`** on listing stats means no day of the range is covered. The sales totals are `null`. When the shop has no sales coverage at all, `series` is `null` too. When the shop is covered but the range lies outside its coverage, `series` keeps its points, with each sales value `null` and the reviews still counted.
* **`refunds_partial`** means refunds are known only through `refunds_through`, before the end of the range. A refund point whose period runs past that day is `null`, not a short month.

Shop stats come from a different source, the Etsy payment ledger, with its own coverage per shop. They also use `no_financials_row` for a day inside coverage whose financials row is missing, and `metric_unavailable` for a metric whose stored value is missing on some days. See [Shop stats](/methodology/shop-stats) and [Listing stats](/methodology/listing-stats).

### Old data

Every list names shops whose data is more than 7 days old in a `stale_shops` notice. Stale data is still real data, as of its `as_of`. [Start a sync](/guides/your-shop-connect-and-sync) to bring it up to date.

## When the numbers fill in

* **A shop that isn't covered** gets its window once Alura's sales data reaches it. Until then its sales numbers stay `null`.
* **`sales_through`** moves forward after syncs, as Alura completes each day. A period that ends after it is `null` until it does.
* **A gap day** clears once Alura has stored the orders behind it correctly again.
* **`refunds_through`** moves forward on the syncs that confirm refunds, so it can lag behind `sales_through`.

A `null` that later turns into a number is expected. A made-up `0` could never be told apart from a real one afterwards, so the API doesn't return one.

## Next steps

> **[Connect and sync](/guides/your-shop-connect-and-sync)**
>
> Start a sync, and fix a shop that needs reconnecting.

> **[Listing stats](/methodology/listing-stats)**
>
> How a listing's units, orders and revenue are counted.

> **[Shop stats](/methodology/shop-stats)**
>
> Revenue, costs and profit from the payment ledger.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.