diff --git a/mintlify-codegen/errors.ts b/mintlify-codegen/errors.ts
index 80d1271c9..0d59f7d50 100644
--- a/mintlify-codegen/errors.ts
+++ b/mintlify-codegen/errors.ts
@@ -282,11 +282,38 @@ function kindSuffix(hasErrors: boolean, hasWarnings: boolean): string {
return hasErrors ? 'Errors' : 'Warnings'
}
-/** Render the full standalone errors/warnings page (frontmatter + sections). */
+/**
+ * Render a note pointing readers to the errors/warnings shared by all devices.
+ * Device sub-category pages (locks/thermostats/phones) are subsets that omit the
+ * device-level codes (`device_offline`, `device_disconnected`, …) applying to
+ * every device, so a reader troubleshooting connectivity there needs a pointer
+ * to the aggregate device page. `kinds` is the lowercased sections phrase
+ * (`errors and warnings` / `errors` / `warnings`) so the prose matches the
+ * page's actual content.
+ */
+function renderCommonDeviceErrorsNote(
+ noun: string,
+ kinds: string,
+ route: string,
+): string {
+ const phrase = kinds.toLowerCase()
+ return [
+ '',
+ ` These are ${noun}-specific ${phrase}. For ${phrase} common to all devices, see [Device Errors and Warnings](${route}).`,
+ '',
+ ].join('\n')
+}
+
+/**
+ * Render the full standalone errors/warnings page (frontmatter + sections). When
+ * `commonDeviceErrorsRoute` is set, a note linking to the shared device
+ * errors/warnings page is prepended — used on device sub-category pages.
+ */
function renderPage(
noun: string,
errorSection: string,
warningSection: string,
+ commonDeviceErrorsRoute?: string,
): string {
const title = `${noun} ${kindSuffix(Boolean(errorSection), Boolean(warningSection))}`
const kinds =
@@ -302,7 +329,10 @@ function renderPage(
`description: '${description.replace(/'/g, "\\'")}'`,
'---',
].join('\n')
- const body = [errorSection, warningSection].filter(Boolean).join('\n\n')
+ const note = commonDeviceErrorsRoute
+ ? renderCommonDeviceErrorsNote(noun, kinds, commonDeviceErrorsRoute)
+ : ''
+ const body = [note, errorSection, warningSection].filter(Boolean).join('\n\n')
return `${frontmatter}\n\n${body}\n`
}
@@ -314,6 +344,13 @@ async function readFileOrNull(path: string): Promise {
}
}
+interface ErrorPageOptions {
+ // When set, prepend a note linking to the errors/warnings shared by all
+ // devices. Used only for device sub-category pages (locks/thermostats/phones),
+ // which are subsets that omit the device-level codes.
+ commonDeviceErrorsRoute?: string
+}
+
/**
* Write one resource's `errors.mdx` (an `## Errors` and/or `## Warnings`
* section) and record its route in `routes`. No-ops when the resource has no
@@ -325,7 +362,16 @@ async function writeErrorPage(
errorsProp: Property | undefined,
warningsProp: Property | undefined,
routes: string[],
+ options: ErrorPageOptions = {},
): Promise {
+ // Defensive against a variant-group key ever coinciding with a real resource
+ // route: the resource loop writes first, so skip a route already emitted
+ // rather than clobbering it from the sub-category loop.
+ if (routes.includes(routePath)) {
+ console.log(` Errors page for ${routePath} already written, skipping`)
+ return
+ }
+
const errorGroups = groupCodes(errorsProp)
const warningGroups = groupCodes(warningsProp)
if (errorGroups.length === 0 && warningGroups.length === 0) return
@@ -342,11 +388,24 @@ async function writeErrorPage(
noun,
renderSection('Errors', 'error', errorsProp, errorGroups),
renderSection('Warnings', 'warning', warningsProp, warningGroups),
+ options.commonDeviceErrorsRoute,
)
await writeFile(join(resourceDir, 'errors.mdx'), page)
routes.push(routePath)
}
+interface DeviceSubcategory {
+ // Route that carries the sub-category's dedicated errors page.
+ routePath: string
+ // Whether the sub-category shares the device-level errors/warnings documented
+ // on /api/devices/errors, and so should cross-link to them. The shared codes
+ // are physical-device connectivity/subscription errors (`device_offline`,
+ // `hub_disconnected`, `salto_ks_subscription_limit_exceeded`, …), relevant to
+ // locks and thermostats but not to phones (mobile devices used for
+ // credentials), so the phones page omits the cross-link.
+ linkCommonDeviceErrors: boolean
+}
+
// Some device sub-categories have their own docs route but no blueprint
// resource of their own — the Seam API models a lock, thermostat, or phone as a
// `device`. Their error/warning codes therefore live on the `device` resource
@@ -359,12 +418,17 @@ async function writeErrorPage(
// but have no standalone sub-category route. In particular /access_codes
// already documents the `access_code` resource's own, distinct errors — routing
// the device `access_codes` group there would clobber it.
-const DEVICE_SUBCATEGORY_ERROR_ROUTES: Record = {
- locks: '/locks',
- phones: '/phones',
- thermostats: '/thermostats',
+const DEVICE_SUBCATEGORY_ERROR_ROUTES: Record = {
+ locks: { routePath: '/locks', linkCommonDeviceErrors: true },
+ phones: { routePath: '/phones', linkCommonDeviceErrors: false },
+ thermostats: { routePath: '/thermostats', linkCommonDeviceErrors: true },
}
+// Route of the aggregate device errors/warnings page. Sub-categories that share
+// the device-level codes link back to it (see writeErrorPage's
+// `commonDeviceErrorsRoute` option).
+const COMMON_DEVICE_ERRORS_ROUTE = '/api/devices/errors'
+
/**
* Narrow a device `errors`/`warnings` property to a single variant group,
* flattening the kept variants to ungrouped (`variantGroupKey` cleared) so their
@@ -384,6 +448,58 @@ function variantGroupProp(
return { ...prop, variants, variantGroups: [] }
}
+/**
+ * Warn when a device errors/warnings variant group looks like it should have its
+ * own sub-category errors page but is missing from DEVICE_SUBCATEGORY_ERROR_ROUTES.
+ *
+ * That allowlist is maintained by hand, so a newly added device sub-category
+ * (its own object + events pages plus an error/warning variant group) would
+ * silently produce no errors page until someone edits the map. This surfaces
+ * that inconsistency at generate time.
+ *
+ * A group is flagged only when it (a) has documented codes, (b) has both an
+ * `object.mdx` and an `events.mdx` under `/api/` — the shape a real
+ * sub-category route has — and (c) is neither already mapped nor itself a
+ * documented blueprint resource. Condition (c) keeps real resources
+ * (`access_codes`, which owns its own distinct errors) and annotation-only
+ * groups (`hardware`, `provider_metadata` — no such route) from tripping it.
+ */
+async function warnUnmappedDeviceSubcategoryGroups(
+ docsDir: string,
+ errorsProp: Property | undefined,
+ warningsProp: Property | undefined,
+ documentedResourceRoutes: Set,
+): Promise {
+ const groupKeys = new Set()
+ for (const prop of [errorsProp, warningsProp]) {
+ if (!isDiscriminatedListProperty(prop)) continue
+ for (const group of prop.variantGroups) groupKeys.add(group.variantGroupKey)
+ }
+
+ for (const groupKey of groupKeys) {
+ if (groupKey in DEVICE_SUBCATEGORY_ERROR_ROUTES) continue
+ if (documentedResourceRoutes.has(`/${groupKey}`)) continue
+
+ const hasCodes =
+ groupCodes(variantGroupProp(errorsProp, groupKey)).length > 0 ||
+ groupCodes(variantGroupProp(warningsProp, groupKey)).length > 0
+ if (!hasCodes) continue
+
+ const resourceDir = join(docsDir, 'api', groupKey)
+ const hasObject =
+ (await readFileOrNull(join(resourceDir, 'object.mdx'))) != null
+ const hasEvents =
+ (await readFileOrNull(join(resourceDir, 'events.mdx'))) != null
+ if (!hasObject || !hasEvents) continue
+
+ console.log(
+ ` WARNING: device errors/warnings variant group "${groupKey}" has codes ` +
+ `and an object+events page (/api/${groupKey}) but no dedicated errors ` +
+ `page. Add it to DEVICE_SUBCATEGORY_ERROR_ROUTES in mintlify-codegen/errors.ts.`,
+ )
+ }
+}
+
/**
* Generate the per-resource `errors.mdx` pages. Returns the route paths that
* received a page (e.g. `/devices`) so the caller can wire them into the
@@ -413,17 +529,35 @@ export async function updateErrorPages(
if (device != null) {
const errorsProp = device.properties.find((p) => p.name === 'errors')
const warningsProp = device.properties.find((p) => p.name === 'warnings')
- for (const [groupKey, routePath] of Object.entries(
- DEVICE_SUBCATEGORY_ERROR_ROUTES,
- )) {
+ for (const [
+ groupKey,
+ { routePath, linkCommonDeviceErrors },
+ ] of Object.entries(DEVICE_SUBCATEGORY_ERROR_ROUTES)) {
await writeErrorPage(
docsDir,
routePath,
variantGroupProp(errorsProp, groupKey),
variantGroupProp(warningsProp, groupKey),
routes,
+ linkCommonDeviceErrors
+ ? { commonDeviceErrorsRoute: COMMON_DEVICE_ERRORS_ROUTE }
+ : {},
)
}
+
+ // Flag any device sub-category that has its own object+events pages and
+ // error codes but was never added to the allowlist above.
+ const documentedResourceRoutes = new Set(
+ blueprint.resources
+ .filter((r) => !r.isUndocumented)
+ .map((r) => r.routePath),
+ )
+ await warnUnmappedDeviceSubcategoryGroups(
+ docsDir,
+ errorsProp,
+ warningsProp,
+ documentedResourceRoutes,
+ )
}
return routes
diff --git a/mintlify-docs/api/locks/errors.mdx b/mintlify-docs/api/locks/errors.mdx
index 3ab463a6f..cad40bcd9 100644
--- a/mintlify-docs/api/locks/errors.mdx
+++ b/mintlify-docs/api/locks/errors.mdx
@@ -3,6 +3,10 @@ title: 'Lock Errors and Warnings'
description: 'Errors and warnings that Seam reports on the Lock resource, each with its code and meaning.'
---
+
+ These are Lock-specific errors and warnings. For errors and warnings common to all devices, see [Device Errors and Warnings](/api/devices/errors).
+
+
## Errors
Each error is an object with the following shape:
diff --git a/mintlify-docs/api/thermostats/errors.mdx b/mintlify-docs/api/thermostats/errors.mdx
index 6d6e9357f..bd0c0837c 100644
--- a/mintlify-docs/api/thermostats/errors.mdx
+++ b/mintlify-docs/api/thermostats/errors.mdx
@@ -3,6 +3,10 @@ title: 'Thermostat Errors and Warnings'
description: 'Errors and warnings that Seam reports on the Thermostat resource, each with its code and meaning.'
---
+
+ These are Thermostat-specific errors and warnings. For errors and warnings common to all devices, see [Device Errors and Warnings](/api/devices/errors).
+
+
## Errors
Each error is an object with the following shape: