---
title: Check-ins
description: Read an Objective's check-in history and record a new value and its progress.
product: gps
section: api
order: 13
---

# Check-ins

A check-in records an [Objective](objectives.md)'s latest value. GPS stores the value, works out the new progress, and keeps the change in the Objective's history. The history also holds check-ins made in GPS and updates from connected integrations.

| Method | Path | Scope | Webhook |
|--------|------|-------|---------|
| `GET` | [`/objectives/{objectiveId}/progress`](#list-check-ins) | `progress:read` | |
| `POST` | [`/objectives/{objectiveId}/progress`](#record-a-check-in) | `progress:write` | None (see below) |

The JavaScript examples assume a `headers` object holding your key:

```js
const headers = {
  'X-API-Key': process.env.GPS_API_KEY_ID,
  'X-API-Secret': process.env.GPS_API_SECRET,
};
```

## The check-in object

| Field | Type | Notes |
|-------|------|-------|
| `id` | string | Check-in ID. |
| `objectiveId` | string | |
| `notes` | string or null | |
| `updatedBy` | string or null | Member ID of who recorded it. `null` for some integration updates. |
| `value` | number or null | The value sent with the check-in. |
| `previousProgress` | number or null | Progress percentage before. |
| `newProgress` | number or null | Progress percentage after. |
| `progressChange` | number or null | `newProgress` minus `previousProgress`. |
| `previousValue` | number or null | The Objective's `currentValue` before. |
| `newValue` | number or null | The Objective's `currentValue` after. |
| `createdAt` | string | ISO 8601 timestamp. |

## List check-ins

`GET /objectives/{objectiveId}/progress`

Returns the Objective's check-in history, newest first.

**Scope:** `progress:read`

| Parameter | In | Notes |
|-----------|----|-------|
| `objectiveId` | path | Objective ID. |
| `limit`, `offset` | query | See [Pagination](../conventions.md#pagination). |

```bash
curl https://plan.empiraa.app/api/v1/objectives/c7e9a1b3-4d5f-4a6b-9c8d-1e2f3a4b5c6d/progress \
  -H "X-API-Key: $GPS_API_KEY_ID" \
  -H "X-API-Secret: $GPS_API_SECRET"
```

```js
const res = await fetch(
  'https://plan.empiraa.app/api/v1/objectives/c7e9a1b3-4d5f-4a6b-9c8d-1e2f3a4b5c6d/progress',
  { headers },
);
const { data: checkIns, meta } = await res.json();
```

**Response `200`**

```json
{
  "data": [
    {
      "id": "4b6d8f0b-2c3e-4f5a-b6c7-8d9e0f1a2b3c",
      "objectiveId": "c7e9a1b3-4d5f-4a6b-9c8d-1e2f3a4b5c6d",
      "notes": "Two new annual contracts signed",
      "updatedBy": "5d7f9b1d-3e5a-4c7e-9f1a-2b3c4d5e6f70",
      "value": 60000,
      "previousProgress": 45,
      "newProgress": 50,
      "progressChange": 5,
      "previousValue": 54000,
      "newValue": 60000,
      "createdAt": "2026-11-12T03:15:42.118Z"
    }
  ],
  "meta": { "total": 1, "limit": 50, "offset": 0 }
}
```

**Errors:** `404 NOT_FOUND` if the Objective doesn't exist or is in another organization; `422 VALIDATION_ERROR` for a bad `limit` or `offset`.

## Record a check-in

`POST /objectives/{objectiveId}/progress`

Sets the Objective's `currentValue` to `value`, recalculates its `progress`, and adds an entry to its history.

How progress is worked out:

- **The Objective has a `targetValue`:** progress is `value ÷ targetValue × 100`. With a target of 120,000, a value of 60,000 gives 50%.
- **No `targetValue`:** `value` is taken as the progress percentage itself.
- **Status Objectives** (measurement type `Status (On Track / Off Track / Complete)`) are done or not done: a `value` of 1 or more records done, anything less records not done.

Progress can go past 100%, since beating a target is a real result, but it must stay between 0% and 1000% (0% and 100% for status Objectives). A check-in that would land outside that range is refused.

A check-in doesn't change the Objective's `status`.

**Scope:** `progress:write`

**Webhook:** none. A check-in through the API doesn't fire `objective.updated`, even though the Objective's progress changes. If you rely on webhooks, re-read the Objective after the check-in.

| Path parameter | Notes |
|----------------|-------|
| `objectiveId` | Objective ID. |

**Request body**

| Field | Type | Required | Allowed values | Notes |
|-------|------|----------|----------------|-------|
| `value` | number | Yes | | The Objective's new value. |
| `notes` | string or null | No | Up to 2,000 characters | |
| `onBehalfOf` | string | No | Active member ID | Who recorded it (`updatedBy`). Default: the key's creator. See [Attribution](../conventions.md#attribution-with-onbehalfof). |

```bash
curl -X POST https://plan.empiraa.app/api/v1/objectives/c7e9a1b3-4d5f-4a6b-9c8d-1e2f3a4b5c6d/progress \
  -H "X-API-Key: $GPS_API_KEY_ID" \
  -H "X-API-Secret: $GPS_API_SECRET" \
  -H "Content-Type: application/json" \
  -d '{ "value": 60000, "notes": "Two new annual contracts signed" }'
```

```js
const res = await fetch(
  'https://plan.empiraa.app/api/v1/objectives/c7e9a1b3-4d5f-4a6b-9c8d-1e2f3a4b5c6d/progress',
  {
    method: 'POST',
    headers: { ...headers, 'Content-Type': 'application/json' },
    body: JSON.stringify({ value: 60000, notes: 'Two new annual contracts signed' }),
  },
);
const { data: checkIn } = await res.json();
```

**Response `201`**

```json
{
  "data": {
    "id": "4b6d8f0b-2c3e-4f5a-b6c7-8d9e0f1a2b3c",
    "objectiveId": "c7e9a1b3-4d5f-4a6b-9c8d-1e2f3a4b5c6d",
    "notes": "Two new annual contracts signed",
    "updatedBy": "5d7f9b1d-3e5a-4c7e-9f1a-2b3c4d5e6f70",
    "value": 60000,
    "previousProgress": 45,
    "newProgress": 50,
    "progressChange": 5,
    "previousValue": 54000,
    "newValue": 60000,
    "createdAt": "2026-11-12T03:15:42.118Z"
  }
}
```

**Errors**

| Status | `code` | When |
|--------|--------|------|
| 404 | `NOT_FOUND` | The Objective doesn't exist or is in another organization. |
| 422 | `VALIDATION_ERROR` | `value` is missing or not a number, `notes` is too long, or the resulting progress is out of range. The `error` text explains the range for that Objective. |
| 422 | `INVALID_USERS` | `onBehalfOf` isn't an active member. |
| 422 | `NO_KEY_OWNER` | No `onBehalfOf`, and the key's creator is no longer a member. |

Example of an out-of-range check-in:

```json
{
  "error": "Progress can go past 100% — that's what beating a target looks like — but it has to be between 0% and 1000%.",
  "code": "VALIDATION_ERROR",
  "docs": "https://empiraa.com/docs/api"
}
```
