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

# Device Explainers

> Show customers why Podero is steering their heat pump or solar inverter

Explainers turn a device's optimization plan into a short, customer-facing narrative. The API supports current and historical explainers for heat pumps and solar inverters, together with feedback and dismissal actions.

An explainer is generated on the first read of a plan or historical window and then cached. Because the first request can take several seconds, show a loading state and do not use an aggressive request timeout.

<Note>
  Only request an explainer when the device is actively using smart optimization and explainers are enabled for the organization. For solar inverters, also confirm that a battery is connected. This is the same fail-closed approach used by myPodero.
</Note>

## Supported devices and endpoints

Substitute `heat-pumps/{heat_pump_id}` or `inverters/{inverter_id}` for `{device_path}` in the endpoints below.

| Purpose                    | Method | Endpoint                                                                        |
| -------------------------- | ------ | ------------------------------------------------------------------------------- |
| Explain the active plan    | `GET`  | `/org/{org_id}/users/{user_id}/{device_path}/explainer/current`                 |
| Explain a completed window | `GET`  | `/org/{org_id}/users/{user_id}/{device_path}/explainer/historical`              |
| Record feedback            | `POST` | `/org/{org_id}/users/{user_id}/{device_path}/explainer/{explainer_id}/feedback` |
| Dismiss an explainer       | `POST` | `/org/{org_id}/users/{user_id}/{device_path}/explainer/{explainer_id}/dismiss`  |

All examples use the base URL `https://app.podero.com/api/partners/v2.0` and require a bearer token. The `user_id` in the path is the owner of the device.

## 1. Fetch the current explainer

Use the current endpoint for the active optimization plan. You can pass an optional `language` query parameter, such as `en` or `de`. When it is omitted, Podero uses the organization's default language.

<Tabs>
  <Tab title="Heat pump">
    <CodeGroup>
      ```bash cURL theme={null}
      curl -G \
        'https://app.podero.com/api/partners/v2.0/org/{org_id}/users/{user_id}/heat-pumps/{heat_pump_id}/explainer/current' \
        -H 'Authorization: Bearer {auth_token}' \
        --data-urlencode 'language=de'
      ```

      ```typescript TypeScript theme={null}
      const params = new URLSearchParams({ language: currentLanguage });
      const response = await fetch(
        `${baseUrl}/org/${orgId}/users/${userId}/heat-pumps/${heatPumpId}/explainer/current?${params}`,
        { headers: { Authorization: `Bearer ${authToken}` } },
      );

      if (!response.ok) throw new Error(`Explainer request failed: ${response.status}`);
      const explainer: Explainer | null = await response.json();
      ```
    </CodeGroup>
  </Tab>

  <Tab title="Solar inverter">
    <CodeGroup>
      ```bash cURL theme={null}
      curl -G \
        'https://app.podero.com/api/partners/v2.0/org/{org_id}/users/{user_id}/inverters/{inverter_id}/explainer/current' \
        -H 'Authorization: Bearer {auth_token}' \
        --data-urlencode 'language=de'
      ```

      ```typescript TypeScript theme={null}
      const params = new URLSearchParams({ language: currentLanguage });
      const response = await fetch(
        `${baseUrl}/org/${orgId}/users/${userId}/inverters/${inverterId}/explainer/current?${params}`,
        { headers: { Authorization: `Bearer ${authToken}` } },
      );

      if (!response.ok) throw new Error(`Explainer request failed: ${response.status}`);
      const explainer: Explainer | null = await response.json();
      ```
    </CodeGroup>
  </Tab>
</Tabs>

A successful response contains a durable `id`, the localized explanation, and the authenticated caller's own most recent feedback:

```json theme={null}
{
  "id": "c92b58d6-14a7-4eba-9836-cef076735a4a",
  "explanation": "Podero will shift energy use toward the lower-price hours this afternoon.",
  "feedback": null
}
```

The response can be `200 OK` with a JSON body of `null` when no explanation is available, for example when a plan has not been solved yet. Treat this as a normal empty state rather than an error.

<Tip>
  Include the selected language in your client cache key. When a customer changes language, fetch again instead of displaying text cached in the previous language. myPodero keeps current explainers fresh for five minutes.
</Tip>

## 2. Fetch a historical explainer

Use the historical endpoint to explain what happened during a completed past window. Supply `start` and `end` as ISO 8601 timestamps. Use URL encoding rather than concatenating the query string so that timezone offsets containing `+` are preserved.

<Tabs>
  <Tab title="Heat pump">
    <CodeGroup>
      ```bash cURL theme={null}
      curl -G \
        'https://app.podero.com/api/partners/v2.0/org/{org_id}/users/{user_id}/heat-pumps/{heat_pump_id}/explainer/historical' \
        -H 'Authorization: Bearer {auth_token}' \
        --data-urlencode 'start=2026-09-16T00:00:00Z' \
        --data-urlencode 'end=2026-09-17T00:00:00Z' \
        --data-urlencode 'language=de'
      ```

      ```typescript TypeScript theme={null}
      const params = new URLSearchParams({
        start: '2026-09-16T00:00:00Z',
        end: '2026-09-17T00:00:00Z',
        language: currentLanguage,
      });

      const response = await fetch(
        `${baseUrl}/org/${orgId}/users/${userId}/heat-pumps/${heatPumpId}/explainer/historical?${params}`,
        { headers: { Authorization: `Bearer ${authToken}` } },
      );

      if (!response.ok) throw new Error(`Explainer request failed: ${response.status}`);
      const explainer: Explainer | null = await response.json();
      ```
    </CodeGroup>
  </Tab>

  <Tab title="Solar inverter">
    <CodeGroup>
      ```bash cURL theme={null}
      curl -G \
        'https://app.podero.com/api/partners/v2.0/org/{org_id}/users/{user_id}/inverters/{inverter_id}/explainer/historical' \
        -H 'Authorization: Bearer {auth_token}' \
        --data-urlencode 'start=2026-09-16T00:00:00Z' \
        --data-urlencode 'end=2026-09-17T00:00:00Z' \
        --data-urlencode 'language=de'
      ```

      ```typescript TypeScript theme={null}
      const params = new URLSearchParams({
        start: '2026-09-16T00:00:00Z',
        end: '2026-09-17T00:00:00Z',
        language: currentLanguage,
      });

      const response = await fetch(
        `${baseUrl}/org/${orgId}/users/${userId}/inverters/${inverterId}/explainer/historical?${params}`,
        { headers: { Authorization: `Bearer ${authToken}` } },
      );

      if (!response.ok) throw new Error(`Explainer request failed: ${response.status}`);
      const explainer: Explainer | null = await response.json();
      ```
    </CodeGroup>
  </Tab>
</Tabs>

Request completed windows only. A future or otherwise unavailable window returns `200 OK` with `null`. For daily history views, normalize the selected day to explicit UTC instants and use the same window in the cache key.

Historical explainers are persisted for their window. myPodero therefore caches a successfully fetched historical explainer for the rest of the session.

## 3. Collect feedback

Use the explainer `id` from either read endpoint when a customer rates the explanation. Positive feedback only needs `helpful`. Negative feedback can include one of these stable reason codes:

* `wrong_language`
* `confusing`
* `disagree_with_steering`

Send the code, never a translated label.

<Tabs>
  <Tab title="Heat pump">
    <CodeGroup>
      ```bash cURL theme={null}
      curl -X POST \
        'https://app.podero.com/api/partners/v2.0/org/{org_id}/users/{user_id}/heat-pumps/{heat_pump_id}/explainer/{explainer_id}/feedback' \
        -H 'Authorization: Bearer {auth_token}' \
        -H 'Content-Type: application/json' \
        -d '{
          "helpful": false,
          "reason": "confusing"
        }'
      ```

      ```typescript TypeScript theme={null}
      const body = { helpful: false, reason: 'confusing' };

      await fetch(
        `${baseUrl}/org/${orgId}/users/${userId}/heat-pumps/${heatPumpId}/explainer/${explainerId}/feedback`,
        {
          method: 'POST',
          headers: {
            Authorization: `Bearer ${authToken}`,
            'Content-Type': 'application/json',
          },
          body: JSON.stringify(body),
        },
      );
      ```
    </CodeGroup>
  </Tab>

  <Tab title="Solar inverter">
    <CodeGroup>
      ```bash cURL theme={null}
      curl -X POST \
        'https://app.podero.com/api/partners/v2.0/org/{org_id}/users/{user_id}/inverters/{inverter_id}/explainer/{explainer_id}/feedback' \
        -H 'Authorization: Bearer {auth_token}' \
        -H 'Content-Type: application/json' \
        -d '{
          "helpful": false,
          "reason": "confusing"
        }'
      ```

      ```typescript TypeScript theme={null}
      const body = { helpful: false, reason: 'confusing' };

      await fetch(
        `${baseUrl}/org/${orgId}/users/${userId}/inverters/${inverterId}/explainer/${explainerId}/feedback`,
        {
          method: 'POST',
          headers: {
            Authorization: `Bearer ${authToken}`,
            'Content-Type': 'application/json',
          },
          body: JSON.stringify(body),
        },
      );
      ```
    </CodeGroup>
  </Tab>
</Tabs>

The response is:

```json theme={null}
{ "recorded": true }
```

Feedback belongs to the authenticated caller, not necessarily the device owner named by `user_id`. The next read returns only that caller's latest rating in the `feedback` field. An unsupported reason code returns `422 Unprocessable Entity`.

## 4. Dismiss an explainer

Dismiss an explainer when the customer closes it permanently:

<Tabs>
  <Tab title="Heat pump">
    ```bash theme={null}
    curl -X POST \
      'https://app.podero.com/api/partners/v2.0/org/{org_id}/users/{user_id}/heat-pumps/{heat_pump_id}/explainer/{explainer_id}/dismiss' \
      -H 'Authorization: Bearer {auth_token}'
    ```
  </Tab>

  <Tab title="Solar inverter">
    ```bash theme={null}
    curl -X POST \
      'https://app.podero.com/api/partners/v2.0/org/{org_id}/users/{user_id}/inverters/{inverter_id}/explainer/{explainer_id}/dismiss' \
      -H 'Authorization: Bearer {auth_token}'
    ```
  </Tab>
</Tabs>

The response is:

```json theme={null}
{ "dismissed": true }
```

Remove the card from local state immediately, then restore it if the request fails. A dismissed explainer is not returned by subsequent reads.

## Recommended UI behavior

1. Check the organization's explainer setting and the device's smart-optimization state before making a request.
2. Show a loading placeholder because the first read may generate the explanation synchronously.
3. Render nothing when the API returns `null`.
4. Display `explanation` as plain text. Do not interpret it as HTML or Markdown.
5. Use `id` for feedback and dismissal actions.
6. Hide the feedback prompt when `feedback` is already present, or reflect the saved rating.
7. Do not retry generation requests automatically in a tight loop. myPodero disables automatic retries and lets the user or a later refresh try again.

## TypeScript types

```typescript theme={null}
type ExplainerFeedback = {
  helpful: boolean;
  reason: 'wrong_language' | 'confusing' | 'disagree_with_steering' | null;
};

type Explainer = {
  id: string;
  explanation: string;
  feedback: ExplainerFeedback | null;
};
```

The complete endpoint schemas are also available in the interactive API Reference.
