Chipi-API
February 22, 2026 · View on GitHub
Chipi-API adds an optional REST/JSON layer on top of the Chipi home-automation framework. It lets you read and update item values via HTTP; every request is authorised with scope-based API keys.
Licence: Apache 2.0 (see root‐level
LICENSE).
Prerequisites
- SBCL ≥ 2.x (or any other supported Common Lisp implementation)
- Quicklisp (or OCICL)
- ASDF systems
chipiandchipi-api
;; Quicklisp
(ql:quickload :chipi-api)
;; OCICL
(asdf:load-system :chipi-api)
Quick start (REPL)
(hab:defconfig "chipi"
;; 1 – initialises runtime, actor system, timers …
;; 2 – Chipi-API specific environment
(api-env:init
:apikey-store (apikey-store:make-simple-file-backend) ; or *memory-backend* for testing
:apikey-lifetime (ltd:duration :day 100))
;; 3 – HTTP server on port 8765
(api:start))
For a complete, runnable example have a look at example-web.lisp in the project root.
Example items
(defitem 'lamp "Living-room lamp" 'boolean :initial-value 'item:false)
(defitem 'temp "Temperature" 'float :initial-value 21.5)
Read-only items
Items can be marked as read-only for external systems by setting the :ext-readonly tag:
(defitem 'sensor "Temperature sensor" 'float
:initial-value 20.0
:tags '((:ext-readonly . t)))
Read-only items can still be read via the REST API but cannot be updated through external calls. The tag value should be the Lisp boolean t.
Note: This is a convention that should be respected by the UI and external systems. The server does not enforce read-only restrictions - it's up to client implementations to check for the :ext-readonly tag and prevent updates accordingly.
Creating an API key
(defparameter *my-key*
(apikey-store:create-apikey :access-rights '(:read :update)))
API key persistence
(api-env:init
:apikey-store (apikey-store:make-simple-file-backend))
The file backend stores keys in runtime/apikeys.
REST interface
A full OpenAPI 3.0 specification lives in chipi-api.yaml.
Besides individual items the API also exposes itemgroups, logical containers that hold multiple items.
| Endpoint | Method | Required scope |
|---|---|---|
/items | GET | read |
/items/{itemName} | GET | read |
/items/{itemName} | POST | update |
/itemgroups | GET | read |
/itemgroups/{groupName} | GET | read |
/events/items | GET | read |
Add header X-Api-Key: <your-key> to every call.
Examples (curl)
# List all items
curl -H "X-Api-Key: $MY_KEY" http://localhost:8765/items
# List all itemgroups
curl -H "X-Api-Key: $MY_KEY" http://localhost:8765/itemgroups
# Get single item
curl -H "X-Api-Key: $MY_KEY" http://localhost:8765/items/lamp
# Get single itemgroup
curl -H "X-Api-Key: $MY_KEY" http://localhost:8765/itemgroups/living
# Update item
curl -X POST -H "X-Api-Key: $MY_KEY" -H "Content-Type: application/json" \
-d '{"value": true}' http://localhost:8765/items/lamp
Server-Sent Events (SSE)
The API provides real-time item updates via Server-Sent Events:
# Connect to item events stream
curl -H "Accept: text/event-stream" \
"http://localhost:8765/events/items?apikey=$MY_KEY"
The SSE endpoint sends:
- Connection confirmation messages
- Real-time item change notifications with full item data
- Periodic heartbeat messages to keep the connection alive
Event format follows the SSE standard with data: fields containing JSON payloads.
Event Types
Connection Event:
{"event":{"type":"connection","message":"Connected to item events"}}
Item Change Event:
{"event":{"type":"item-change","item":{"name":"lamp","label":"Living-room lamp","type-hint":"boolean","tags":{},"item-state":{"value":true,"timestamp":1703123456}}}}
Heartbeat Event:
{"event":{"type":"heartbeat","timestamp":1703123456}}
Item Change Payload Structure
| Field | Type | Description |
|---|---|---|
event.type | string | Always "item-change" |
event.item.name | string | Item identifier |
event.item.label | string | Human-readable item name |
event.item.type-hint | string | Data type (boolean, float, integer, string) |
event.item.tags | object | Item metadata (including :ext-readonly flag) |
event.item.item-state.value | any | Current item value |
event.item.item-state.timestamp | number | Common Lisp universal-time timestamp (seconds since 1900-01-01) |
Timestamp Format
Timestamps are provided as Common Lisp universal-time values (seconds since January 1st, 1900, 00:00:00 GMT). To convert to Unix timestamp (seconds since 1970-01-01), subtract 2208988800:
// Convert CL universal-time to Unix timestamp
const unixTimestamp = universalTime - 2208988800;
const date = new Date(unixTimestamp * 1000);
# Convert in shell (example with timestamp 3913123456)
echo $((3913123456 - 2208988800)) # Result: 1704134656 (Unix timestamp)
Scope model
| Scope | Meaning |
|---|---|
read | read-only |
update | push new values |
delete | reserved |
admin | full access |
The highest requested scope must not exceed the highest scope granted to the API key.
Shutting down
(api:stop) ; stop only the web API
(hab:shutdown) ; stop the entire Chipi instance
Chipi UI
Chipi UI is a responsive UI, based on CLOG and Bootstrap 5 with realtime updates of values.
Example of setup:
(hab:defconfig "chipi"
;; 1 – initialises runtime, actor system, timers …
;; 2 – Chipi-API specific environment
(api-env:init
:apikey-store (apikey-store:make-simple-file-backend) ; or *memory-backend* for testing
:apikey-lifetime (ltd:duration :day 100))
;; 3 – HTTP server on port 8765
;;(api:start)
;; 4 - UI
;; API server is not needed for UI, but api-env is (eventually, because we want API key security)
;; ATM it's WIP and doesn't need that either
(ui:start :host "your-host" :port <your-port>)
)
UI Tags
Items and itemgroups support special tags that control how they are rendered in the UI.
Item tags
| Tag | Value | Description |
|---|---|---|
:ui-type | string | Overrides the default type badge label (e.g. "Light" instead of "Switch") |
:ui-readonly | t | Boolean items render as plain "ON"/"OFF" text instead of an interactive toggle |
;; Custom type label
(defitem 'switch1 "Switch1" 'boolean :initial-value 'item:false
:tags '((:ui-type . "Light")))
;; Read-only boolean displayed as ON/OFF text
(defitem 'motion-sensor "Motion Sensor" 'boolean :initial-value 'item:true
:tags '((:ui-readonly . t)))
Itemgroup tags
| Tag | Value | Description |
|---|---|---|
:ui-link | — | Renders the itemgroup as a clickable navigation link instead of an inline card |
;; Renders as a navigation link that opens a detail page
(defitemgroup 'lights "Lights" :tags '((:ui-link)))
Screenshot

This is mostly WIP. Still under investigation if this is a practical or not.