# Moments & trackers

> Define trackers, then log, read, edit, and delete moments with the Reassign REST API.

Source: https://reassign.dev/reference/moments-and-trackers/

A tracker is a thing that you count or rate, for example coffee, mood, or water. A moment is one log against a tracker at a given time.

The machine-readable spec of all public endpoints: [openapi.json](https://reassign.dev/openapi.json).

## Concepts

| Term           | Meaning                                                                                          |
| -------------- | ------------------------------------------------------------------------------------------------ |
| Tracker        | The definition of a thing to log. It has a name, a color, and a scale.                           |
| Moment         | One logged entry against a tracker. A day can have many moments.                                 |
| Shared default | A tracker that Reassign gives to all users. When you edit or archive it, you get a private copy. |

### Scales

| Scale      | Meaning                                                                                                      | Default value of a quick log        |
| ---------- | ------------------------------------------------------------------------------------------------------------ | ----------------------------------- |
| `tally`    | Each log adds the stored `step`, or 1 when no `step` is stored. The API refuses `step` on a `tally` tracker. | The stored `step`, or 1             |
| `rating`   | A bounded scale, for example mood from 1 to 5. The default bounds are 1 and 5.                               | The midpoint of the bounds, rounded |
| `quantity` | An amount in `unit`, for example 250 ml.                                                                     | `step`                              |

### Time of a moment

- `time` is a local datetime, `"YYYY-MM-DDTHH:MM"`, in the time zone of the account. It has no seconds and no offset.
- The day of a moment is the date part of `time`. A moment after midnight has the date of the next day, for example `"2026-09-24T00:30"`.

### Scopes

| Route                           | Scope                           |
| ------------------------------- | ------------------------------- |
| `GET /trackers`, `GET /moments` | `events:read` or `events:write` |
| All other routes on this page   | `events:write`                  |

All routes need Reassign Pro. The 7-day free trial includes it. Refer to [Authentication](https://reassign.dev/authentication.md).

- A create (`POST /trackers`, `POST /moments`) returns `201`. The other routes return `200`.
- A request body rejects a field that it does not know, with `422` and the code `validation`. A query parameter that the route does not know also returns `422`.
- The routes on this page return no `undoToken`. This is a stated exception to the undo rule of the API. To reverse a change, send a different write, for example a `DELETE` after a log.

For the error format, refer to [Errors](https://reassign.dev/errors.md).

## The tracker model

- `color` is a hex color with 6 lowercase digits, `#rrggbb`.
- `icon` is `""` when no icon is set.
- The server sends `unit`, `step`, `ratingMin`, `ratingMax`, `target`, `energyKernel`, `energyImpact`, and `energyRole` only when they are set.
- `isGlobal` is always present. It is `true` for a shared default.
- `target` is a daily target. You cannot set it with this API.
- `overridesId` is always present. On a private copy of a shared default, it is the ID of the shared default. Else it is `null`.
- `order` is the display order in the tray of the app. It is a whole number and it is always present. `GET /trackers` sorts the trackers from the lowest `order` to the highest.

* `id` (_string, required_)
* `name` (_string, required_)
* `icon` (_string, required_)
* `color` (_string, required_)
* `scale` (_"tally" | "rating" | "quantity", required_)
* `order` (_integer, required_)
* `unit` (_string_)
* `step` (_number_)
* `ratingMin` (_integer_)
* `ratingMax` (_integer_)
* `target` (_number_)
* `isGlobal` (_boolean, required_)
* `overridesId` (_string | null, required_): The global tracker this one replaces; null when none.
* `energyImpact` (_number_): Read-only legacy value. Nothing writes it now; `energyKernel` is the energy effect.
* `energyRole` (_string_): Read-only legacy value. Nothing writes it now; `energyKernel` is the energy effect.
* `energyKernel` (_object_)
  - `X` (_number, required_)
  - `tauAbs` (_number, required_)
  - `tauElim` (_number, required_)
  - `rebound` (_object | null_)
    - `delay` (_number, required_)
    - `X` (_number, required_)
    - `tauAbs` (_number, required_)
    - `tauElim` (_number, required_)
  - `note` (_string | null_)

## Display order

| Action       | Effect on `order`                                                                                                                                                        |
| ------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| Create       | Reassign sets `order` to the highest `order` of all your trackers and the shared defaults, plus 1. Archived trackers count. The new tracker goes to the end of the tray. |
| Update       | This API cannot change `order`. The body of `PATCH /trackers/{id}` does not accept it.                                                                                   |
| Private copy | When you edit a shared default, the copy keeps the `order` of the shared default.                                                                                        |

## Energy fields (Beta)

> The energy fields are Beta. Their names, values, and effect can change.

The energy fields tell the energy model of Reassign how a log of this tracker changes the energy curve. The curve is a score of alertness from 0 to 100. Refer to [Energy & weather](https://reassign.dev/reference/energy-and-weather.md).

| Field          | What it is                                                                                                                                                                                                                                      | When it is absent                                              |
| -------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------- |
| `energyKernel` | The full energy effect of one log. This is the main source for the energy model. Refer to the next table.                                                                                                                                       | The model uses `energyImpact`.                                 |
| `energyImpact` | An old form of the effect: one signed number. The model uses it only when `energyKernel` is absent or has `X` equal to 0. The effect then has a fixed shape: onset 0.1 hours, decay 3 hours, and no rebound.                                    | With no `energyKernel` also, a log has no effect on the curve. |
| `energyRole`   | A free-form text tag, for example `caffeine`. It has no fixed list of values. For a tracker, the model uses it only as the key that groups the logs for the calibration of each user. With no `energyRole`, the name of the tracker is the key. | The name is the key.                                           |

The fields of `energyKernel`:

| Field     | Meaning                                                                                                                                                         |
| --------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `X`       | The peak of the effect, in points on the 0 to 100 scale. A positive value lifts the curve, for example coffee. A negative value lowers it. `0` gives no effect. |
| `tauAbs`  | The onset time, in hours. A small value makes a fast rise.                                                                                                      |
| `tauElim` | The decay time, in hours. A large value makes a slow return. When `tauElim` is not more than `tauAbs`, the main effect is 0.                                    |
| `rebound` | Optional. A second effect with the opposite sign, for example the dip after caffeine. It has `delay` (hours after the log), `X`, `tauAbs`, and `tauElim`.       |
| `note`    | Optional. A short reason in plain words. The app shows it on the energy dial.                                                                                   |

How the model uses a log:

- The effect starts at the time of the moment. It rises with `tauAbs`, reaches `X` at its peak, and falls with `tauElim`.
- The model multiplies `X` by the amount of the log, with a minimum of 1. For a `quantity` tracker, the amount is `value` divided by `step`. For a `tally` tracker, the amount is `value`. For example, a log with `value` 2 on a `tally` tracker makes the peak and the rebound two times larger.
- A `rating` tracker has no energy effect. The model uses a rating log as a felt-energy label, for example a mood.
- The calibration of each user can make the effect of a key larger, smaller, or opposite.

Examples from the shared defaults:

| Tracker | `energyKernel`                                                                                         |
| ------- | ------------------------------------------------------------------------------------------------------ |
| Coffee  | `X` 15, `tauAbs` 0.12, `tauElim` 4, and a `rebound` with `delay` 5, `X` -6, `tauAbs` 0.3, `tauElim` 2. |
| Water   | `X` 0: no effect on the curve.                                                                         |

Who writes the energy fields:

- You cannot write them with this API. `POST /trackers` and `PATCH /trackers/{id}` reject them with `422` and the code `validation`.
- After a create or a change of name, Reassign can score the tracker with AI. The score sets or clears `energyKernel`. This occurs after the response, so the response does not show the score.
- Reassign scores a tracker only when all these conditions are true:
  - The scale is not `rating`.
  - The tracker is not archived.
  - The name has 2 or more characters.
  - The name changed since the last score.
  - The account has a trial or Pro plan, with AI classification on.
- A private copy of a shared default keeps the energy fields of the shared default.

## The moment model

- `time` is the local datetime of the moment. Refer to [Concepts](https://reassign.dev/reference/moments-and-trackers.md#concepts).
- `value` is the effective value: the stored value, or else the default value of the tracker.
- The server sends `note` only when it is not empty.

* `id` (_string, required_)
* `trackerId` (_string, required_)
* `time` (_string, required_)
* `value` (_number, required_)
* `note` (_string_)

---

## List trackers

**GET** `/api/v1/trackers`

Get all trackers that you can log against. The list contains your own trackers and the shared defaults. It does not contain archived trackers. The list is in [display order](https://reassign.dev/reference/moments-and-trackers.md#display-order): the lowest `order` first.

Each item of `trackers` is a [tracker object](https://reassign.dev/reference/moments-and-trackers.md#the-tracker-model).

### Response fields

- `trackers` (_object\[], required_)
  - `id` (_string, required_)
  - `name` (_string, required_)
  - `icon` (_string, required_)
  - `color` (_string, required_)
  - `scale` (_"tally" | "rating" | "quantity", required_)
  - `order` (_integer, required_)
  - `unit` (_string_)
  - `step` (_number_)
  - `ratingMin` (_integer_)
  - `ratingMax` (_integer_)
  - `target` (_number_)
  - `isGlobal` (_boolean, required_)
  - `overridesId` (_string | null, required_): The global tracker this one replaces; null when none.
  - `energyImpact` (_number_): Read-only legacy value. Nothing writes it now; `energyKernel` is the energy effect.
  - `energyRole` (_string_): Read-only legacy value. Nothing writes it now; `energyKernel` is the energy effect.
  - `energyKernel` (_object_)
    - `X` (_number, required_)
    - `tauAbs` (_number, required_)
    - `tauElim` (_number, required_)
    - `rebound` (_object | null_)
      - `delay` (_number, required_)
      - `X` (_number, required_)
      - `tauAbs` (_number, required_)
      - `tauElim` (_number, required_)
    - `note` (_string | null_)

### Responses

| Status | Codes                 | Meaning                                                                                                                                                                                                                                                                                                                                                                                                                                                                                |
| ------ | --------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `200`  | —                     | Success.                                                                                                                                                                                                                                                                                                                                                                                                                                                                               |
| `401`  | `unauthorized`        | No Bearer token and no session cookie, or the token is invalid, expired, or revoked. The response carries a `WWW-Authenticate` challenge that points at the protected-resource metadata.                                                                                                                                                                                                                                                                                               |
| `403`  | `permission`, `scope` | A token without `events:read` or `events:write` gets `scope` "This token is not authorized to read events (requires events:read or events:write scope)." An account without Reassign Pro access gets `permission` "Reassign needs an active subscription." The 7-day free trial includes Reassign Pro. A cookie session from another site gets `permission` "Cross-origin request."                                                                                                    |
| `422`  | `validation`          | The request has a query parameter; this operation takes none.                                                                                                                                                                                                                                                                                                                                                                                                                          |
| `429`  | `rate_limited`        | Too many requests. The edge firewall limits each IP to 2000 requests per minute across `/api/v1`; that 429 comes from the edge and can have a body that is not this JSON and no `Retry-After` header. When an edge 429 has no `Retry-After`, retry after 60 seconds. The app also limits each Bearer credential to 600 requests per minute, and some routes limit each user. These limits return this body. This app 429 always has `Retry-After`: retry after that number of seconds. |
| `503`  | `internal`            | A fault on our side. The request is safe to retry.                                                                                                                                                                                                                                                                                                                                                                                                                                     |

**Request** — **GET** `/api/v1/trackers`

**cURL**

```bash
curl "https://reassign.app/api/v1/trackers" \
  -H "Authorization: Bearer rsk_live_YOUR_TOKEN"
```

**JavaScript**

```js
const res = await fetch("https://reassign.app/api/v1/trackers", {
  headers: {
    Authorization: "Bearer rsk_live_YOUR_TOKEN",
  },
});
const data = await res.json();
```

**Response**

```json
{
  "trackers": [
    {
      "id": "00000000-0000-4000-8000-000000000010",
      "name": "Coffee",
      "icon": "coffee",
      "color": "#8b5cf6",
      "scale": "tally",
      "order": 0,
      "step": 1,
      "isGlobal": false,
      "overridesId": null
    },
    {
      "id": "00000000-0000-4000-8000-000000000012",
      "name": "Mood",
      "icon": "smile",
      "color": "#f59e0b",
      "scale": "rating",
      "order": 1,
      "ratingMin": 1,
      "ratingMax": 5,
      "isGlobal": false,
      "overridesId": null
    }
  ]
}
```

---

## Create a tracker

**POST** `/api/v1/trackers`

Create one tracker. The new tracker goes to the end of the tray: its `order` is the highest `order` plus 1. The response is `201` with the new tracker in `created`.

| Field                    | Rules                                                                                                  |
| ------------------------ | ------------------------------------------------------------------------------------------------------ |
| `name`                   | The server removes the spaces at the start and at the end. Then the name must have 1 to 60 characters. |
| `scale`                  | `tally`, `rating`, or `quantity`.                                                                      |
| `color`                  | A hex color with 6 lowercase digits, `#rrggbb`. Omit `color` to get the default color, `#64748b`.      |
| `ratingMin`, `ratingMax` | For `rating` only. Whole numbers. The defaults are 1 and 5.                                            |
| `unit`                   | For `quantity` only. The unit, for example `ml`.                                                       |
| `step`                   | For `quantity` only. The amount of one log, more than 0. The default is 1.                             |

- A field of a different scale returns 422, for example `` `unit`: is not valid with scale `tally`. ``
- A `ratingMin` above `ratingMax` returns 422. The server compares the bounds after it applies the defaults.
- A color that is not lowercase `#rrggbb` returns 422.
- You cannot send `order` or the [energy fields](https://reassign.dev/reference/moments-and-trackers.md#energy-fields-beta). The body rejects them with 422. Reassign can add `energyKernel` after the response (Beta).

### Request body

- `name` (_string, required_): The tracker name, 1 to 60 characters.
- `scale` (_"tally" | "rating" | "quantity", required_): `tally` counts: a log without `value` adds the read `step`, 1 when absent (a write cannot set `step` on a tally). `rating` is a bounded scale. `quantity` is an amount in `unit`; a log without `value` adds `step`.
- `color` (_string_)
- `unit` (_string | null_): Only with scale `quantity`.
- `step` (_number_): Only with scale `quantity`.
- `ratingMin` (_integer_): Only with scale `rating`.
- `ratingMax` (_integer_): Only with scale `rating`.

### Response fields

- `created` (_object, required_)
  - `id` (_string, required_)
  - `name` (_string, required_)
  - `icon` (_string, required_)
  - `color` (_string, required_)
  - `scale` (_"tally" | "rating" | "quantity", required_)
  - `order` (_integer, required_)
  - `unit` (_string_)
  - `step` (_number_)
  - `ratingMin` (_integer_)
  - `ratingMax` (_integer_)
  - `target` (_number_)
  - `isGlobal` (_boolean, required_)
  - `overridesId` (_string | null, required_): The global tracker this one replaces; null when none.
  - `energyImpact` (_number_): Read-only legacy value. Nothing writes it now; `energyKernel` is the energy effect.
  - `energyRole` (_string_): Read-only legacy value. Nothing writes it now; `energyKernel` is the energy effect.
  - `energyKernel` (_object_)
    - `X` (_number, required_)
    - `tauAbs` (_number, required_)
    - `tauElim` (_number, required_)
    - `rebound` (_object | null_)
      - `delay` (_number, required_)
      - `X` (_number, required_)
      - `tauAbs` (_number, required_)
      - `tauElim` (_number, required_)
    - `note` (_string | null_)

### Responses

| Status | Codes                 | Meaning                                                                                                                                                                                                                                                                                                                                                                                                                                                                                |
| ------ | --------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `201`  | —                     | Success.                                                                                                                                                                                                                                                                                                                                                                                                                                                                               |
| `401`  | `unauthorized`        | No Bearer token and no session cookie, or the token is invalid, expired, or revoked. The response carries a `WWW-Authenticate` challenge that points at the protected-resource metadata.                                                                                                                                                                                                                                                                                               |
| `403`  | `permission`, `scope` | A token without `events:write` gets `scope` "This token is not authorized to modify events (missing events:write scope)." An account without Reassign Pro access gets `permission` "Reassign needs an active subscription." The 7-day free trial includes Reassign Pro. A cookie session from another site gets `permission` "Cross-origin request."                                                                                                                                   |
| `422`  | `validation`          | The request body is not valid. The message names the first bad field by its path. A repeated or unknown query parameter is also a 422.                                                                                                                                                                                                                                                                                                                                                 |
| `429`  | `rate_limited`        | Too many requests. The edge firewall limits each IP to 2000 requests per minute across `/api/v1`; that 429 comes from the edge and can have a body that is not this JSON and no `Retry-After` header. When an edge 429 has no `Retry-After`, retry after 60 seconds. The app also limits each Bearer credential to 600 requests per minute, and some routes limit each user. These limits return this body. This app 429 always has `Retry-After`: retry after that number of seconds. |
| `503`  | `internal`            | A fault on our side. The request is safe to retry.                                                                                                                                                                                                                                                                                                                                                                                                                                     |

**Request** — **POST** `/api/v1/trackers`

**cURL**

```bash
curl -X POST "https://reassign.app/api/v1/trackers" \
  -H "Authorization: Bearer rsk_live_YOUR_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
  "name": "Water",
  "color": "#38bdf8",
  "scale": "quantity",
  "unit": "ml",
  "step": 250
}'
```

**JavaScript**

```js
const res = await fetch("https://reassign.app/api/v1/trackers", {
  method: "POST",
  headers: {
    Authorization: "Bearer rsk_live_YOUR_TOKEN",
    "Content-Type": "application/json",
  },
  body: JSON.stringify({
    "name": "Water",
    "color": "#38bdf8",
    "scale": "quantity",
    "unit": "ml",
    "step": 250
  }),
});
const data = await res.json();
```

**Response**

```json
{
  "created": {
    "id": "00000000-0000-4000-8000-000000000013",
    "name": "Water",
    "icon": "",
    "color": "#38bdf8",
    "scale": "quantity",
    "order": 2,
    "unit": "ml",
    "step": 250,
    "isGlobal": false,
    "overridesId": null
  }
}
```

---

## Update a tracker

**PATCH** `/api/v1/trackers/{id}`

Change the name, the color, the scale, or the scale data of a tracker. Send only the fields that you want to change. Send at least one field, or the server returns 422. The fields have the same rules as for a create.

When you edit a shared default, the server makes a private copy for you. The `id` in `updated` is then the ID of the copy, not the ID in the path.

- The server checks the scale fields against the scale of the result. When you do not send `scale`, that is the stored scale. A field of a different scale returns 422, and the server keeps the old tracker.
- When you change `ratingMin`, `ratingMax`, or set `scale` to `rating`, the server checks the stored result. A `ratingMin` above `ratingMax` then returns 422, and the server keeps the old tracker.
- Send `unit: null` to remove the unit of a `quantity` tracker.
- A tracker ID that does not exist returns 404 with the message `"Tracker not found."`.
- You cannot change `order` or the [energy fields](https://reassign.dev/reference/moments-and-trackers.md#energy-fields-beta). The body rejects them with 422. A new `name` can make Reassign score the tracker again after the response (Beta).

### Path parameters

- `id` (_string, required_): The id of the tracker.

### Request body

- `name` (_string_): The tracker name, 1 to 60 characters.
- `scale` (_"tally" | "rating" | "quantity"_): `tally` counts: a log without `value` adds the read `step`, 1 when absent (a write cannot set `step` on a tally). `rating` is a bounded scale. `quantity` is an amount in `unit`; a log without `value` adds `step`.
- `color` (_string_)
- `unit` (_string | null_): Only with scale `quantity`.
- `step` (_number_): Only with scale `quantity`.
- `ratingMin` (_integer_): Only with scale `rating`.
- `ratingMax` (_integer_): Only with scale `rating`.

### Response fields

- `updated` (_object, required_)
  - `id` (_string, required_)
  - `name` (_string, required_)
  - `icon` (_string, required_)
  - `color` (_string, required_)
  - `scale` (_"tally" | "rating" | "quantity", required_)
  - `order` (_integer, required_)
  - `unit` (_string_)
  - `step` (_number_)
  - `ratingMin` (_integer_)
  - `ratingMax` (_integer_)
  - `target` (_number_)
  - `isGlobal` (_boolean, required_)
  - `overridesId` (_string | null, required_): The global tracker this one replaces; null when none.
  - `energyImpact` (_number_): Read-only legacy value. Nothing writes it now; `energyKernel` is the energy effect.
  - `energyRole` (_string_): Read-only legacy value. Nothing writes it now; `energyKernel` is the energy effect.
  - `energyKernel` (_object_)
    - `X` (_number, required_)
    - `tauAbs` (_number, required_)
    - `tauElim` (_number, required_)
    - `rebound` (_object | null_)
      - `delay` (_number, required_)
      - `X` (_number, required_)
      - `tauAbs` (_number, required_)
      - `tauElim` (_number, required_)
    - `note` (_string | null_)

### Responses

| Status | Codes                 | Meaning                                                                                                                                                                                                                                                                                                                                                                                                                                                                                |
| ------ | --------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `200`  | —                     | Success.                                                                                                                                                                                                                                                                                                                                                                                                                                                                               |
| `401`  | `unauthorized`        | No Bearer token and no session cookie, or the token is invalid, expired, or revoked. The response carries a `WWW-Authenticate` challenge that points at the protected-resource metadata.                                                                                                                                                                                                                                                                                               |
| `403`  | `permission`, `scope` | A token without `events:write` gets `scope` "This token is not authorized to modify events (missing events:write scope)." An account without Reassign Pro access gets `permission` "Reassign needs an active subscription." The 7-day free trial includes Reassign Pro. A cookie session from another site gets `permission` "Cross-origin request."                                                                                                                                   |
| `404`  | `not_found`           | The resource that the request names does not exist.                                                                                                                                                                                                                                                                                                                                                                                                                                    |
| `422`  | `validation`          | The request body is not valid. The message names the first bad field by its path. A repeated or unknown query parameter is also a 422.                                                                                                                                                                                                                                                                                                                                                 |
| `429`  | `rate_limited`        | Too many requests. The edge firewall limits each IP to 2000 requests per minute across `/api/v1`; that 429 comes from the edge and can have a body that is not this JSON and no `Retry-After` header. When an edge 429 has no `Retry-After`, retry after 60 seconds. The app also limits each Bearer credential to 600 requests per minute, and some routes limit each user. These limits return this body. This app 429 always has `Retry-After`: retry after that number of seconds. |
| `503`  | `internal`            | A fault on our side. The request is safe to retry.                                                                                                                                                                                                                                                                                                                                                                                                                                     |

**Request** — **PATCH** `/api/v1/trackers/{id}`

**cURL**

```bash
curl -X PATCH "https://reassign.app/api/v1/trackers/TRACKER_ID" \
  -H "Authorization: Bearer rsk_live_YOUR_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
  "name": "Water intake",
  "color": "#0ea5e9"
}'
```

**JavaScript**

```js
const res = await fetch("https://reassign.app/api/v1/trackers/TRACKER_ID", {
  method: "PATCH",
  headers: {
    Authorization: "Bearer rsk_live_YOUR_TOKEN",
    "Content-Type": "application/json",
  },
  body: JSON.stringify({
    "name": "Water intake",
    "color": "#0ea5e9"
  }),
});
const data = await res.json();
```

**Response**

```json
{
  "updated": {
    "id": "00000000-0000-4000-8000-000000000013",
    "name": "Water intake",
    "icon": "",
    "color": "#0ea5e9",
    "scale": "quantity",
    "order": 2,
    "unit": "ml",
    "step": 250,
    "isGlobal": false,
    "overridesId": null
  }
}
```

---

## Archive a tracker

**DELETE** `/api/v1/trackers/{id}`

Archive a tracker. The server does not delete it. The tracker is no longer in `GET /trackers`, and you cannot log new moments against it. The moments that you logged stay readable.

- The response is `{ "deletedIds": [id] }`, with the ID from the path.
- For a shared default, the server writes an archived private copy.
- This API has no route to restore an archived tracker.
- A tracker ID that does not exist returns 404 with the message `"Tracker not found."`.

### Path parameters

- `id` (_string, required_): The id of the tracker.

### Response fields

- `deletedIds` (_string\[], required_)

### Responses

| Status | Codes                 | Meaning                                                                                                                                                                                                                                                                                                                                                                                                                                                                                |
| ------ | --------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `200`  | —                     | Success.                                                                                                                                                                                                                                                                                                                                                                                                                                                                               |
| `401`  | `unauthorized`        | No Bearer token and no session cookie, or the token is invalid, expired, or revoked. The response carries a `WWW-Authenticate` challenge that points at the protected-resource metadata.                                                                                                                                                                                                                                                                                               |
| `403`  | `permission`, `scope` | A token without `events:write` gets `scope` "This token is not authorized to modify events (missing events:write scope)." An account without Reassign Pro access gets `permission` "Reassign needs an active subscription." The 7-day free trial includes Reassign Pro. A cookie session from another site gets `permission` "Cross-origin request."                                                                                                                                   |
| `404`  | `not_found`           | The resource that the request names does not exist.                                                                                                                                                                                                                                                                                                                                                                                                                                    |
| `422`  | `validation`          | The request has a query parameter; this operation takes none.                                                                                                                                                                                                                                                                                                                                                                                                                          |
| `429`  | `rate_limited`        | Too many requests. The edge firewall limits each IP to 2000 requests per minute across `/api/v1`; that 429 comes from the edge and can have a body that is not this JSON and no `Retry-After` header. When an edge 429 has no `Retry-After`, retry after 60 seconds. The app also limits each Bearer credential to 600 requests per minute, and some routes limit each user. These limits return this body. This app 429 always has `Retry-After`: retry after that number of seconds. |
| `503`  | `internal`            | A fault on our side. The request is safe to retry.                                                                                                                                                                                                                                                                                                                                                                                                                                     |

**Request** — **DELETE** `/api/v1/trackers/{id}`

**cURL**

```bash
curl -X DELETE "https://reassign.app/api/v1/trackers/TRACKER_ID" \
  -H "Authorization: Bearer rsk_live_YOUR_TOKEN"
```

**JavaScript**

```js
const res = await fetch("https://reassign.app/api/v1/trackers/TRACKER_ID", {
  method: "DELETE",
  headers: {
    Authorization: "Bearer rsk_live_YOUR_TOKEN",
  },
});
const data = await res.json();
```

**Response**

```json
{
  "deletedIds": [
    "00000000-0000-4000-8000-000000000010"
  ]
}
```

---

## List moments

**GET** `/api/v1/moments`

Get the moments of an inclusive range of days. The list also contains moments on archived trackers.

- `from` and `to` are required. For one day, send the same date in the two.
- A range can have at most 366 days. A wider range returns 422 `` `to`: The range is longer than 366 days. ``
- A `from` after `to` returns 422.
- Each item of `entries` is a [moment object](https://reassign.dev/reference/moments-and-trackers.md#the-moment-model).

### Query parameters

- `from` (_string, required_): The first day, inclusive.
- `to` (_string, required_): The last day, inclusive; the range is at most 366 days. Equal to `from` for one day.

### Response fields

- `entries` (_object\[], required_)
  - `id` (_string, required_)
  - `trackerId` (_string, required_)
  - `time` (_string, required_)
  - `value` (_number, required_)
  - `note` (_string_)

### Responses

| Status | Codes                 | Meaning                                                                                                                                                                                                                                                                                                                                                                                                                                                                                |
| ------ | --------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `200`  | —                     | Success.                                                                                                                                                                                                                                                                                                                                                                                                                                                                               |
| `401`  | `unauthorized`        | No Bearer token and no session cookie, or the token is invalid, expired, or revoked. The response carries a `WWW-Authenticate` challenge that points at the protected-resource metadata.                                                                                                                                                                                                                                                                                               |
| `403`  | `permission`, `scope` | A token without `events:read` or `events:write` gets `scope` "This token is not authorized to read events (requires events:read or events:write scope)." An account without Reassign Pro access gets `permission` "Reassign needs an active subscription." The 7-day free trial includes Reassign Pro. A cookie session from another site gets `permission` "Cross-origin request."                                                                                                    |
| `422`  | `validation`          | A query parameter is not valid. The message names the first bad field by its path. A repeated or unknown query parameter is also a 422.                                                                                                                                                                                                                                                                                                                                                |
| `429`  | `rate_limited`        | Too many requests. The edge firewall limits each IP to 2000 requests per minute across `/api/v1`; that 429 comes from the edge and can have a body that is not this JSON and no `Retry-After` header. When an edge 429 has no `Retry-After`, retry after 60 seconds. The app also limits each Bearer credential to 600 requests per minute, and some routes limit each user. These limits return this body. This app 429 always has `Retry-After`: retry after that number of seconds. |
| `503`  | `internal`            | A fault on our side. The request is safe to retry.                                                                                                                                                                                                                                                                                                                                                                                                                                     |

**Request** — **GET** `/api/v1/moments`

**cURL**

```bash
curl "https://reassign.app/api/v1/moments?from=2026-08-14&to=2026-08-14" \
  -H "Authorization: Bearer rsk_live_YOUR_TOKEN"
```

**JavaScript**

```js
const res = await fetch("https://reassign.app/api/v1/moments?from=2026-08-14&to=2026-08-14", {
  headers: {
    Authorization: "Bearer rsk_live_YOUR_TOKEN",
  },
});
const data = await res.json();
```

**Response**

```json
{
  "entries": [
    {
      "id": "00000000-0000-4000-8000-000000000011",
      "trackerId": "00000000-0000-4000-8000-000000000010",
      "time": "2026-08-13T09:30",
      "value": 1,
      "note": "double shot"
    }
  ]
}
```

---

## Log a moment

**POST** `/api/v1/moments`

Log one moment against a tracker. A body with only `trackerId` is a quick log. The server then uses the current time and the default value of the tracker. The response is `201` with the new moment in `created`.

When you log a moment, Reassign also shows the layer of that tracker on the dial in the app.

| Field       | Rules                                                                                                                                                                                                                               |
| ----------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `trackerId` | The ID of a tracker from `GET /trackers`. An archived tracker or an unknown ID returns 404.                                                                                                                                         |
| `time`      | A local datetime, `"YYYY-MM-DDTHH:MM"`. The default is the current time.                                                                                                                                                            |
| `value`     | The default is the default value of the scale. For a `rating` tracker, the value must be a whole number from `ratingMin` to `ratingMax`. Else the server returns 422, for example `` `value` must be a whole number from 1 to 5. `` |
| `note`      | 1000 characters or less. The server removes the spaces at the start and at the end. A blank note gives no note.                                                                                                                     |

### Request body

- `trackerId` (_string, required_): The tracker to log against.
- `time` (_string_): When it happened; now if absent.
- `value` (_number_): The value; the tracker default if absent.
- `note` (_string_)

### Response fields

- `created` (_object, required_)
  - `id` (_string, required_)
  - `trackerId` (_string, required_)
  - `time` (_string, required_)
  - `value` (_number, required_)
  - `note` (_string_)

### Responses

| Status | Codes                 | Meaning                                                                                                                                                                                                                                                                                                                                                                                                                                                                                |
| ------ | --------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `201`  | —                     | Success.                                                                                                                                                                                                                                                                                                                                                                                                                                                                               |
| `401`  | `unauthorized`        | No Bearer token and no session cookie, or the token is invalid, expired, or revoked. The response carries a `WWW-Authenticate` challenge that points at the protected-resource metadata.                                                                                                                                                                                                                                                                                               |
| `403`  | `permission`, `scope` | A token without `events:write` gets `scope` "This token is not authorized to modify events (missing events:write scope)." An account without Reassign Pro access gets `permission` "Reassign needs an active subscription." The 7-day free trial includes Reassign Pro. A cookie session from another site gets `permission` "Cross-origin request."                                                                                                                                   |
| `404`  | `not_found`           | The resource that the request names does not exist.                                                                                                                                                                                                                                                                                                                                                                                                                                    |
| `422`  | `validation`          | The request body is not valid. The message names the first bad field by its path. A repeated or unknown query parameter is also a 422.                                                                                                                                                                                                                                                                                                                                                 |
| `429`  | `rate_limited`        | Too many requests. The edge firewall limits each IP to 2000 requests per minute across `/api/v1`; that 429 comes from the edge and can have a body that is not this JSON and no `Retry-After` header. When an edge 429 has no `Retry-After`, retry after 60 seconds. The app also limits each Bearer credential to 600 requests per minute, and some routes limit each user. These limits return this body. This app 429 always has `Retry-After`: retry after that number of seconds. |
| `503`  | `internal`            | A fault on our side. The request is safe to retry.                                                                                                                                                                                                                                                                                                                                                                                                                                     |

**Request** — **POST** `/api/v1/moments`

**cURL**

```bash
curl -X POST "https://reassign.app/api/v1/moments" \
  -H "Authorization: Bearer rsk_live_YOUR_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
  "trackerId": "00000000-0000-4000-8000-000000000010",
  "time": "2026-08-13T09:30",
  "note": "double shot"
}'
```

**JavaScript**

```js
const res = await fetch("https://reassign.app/api/v1/moments", {
  method: "POST",
  headers: {
    Authorization: "Bearer rsk_live_YOUR_TOKEN",
    "Content-Type": "application/json",
  },
  body: JSON.stringify({
    "trackerId": "00000000-0000-4000-8000-000000000010",
    "time": "2026-08-13T09:30",
    "note": "double shot"
  }),
});
const data = await res.json();
```

**Response**

```json
{
  "created": {
    "id": "00000000-0000-4000-8000-000000000011",
    "trackerId": "00000000-0000-4000-8000-000000000010",
    "time": "2026-08-13T09:30",
    "value": 1,
    "note": "double shot"
  }
}
```

---

## Clear moments in a range (Beta)

**DELETE** `/api/v1/moments`

> This route is in Beta. The response can change.

Delete all moments that you logged in an inclusive range of days. The trackers do not change. The response lists the IDs of the deleted moments in `deletedIds`.

> `from` and `to` are required, with the same rules as for `GET /moments`. For one day, send the same date in the two. This route returns no `undoToken`, so you cannot reverse a clear.

### Query parameters

- `from` (_string, required_): The first day, inclusive.
- `to` (_string, required_): The last day, inclusive; the range is at most 366 days. Equal to `from` for one day.

### Response fields

- `deletedIds` (_string\[], required_)

### Responses

| Status | Codes                 | Meaning                                                                                                                                                                                                                                                                                                                                                                                                                                                                                |
| ------ | --------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `200`  | —                     | Success.                                                                                                                                                                                                                                                                                                                                                                                                                                                                               |
| `401`  | `unauthorized`        | No Bearer token and no session cookie, or the token is invalid, expired, or revoked. The response carries a `WWW-Authenticate` challenge that points at the protected-resource metadata.                                                                                                                                                                                                                                                                                               |
| `403`  | `permission`, `scope` | A token without `events:write` gets `scope` "This token is not authorized to modify events (missing events:write scope)." An account without Reassign Pro access gets `permission` "Reassign needs an active subscription." The 7-day free trial includes Reassign Pro. A cookie session from another site gets `permission` "Cross-origin request."                                                                                                                                   |
| `422`  | `validation`          | A query parameter is not valid. The message names the first bad field by its path. A repeated or unknown query parameter is also a 422.                                                                                                                                                                                                                                                                                                                                                |
| `429`  | `rate_limited`        | Too many requests. The edge firewall limits each IP to 2000 requests per minute across `/api/v1`; that 429 comes from the edge and can have a body that is not this JSON and no `Retry-After` header. When an edge 429 has no `Retry-After`, retry after 60 seconds. The app also limits each Bearer credential to 600 requests per minute, and some routes limit each user. These limits return this body. This app 429 always has `Retry-After`: retry after that number of seconds. |
| `503`  | `internal`            | A fault on our side. The request is safe to retry.                                                                                                                                                                                                                                                                                                                                                                                                                                     |

**Request** — **DELETE** `/api/v1/moments`

**cURL**

```bash
curl -X DELETE "https://reassign.app/api/v1/moments?from=2026-08-14&to=2026-08-14" \
  -H "Authorization: Bearer rsk_live_YOUR_TOKEN"
```

**JavaScript**

```js
const res = await fetch("https://reassign.app/api/v1/moments?from=2026-08-14&to=2026-08-14", {
  method: "DELETE",
  headers: {
    Authorization: "Bearer rsk_live_YOUR_TOKEN",
  },
});
const data = await res.json();
```

**Response**

```json
{
  "deletedIds": [
    "00000000-0000-4000-8000-000000000011"
  ]
}
```

---

## Update a moment

**PATCH** `/api/v1/moments/{id}`

Change the time, the value, or the note of one moment. The fields that you do not send keep their stored value. You cannot change `trackerId`. Send at least one field, or the server returns 422.

- A new `time` can also move the moment to a different day.
- `value` has the same rules as for a log. For a `rating` tracker, a value that is not a whole number inside the bounds returns 422.
- Send `note: null` to remove the note.
- A moment ID that does not exist returns 404 with the message `"No logged entry with that id."`.

### Path parameters

- `id` (_string, required_): The id of the logged entry.

### Request body

- `time` (_string_)
- `value` (_number_)
- `note` (_string | null_)

### Response fields

- `updated` (_object, required_)
  - `id` (_string, required_)
  - `trackerId` (_string, required_)
  - `time` (_string, required_)
  - `value` (_number, required_)
  - `note` (_string_)

### Responses

| Status | Codes                 | Meaning                                                                                                                                                                                                                                                                                                                                                                                                                                                                                |
| ------ | --------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `200`  | —                     | Success.                                                                                                                                                                                                                                                                                                                                                                                                                                                                               |
| `401`  | `unauthorized`        | No Bearer token and no session cookie, or the token is invalid, expired, or revoked. The response carries a `WWW-Authenticate` challenge that points at the protected-resource metadata.                                                                                                                                                                                                                                                                                               |
| `403`  | `permission`, `scope` | A token without `events:write` gets `scope` "This token is not authorized to modify events (missing events:write scope)." An account without Reassign Pro access gets `permission` "Reassign needs an active subscription." The 7-day free trial includes Reassign Pro. A cookie session from another site gets `permission` "Cross-origin request."                                                                                                                                   |
| `404`  | `not_found`           | The resource that the request names does not exist.                                                                                                                                                                                                                                                                                                                                                                                                                                    |
| `422`  | `validation`          | The request body is not valid. The message names the first bad field by its path. A repeated or unknown query parameter is also a 422.                                                                                                                                                                                                                                                                                                                                                 |
| `429`  | `rate_limited`        | Too many requests. The edge firewall limits each IP to 2000 requests per minute across `/api/v1`; that 429 comes from the edge and can have a body that is not this JSON and no `Retry-After` header. When an edge 429 has no `Retry-After`, retry after 60 seconds. The app also limits each Bearer credential to 600 requests per minute, and some routes limit each user. These limits return this body. This app 429 always has `Retry-After`: retry after that number of seconds. |
| `503`  | `internal`            | A fault on our side. The request is safe to retry.                                                                                                                                                                                                                                                                                                                                                                                                                                     |

**Request** — **PATCH** `/api/v1/moments/{id}`

**cURL**

```bash
curl -X PATCH "https://reassign.app/api/v1/moments/MOMENT_ID" \
  -H "Authorization: Bearer rsk_live_YOUR_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
  "time": "2026-08-13T10:15",
  "value": 2
}'
```

**JavaScript**

```js
const res = await fetch("https://reassign.app/api/v1/moments/MOMENT_ID", {
  method: "PATCH",
  headers: {
    Authorization: "Bearer rsk_live_YOUR_TOKEN",
    "Content-Type": "application/json",
  },
  body: JSON.stringify({
    "time": "2026-08-13T10:15",
    "value": 2
  }),
});
const data = await res.json();
```

**Response**

```json
{
  "updated": {
    "id": "00000000-0000-4000-8000-000000000011",
    "trackerId": "00000000-0000-4000-8000-000000000010",
    "time": "2026-08-13T10:15",
    "value": 2,
    "note": "double shot"
  }
}
```

---

## Delete a moment

**DELETE** `/api/v1/moments/{id}`

Delete one moment, for example a wrong log. The tracker does not change. The response is `{ "deletedIds": [id] }`. This route returns no `undoToken`.

A moment ID that does not exist returns 404 with the message `"No logged entry with that id."`.

### Path parameters

- `id` (_string, required_): The id of the logged entry.

### Response fields

- `deletedIds` (_string\[], required_)

### Responses

| Status | Codes                 | Meaning                                                                                                                                                                                                                                                                                                                                                                                                                                                                                |
| ------ | --------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `200`  | —                     | Success.                                                                                                                                                                                                                                                                                                                                                                                                                                                                               |
| `401`  | `unauthorized`        | No Bearer token and no session cookie, or the token is invalid, expired, or revoked. The response carries a `WWW-Authenticate` challenge that points at the protected-resource metadata.                                                                                                                                                                                                                                                                                               |
| `403`  | `permission`, `scope` | A token without `events:write` gets `scope` "This token is not authorized to modify events (missing events:write scope)." An account without Reassign Pro access gets `permission` "Reassign needs an active subscription." The 7-day free trial includes Reassign Pro. A cookie session from another site gets `permission` "Cross-origin request."                                                                                                                                   |
| `404`  | `not_found`           | The resource that the request names does not exist.                                                                                                                                                                                                                                                                                                                                                                                                                                    |
| `422`  | `validation`          | The request has a query parameter; this operation takes none.                                                                                                                                                                                                                                                                                                                                                                                                                          |
| `429`  | `rate_limited`        | Too many requests. The edge firewall limits each IP to 2000 requests per minute across `/api/v1`; that 429 comes from the edge and can have a body that is not this JSON and no `Retry-After` header. When an edge 429 has no `Retry-After`, retry after 60 seconds. The app also limits each Bearer credential to 600 requests per minute, and some routes limit each user. These limits return this body. This app 429 always has `Retry-After`: retry after that number of seconds. |
| `503`  | `internal`            | A fault on our side. The request is safe to retry.                                                                                                                                                                                                                                                                                                                                                                                                                                     |

**Request** — **DELETE** `/api/v1/moments/{id}`

**cURL**

```bash
curl -X DELETE "https://reassign.app/api/v1/moments/MOMENT_ID" \
  -H "Authorization: Bearer rsk_live_YOUR_TOKEN"
```

**JavaScript**

```js
const res = await fetch("https://reassign.app/api/v1/moments/MOMENT_ID", {
  method: "DELETE",
  headers: {
    Authorization: "Bearer rsk_live_YOUR_TOKEN",
  },
});
const data = await res.json();
```

**Response**

```json
{
  "deletedIds": [
    "00000000-0000-4000-8000-000000000011"
  ]
}
```
