- Overview
- Authentication
- Errors and limits
- Code examples
- Moving from Classic
- KPIs
- KPI Categories
- KPI Categories / KPIs
- Breakdown Items
- KPI Entries
- KPI Frequencies
- KPI Icons
- KPI Units
- Reports
- Users
SimpleKPI AI › API reference
KPI Entries
Entries are the actual and target values recorded against a KPI for a date. This is the endpoint most integrations spend their time in.
Writes are an upsert: an entry is identified by the combination
of kpi_id, source_id and entry_date, so
sending the same combination twice updates the existing row rather than creating a
second one. Pushing a whole day or month at once is what
the batch endpoint is for.
Base URL https://api.simplekpi.com/v1
Endpoints
- List KPI entries —
GET /v1/kpientries - Add or update a KPI entry —
POST /v1/kpientries - Get a KPI entry —
GET /v1/kpientries/{id} - Update a KPI entry —
PUT /v1/kpientries/{id} - Delete a KPI entry —
DELETE /v1/kpientries/{id} - Add or update many KPI entries —
POST /v1/kpientries/list
KPI entry
Entry object to send
| Name | Type | Read Only | Mandatory | Max Len. | Notes |
|---|---|---|---|---|---|
user_id | integer | no | no | Nullable. The user the entry is recorded against. Null on a KPI with no breakdown. | |
email | string | no | no | Nullable. Identifies the user by email address instead of user_id. Send one or the other, not both. Kept for integrations built against v1. | |
kpi_id | integer (16 bit) | no | no | The KPI the entry belongs to. | |
source_id | integer | no | no | Nullable. The breakdown source the value belongs to. Omit it and the entry goes to the KPI's default: source 0 for a KPI with no breakdown, otherwise the source the user is granted. | |
entry_date | datetime | no | no | The date the value is recorded against. It must be a date the KPI's frequency allows — a weekly KPI will reject a mid-week date. | |
actual | decimal | no | no | Nullable. The value achieved. | |
target | decimal | no | no | Nullable. The value being aimed at. | |
notes | string | no | no | 500 | Nullable. Free text stored with the entry. |
setActual | boolean | no | no | Send true to write actual, false to leave whatever is already stored untouched. This is how you update a target without clearing the actual. | |
setTarget | boolean | no | no | Send true to write target, false to leave the stored value untouched. | |
setNotes | boolean | no | no | Send true to write notes, false to leave the stored text untouched. | |
addToActual | boolean | no | no | Send true to add actual to the stored value instead of replacing it. Useful for feeding in events as they happen rather than recalculating a daily total. |
Entry object returned
| Name | Type | Read Only | Mandatory | Max Len. | Notes |
|---|---|---|---|---|---|
id | integer (64 bit) | yes | no | Assigned by SimpleKPI when the entry is first stored. | |
user_id | integer | yes | no | Nullable. The user the entry is recorded against. Null on a KPI with no breakdown. | |
source_id | integer | yes | no | The breakdown source the value belongs to. Omit it and the entry goes to the KPI's default: source 0 for a KPI with no breakdown, otherwise the source the user is granted. | |
kpi_id | integer (16 bit) | yes | no | The KPI the entry belongs to. | |
entry_date | datetime | yes | no | The date the value is recorded against. It must be a date the KPI's frequency allows — a weekly KPI will reject a mid-week date. | |
actual | decimal | yes | no | Nullable. The value achieved. | |
target | decimal | yes | no | Nullable. The value being aimed at. | |
notes | string | yes | no | Nullable. Free text stored with the entry. | |
created_at | datetime | yes | no | ||
updated_at | datetime | yes | no |
Batch request and result
| Name | Type | Read Only | Mandatory | Max Len. | Notes |
|---|---|---|---|---|---|
rows_added | integer | yes | no | How many of the submitted entries were resolved and written. | |
rows_rejected | integer | yes | no | How many were dropped. An entry is rejected when the KPI is unknown, no source could be resolved for it, or the date is one the KPI's frequency does not allow. A batch where every row is rejected still returns 200, so check this value rather than relying on the status code. | |
entries | array | no | no | The entries to write. Each one takes the same shape as a single entry. | |
hasActuals | boolean | no | no | Send true when the batch carries actual values. | |
hasTargets | boolean | no | no | Send true when the batch carries target values. | |
hasNotes | boolean | no | no | Send true when the batch carries notes. |
Nothing-stored result
| Name | Type | Read Only | Mandatory | Max Len. | Notes |
|---|---|---|---|---|---|
stored | boolean | yes | no | Always false. A single-entry write returns this shape with 200 when the request was valid but left nothing to store, because actual, target and notes were all empty. Branch on this rather than on the status code. | |
source_id | integer | yes | no | The source the entry would have been written to. | |
kpi_id | integer (16 bit) | yes | no | The KPI the entry would have belonged to. | |
entry_date | date | yes | no | The date the entry would have been recorded against. | |
detail | string | yes | no | Why nothing was stored. |
List KPI entries
GET /v1/kpientries
Every filter is optional, but narrowing by KPI and date range is what keeps the response a sensible size.
Requires entries:read on the API key.
Parameters
| Name | Type | Required | Default | Notes |
|---|---|---|---|---|
userId | integer | no | Only entries recorded against this user. | |
kpiId | integer | no | Only entries for this KPI. | |
sourceId | integer | no | Only entries for this breakdown source. | |
dateFrom | datetime | no | Earliest entry date to include. | |
dateTo | datetime | no | Latest entry date to include. | |
page | integer | no | 1 | Which page to return, counting from 1. |
rows | integer | no | 500 | How many rows per page. Defaults to 500. |
Using curl
curl https://api.simplekpi.com/v1/kpientries \ -H "Authorization: Bearer skpi_your_api_key"
Example response
Status: 200 OK
[
{
"id": 987654,
"user_id": null,
"source_id": 0,
"kpi_id": 1234,
"entry_date": "2026-08-03T00:00:00",
"actual": 42,
"target": 50,
"notes": null,
"created_at": "2026-08-04T09:12:00",
"updated_at": "2026-08-04T09:12:00"
}
]Status codes
| Code | Meaning |
|---|---|
200 | OK |
400 | Bad Request |
401 | Unauthorized |
403 | Forbidden |
429 | Too Many Requests |
Add or update a KPI entry
POST /v1/kpientries
An upsert. The entry is identified by kpi_id,
source_id and entry_date, so sending the same combination
again updates the existing entry — you do not need to look up an id first.
201 means a new entry was created, 200 that an existing one was changed. If the request was valid but left nothing to store, 200 comes back with the nothing-stored object instead.
Requires entries:write on the API key.
Using curl
curl https://api.simplekpi.com/v1/kpientries \
-X POST \
-H "Authorization: Bearer skpi_your_api_key" \
-H "Content-Type: application/json" \
-d '{"kpi_id":1234,"entry_date":"2026-08-03T00:00:00","actual":42,"target":50,"setActual":true,"setTarget":true}'Example request body
{
"kpi_id": 1234,
"entry_date": "2026-08-03T00:00:00",
"actual": 42,
"target": 50,
"setActual": true,
"setTarget": true
}Example response
Status: 201 Created
{
"id": 987654,
"user_id": null,
"source_id": 0,
"kpi_id": 1234,
"entry_date": "2026-08-03T00:00:00",
"actual": 42,
"target": 50,
"notes": null,
"created_at": "2026-08-04T09:12:00",
"updated_at": "2026-08-04T09:12:00"
}Status codes
| Code | Meaning |
|---|---|
200 | OK |
201 | Created |
400 | Bad Request |
401 | Unauthorized |
403 | Forbidden |
429 | Too Many Requests |
Get a KPI entry
GET /v1/kpientries/{id}
Requires entries:read on the API key.
Parameters
| Name | Type | Required | Default | Notes |
|---|---|---|---|---|
id | integer (64 bit) | yes | The entry to return. |
Using curl
curl https://api.simplekpi.com/v1/kpientries/{id} \
-H "Authorization: Bearer skpi_your_api_key"Status codes
| Code | Meaning |
|---|---|
200 | OK |
401 | Unauthorized |
403 | Forbidden |
404 | Not Found |
429 | Too Many Requests |
Update a KPI entry
PUT /v1/kpientries/{id}
Changes the entry named in the path. Use the set… flags to choose which values are written.
Requires entries:write on the API key.
Parameters
| Name | Type | Required | Default | Notes |
|---|---|---|---|---|
id | integer (64 bit) | yes | The entry to update. |
Using curl
curl https://api.simplekpi.com/v1/kpientries/{id} \
-X PUT \
-H "Authorization: Bearer skpi_your_api_key"Status codes
| Code | Meaning |
|---|---|
200 | OK |
400 | Bad Request |
401 | Unauthorized |
403 | Forbidden |
404 | Not Found |
409 | Conflict |
429 | Too Many Requests |
Delete a KPI entry
DELETE /v1/kpientries/{id}
Requires entries:write on the API key.
Parameters
| Name | Type | Required | Default | Notes |
|---|---|---|---|---|
id | integer (64 bit) | yes | The entry to delete. |
Using curl
curl https://api.simplekpi.com/v1/kpientries/{id} \
-X DELETE \
-H "Authorization: Bearer skpi_your_api_key"Status codes
| Code | Meaning |
|---|---|
204 | No Content |
400 | Bad Request |
401 | Unauthorized |
403 | Forbidden |
404 | Not Found |
429 | Too Many Requests |
Add or update many KPI entries
POST /v1/kpientries/list
The same upsert as above, for a whole set of entries in one request. This is the endpoint to use for a nightly sync — a few hundred entries in one call rather than a few hundred calls.
Rows are resolved individually. A row that cannot be resolved is dropped and
counted in rows_rejected while the rest are still written, so a batch
where everything failed still returns 200. Check the counts.
Requires entries:write on the API key.
Using curl
curl https://api.simplekpi.com/v1/kpientries/list \
-X POST \
-H "Authorization: Bearer skpi_your_api_key" \
-H "Content-Type: application/json" \
-d '{"hasActuals":true,"hasTargets":false,"hasNotes":false,"entries":[{"kpi_id":1234,"entry_date":"2026-08-03T00:00:00","actual":42,"setActual":true},{"kpi_id":1235,"entry_date":"2026-08-03T00:00:00","actual":17,"setActual":true}]}'Example request body
{
"hasActuals": true,
"hasTargets": false,
"hasNotes": false,
"entries": [
{
"kpi_id": 1234,
"entry_date": "2026-08-03T00:00:00",
"actual": 42,
"setActual": true
},
{
"kpi_id": 1235,
"entry_date": "2026-08-03T00:00:00",
"actual": 17,
"setActual": true
}
]
}Example response
Status: 200 OK
{
"rows_added": 2,
"rows_rejected": 0
}Status codes
| Code | Meaning |
|---|---|
200 | OK |
400 | Bad Request |
401 | Unauthorized |
403 | Forbidden |
429 | Too Many Requests |