# 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):

1. `--token <token>`
2. `--token-file <path>`
3. environment variable `SPARKU_TOKEN_<ADDRESS>` (address in uppercase, hyphens → `_`; for example `home-server` → `SPARKU_TOKEN_HOME_SERVER`)
4. variable `SPARKU_TOKEN`
5. 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](/docs/api/fields.md)), 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](/docs/api/cli.md#push)).

## 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.
