Skip to content

Updated

View MarkdownOpen in ChatGPTOpen in Claude

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

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).