# Time series

{{% notice style="note" title="Limited access" icon="lock" %}}
{{% include file="include/restricted_access.md" hidefirstheading="true"  %}}
{{% /notice %}}

## What a time series is

A **time series** turns the stream of values a device sends into a table of rows over time: one row per
device per time period, with each column holding a value **aggregated** over that period.

Ask for the average temperature per hour of ten thousand devices for the last month. Over
[data points](../data_points/) that is millions of raw values to fetch and aggregate yourself. Over a
time series it is already computed — the engine aggregated each hour as the data arrived.

That is the trade: you declare up front what you want aggregated and how, and in exchange the query is
cheap.

## Time buckets

The aggregation period is called a **time bucket**, and two fields define it:

| Field | Meaning |
|---|---|
| `origin` | The starting date of the time series |
| `timeBucket` | The length of each period in seconds, counted from `origin` |

With an origin of `2022-01-01T00:00:00.000Z` and a one hour bucket, the first period runs from
`2022-01-01T00:00:00.001Z` to `2022-01-01T01:00:00.000Z`, the second from `2022-01-01T01:00:00.001Z` to
`2022-01-01T02:00:00.000Z`, and so on:

```log
2022-01-01T00:00:00.000Z -> 2022-01-01T01:00:00.000Z -> 2022-01-01T02:00:00.000Z ->...
```

Setting `timeBucket` to `0` seconds switches the engine into a different mode, storing every value instead
of aggregating:

- With only **context** columns defined, one record is saved per event received, and only when a column
  value actually changed — so you get a change log over time.
- With **aggregated** columns defined, data is grouped by the `at` field of the incoming data points, and
  the aggregation function is applied when a new event arrives with the same `at`.

## Which store do I want?

| | Time series | [Data points](../data_points/) | [Data sets](../data_sets/) |
|---|---|---|---|
| Holds | Values aggregated per period | Every raw value | Latest values, flat table |
| Rows | One per device per bucket | One per measurement | One per device |
| Aggregation | Computed on ingestion | You compute it | None |
| Best for | Trends and history at scale | Auditing exact readings | Exports and tabular views |

## The two halves of the API

**[Defining a time series](defining/)** is administration: you declare the columns, their aggregation
functions, the bucket length and the retention. Done once, usually by an administrator.

**[Querying a time series](querying/)** is what applications do every day: `POST` a filter and read rows
back, as JSON or CSV.

The aggregation functions available to columns come from the
**[time series functions catalog](timeseries_functions/)**, which also lets you register your own.

{{% children sort="weight" %}}

## API specification

{{< openapi src="north-provision-timeseries.yaml" >}}
