SimpleKPI Logo

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

  1. List KPI entriesGET /v1/kpientries
  2. Add or update a KPI entryPOST /v1/kpientries
  3. Get a KPI entryGET /v1/kpientries/{id}
  4. Update a KPI entryPUT /v1/kpientries/{id}
  5. Delete a KPI entryDELETE /v1/kpientries/{id}
  6. Add or update many KPI entriesPOST /v1/kpientries/list

KPI entry

Entry object to send

NameTypeRead OnlyMandatoryMax Len.Notes
user_idintegernono Nullable. The user the entry is recorded against. Null on a KPI with no breakdown.
emailstringnono 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_idinteger (16 bit)nono The KPI the entry belongs to.
source_idintegernono 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_datedatetimenono 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.
actualdecimalnono Nullable. The value achieved.
targetdecimalnono Nullable. The value being aimed at.
notesstringnono500Nullable. Free text stored with the entry.
setActualbooleannono Send true to write actual, false to leave whatever is already stored untouched. This is how you update a target without clearing the actual.
setTargetbooleannono Send true to write target, false to leave the stored value untouched.
setNotesbooleannono Send true to write notes, false to leave the stored text untouched.
addToActualbooleannono 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

NameTypeRead OnlyMandatoryMax Len.Notes
idinteger (64 bit)yesno Assigned by SimpleKPI when the entry is first stored.
user_idintegeryesno Nullable. The user the entry is recorded against. Null on a KPI with no breakdown.
source_idintegeryesno 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_idinteger (16 bit)yesno The KPI the entry belongs to.
entry_datedatetimeyesno 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.
actualdecimalyesno Nullable. The value achieved.
targetdecimalyesno Nullable. The value being aimed at.
notesstringyesno Nullable. Free text stored with the entry.
created_atdatetimeyesno  
updated_atdatetimeyesno  

Batch request and result

NameTypeRead OnlyMandatoryMax Len.Notes
rows_addedintegeryesno How many of the submitted entries were resolved and written.
rows_rejectedintegeryesno 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.
entriesarraynono The entries to write. Each one takes the same shape as a single entry.
hasActualsbooleannono Send true when the batch carries actual values.
hasTargetsbooleannono Send true when the batch carries target values.
hasNotesbooleannono Send true when the batch carries notes.

Nothing-stored result

NameTypeRead OnlyMandatoryMax Len.Notes
storedbooleanyesno 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_idintegeryesno The source the entry would have been written to.
kpi_idinteger (16 bit)yesno The KPI the entry would have belonged to.
entry_datedateyesno The date the entry would have been recorded against.
detailstringyesno 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

NameTypeRequiredDefaultNotes
userIdintegerno Only entries recorded against this user.
kpiIdintegerno Only entries for this KPI.
sourceIdintegerno Only entries for this breakdown source.
dateFromdatetimeno Earliest entry date to include.
dateTodatetimeno Latest entry date to include.
pageintegerno1Which page to return, counting from 1.
rowsintegerno500How 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

CodeMeaning
200OK
400Bad Request
401Unauthorized
403Forbidden
429Too 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

CodeMeaning
200OK
201Created
400Bad Request
401Unauthorized
403Forbidden
429Too Many Requests

 

Get a KPI entry

GET /v1/kpientries/{id}

Requires entries:read on the API key.

Parameters

NameTypeRequiredDefaultNotes
idinteger (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

CodeMeaning
200OK
401Unauthorized
403Forbidden
404Not Found
429Too 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

NameTypeRequiredDefaultNotes
idinteger (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

CodeMeaning
200OK
400Bad Request
401Unauthorized
403Forbidden
404Not Found
409Conflict
429Too Many Requests

 

Delete a KPI entry

DELETE /v1/kpientries/{id}

Requires entries:write on the API key.

Parameters

NameTypeRequiredDefaultNotes
idinteger (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

CodeMeaning
204No Content
400Bad Request
401Unauthorized
403Forbidden
404Not Found
429Too 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

CodeMeaning
200OK
400Bad Request
401Unauthorized
403Forbidden
429Too Many Requests