# Flespi

> flespi is an IoT and telematics cloud platform. Provides skills for developing applications and consulting agents that integrate with the flespi REST API and MQTT broker.

- Skill: `flespi-software/flespi` (Agent Skill)
- Install (CLI): `npx skillmds@latest add flespi-software/flespi`
- Raw SKILL.md: https://api.skillmd.com/api/skills/flespi-software/flespi/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: DevOps & Infra
- Author: flespi-software (https://skillmd.com/u/flespi-software)
- Updated: 2026-09-17
- Page: https://skillmd.com/skills/flespi-software/flespi

---


# flespi Platform Concepts

## Data Flow

```
Physical Device  -->  Channel  -->  Device  -->  Stream (export)
  (tracker)        (protocol      (message     Calculator (analytics)
                    endpoint)      storage)     Plugin (transform)
```

1. **Physical device** sends data over TCP/UDP/HTTP using a vendor protocol (e.g. Teltonika, Queclink)
2. **Channel** listens on a port, parses raw bytes into normalized JSON messages
3. **Device** receives parsed messages, stores them as time-series data, maintains latest telemetry snapshot
4. **Stream** forwards device messages to external systems in real-time (AWS IoT, Azure, HTTP, MQTT, etc.)
5. **Calculator** processes device messages into analytics intervals (trips, stops, geofence events, fuel consumption)
6. **Plugin** transforms or enriches messages in the pipeline before storage

## Core Entities

### Channel
Inbound protocol endpoint. Each channel binds to one protocol and listens for connections.
- Key fields: `name`, `protocol_id`, `enabled`
- Sub-resources: `/messages`, `/logs`, `/connections`
- Devices link to channels through `device_type_id` (protocol family) and `configuration.ident` (IMEI)

### Device
Logical representation of a physical tracker. Central entity for data storage.
- Key fields: `name`, `device_type_id`, `configuration.ident` (IMEI/serial), `enabled`, `connected`
- Sub-resources: `/messages` (time-series), `/telemetry` (latest snapshot), `/settings`, `/commands_queue`, `/logs`
- `connected` field indicates whether device is currently sending data

### Stream
Forwards device messages to external systems in real-time.
- Key fields: `name`, `type`, `configuration`, `queue_ttl`
- Types: http, mqtt, aws_iot, azure_event_hub, google_pubsub, wialon, and more
- Sub-resources: `/logs` (delivery status and errors)

### Calculator
Real-time analytics engine. Processes device messages into intervals.
- Key fields: `name`, `selectors` (interval boundary conditions), `counters` (what to compute)
- **Selectors** define when intervals start/end (e.g. ignition on/off, speed threshold, geofence enter/exit)
- **Counters** define what to measure (duration, distance, fuel, max speed, etc.)
- Sub-resources: `/devices/{device_id}/intervals/all`

### Plugin
Transforms or enriches messages in the processing pipeline.
- Key fields: `name`, `item_type`, `configuration`, `validate_message`
- Can add/modify/filter message parameters before storage

### Geofence
Geographic area used by calculators and plugins for location-based logic.
- Types: polygon, circle, corridor
- Key fields: `name`, `geometry`

### Group
Organizes devices for bulk assignment to streams, calculators, and plugins.

### Webhook
Reacts to MQTT events with HTTP requests. Event-driven automation.
- Key fields: `name`, `triggers` (MQTT topic filters), `configuration` (HTTP request template)
- Uses `%{expression}%` syntax in request body templates

### Token
API access credential with configurable permissions.
- Access types: Master (type=1, full access), Standard (type=0, telematics access), ACL (type=2, granular permissions)
- Every API request requires `Authorization: FlespiToken <key>` header

### Subaccount
Hierarchical customer structure for multi-tenant deployments.
- Children inherit resource limits from parent
- Operate as a subaccount using `x-flespi-cid` header or `cid` parameter

### Container
Generic message storage (not tied to devices). Used for custom data.

### CDN
File storage service.

## Key Message Parameters

Device messages are JSON objects with normalized parameter names:

| Parameter | Description |
|-----------|-------------|
| `timestamp` | Device-reported time (unix UTC) |
| `server.timestamp` | flespi reception time (unix UTC) |
| `position.latitude`, `position.longitude` | GPS coordinates |
| `position.speed` | Speed (km/h) |
| `position.direction` | Heading (degrees) |
| `position.altitude` | Altitude (meters) |
| `position.satellites` | Satellite count |
| `position.valid` | GPS fix validity |
| `ident` | Device IMEI/serial number |
| `device.id`, `device.name` | flespi device identity |
| `device.type.id` | Device type ID |
| `channel.id` | Channel ID that received the message |

If `server.timestamp - timestamp > 3600`, it usually indicates time sync issues or buffered/historical (blackbox) data.

## Expressions

flespi expressions are used across the platform: in calculators, plugins, streams, webhooks, REST API filters, and MQTT topic filters.

- Parameter access: `$param_name` (safe - returns null if missing) or `param_name`
- Nested access: `$position.speed`, `$can.vehicle.mileage`
- Previous value (calculators/plugins only): `previous("param_name")`
- Built-in functions: `mileage()`, `distance_to(lat,lon)`, `duration()`, `geofence("name")`
- Operators: `+`, `-`, `*`, `/`, `%`, `==`, `!=`, `>`, `<`, `>=`, `<=`, `&&`, `||`, `!`, `? :`
- String matching: `~` (wildcard match)
- Webhook templating: `%{$device.name}%`
- REST filter: `{"filter": "position.speed>60&&position.valid==true"}`

# flespi REST API

Base URL: `https://flespi.io`

| Namespace | Contents |
|-----------|----------|
| `/gw/` | Channels, devices, streams, calculators, plugins, geofences, groups, modems, protocols |
| `/storage/` | Containers, CDNs |
| `/platform/` | Customer, tokens, subaccounts, webhooks, grants, realms, logs, statistics |
| `/mqtt/` | MQTT sessions, subscriptions |

## Authentication

Every request requires: `Authorization: FlespiToken <key>`

### Token Types

| Type | `access.type` | Scope |
|------|---------------|-------|
| Master | 1 | Full access including platform API (tokens, subaccounts, webhooks, realms). Can create other tokens. |
| Standard | 0 | Telematics entities only (channels, devices, streams, calcs, plugins, etc.). No platform API. |
| ACL | 2 | Granular per-URI, per-method, per-item permissions. Default-deny - only explicitly allowed actions work. |

### Obtaining a Token

**Via flespi panel**: Tokens section in left menu -> "+" button -> choose type, configure ACL, set expiration.

**Via REST API** (requires Master token):
```
POST /platform/tokens
[{"info": "My token", "ttl": 31536000, "access": {"type": 2, "acl": [{"uri": "gw/devices", "methods": ["GET"]}]}}]
```

**Via realm login** (for realm-managed users):
```
POST /realm/{realm-public-id}/login
{"username": "user", "password": "pass"}
```

**Token validation**: `GET /auth/info` - returns token info if valid, 401 if not.

### Token Expiration

Tokens require either `ttl` (seconds since last use) or `expire` (unix timestamp), or both. Token remains valid as long as at least one is valid. When both expire, the token is auto-deleted.

## Response Format

Success:
```json
{"result": [...]}
```

Error:
```json
{"errors": [{"code": 404, "reason": "not found"}]}
```

HTTP 400 errors often include `schema_hint` with valid field names - use it to retry with correct fields.

## Selectors

Selectors in URL paths address which items to operate on:

| Selector | Example | Description |
|----------|---------|-------------|
| By ID | `/gw/devices/123` | Single item |
| Multiple IDs | `/gw/devices/123,456,789` | Comma-separated |
| All | `/gw/devices/all` | Every item |
| By field | `/gw/devices/configuration.ident=352625066004561` | Exact field match |
| By expression | `/gw/devices/{name=="my tracker" || device_type_id==42}` | Expression filter |

**Caution**: `all` in write/delete operations (`PUT`, `PATCH`, `DELETE`) affects every item.

## Limiting Response Size

### Collection endpoints (list entities)

Use `?fields=` query parameter:
```
GET /gw/devices/all?fields=id,name,device_type_id,connected
```
Field names are flat - use `errors`, not `counters.errors`.

### Message/log/interval endpoints

Use `data` parameter (JSON body) instead of `?fields=`:
```
GET /gw/devices/123/messages
data: {"count": 10, "fields": "timestamp,position.latitude,position.longitude,position.speed", "reverse": true}
```

| Parameter | Description |
|-----------|-------------|
| `count` | Max records to return |
| `fields` | Comma-separated field names |
| `reverse` | `true` for latest-first |
| `from` / `to` | Time range (unix UTC) |
| `filter` | flespi expression to filter records |

Latest message: `data={"count":1,"reverse":true}`

## CRUD Operations

| Method | Action | Body |
|--------|--------|------|
| `POST` | Create | Array of objects: `[{"name":"My Device","device_type_id":42}]` |
| `GET` | Read | - |
| `PUT` | Replace specified fields | Fields to replace |
| `PATCH` | Patch JSON object fields | Partial object: `{"configuration":{"ident": "new ident"}}` |
| `DELETE` | Remove | - |

POST accepts arrays. Response `result` contains items with assigned `id` fields.

## Pagination

### Collection endpoints

Use `limit` and `offset` query parameters:
```
GET /gw/devices/all?fields=id,name&limit=10&offset=0
```
Response includes `pagination` object with `limit`, `offset`, `count` (total). Increment `offset` by `limit` until done.

### Message/log/interval endpoints

No `limit`/`offset`. Use time-based pagination via `data` body:

1. Fetch batch: `data={"from":1700000000,"to":1700086400,"count":1000}`
2. Take `timestamp` of last message
3. Use as new `from` (or `to` if `reverse:true`) for next request

For calculator intervals, use `begin`/`end` instead of `from`/`to`.

Note: for messages, `count` limits total across all queried devices. For intervals, `count` limits per device.

### Telemetry endpoints

No pagination. Page the device list first, then fetch telemetry per batch of IDs.

## Calculate Method

Server-side aggregation without a persistent calculator:
```
GET /gw/devices/{selector}/messages/calculate
data: {
  "from": 1700000000,
  "to": 1700086400,
  "selectors": [{"type": "datetime"}],
  "counters": [
    {"type": "expression", "name": "mileage", "method": "summary", "expression": "mileage()"},
    {"type": "parameter", "name": "max_speed", "parameter": "position.speed", "method": "max"}
  ]
}
```
Use it for on-demand aggregation instead of fetching all messages client-side. For aggregating already-calculated intervals across devices, use `GET /gw/calcs/{selector}/devices/{dev-selector}/intervals/calculate` with the same body shape.

## Auxiliary Headers

| Header | Description |
|--------|-------------|
| `x-flespi-cid` | Operate as a specific subaccount (by customer ID) |
| `x-flespi-app` | Application name for logging/tracing (max 63 chars) |
| `x-flespi-trace` | Request trace ID for debugging (max 31 chars) |

## Error Codes

| Code | Meaning |
|------|---------|
| 400 | Bad request (check `schema_hint` in response) |
| 401 | Invalid or expired token |
| 403 | Insufficient permissions (token ACL) |
| 404 | Entity not found |
| 429 | Rate limit exceeded |

## Messages, Telemetry, and Logs

Three distinct read shapes. They are not interchangeable - pick by the question shape:

- **`/messages`** - time-series of every data point ever received. Historical, complete, can be large. Retention bounded by the device's `messages_ttl`. Use to trace behavior over time, compute aggregates via `/calculate`, or retrieve specific records by timestamp.
- **`/telemetry`** - latest-value snapshot per parameter. flespi merges each new message into a single current-state object keyed by parameter name, with each parameter carrying its own last-update timestamp. Cheaper and smaller than `/messages` for "what is this device doing right now" questions.
- **`/logs`** - operational events on the entity itself (connections, settings, commands, errors). Not data. Use for diagnostic questions about the entity's lifecycle.

Common mis-selection: "what is the device's last position" fetched via `/messages` with `data={"count":1,"reverse":true}` when `/telemetry` is smaller and faster. Use `/messages` only when you need the full record shape for a specific timestamp or when recent activity patterns are what matters.

## Logs

Entity `/logs` sub-resources carry operational events. GET pattern:
```
GET /gw/devices/123/logs  data={"count":10,"reverse":true}
GET /gw/streams/456/logs  data={"count":10,"reverse":true}
GET /platform/customer/logs  data={"count":10,"reverse":true}
```

### Common event codes

Channel events (`origin_type=9`):
- **100** - connection accepted (raw TCP/UDP/HTTP)
- **101** - device identified (handshake complete)
- **102** - connection closed with `close_code`

Device events (`origin_type=11`):
- **300 / 301** - connected / disconnected; 301 carries `close_code`, `duration`, `msgs`, `recv`, `send`
- **305** - data processing warning, invalid parameters removed (commonly zero/invalid GPS during cold start, or unsupported protocol-specific data)
- **310** - setting value received from the device
- **312 / 313** - device accepted / rejected a setting
- **316** - setting unreadable after retries
- **317** - setting sent to the device
- **330 / 334** - command queued / delivered
- **333** - queued command expired before delivery

### Close codes

On events 102 and 301:

- **4** / `{ext}` - device violates protocol; compare raw bytes against manufacturer spec
- **5** - flespi-side implementation bug; route to flespi support
- **7** - flespi server restart (benign)
- **8** - protocol implementation updated by flespi (mass brief reconnect expected)
- **12** - ident collision: device opened a new connection without closing the old
- **17** - inactivity timeout (10-minute fixed)
- **18** / `{uns}` - parameter not yet integrated; route to flespi for integration
- **19** / `{mis}` - misaligned traffic; usually bot noise, ignore
- **21 / 23** - channel blocked / device disabled (account-side state change)

## Device Commands and Settings

### Execution modes

Two endpoints for sending commands:

- `POST /gw/devices/{selector}/commands` - real-time. Blocks 5-60 seconds waiting for the device's reply (default `timeout` 30s), returns the reply inline, fails immediately if the device is offline. Use for immediate actions on a device known to be online.
- `POST /gw/devices/{selector}/commands-queue` - queued. Returns a command id immediately, persists until `ttl` / `expires`, delivers on next connect. Retrieve the reply asynchronously via `GET /gw/devices/{selector}/commands-result`. Queued is the reliable default and natively handles offline devices.

Both endpoints accept shared options: `priority`, `max_attempts` (default 3), `condition` (flespi expression re-evaluated before each delivery attempt - e.g., gate a reboot on `$position.speed == 0`), `meta` (small JSON blob echoed in logs and the command result). Queued-only: `ttl` (delivery window, up to 30 days).

### Settings as a device shadow

Each setting on a device carries `current` (last value read from or applied to the device), `pending` (a write waiting for the device to reconnect), `mode` (readable / writable bit flags), `status` (source of `current` plus flespi intent bits).

REST on `/gw/devices/{selector}/settings/{name}`: `PUT` writes a new `pending` value and overwrites any previous pending (settings are not a queue); `DELETE` forces flespi to re-read `current` from the device on next connect; `DELETE` with `data={"pending":true}` cancels a pending write without triggering a re-read.

### Verify after write

A queued command or setting write is not complete when the queue API returns - it completes when the device acknowledges. Real-time command replies arrive inline in the POST response; queued command replies retrieve via `/commands-result` keyed on the returned command id; settings verify via the updated `current` value on the settings object plus lifecycle events 312 (accepted) / 313 (rejected) / 316 (unreadable) in device `/logs` (see Logs above). Confirm the outcome to the user only after the device-side acknowledgement is observed - quote the applied value or the rejection reason, not just "the write was queued".

## Recycle Bin and Restoration

Most entity DELETEs are reversible for 30 days. Deleted items land in `/platform/deleted/` instead of being permanently removed, with their original `id`, configuration, and messages preserved. Listing `/platform/deleted/` (or the endpoint's schema) is the authoritative check on whether a specific entity type can be restored.

**Permanent DELETE, no bin**: telemetry field DELETE, message DELETE, container record DELETE, CDN or media file DELETE.

Restore via:
```
flespi-api-write: method=POST url=/platform/deleted/{deleted-selector}/restore
  data={}
```

Selector forms: deleted-item trash `id`; original `item_id==<id>`; expression on the deleted record (e.g., `{data.configuration.ident=="IMEI_VALUE"}`).

**Restoration gotchas**:
- Assignments are not auto-restored - device-to-group, device-to-calculator, device-to-stream, device-to-plugin links must be manually re-linked after restore.
- Calculator intervals on restored devices are not retained - need recomputation from restored messages.
- Restore fails if the channel URI is reoccupied, an `ident` collides, or account limits have been reached.
- The recycle bin is subaccount-scoped - access each subaccount's bin via `x-flespi-cid` (or `cid` param) to see its deleted items.

## Gateway API Surface (`/gw/`)

Each entity supports standard CRUD via selectors. Sub-resources provide access to related data.

| Entity | Path | Key Sub-resources |
|--------|------|-------------------|
| Channels | `/gw/channels` | `/messages`, `/logs`, `/connections`, `/idents` |
| Devices | `/gw/devices` | `/messages`, `/messages/calculate`, `/telemetry`, `/logs`, `/commands-queue`, `/commands-result`, `/settings`, `/connections`, `/media`, `/packets`, `/sms`, `/geofences` |
| Streams | `/gw/streams` | `/logs`, `/messages`, `/packets`, `/channels/*`, `/devices/*`, `/groups/*`, `/geofences/*` |
| Calculators | `/gw/calcs` | `/logs`, `/devices/*/intervals`, `/devices/*/intervals/calculate`, `/devices/*/recalculate`, `/assets/*`, `/groups/*`, `/geofences/*` |
| Plugins | `/gw/plugins` | `/logs`, `/packets`, `/devices/*`, `/groups/*`, `/geofences/*` |
| Geofences | `/gw/geofences` | `/logs`, `/hittest` |
| Groups | `/gw/groups` | `/logs`, `/devices/*`, `/assets/*`, `/geofences/*` |
| Assets | `/gw/assets` | `/logs`, `/intervals` |
| Modems | `/gw/modems` | `/logs` |
| Protocols | `/gw/channel-protocols` | `/device-types`, `/device-types/*/knowledge`, `/device-types/*/assistance` |
| Message params | `/gw/message-parameters` | - |

CRUD pattern: `POST /gw/{entity}` to create, `GET/PUT/PATCH/DELETE /gw/{entity}/{selector}` to read/update/delete.

Sub-resource assignment pattern (streams, calcs, plugins, groups): `POST /gw/{entity}/{id}/{sub}/{sub_ids}` to assign, `DELETE` to unassign, `GET` to list.

### Other API Namespaces

| Namespace | Path | Key Entities |
|-----------|------|--------------|
| Platform | `/platform/` | `customer`, `tokens`, `subaccounts`, `webhooks`, `grants`, `realms`, `logs`, `statistics` |
| Storage | `/storage/` | `containers` (generic message storage), `cdns` (file storage) |
| MQTT | `/mqtt/` | `sessions`, `subscriptions` |
| AI tools | `/ai/` | `tools/search-api-methods`, `tools/api-method-schema`, `tools/search-flespi-documentation`, `tools/generate-flespi-expression`, `logs` |

## API Discovery

The flespi REST API schema is large (76+ endpoints in `/gw/` alone, plus `/platform/`, `/storage/`, `/mqtt/`, `/ai/`). When a flespi token is available, prefer using the `search-api-methods` and `api-method-schema` MCP tools for on-demand endpoint discovery rather than reading the full Swagger specs. Schema retrieval is not optional - see API Call Discipline in the MCP Tools Guide for the discover-schema-compose-execute pattern every call follows.

### Swagger Specifications

Static specs for offline reference:
- `https://flespi.io/gw/api.json` - channels, devices, streams, calcs, plugins, etc.
- `https://flespi.io/mqtt/api.json` - MQTT sessions and subscriptions
- `https://flespi.io/platform/api.json` - platform (tokens, subaccounts, webhooks, realms, etc.)
- `https://flespi.io/ai/api.json` - AI tools

### Search Tools (preferred when token is available)

Find endpoints by semantic search:
```
search-api-methods: queries=["list device types", "get device messages"]
```

Get full schema for a specific endpoint (use links from search results):
```
api-method-schema: link="https://flespi.io/docs/#/gw/devices/get_devices_dev_selector_messages"
```

# flespi MQTT Broker

Broker endpoint: `mqtt.flespi.io` (ports 8883 TLS, 443 WSS, 1883 plain TCP).

## Authentication

Use flespi token as MQTT `username`, leave `password` empty.

For ACL tokens on memory-constrained devices: token ID as `username`, first 16 characters of token key as `password`.

Token type determines MQTT permissions - ACL tokens can restrict which topics a client may subscribe to or publish on.

Note: some protocol channels (e.g. Teltonika MQTT, Positioning Universal) act as their own MQTT broker endpoints with separate channel-level authentication. This is unrelated to flespi broker authentication.

## QoS Levels

| QoS | Supported | Behavior |
|-----|-----------|----------|
| 0 | Yes | At most once, no delivery guarantee |
| 1 | Yes | At least once, broker stores and redelivers unacknowledged messages |
| 2 | No | Not supported |

Use QoS 1 with persistent sessions (`clean=false`) for reliable message delivery.

## Topic Structure

All flespi topics follow the pattern: `flespi/{category}/{module}/{entity}/{id}/...`

Full topics dictionary: https://flespi.io/const/values?names=toolbox/topics

## Payload

All flespi topics contain valid UTF-8 string: number, boolean, string, JSON

### Categories

| Category | Description | Retained |
|----------|-------------|----------|
| `message` | New data (device messages, chat, container messages) | No |
| `state` | Entity state and telemetry (full config, field values) | Yes |
| `log` | Operation logs (CRUD, connections, errors, commands) | No |
| `interval` | Calculator interval lifecycle (created, updated, deleted) | No |

## Comma Subscriptions

flespi extension: use comma-separated values in a single topic level to match multiple items without separate subscriptions:

```
flespi/state/gw/devices/123,456,789/connected
flespi/message/gw/channels/10,20/+
```

## Shared Subscriptions

Distribute messages across multiple clients for load balancing:

```
$share/group_name/flespi/message/gw/devices/+
```

Messages are distributed randomly among members of `group_name`. Requirements:
- QoS 1 subscriptions (for delivery guarantees)
- Persistent sessions (`clean=false`) recommended to prevent message loss when all subscribers offline

### Sticky Shared Subscriptions

Ensure messages from the same source always go to the same subscriber (useful for local caches/state):

**By topic prefix length**: `$share/name:count/topic`
- `count` = number of topic levels from the beginning used for hash
- `$share/by_ident:6/flespi/message/gw/channels/+/+` - routes by ident (6th level)

**By topic position**: `$share/name:from:count/topic`
- `from` = starting topic level (zero-based), `count` = number of levels for hash
- `$share/by_device:4:1/flespi/+/gw/devices/+/#` - routes by device ID (level 4)

## `cid` User Property

Filter messages by subaccount ID in hierarchical account structures:

**As topic prefix**:
```
$filter/cid=SUBACCOUNT_ID/flespi/message/gw/devices/+
```

**As MQTT v5 User Property** in SUBSCRIBE packet:
- Key: `cid`, Value: subaccount ID

## Message Filtering

Server-side filtering using `$filter/` prefix reduces traffic to the client:

```
$filter/<filter-spec>/original-topic
```

### Filter Types

**By subaccount**:
```
$filter/cid=123/flespi/message/gw/devices/+
```

**By publication time**:
```
$filter/modified_since>1700000000/flespi/message/gw/devices/+
```

**By payload** (flespi expression, URL-encoded):
```
$filter/payload=position.speed%3E60/flespi/message/gw/devices/+
```

**By message object** (access topic, cid, timestamp, user_properties, payload):
```
$filter/message=cid%3D%3D123%26%26tonumber(payload)%3E10/some/topic
```

Filters can be combined with shared subscriptions:
```
$share/workers/$filter/cid=123/flespi/message/gw/devices/+
```

# flespi MCP Tools Guide

How to effectively use flespi MCP tools. Choose the right tool for the task to minimize cost and latency.

## Available Tools

| Tool | Cost | Description |
|------|------|-------------|
| `flespi-api-read` | 0 | Execute GET requests to flespi REST API |
| `flespi-api-write` | 0 | Execute POST/PUT/PATCH/DELETE requests (requires user approval) |
| `search-api-methods` | 0 | Discover API endpoints by semantic search |
| `api-method-schema` | 0 | Get full Swagger schema for a specific endpoint |
| `search-flespi-documentation` | 5 | Search knowledge base, blog, API docs for concepts and guidance |
| `search-device-documentation` | 10 | Search device/protocol-specific documentation (hardware specs, commands, wiring) |

## Knowledge Authority

Different sources are authoritative for different question kinds. When sources conflict, the source authoritative for the question at hand wins - there is no single linear ranking.

| Question | Authoritative source | Not authorized by |
|---|---|---|
| What does this endpoint expect or return (URL shape, selectors, fields, body, response)? | `api-method-schema` for that path | `search-flespi-documentation` (docs describe intent, not contract); a prior response at that path (a result is not a contract); training-data recall |
| How does a flespi feature work, why, and what are the tradeoffs? | `search-flespi-documentation` | `api-method-schema` (shape, not rationale); training-data recall |
| What is this account's current state (entities, config, connectivity, message values)? | `flespi-api-read` against the account | Docs, schema, training-data recall |
| What does a specific device model do (protocol spec, command catalog, wiring, firmware)? | `search-device-documentation` | `search-flespi-documentation` (platform behavior, not device facts); training-data recall |

Training-data knowledge of flespi is the weakest source - useful for framing the right question to the authoritative tool, never authoritative for the answer itself. A documentation result describing an endpoint's fields is not schema: call `api-method-schema` for that path even when docs appear to fully describe it.

## Design Discipline

For any change that involves more than one entity, reshapes data flow, or leaves a composition decision open, compose on paper before composing any call. Simple reads and single-entity updates skip this; multi-entity setups, new integrations, and architectural questions start here.

1. **Ground each decision with documentation**. Every architectural decision (where to place data, which primitive to use, how to scope access, how to isolate) is its own question for `search-flespi-documentation`. Frame each search specifically - "how are custom parameters scoped across subaccounts" rather than "tell me about plugins". One generic sweep does not cover multiple decisions.
2. **Verify what the token can reach**. Documentation describes platform-wide intent; a live catalog read confirms what this specific token actually has access to. `/auth/info` returns the token's access shape; list-endpoint reads confirm entity availability.
3. **Sample current account state**. Focused reads of the namespaces the design will touch - existing entities with overlapping purpose (reuse beats create), naming conventions to respect, active traffic on items about to be changed.
4. **Map each input to the richest primitive**. For each user input (identifiers, phone numbers, metadata, routing rules), pick the most semantically-fitting field:
   - Native fields first (searchable, command-aware): `configuration.ident`, `phone`, `device_type_id`, and similar schema-declared fields.
   - Entity metadata for reference data not covered by native fields.
   - Custom message parameters via plugin for values that must appear on every message.
   - Calculator counters for computed or aggregated values.
   An input that does not cleanly fit surfaces as an open question to the user, not as a forced metadata entry.
5. **Compose a proposal**. Decisions with recommended choice + rejected alternative + rationale - not an execution plan only. Confirm with the user before any write.

## API Call Discipline

Every `flespi-api-read` and `flespi-api-write` call follows this pattern, no exceptions. Both discovery and schema retrieval are free (0 credits) - use them liberally.

1. **Discover** - use `search-api-methods` when the endpoint path is not already known from an earlier call in this conversation. Pick the matching entry; its `link` feeds step 2.
2. **Retrieve the schema** - before any `flespi-api-read`/`flespi-api-write` call, ensure `api-method-schema` has been retrieved for that exact endpoint path in this conversation. A schema already fetched for the same path earlier is reusable; a schema mentioned by docs or implied by prior responses substitutes only when it names the exact path you are about to call. A field the schema does not declare is rejected on write and reads back absent on response.
3. **Compose** - build the URL from the schema: selectors and field projection in the path, query parameters where declared, a body only when the schema declares a request body.
4. **Execute** - `flespi-api-read` for GET; `flespi-api-write` for POST/PUT/PATCH/DELETE. Always confirm writes with the user before calling.
5. **Handle the response** - check `errors` before indexing into `result`. On a code=2 (bad-request) retry, re-read the schema before composing the retry rather than tweaking the failed body - a body the platform rejected is evidence the cached schema-understanding is wrong.

Guessing field names or paths from training knowledge is the primary source of code=2 rejections - the schema is the authoritative contract.

## Decision Tree

```
What do you need?
|
+-- Simple data retrieval (list items, check status, get messages)
|   --> flespi-api-read directly
|
+-- Don't know the endpoint path
|   --> search-api-methods, then flespi-api-read
|
+-- Need exact field names or request body format
|   --> api-method-schema (requires link from search-api-methods)
|
+-- Data aggregation or analytics (total mileage, trip count, max speed)
|   --> search-flespi-documentation FIRST (learn about calculate method/calculators)
|   --> then flespi-api-write for calculate, or flespi-api-read for existing intervals
|
+-- Complex configuration (streams, plugins, webhooks, calculators)
|   --> search-flespi-documentation to understand the feature
|   --> api-method-schema for exact field format
|   --> flespi-api-write to create (with user approval)
|
+-- Write flespi expressions (selectors, counters, filters, templates)
|   --> flespi-api-write: POST /ai/tools/generate-flespi-expression {"question": "..."}
|
+-- Device hardware, wiring, firmware reference
|   --> search-device-documentation (requires protocol_name + device_type_name)
|
+-- Compose a device command (reboot, setting change, custom)
|   --> api-method-schema for /gw/devices/*/commands or /commands-queue (REST contract)
|   --> search-device-documentation for the protocol's command catalog (name, properties, reply shape)
|   --> then flespi-api-write with user approval
|
+-- API call returned unexpected results or errors
|   --> search-api-methods or api-method-schema to verify endpoint details
|   --> Check schema_hint in error response for valid field names
```

## Tool Details

### flespi-api-read
Execute read-only GET requests. Authenticated automatically with the user's token.

**Parameters:**
- `url` (required): API path with selector and query params
- `data` (optional): JSON body for message/log/interval queries
- `cid` (optional): Customer ID for subaccount access

**Best practices:**
- Always add `?fields=field1,field2` to collection endpoints
- For messages/logs, use `data` with `count`, `fields`, `reverse`
- Use selectors to narrow results: `/gw/devices/123` not `/gw/devices/all`
- To get latest data: `data={"count":1,"reverse":true}`

**Examples:**
```
url: /gw/devices/all?fields=id,name,connected

url: /gw/devices/123/messages
data: {"count":5, "fields":"timestamp,position.speed,position.latitude,position.longitude", "reverse":true}

url: /gw/devices/configuration.ident=352625066004561?fields=id,name

url: /gw/devices/123/telemetry

url: /gw/calcs/456/devices/123/intervals/all
data: {"count":3, "reverse":true}

url: /platform/customer?fields=id,name,limits,counters
```

### flespi-api-write
Execute write operations. **Always confirm with the user before calling.**

**Parameters:**
- `method` (required): POST, PUT, PATCH, or DELETE
- `url` (required): API path
- `data` (optional): Request body (object or array)
- `cid` (optional): Customer ID for subaccount access

**Examples:**
```
method: POST
url: /gw/devices
data: [{"name": "My Tracker", "device_type_id": 42}]

method: PATCH
url: /gw/devices/123
data: {"name": "Renamed Device"}

method: DELETE
url: /gw/devices/123
```

### search-api-methods
Find API endpoints by natural language. Returns method links, titles, descriptions, and available selector/response fields.

**Parameters:**
- `queries` (required): Array of 1-3 search strings

**Examples:**
```
queries: ["list device types"]
queries: ["get device messages", "device telemetry"]
queries: ["calculate method for devices"]
```

Use the returned `link` field with `api-method-schema` for full details.

### api-method-schema
Get full Swagger schema for a specific endpoint. Use links from `search-api-methods`.

**Parameters:**
- `link` (required): ApiBox documentation URL starting with `https://flespi.io/docs/#/`

### search-flespi-documentation
Search knowledge base for platform concepts, configuration guidance, and best practices.

**Parameters:**
- `question` (required): Natural language question

**When to use:**
- Before any data aggregation task (calculators, calculate method)
- For unfamiliar features (streams, plugins, webhooks, geofences)
- For expression syntax and patterns
- For MQTT topics and integration patterns
- For troubleshooting guidance

### search-device-documentation
Search device manufacturer documentation and protocol specifications.

**Parameters:**
- `protocol_name` (required): Protocol name (e.g. `teltonika`, `queclink`)
- `device_type_name` (required): Device type in lowercase (e.g. `fmb120`, `gl300`)
- `question` (required): Question about the device

To find valid protocol/device names:
```
flespi-api-read: url=/gw/channel-protocols/{protocol}/device-types/all?fields=title,device_type_name
```

Not all protocols have device documentation (e.g. software protocols like wialon-ips, mqtt).

## Common Patterns

### Check device connectivity
```
flespi-api-read: url=/gw/devices/{id}/telemetry
--> Look at server.timestamp to see when device last reported
```

### Find device by IMEI
```
flespi-api-read: url=/gw/devices/configuration.ident=IMEI_VALUE?fields=id,name,connected
```

### Get recent messages with position
```
flespi-api-read: url=/gw/devices/{id}/messages
  data={"count":10,"reverse":true,"fields":"timestamp,position.latitude,position.longitude,position.speed"}
```

### Check stream health
```
flespi-api-read: url=/gw/streams/{id}?fields=id,name,enabled
flespi-api-read: url=/gw/streams/{id}/logs  data={"count":10,"reverse":true}
```

### Get calculator intervals
```
flespi-api-read: url=/gw/calcs/{calc_id}/devices/{device_id}/intervals/all
  data={"count":10,"reverse":true}
```

### Check account limits
```
flespi-api-read: url=/platform/customer?fields=limits,counters
```

### Create a device
```
flespi-api-write: method=POST url=/gw/devices
  data=[{"name":"My Tracker","device_type_id":42,"configuration":{"ident":"352625066004561"}}]
```
To find device_type_id: `flespi-api-read: url=/gw/channel-protocols/all?fields=id,title`

### Send a command to device

Command composition pairs two authoritative sources: `api-method-schema` for the REST contract (real-time `/commands` vs queued `/commands-queue`, shared options like `ttl`, `condition`, `max_attempts`) and `search-device-documentation` for the per-device-type catalog (correct name, properties, reply shape, SMS-only flags). Command names vary by device type - never compose a payload from training-data knowledge.

```
flespi-api-write: method=POST url=/gw/devices/123/commands
  data=[{"name":"getinfo","properties":{}}]
```
Check available commands: `flespi-api-read: url=/gw/channel-protocols/{protocol_id}/device-types/{type_id}?fields=commands`

### Generate a flespi expression
```
flespi-api-write: method=POST url=/ai/tools/generate-flespi-expression
  data={"question":"Filter devices with speed greater than 80 km/h"}
```
Use for: calculator selectors/counters, webhook templates, plugin expressions, REST API filters, stream filters.

### On-demand analytics (calculate method)
```
flespi-api-read: url=/gw/devices/123/messages/calculate
  data={"from":1700000000,"to":1700086400,"selectors":[{"type":"datetime"}],"counters":[{"type":"expression","name":"mileage","method":"summary","expression":"mileage()"}]}
```

## Key Principles

1. **Fetch schema before calling** - every endpoint path touched by `flespi-api-read`/`flespi-api-write` needs its own `api-method-schema` retrieval in this conversation first (see API Call Discipline); guessing field names causes code=2 rejections
2. **Prefer server-side operations** - use API selectors, expressions, `?fields=`, and the calculate method instead of fetching everything and filtering locally
3. **Search docs before aggregation** - flespi has powerful server-side analytics; learn them before iterating client-side
4. **Minimize response size** - always use `?fields=` for collections and `count`+`fields` for messages
5. **Escalate gradually** - start with free tools (api-read, search-api-methods), use paid tools only when needed
6. **Never write without approval** - always present planned mutations to the user first

