Skip to content

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.json route 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

ParameterTypeDescription
qstringFree-text. Matches displayName and description.
compatSdkstringSDK semver (e.g. 0.5.2). Must be a valid semver string or the endpoint returns 400. Adds latestCompatibleVersion per result.
scopestringRestrict to a scope, e.g. glasshome.
officialbooleantrue limits to Official widgets; false limits to non-official.
pagenumberPage number, default 1.
limitnumberPage size, default 20, max 100.

Unlisted widgets are excluded from results.

Response

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

WidgetSummary fields

FieldTypeDescription
scopestringPublishing scope (e.g. "glasshome")
namestringWidget identifier
displayNamestringHuman-readable display name
descriptionstring or nullShort description
iconstring or nullIconify icon identifier
isOfficialboolean or nullWhether this is an Official (GlassHome-maintained) widget
downloadCountnumber or nullCumulative installs
latestVersionVersionEntry or nullLatest non-yanked version. null if no published version exists.
latestCompatibleVersionVersionEntry or nullOnly present when compatSdk is supplied. Newest version satisfying the requested SDK range, or null.

VersionEntry fields

FieldTypeDescription
scopestringScope (same as parent)
namestringWidget name (same as parent)
versionstringSemver version string
bundleUrlstringCDN URL for the JS bundle
bundleSizenumberBundle size in bytes (raw, not gzip)
sha256HashstringSHA-256 hex digest of the bundle
publishedAtstringISO 8601 timestamp
yankedbooleanWhether this version has been yanked
yankedReasonstring or nullHuman-readable reason if yanked
deprecationMessagestring or nullWarning shown on widgets using this version
releaseNotesstring or nullUser-facing changelog text
displayNamestringFrom version manifest, falls back to widget row
descriptionstring or nullFrom version manifest, falls back to widget row
iconstring or nullFrom version manifest, falls back to widget row
minSize{w, h}Minimum grid size
maxSize{w, h}Maximum grid size
sdkVersionstringSDK version range the bundle was built against
configVersionnumber or nullConfig version for migration support
permissionsstring[]Declared permissions
licensestring or nullLicense identifier

Error responses

StatusCondition
400compatSdk 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

StatusCondition
404Widget 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

StatusCondition
404Widget 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).