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

# Widgets

> Embed your status page's live status into your website as a badge or banner, or integrate it yourself via the public JSON API

Flashduty Status Pages provide **widgets**: a snippet you can paste into any website's HTML to show your live service status on your own site, help center, or internal system. The widget runs as a Web Component (`<flashduty-status-widget>`) with styles isolated from the host page, so neither side affects the other.

<Warning>
  Widgets are only available for **public status pages**. The Widget settings page is not available for internal status pages.
</Warning>

## Two forms

| Form             | Description                                                                                                      | Best for                                                                                   |
| ---------------- | ---------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------ |
| **Status badge** | Always shows the current overall status; clicking it opens your status page                                      | Footers, help centers, and anywhere you want a persistent status indicator                 |
| **Event banner** | Appears only during an incident or maintenance, pinned to the top or bottom of the page; visitors can dismiss it | Your homepage or console, where visitors should be proactively informed during an incident |

Both forms support theme (Auto / Light / Dark) and language (中文 / English). The badge additionally supports size options, and the banner supports position options.

***

## Generate the embed code in the console

<Steps>
  <Step title="Open the Widget settings">
    Go to your status page detail view and choose **Settings → Widget**. The page has three tabs: **Status Badge**, **Event Banner**, and **API**.
  </Step>

  <Step title="Configure appearance">
    On each tab, adjust the theme, language, size (badge), or position (banner); the **live preview** updates as you go. The preview is for visualization only and never modifies the real status page. For the banner, you can also switch among three preview scenarios: Operational / Incident / Scheduled maintenance.
  </Step>

  <Step title="Copy the embed code">
    Click **Copy code** and paste the snippet into your website's HTML. The script version in the embed code always matches what your status page deployment actually serves — no manual maintenance needed.
  </Step>
</Steps>

### Embed code examples

<CodeGroup>
  ```html Status badge theme={null}
  <script async src="https://status.example.com/status-page-widget/1.0.1/widget.js" integrity="sha384-UPNiiTsMdVbjrBGP/eTAW+OBpaWrL5bFa4L0IJkxrGelurQ+sELET6of/tdLcrOn" crossorigin="anonymous"></script>

  <flashduty-status-widget
    page="https://status.example.com"
    type="badge"
    theme="auto"
    locale="en"
    size="medium"
  ></flashduty-status-widget>
  ```

  ```html Event banner theme={null}
  <script async src="https://status.example.com/status-page-widget/1.0.1/widget.js" integrity="sha384-UPNiiTsMdVbjrBGP/eTAW+OBpaWrL5bFa4L0IJkxrGelurQ+sELET6of/tdLcrOn" crossorigin="anonymous"></script>

  <flashduty-status-widget
    page="https://status.example.com"
    type="banner"
    theme="auto"
    locale="en"
    position="top"
  ></flashduty-status-widget>
  ```
</CodeGroup>

<Note>
  The script tag carries `integrity` (SRI) and `crossorigin="anonymous"` attributes — copy the whole snippet, not just the `<flashduty-status-widget>` tag. Replace `https://status.example.com` in the examples with your actual status page URL (the code generated in the console already contains the real URL).
</Note>

### Attribute reference

`<flashduty-status-widget>` supports the following attributes:

| Attribute                   | Values                            | Default                  | Applies to  | Description                                                                   |
| --------------------------- | --------------------------------- | ------------------------ | ----------- | ----------------------------------------------------------------------------- |
| `page`                      | Full status page URL (http/https) | None (**required**)      | Both        | The widget fetches status data from `<page>/api/widget/v1/summary.json`       |
| `type`                      | `badge` / `banner`                | `badge`                  | Both        | Badge or banner form                                                          |
| `theme`                     | `auto` / `light` / `dark`         | `auto`                   | Both        | `auto` follows the visitor's system dark-mode setting                         |
| `locale`                    | `zh` / `en`                       | Follows browser language | Both        | Language of the widget copy                                                   |
| `size`                      | `small` / `medium` / `large`      | `medium`                 | Badge only  | Badge size                                                                    |
| `position`                  | `top` / `bottom`                  | `top`                    | Banner only | Pins the banner to the top or bottom of the page                              |
| `show-upcoming-maintenance` | `true` / `false`                  | `true`                   | Banner only | Whether scheduled maintenance appears automatically 24 hours before it starts |

***

## Behavior

### Data refresh

* The widget polls the status endpoint every **30 seconds** by default (the interval is delivered by the API's `poll_after_seconds` field), with random jitter so large numbers of visitors don't all request at once
* Requests carry the `If-None-Match` (ETag) conditional header; when data hasn't changed, the server returns `304` without re-sending the body
* Polling **pauses while the page is hidden** (background tab) and refreshes immediately when the page becomes visible again
* Failed requests retry with exponential backoff (starting at 5 seconds, capped at 5 minutes)

### Stale data

The API declares a freshness window via `max_stale_seconds` (**120 seconds** by default). If data can't be validated within that window:

* The **badge** switches to an "unknown" state and shows when the data was last confirmed
* The **banner** does not render without valid data

### Banner display and dismissal

* The banner picks what to show by priority: **ongoing incident** > **in-progress maintenance** > **scheduled maintenance starting within 24 hours**; when multiple events exist, the banner shows "N more"
* Visitors can dismiss the banner with ×. The dismissal is remembered for the **current browser session**, keyed by "event ID + last updated time" — when the event has new progress (a status change or a new timeline update), the banner reappears
* Set `show-upcoming-maintenance="false"` to disable the 24-hour advance display of scheduled maintenance

### Status values and colors

| Status           | Meaning              | Color     |
| ---------------- | -------------------- | --------- |
| `operational`    | Operating normally   | 🟢 Green  |
| `degraded`       | Degraded performance | 🟡 Yellow |
| `partial_outage` | Partial outage       | 🟠 Orange |
| `full_outage`    | Full outage          | 🔴 Red    |
| `maintenance`    | Under maintenance    | 🔵 Blue   |

<Note>
  Components set to **hidden** on the status page never appear in widget data — the snapshot only contains incidents and maintenance for components visible to the public.
</Note>

***

## Public API: summary.json

If you prefer not to use the ready-made Web Component, call the JSON snapshot endpoint built into every public status page and render or integrate the data yourself. The console's **Widget → API** tab shows the exact URL.

```
GET {status page URL}/api/widget/v1/summary.json
```

* **Public access**: no authentication, no API key
* **Cross-origin**: responses carry `Access-Control-Allow-Origin: *`, so browsers can call it directly; `GET`, `HEAD`, and `OPTIONS` (preflight) are supported
* **Caching**: responses include `Cache-Control: public, max-age=30, s-maxage=30, stale-while-revalidate=120, stale-if-error=3600` and an `ETag` — send `If-None-Match` and receive `304` when nothing changed
* **Freshness**: the `X-Status-Validated-At` response header records when the snapshot was last successfully validated against the backend; use it to judge whether data is stale

<Warning>
  This endpoint is designed for low-frequency polling from browsers. **In high-traffic scenarios, proxy the requests through your own server and cache the responses** — don't let large numbers of clients hit this URL directly.
</Warning>

### Response fields

The response is a single JSON object whose `schema_version` is currently fixed at `"1.0"`:

| Field                      | Type   | Description                                                                                                      |
| -------------------------- | ------ | ---------------------------------------------------------------------------------------------------------------- |
| `schema_version`           | string | Schema version, currently `"1.0"`                                                                                |
| `generated_at`             | string | Data version timestamp (ISO 8601); only advances when the data changes                                           |
| `poll_after_seconds`       | number | Suggested polling interval in seconds, currently `30`                                                            |
| `max_stale_seconds`        | number | Freshness window in seconds; treat data as stale beyond this, currently `120`                                    |
| `page`                     | object | Status page info: `name`, `url`                                                                                  |
| `overall`                  | object | Overall status: `status` is one of `operational` / `degraded` / `partial_outage` / `full_outage` / `maintenance` |
| `ongoing_incidents`        | array  | Ongoing incidents (see below)                                                                                    |
| `in_progress_maintenances` | array  | In-progress maintenances (see below)                                                                             |
| `scheduled_maintenances`   | array  | Scheduled maintenances starting within 72 hours, at most 3 entries                                               |

Elements of `ongoing_incidents`:

| Field                 | Type           | Description                                                                                          |
| --------------------- | -------------- | ---------------------------------------------------------------------------------------------------- |
| `id`                  | string         | Event ID                                                                                             |
| `title`               | string         | Event title                                                                                          |
| `phase`               | string         | Lifecycle status: `investigating` / `identified` / `monitoring`                                      |
| `impact`              | string         | Impact level, same value set as the status enum                                                      |
| `started_at`          | string         | Start time (ISO 8601)                                                                                |
| `updated_at`          | string         | Last updated time (ISO 8601)                                                                         |
| `url`                 | string         | Link to the event detail on the status page                                                          |
| `last_update`         | object \| null | Latest timeline update: `at`, `message`                                                              |
| `affected_components` | array          | Affected components: `id`, `name`, `group_name` (optional), `status` (one of the status enum values) |

Elements of `in_progress_maintenances` and `scheduled_maintenances`:

| Field                 | Type           | Description                                                                                          |
| --------------------- | -------------- | ---------------------------------------------------------------------------------------------------- |
| `id`                  | string         | Event ID                                                                                             |
| `title`               | string         | Event title                                                                                          |
| `phase`               | string         | Lifecycle status: `scheduled` / `ongoing`                                                            |
| `starts_at`           | string         | Planned start time (ISO 8601)                                                                        |
| `ends_at`             | string \| null | Planned end time; a manually advanced maintenance may have no end time, in which case this is `null` |
| `updated_at`          | string         | Last updated time (ISO 8601)                                                                         |
| `overdue`             | boolean        | Whether the maintenance has passed its planned end time without completing                           |
| `url`                 | string         | Link to the event detail on the status page                                                          |
| `last_update`         | object \| null | Latest timeline update: `at`, `message`                                                              |
| `affected_components` | array          | Affected components, same structure as incidents                                                     |

### Example response

```json theme={null}
{
  "schema_version": "1.0",
  "generated_at": "2026-08-11T08:00:00Z",
  "poll_after_seconds": 30,
  "max_stale_seconds": 120,
  "page": {
    "name": "Example Status",
    "url": "https://status.example.com"
  },
  "overall": {
    "status": "partial_outage"
  },
  "ongoing_incidents": [
    {
      "id": "1024",
      "title": "Elevated API error rates",
      "phase": "investigating",
      "impact": "partial_outage",
      "started_at": "2026-08-11T07:40:00Z",
      "updated_at": "2026-08-11T07:55:00Z",
      "url": "https://status.example.com/incidents/1024",
      "last_update": {
        "at": "2026-08-11T07:55:00Z",
        "message": "We are investigating elevated error rates."
      },
      "affected_components": [
        {
          "id": "cmp-1",
          "name": "Public API",
          "group_name": "Core services",
          "status": "partial_outage"
        }
      ]
    }
  ],
  "in_progress_maintenances": [],
  "scheduled_maintenances": []
}
```

### Error responses

| Status code | Body                                      | Description                                                                                   |
| ----------- | ----------------------------------------- | --------------------------------------------------------------------------------------------- |
| `404`       | `{"error": "status_page_not_found"}`      | The status page doesn't exist, is not public, or widgets are disabled at the deployment level |
| `503`       | `{"error": "widget_summary_unavailable"}` | Status data is temporarily unavailable; retry later                                           |

<Tip>
  Widgets are enabled by default — no configuration needed. In self-hosted deployments, an administrator can turn the feature off globally with the `deploy.widgetEnabled` switch (the API then returns 404).
</Tip>
