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; 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.jsonsends 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
{
"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
{
"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
{ "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. Include the full request (URL + payload) and the response (status + body).