---
title: "API Reference"
description: "GlassHome Hub public HTTP API. Versioned widget catalog endpoints for external integrators."
canonical: https://glasshome.app/docs/hub/api-reference
section: "Hub"
updated: 2026-08-10
---
# API Reference

Public, unauthenticated HTTP API at `https://glasshome.app` for reading the widget catalog. `/api/v1/widgets/*` is CORS-open (one exception under Conventions).

## What's public

Only the endpoints below are public and versioned. `/api/*` without `/v1/` is internal: it powers the Hub UI and `@glasshome/widget-cli`, requires a session, and may change without notice. Do not integrate against it.

To publish widgets, use the [Widget CLI](/docs/widgets/widget-cli); it handles auth and the publish flow.

## API versioning and sunset

`/api/v1/` is the current stable version. A breaking change gets a new prefix (e.g. `/api/v2/`); the old one is announced for sunset with reasonable notice.

## Conventions

- All requests/responses are JSON.
- Errors: `{ "error": "<human-readable message>" }` with an appropriate HTTP status.
- Timestamps: ISO 8601, UTC.
- Pagination: page-based (`?page=`, `?limit=`).
- CORS: `Access-Control-Allow-Origin: *` on `/api/v1/widgets/*` only. `/api/widgets/{scope}/registry.json` sends no CORS header: cross-origin browser fetches fail; server-to-server and same-origin fetches work.

## `GET /api/v1/widgets/search`

Search the public widget registry.

### Query parameters

| Parameter   | Type    | Description                                                                                                                      |
| ----------- | ------- | -------------------------------------------------------------------------------------------------------------------------------- |
| `q`         | string  | Free-text. Matches `displayName` and `description`.                                                                              |
| `compatSdk` | string  | SDK semver (e.g. `0.5.2`). Must be a valid semver string or the endpoint returns 400. Adds `latestCompatibleVersion` per result. |
| `scope`     | string  | Restrict to a scope, e.g. `glasshome`.                                                                                           |
| `official`  | boolean | `true` limits to Official widgets; `false` limits to non-official.                                                               |
| `page`      | number  | Page number, default `1`.                                                                                                        |
| `limit`     | number  | Page size, default `20`, max `100`.                                                                                              |

Unlisted widgets are excluded from results.

### Response

```json
{
  "widgets": [/* WidgetSummary */],
  "total": 123,
  "page": 1,
  "limit": 20
}
```

### WidgetSummary fields

| Field                     | Type                 | Description                                                                                              |
| ------------------------- | -------------------- | -------------------------------------------------------------------------------------------------------- |
| `scope`                   | string               | Publishing scope (e.g. `"glasshome"`)                                                                    |
| `name`                    | string               | Widget identifier                                                                                        |
| `displayName`             | string               | Human-readable display name                                                                              |
| `description`             | string or null       | Short description                                                                                        |
| `icon`                    | string or null       | Iconify icon identifier                                                                                  |
| `isOfficial`              | boolean or null      | Whether this is an Official (GlassHome-maintained) widget                                                |
| `downloadCount`           | number or null       | Cumulative installs                                                                                      |
| `latestVersion`           | VersionEntry or null | Latest non-yanked version. `null` if no published version exists.                                        |
| `latestCompatibleVersion` | VersionEntry or null | Only present when `compatSdk` is supplied. Newest version satisfying the requested SDK range, or `null`. |

### VersionEntry fields

| Field                | Type           | Description                                     |
| -------------------- | -------------- | ----------------------------------------------- |
| `scope`              | string         | Scope (same as parent)                          |
| `name`               | string         | Widget name (same as parent)                    |
| `version`            | string         | Semver version string                           |
| `bundleUrl`          | string         | CDN URL for the JS bundle                       |
| `bundleSize`         | number         | Bundle size in bytes (raw, not gzip)            |
| `sha256Hash`         | string         | SHA-256 hex digest of the bundle                |
| `publishedAt`        | string         | ISO 8601 timestamp                              |
| `yanked`             | boolean        | Whether this version has been yanked            |
| `yankedReason`       | string or null | Human-readable reason if yanked                 |
| `deprecationMessage` | string or null | Warning shown on widgets using this version     |
| `releaseNotes`       | string or null | User-facing changelog text                      |
| `displayName`        | string         | From version manifest, falls back to widget row |
| `description`        | string or null | From version manifest, falls back to widget row |
| `icon`               | string or null | From version manifest, falls back to widget row |
| `minSize`            | `{w, h}`       | Minimum grid size                               |
| `maxSize`            | `{w, h}`       | Maximum grid size                               |
| `sdkVersion`         | string         | SDK version range the bundle was built against  |
| `configVersion`      | number or null | Config version for migration support            |
| `permissions`        | string\[]      | Declared permissions                            |
| `license`            | string or null | License identifier                              |

### Error responses

| Status | Condition                                |
| ------ | ---------------------------------------- |
| `400`  | `compatSdk` is not a valid semver string |

## `GET /api/v1/widgets/{scope}/{name}`

Single widget detail, including the full version list.

### Response

```json
{
  "widget": {
    "scope": "glasshome",
    "name": "my-widget",
    "displayName": "My Widget",
    "description": "...",
    "icon": "mdi:lightbulb",
    "isOfficial": false,
    "downloadCount": 42,
    "createdAt": "2025-01-01T00:00:00.000Z"
  },
  "versions": [/* VersionEntry (see above) */],
  "latestVersion": "1.2.0"
}
```

`versions` is ordered by `publishedAt` descending. `latestVersion` is the newest non-yanked published version string, or `null` if none exists.

### Error responses

| Status | Condition                       |
| ------ | ------------------------------- |
| `404`  | Widget not found or is unlisted |

## `POST /api/v1/widgets/{scope}/{name}`

Increments the download counter; dashboards call it on install. No request body, no auth. Best-effort: a plain `+1` SQL update, no rate-limiting or deduplication, so not suitable for precise install analytics.

### Response

```json
{ "success": true }
```

### Error responses

| Status | Condition        |
| ------ | ---------------- |
| `404`  | Widget not found |

## `GET /api/widgets/{scope}/registry.json`

Static-shaped registry for one scope, used by the dashboard for fast bulk fetch on first load. Edge-cacheable (the route sets no `Cache-Control`). No CORS header (see Conventions): fetch server-to-server only.

## Operational endpoints

Unauthenticated, unversioned tooling and status endpoints outside `/v1/`: `GET /api/health`, `GET /api/version` (latest Hub release), `GET /api/widgets/cli-version` (minimum supported `widget-cli` version), `GET /api/stats`, `GET /api/settings`. No stability guarantee or CORS header; not an integration surface.

## Rate limits

Currently unmetered. Be reasonable; aggressive scraping may get rate-limited later with `429` + `Retry-After`.

## Reporting issues

[Bug Reports](/docs/dash/bug-reports). Include the full request (URL + payload) and the response (status + body).