> ## Documentation Index
> Fetch the complete documentation index at: https://docs.coreflux.org/llms.txt
> Use this file to discover all available pages before exploring further.

# Memfault

> Client-mode REST API connector for the Memfault REST API: read and update devices, manage cohorts, create releases, deploy them to cohorts and follow the rollout, and triage crash issues from MQTT, plus an opt-in issue and alert webhook receiver

Technical Preview

## Overview

[Memfault](https://memfault.com) is a device reliability platform for MCU, Linux and Android fleets: it collects crashes, metrics and logs from devices through the Memfault SDKs and runs OTA updates by cohort. These connectors connect the Coreflux broker to the Memfault [REST API](https://docs.memfault.com/api), so plant logic can check a device's health and release, move devices between cohorts, roll out firmware and react to new crash issues over MQTT.

* **MemfaultRestApi** (`MemfaultRestApi.lot`) is a client connector on `https://api.memfault.com` with 32 events.
* **MemfaultWebhookReceiver** (`cloud-iot_memfault_webhook.lot`, opt-in, server mode) receives Memfault issue and alert webhooks, so new or regressed issues reach MQTT without polling.

The events follow Memfault's published [OpenAPI definition](https://docs.memfault.com/openapi/memfault-api.yaml).

## Authentication

> [!WARNING]
>
> An Organization Auth Token has project-manager access to every project in the organization, and a user API key acts as that user. Keep the credential in a Hub secret, map it in with `GET SECRET`, and never publish it on a topic other clients can read.

Every event takes `"authorization"` in its MQTT payload and sends it as the whole `Authorization` header, so you can use either Memfault credential:

| Credential | Where to create it | `authorization` value |
| - | - | - |
| Organization Auth Token (recommended) | **Admin → Organization Auth Tokens** (organization admins). The only option when the organization enforces SSO. | `Bearer oat_...` |
| User API key | **Project Settings → User API Key**. Stops working when the user leaves the organization. | `Basic <base64 of email:api_key>` |

1. Store the full value as the secret `MEMFAULT_AUTHORIZATION`.
2. Every project event also takes `"organization_slug"` and `"project_slug"`, as shown in the Memfault app URL.
3. `GetLatestReleaseForDevice` is the device-side check and sends `"project_key"` in the `Memfault-Project-Key` header instead. Project keys aren't secret, but they let anyone send data into the project.

A missing or wrong credential returns `401` with `{"error": {"message": "Unauthorized", "http_code": 401, "type": "UnauthorizedError", "code": 1004}}`.

## Payload conventions

* Path ids (`device_serial`, `cohort_slug`, `release_version`, `deployment_id`, `issue_id`, `task_id`, `software_type_slug`, `version`) go at the top level of the MQTT payload.
* Events marked `?{query}` take the query string already URL-encoded, for example `page=1&per_page=100` (default 20, max 100) or `status=open&sort=-created_date`. Timestamps in filters must be ISO 8601 with `Z`. Send `"query": ""` when there is nothing to add.
* Request bodies come from `"body"` and are sent as JSON.

## Projects and tasks

| Event | Request | Topic | Notes |
| - | - | - | - |
| GetMe | `GET /auth/me` | `cloud-iot/memfault/me/get` | Auth smoke (user API key, Basic): the logged-in user and their organizations; with an org token use ListProjects instead |
| ListProjects | `GET /api/v0/organizations/{organization_slug}/projects?{query}` | `cloud-iot/memfault/project/list` | Auth smoke (org token or user key): projects in the organization; query page=1\&per\_page=100 \| sort=-created\_date (send "" for none) |
| GetProject | `GET /api/v0/organizations/{organization_slug}/projects/{project_slug}` | `cloud-iot/memfault/project/get` | One project by slug: name, slug, platform, os, count\_devices (the response also carries the project key) |
| GetTaskStatus | `GET /api/v0/queue/{task_id}` | `cloud-iot/memfault/task/get` | Status of an asynchronous task, e.g. \{"status": "SUCCESS"} |

## Devices

| Event | Request | Topic | Notes |
| - | - | - | - |
| ListDevices | `GET /api/v0/organizations/{organization_slug}/projects/{project_slug}/devices?{query}` | `cloud-iot/memfault/device/list` | Devices in the project (paged, max 100 per page); query page=1\&per\_page=100 \| sort=-created\_date (send "" for none) |
| GetDevice | `GET /api/v0/organizations/{organization_slug}/projects/{project_slug}/devices/{device_serial}` | `cloud-iot/memfault/device/get` | Catalog: one device by serial: cohort, hardware\_version, last\_seen, last\_seen\_release, latest\_install, latest\_trace |
| CreateDevice | `POST /api/v0/organizations/{organization_slug}/projects/{project_slug}/devices` | `cloud-iot/memfault/device/create` | **Write.** Create a device; body \{"device\_serial": "ABCD1234", "hardware\_version": "proto"} (409 if it already exists) |
| UpdateDevice | `PATCH /api/v0/organizations/{organization_slug}/projects/{project_slug}/devices/{device_serial}` | `cloud-iot/memfault/device/update` | **Write.** Update a device, e.g. move it to a cohort; body \{"cohort": "beta"} (cohort slug) |
| GetDeviceAttributes | `GET /api/v0/organizations/{organization_slug}/projects/{project_slug}/devices/{device_serial}/attributes?{query}` | `cloud-iot/memfault/device-attributes/get` | Device attributes; query q=line\_\* filters by string\_key (wildcard \*), send "" for all |
| UpdateDeviceAttributes | `PATCH /api/v0/organizations/{organization_slug}/projects/{project_slug}/devices/{device_serial}/attributes` | `cloud-iot/memfault/device-attributes/update` | **Write.** Update one or more device attributes (attribute must exist in Settings > Metrics); request body shape is not in the published spec, confirm on unblock |

## Cohorts

| Event | Request | Topic | Notes |
| - | - | - | - |
| ListCohorts | `GET /api/v0/organizations/{organization_slug}/projects/{project_slug}/cohorts?{query}` | `cloud-iot/memfault/cohort/list` | Cohorts in the project (paged); send query "" for none |
| GetCohort | `GET /api/v0/organizations/{organization_slug}/projects/{project_slug}/cohorts/{cohort_slug}` | `cloud-iot/memfault/cohort/get` | One cohort: name, slug, count\_devices, last\_deployment |
| CreateCohort | `POST /api/v0/organizations/{organization_slug}/projects/{project_slug}/cohorts` | `cloud-iot/memfault/cohort/create` | **Write.** Create a cohort; body \{"name": "Beta", "slug": "beta"} (409 if it already exists) |
| UpdateCohort | `PATCH /api/v0/organizations/{organization_slug}/projects/{project_slug}/cohorts/{cohort_slug}` | `cloud-iot/memfault/cohort/update` | **Write.** Update a cohort; body \{"name": "Beta line 1"} (fields from the Cohort schema) |
| ListCohortDevices | `GET /api/v0/organizations/{organization_slug}/projects/{project_slug}/cohorts/{cohort_slug}/devices?{query}` | `cloud-iot/memfault/cohort-device/list` | Devices in one cohort (paged); query page=1\&per\_page=100 (send "" for none) |
| MoveDevicesToCohort | `PATCH /api/v0/organizations/{organization_slug}/projects/{project_slug}/cohorts/{cohort_slug}/devices` | `cloud-iot/memfault/cohort-device/move` | **Write.** Bulk: move devices to the cohort (removes them from their previous cohort, async, 202); request body shape is not in the published spec, for one device prefer UpdateDevice |

## OTA: releases and deployments

A **release** is a firmware version with its payloads. A **deployment** activates a release for one **cohort**, and the devices in that cohort pick it up the next time they ask for the latest release. A `staged_rollout` deployment only offers it to a share of the cohort.

| Event | Request | Topic | Notes |
| - | - | - | - |
| ListReleases | `GET /api/v0/organizations/{organization_slug}/projects/{project_slug}/releases?{query}` | `cloud-iot/memfault/release/list` | Releases in the project (paged); query page=1\&per\_page=20 \| sort=-created\_date (send "" for none) |
| GetRelease | `GET /api/v0/organizations/{organization_slug}/projects/{project_slug}/releases/{release_version}` | `cloud-iot/memfault/release/get` | One release by version: artifacts, count\_devices, min\_version, notes, revision |
| CreateRelease | `POST /api/v0/organizations/{organization_slug}/projects/{project_slug}/releases` | `cloud-iot/memfault/release/create` | **Write.** Create a release record; body \{"version": "1.2.0", "revision": "g31cffde", "notes": "Line 1 fix"} (fields from the Release schema; binaries go through the CLI upload flow) |
| UpdateRelease | `PATCH /api/v0/organizations/{organization_slug}/projects/{project_slug}/releases/{release_version}` | `cloud-iot/memfault/release/update` | **Write.** Update a release; body \{"notes": "Line 1 fix"} (fields from the Release schema) |
| GetLatestReleaseForDevice | `GET /api/v0/releases/latest?{query}` | `cloud-iot/memfault/release-latest/get` | Device-side check (Memfault-Project-Key, not secret): latest release a device should install; query hardware\_version=evt\&software\_type=main\&current\_version=1.0.0\&device\_serial=ABCD1234; 204 = up to date; 100 calls per device per day |
| ListDeployments | `GET /api/v0/organizations/{organization_slug}/projects/{project_slug}/deployments?{query}` | `cloud-iot/memfault/deployment/list` | Deployments (paged); query cohort=beta\&release=1.2.0 (send "" for none) |
| GetDeployment | `GET /api/v0/organizations/{organization_slug}/projects/{project_slug}/deployments/{deployment_id}` | `cloud-iot/memfault/deployment/get` | One deployment: cohort, release, status, type, rollout\_percent, deployer |
| CreateDeployment | `POST /api/v0/organizations/{organization_slug}/projects/{project_slug}/deployments` | `cloud-iot/memfault/deployment/create` | **Write.** Catalog: deploy a release to a cohort (devices in the cohort start updating); body \{"type": "normal", "release": "1.2.0", "cohort": "beta"} or \{"type": "staged\_rollout", "release": "1.2.0", "cohort": "beta", "rollout\_percent": 10} |
| UpdateDeployment | `PATCH /api/v0/organizations/{organization_slug}/projects/{project_slug}/deployments/{deployment_id}` | `cloud-iot/memfault/deployment/update` | **Write.** Update a deployment, e.g. widen a staged rollout; body \{"rollout\_percent": 50} (fields from the Deployment schema) |
| ListSoftwareTypes | `GET /api/v0/organizations/{organization_slug}/projects/{project_slug}/software_types?{query}` | `cloud-iot/memfault/software-type/list` | Software types (firmware components) in the project; send query "" for none |
| GetSoftwareType | `GET /api/v0/organizations/{organization_slug}/projects/{project_slug}/software_types/{software_type_slug}` | `cloud-iot/memfault/software-type/get` | One software type |
| ListSoftwareVersions | `GET /api/v0/organizations/{organization_slug}/projects/{project_slug}/software_types/{software_type_slug}/software_versions?{query}` | `cloud-iot/memfault/software-version/list` | Software versions of one software type (paged); send query "" for none |
| GetSoftwareVersion | `GET /api/v0/organizations/{organization_slug}/projects/{project_slug}/software_types/{software_type_slug}/software_versions/{version}` | `cloud-iot/memfault/software-version/get` | One software version (also tells whether a version exists before deploying) |

## Issues

| Event | Request | Topic | Notes |
| - | - | - | - |
| ListIssues | `GET /api/v0/organizations/{organization_slug}/projects/{project_slug}/issues?{query}` | `cloud-iot/memfault/issue/list` | Issues (crash / trace groups, paged); query status=open\&cohort=beta\&sort=-created\_date (send "" for none) |
| GetIssue | `GET /api/v0/organizations/{organization_slug}/projects/{project_slug}/issues/{issue_id}` | `cloud-iot/memfault/issue/get` | One issue: title, reason, status, device\_count, trace\_count, first / last release, last\_trace stack |
| UpdateIssue | `PATCH /api/v0/organizations/{organization_slug}/projects/{project_slug}/issues/{issue_id}` | `cloud-iot/memfault/issue/update` | **Write.** Set the issue state; body \{"status": "open"} (spec example; UI states are Unresolved, Resolved, Muted, the API strings for resolve / mute are unconfirmed) |

## Webhook receiver

Deploy `cloud-iot_memfault_webhook.lot` only if you want Memfault to push notifications to the broker. It opens HTTP port `9004`. Put a reverse proxy or tunnel with a public TLS certificate in front of it, and have it verify the `X-Signature-SHA256` header (HMAC-SHA256 of the raw body with the target's signing secret), because the connector itself doesn't.

1. In Memfault, open **Settings → Notifications → External Targets** and add a [Webhook](https://docs.memfault.com/docs/platform/webhooks) target with the public URL `https://<public host>/memfault/webhook/issues`. Keep the signing secret it shows for your proxy.
2. Add a second target for `https://<public host>/memfault/webhook/alerts` and pick it in your [alerts](https://docs.memfault.com/docs/platform/alerts).
3. Use the target's test notification to check the path: it arrives with `"type": "test_notification.dispatched"`.

| Path | Topic | Memfault event types |
| - | - | - |
| `POST /memfault/webhook/issues` | `cloud-iot/memfault/webhook-issue/received` | `issue.created`, `issue.reopened`, `issue.regressed`, `issue.resolved`, `issue_comment.created` |
| `POST /memfault/webhook/alerts` | `cloud-iot/memfault/webhook-alert/received` | `device_threshold_alert.summary`, `device_threshold_alert.incident_started`, `device_threshold_alert.incident_ended`, `fleet_threshold_alert.triggered` |

The body is `{"type": "<event type>", "project": {"id": "...", "slug": "..."}, ...}`. The `Memfault-Delivery` header stays the same on retries, so your proxy can use it to drop duplicates. Memfault marks generic webhooks as experimental, so payload fields beyond `type` and `project` may change.

## Golioth chunk forwarding

If your devices connect through [Golioth](/latest/lot-language/routes/rest-api-apps/golioth), Golioth forwards their Memfault chunks itself with the `memfault` pipeline destination and the project key. Nothing extra runs on the broker. This connector then reads the resulting devices, issues and releases from Memfault. Chunk upload (`chunks.memfault.com`) isn't packaged, because chunks are binary.

## Examples

Read one device:

```json theme={"theme":"css-variables","languages":{"custom":["/languages/lot.json"]}}
{"authorization": "Bearer <MEMFAULT_ORG_TOKEN>", "organization_slug": "coreflux", "project_slug": "porto-line-1", "device_serial": "ABCD1234"}
```

Move a device to the `beta` cohort:

```json theme={"theme":"css-variables","languages":{"custom":["/languages/lot.json"]}}
{"authorization": "Bearer <MEMFAULT_ORG_TOKEN>", "organization_slug": "coreflux", "project_slug": "porto-line-1", "device_serial": "ABCD1234", "body": {"cohort": "beta"}}
```

Deploy release `1.2.0` to 10% of the `beta` cohort:

```json theme={"theme":"css-variables","languages":{"custom":["/languages/lot.json"]}}
{"authorization": "Bearer <MEMFAULT_ORG_TOKEN>", "organization_slug": "coreflux", "project_slug": "porto-line-1", "body": {"type": "staged_rollout", "release": "1.2.0", "cohort": "beta", "rollout_percent": 10}}
```

Check which release a device would get:

```json theme={"theme":"css-variables","languages":{"custom":["/languages/lot.json"]}}
{"project_key": "<MEMFAULT_PROJECT_KEY>", "query": "hardware_version=evt&software_type=main&current_version=1.1.0&device_serial=ABCD1234"}
```

LoT actions that wire these events to plant topics:

```lot theme={"theme":"css-variables","languages":{"custom":["/languages/lot.json"]}}
DEFINE ACTION MemfaultDeployToCohort
ON TOPIC "porto/ota/memfault/deploy" DO
    SET "auth" WITH GET SECRET "MEMFAULT_AUTHORIZATION"
    SET "org" WITH GET SECRET "MEMFAULT_ORG_SLUG"
    SET "proj" WITH GET SECRET "MEMFAULT_PROJECT_SLUG"
    SET "rel" WITH GET JSON "release" IN PAYLOAD AS STRING
    SET "coh" WITH GET JSON "cohort" IN PAYLOAD AS STRING
    PUBLISH TOPIC "cloud-iot/memfault/deployment/create" WITH { "authorization": {auth}, "organization_slug": {org}, "project_slug": {proj}, "body": { "type": "normal", "release": {rel}, "cohort": {coh} } }

DEFINE ACTION MemfaultPollOpenIssues
ON EVERY 5 MINUTES DO
    SET "auth" WITH GET SECRET "MEMFAULT_AUTHORIZATION"
    SET "org" WITH GET SECRET "MEMFAULT_ORG_SLUG"
    SET "proj" WITH GET SECRET "MEMFAULT_PROJECT_SLUG"
    PUBLISH TOPIC "cloud-iot/memfault/issue/list" WITH { "authorization": {auth}, "organization_slug": {org}, "project_slug": {proj}, "query": "status=open&sort=-created_date&per_page=20" }

DEFINE ACTION MemfaultWebhookToPlant
ON TOPIC "cloud-iot/memfault/webhook-issue/received" DO
    SET "kind" WITH (GET JSON "type" IN PAYLOAD AS STRING)
    PUBLISH TOPIC "plant/memfault/issues/" + {kind} WITH PAYLOAD
```

## Writes

11 events change data in Memfault, and each one is marked **Write** above. `CreateDeployment`, `UpdateDeployment`, `UpdateDevice` and `MoveDevicesToCohort` decide which firmware devices in the field install, so test them on a lab cohort first. No `DELETE` operation is packaged, so this connector can't deactivate a deployment or delete devices, cohorts, releases or issues.

> [!NOTE]
>
> Memfault's published spec has no request schemas for most writes. The `CreateDevice`, `UpdateDevice`, `CreateCohort` and `CreateDeployment` bodies above come from the Memfault docs and CLI. Check the other bodies (`CreateRelease`, `UpdateRelease`, `UpdateCohort`, `UpdateDeployment`, `UpdateDeviceAttributes`, `MoveDevicesToCohort`, and the `UpdateIssue` values for resolved or muted) against your project before you rely on them.

## Not packaged

User API key management, project create, update and delete, project and client keys, data uploads (chunks, events, traces, coredumps, bug reports, symbols and release artifacts on the `chunks`, `ingress` and `files` hosts), software type and version create, installs (listed in the spec, but the live API returns `404` for them), SBOMs, nRF Cloud bulk device creation and every `DELETE` are left out of this template.

## Status

Technical preview, and blocked on a live test. The events were checked against Memfault's published OpenAPI, and all 32 events reach the real `api.memfault.com` host and are rejected without a valid credential as expected. An authenticated read and write with a real Memfault organization token is still pending.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.