API Reference
GlassHome Hub exposes a small public HTTP API at https://glasshome.app for reading the widget catalog. These endpoints are unauthenticated. The versioned /api/v1/widgets/* endpoints are CORS-open; see the CORS note under Conventions for the one exception.
What’s public
Only the endpoints documented below are part of the public API and covered by versioning. Everything under /api/* (without /v1/) is internal: it powers the Hub UI and the @glasshome/widget-cli, requires a session, and may change without notice. Do not build integrations against /api/*.
If you need to publish widgets, use the Widget CLI. It handles auth and the publish flow for you.
API versioning and sunset
The /api/v1/ prefix is the current stable version. When a breaking change to the API is needed, a new prefix (e.g. /api/v2/) will be introduced and the old one will be 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 the/api/v1/widgets/*endpoints only. The/api/widgets/{scope}/registry.jsonroute sits outside the/v1/prefix and sends no CORS header, so cross-origin browser fetches against it will fail (server-to-server and same-origin fetches are unaffected).
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 version string of the newest non-yanked published version, or null if none exists.
Error responses
| Status | Condition |
|---|---|
404 | Widget not found or is unlisted |
POST /api/v1/widgets/{scope}/{name}
Increments the widget’s download counter. Used by dashboards on install. No request body, no authentication required.
This is a best-effort counter. It uses a simple +1 SQL update with no rate-limiting or deduplication in the current implementation. Do not rely on it 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 a single scope. The dashboard uses this for fast bulk fetch on first load. Suitable for edge caching (no Cache-Control set by the route itself). No CORS header (see Conventions): fetch it server-to-server, not from a cross-origin browser context.
Operational endpoints
A few unauthenticated endpoints exist outside the /v1/ namespace for tooling and status checks: GET /api/health, GET /api/version (latest Hub release), GET /api/widgets/cli-version (minimum supported widget-cli version), GET /api/stats, and GET /api/settings. They are operational, not versioned, and carry no stability guarantee or CORS header. Use them for status/update checks, not as 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).