Sections · 3/11
Concepts
Integration, slug, token, fields, Island, card, priority, and value TTL.
Integration#
An integration is what you assemble in Settings: a name, an address (slug), a set of typed fields, and how they appear on the Island (indicator, line, card). The integration structure is static: scripts send values but cannot add a field, button, or element beyond what you configured. One integration gives exactly one indicator and one line on the Island — if you need two lines, they're two separate integrations.
A disabled integration responds with 409 integration_disabled to writes and displays nothing; its configuration and token are preserved.
Slug (address)#
A slug is how a script finds an integration: /v1/integrations/{slug}/…, sparku push {slug} …. Rules:
- lowercase Latin letters, digits, and hyphens;
- starts with a letter or digit, doesn't end with a hyphen;
- no longer than 32 characters.
Valid slug examples: deploy, build, server, home-server. On .sparku import, if the address matches, SparkU picks a free one: deploy → deploy-2.
Token#
Each integration has its own token, issued when it's created. It's stored in Keychain and doesn't protect against other processes under your user — they have access to the socket anyway. Its purpose is addressing, protection from typos and other scripts, and selective access revocation by reissuing the token.
Where to get it. Settings > Custom Integrations > your integration > "Connection" section: the line export SPARKU_TOKEN_<ADDRESS>=… and ready code examples.
How to pass it. Header Authorization: Bearer <token> in every request.
Search order in CLI (sparku tries in order until it finds a non-empty value):
--token <token>--token-file <path>- environment variable
SPARKU_TOKEN_<ADDRESS>(address in uppercase, hyphens →_; for examplehome-server→SPARKU_TOKEN_HOME_SERVER) - variable
SPARKU_TOKEN - variable
SPARKU_TOKEN_FILE(path to file with token)
SPARKU_TOKEN without an address suffix is convenient when a script writes to one integration; SPARKU_TOKEN_<ADDRESS> — when writing to several at once.
Fields#
A field is a named, typed slot: a key (progress, cpu), a type (field types), and a label. A field key is lowercase Latin letters, digits, and _, starts with a letter, no longer than 32 characters (it's also an environment variable name SPARKU_FIELD_<KEY> for button shell actions, so hyphens are not allowed). An integration has at most 32 fields.
Writing fields is all or nothing: if any value fails validation, POST …/fields rejects the entire request, and the Island doesn't end up half-updated.
Island: indicator, line, card#
Indicator (collapsed Island, 28 pt slot)#
One per integration. Styles:
| Style | Shows | Source |
|---|---|---|
| Icon | static SF Symbol | — |
| Ring | progress ring with icon or number inside | field progress or number with min/max |
| Value | short string up to 4 chars | text, number, progress, state, bool |
| Equalizer | animation like "Media" | field bool enables motion |
| Spinner | rotating indicator | field bool enables rotation |
Field state can be assigned separately (stateField) — its value replaces the icon, color, and animation of the indicator without changing its type.
Card line (one per integration)#
| Style | Shows | Source |
|---|---|---|
| Text | template {title} · {progress} |
any fields via {key} |
| Timer | live countdown | field time |
| Scale | label and mini bar | field progress or number with min/max |
| Avatars | stack of circles with initials, "+N" above three | field list |
Line template ({key}): substitutes the value in the default display for the field type. {{ and }} are literal braces, no format specifiers. Default display: number — up to two decimal places plus unit (21°C, 120 ms), progress — 43%, state — its label, time — countdown to moment or time since moment (4:59, 1:02:03), list — number of lines, boolean — "yes"/"no". No value — em dash (—).
Card (expanded Island)#
Built from a template with slots — there's no free grid to keep the visual row unified with built-in integrations.
| Template | Model | Slots / fields |
|---|---|---|
| Value | Currency | main (number/progress/text), label |
| Status | agents | main (state), label, "since" (time) |
| Progress | Pomodoro | main (progress/number), label, countdown (time) |
| Timer | Pomodoro | main (time), label, ring (progress) |
| Scales | Resource Monitor | 2–4 progress/number fields (with min/max) in a row |
| Limit | Claude Code | fast window (progress), slow window (progress), label |
| Metrics | agents | 2–4 text/number/progress/state/bool/time fields in columns |
| List | Calendar, Todo | one list field |
| Now | Media | title (text, required), label, cover (image), scale (progress) |
| Graph | — | one number field — drawn from accumulated history |
The card has a common header and a row of 0–3 controls. A card can request normal or wide width (like built-in integrations) and a suitable compact variant.
Controls and their actions#
A control is a button, capsule button, toggle (controls a bool field), or menu (picks a state field value). Up to three per card. What the tap does is decided by you in the editor, not the script:
| Action | What happens |
|---|---|
event |
sends an event to listeners GET …/events / sparku listen; without a listener the control is inactive |
url |
opens a link, file, or app (address — static or {field}) |
shell |
runs a command; field values are passed only through environment variables SPARKU_FIELD_<KEY>, never substituted into the command text |
shortcut |
launches an app command from the Shortcuts app by name |
A listener gets an event for any tap regardless of what action is set above.
Priority#
The Island is a shared resource: the notch border, attention beacon, and dialog queue work as a stack of requests, not "first wins, rest wait". Your custom integration has three priority levels:
- Low
- Normal — equal to SparkU's built-in integrations (default)
- High — above built-in
With equal priority, the request submitted last wins. Updating an already-submitted request doesn't bump it forward: a module reporting progress once a second doesn't steal the slot from one that requested after it.
Value TTL (time-to-live)#
Field values live in memory only and don't survive a SparkU restart — integration configuration is always saved. By default, values live until a SparkU restart or explicit sparku end; an integration can set its own TTL, and a specific request can override it with the ?ttl= parameter:
POST /v1/integrations/deploy/fields?ttl=5m
POST /v1/integrations/deploy/fields?ttl=90
ttl is a number of seconds or 5m / 1h30m (same units as time fields), at most 7 days. Empty or missing parameter — TTL from integration settings. Each new write restarts the TTL count.
sparku push supports the same parameter as the --ttl flag (see CLI).
Binding to a process#
A TTL doesn't help when a script crashes: it wrote "Deploy 40%" and died, and the island keeps showing it until the TTL runs out. Binding solves this differently — values live as long as a process does:
POST /v1/integrations/deploy/fields?bind=process # the process that sent the request
POST /v1/integrations/deploy/fields?bind=4211 # the process with this PID
POST /v1/integrations/deploy/fields?bind=none # remove the binding
When the process exits, the values are cleared at once, as with sparku end. The binding stays for later writes without bind; a new bind replaces it. The TTL still applies too — whichever comes first.
bind=process is for programs that keep running themselves (a Python script, a Node service). curl and sparku exit right after the request, so they need the script's PID: in a shell that's ?bind=$, and sparku push --bind fills it in for you.
Notify rate limit#
POST …/notify (flash + line for a few seconds) can be called at most once per 2 seconds per integration. More frequent calls get 429 too_many_notices with a Retry-After header. The limit is separate from writing values through fields — that's accepted without limits, just displayed no more than once per frame.
Number history#
For the graph (template "Graph") and trend, SparkU accumulates history for each numeric field: at most one point per second (a write within the same second replaces the last point), 120 points per field, in memory only — history is lost with values when SparkU restarts.