Skip to content

Commit 9f3436a

Browse files
sybohyclaude
andcommitted
feat: make Access Grants the front door for granting access
Access Grants are GA and are now the default, recommended way to grant access to any physical space, irrespective of locking hardware. The docs previously framed Access Grants as ACS-only (entrances/spaces), which led developers and AI assistants (including the Seam MCP) to wrongly steer standalone-smart-lock users to the legacy Access Codes API. - Add new top-level "Use Cases" nav section with a "Granting Access" decision page (which API to use, with the recommended ladder: one device -> multiple devices/spaces -> ACS entrances) - Move Access Grants guides under use-cases/granting-access/ with .gitbook.yaml redirects for all old URLs - Rewrite Access Grants overview: universal framing, device_ids first in the "Where" table, devices-first teaching order - Add "Creating an Access Grant Using Devices" guide (device_ids + code access method, capability checks) - Rewrite Quick Start step 3 around access_grants.create with a code access method instead of lock/unlock - Lead the access_grants/create API reference with a devices-only code sample (new sample in code-sample-definitions) - Add "Grant Access" card first on the docs landing page - Teach validate-links to accept URLs covered by .gitbook.yaml redirects (upstream @seamapi/types still links old URLs; resolved via redirects until the upstream descriptions are updated) Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
1 parent ae7748d commit 9f3436a

25 files changed

Lines changed: 1975 additions & 613 deletions

File tree

.gitbook.yaml

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,3 +23,13 @@ redirects:
2323
core-concepts/connect-webviews: core-concepts/connect-webviews/README.md
2424
capability-guides/access-systems/understanding-acs-differences: capability-guides/access-systems/README.md
2525
capability-guides/thermostats/creating-and-managing-climate-schedules: capability-guides/thermostats/creating-and-managing-thermostat-schedules.md
26+
capability-guides/access-grants: use-cases/granting-access/access-grants/README.md
27+
capability-guides/access-grants/access-grant-quick-start: use-cases/granting-access/access-grants/access-grant-quick-start.md
28+
capability-guides/access-grants/creating-an-access-grant-using-entrances: use-cases/granting-access/access-grants/creating-an-access-grant-using-entrances.md
29+
capability-guides/access-grants/creating-an-access-grant-using-spaces: use-cases/granting-access/access-grants/creating-an-access-grant-using-spaces.md
30+
capability-guides/access-grants/delivering-access-methods: use-cases/granting-access/access-grants/delivering-access-methods.md
31+
capability-guides/access-grants/reservation-access-grants: use-cases/granting-access/access-grants/reservation-access-grants.md
32+
capability-guides/access-grants/retrieving-access-grants-and-access-methods: use-cases/granting-access/access-grants/retrieving-access-grants-and-access-methods.md
33+
capability-guides/access-grants/updating-an-access-grant: use-cases/granting-access/access-grants/updating-an-access-grant.md
34+
capability-guides/access-grants/revoking-an-access-method: use-cases/granting-access/access-grants/revoking-an-access-method.md
35+
capability-guides/access-grants/deleting-an-access-grant: use-cases/granting-access/access-grants/deleting-an-access-grant.md

codegen/data/code-sample-definitions/access_grants.yaml

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,36 @@
11
---
2+
- title: Create an Access Grant for devices
3+
description:
4+
Creates a new Access Grant that gives a user identity a PIN code on one
5+
or more devices, such as standalone smart locks.
6+
request:
7+
path: /access_grants/create
8+
parameters:
9+
user_identity_id: e3d736c1-540d-4d10-83e5-9a4e135453b4
10+
device_ids:
11+
- 6ba7b811-9dad-11d1-80b4-00c04fd430c8
12+
requested_access_methods:
13+
- mode: code
14+
starts_at: '2025-06-16T16:54:17.946606Z'
15+
ends_at: '2025-06-18T16:54:17.946606Z'
16+
response:
17+
body:
18+
access_grant:
19+
access_grant_id: ef83cca9-5fdf-4ac2-93f3-c21c5a8be54b
20+
access_method_ids:
21+
- a1b2c3d4-e5f6-4a3b-2c1d-0e9f8a7b6c5d
22+
created_at: '2025-06-16T16:54:17.946606Z'
23+
display_name: My Access Grant
24+
ends_at: '2025-06-18T16:54:17.946606Z'
25+
requested_access_methods:
26+
- display_name: PIN Code Credential
27+
mode: code
28+
created_at: '2025-06-16T16:54:17.946606Z'
29+
created_access_method_ids:
30+
- a1b2c3d4-e5f6-4a3b-2c1d-0e9f8a7b6c5d
31+
starts_at: '2025-06-16T16:54:17.946606Z'
32+
user_identity_id: e3d736c1-540d-4d10-83e5-9a4e135453b4
33+
workspace_id: 750fc0bc-4450-4356-8d9f-18c6a3a6b2c7
234
- title: Create an Access Grant using spaces
335
description: Creates a new Access Grant using space IDs and an existing user identity.
436
request:

codegen/validate-links.ts

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
import { existsSync, readdirSync, readFileSync } from 'node:fs'
22
import { dirname, join, resolve } from 'node:path'
33

4+
import YAML from 'yaml'
5+
46
import {
57
baseUrl,
68
type SiteSection,
@@ -12,6 +14,36 @@ function findSiteSection(filePath: string): SiteSection | undefined {
1214
return siteSections.find(({ root }) => filePath.startsWith(root + '/'))
1315
}
1416

17+
// Each site section has its own .gitbook.yaml with a redirects map.
18+
// A URL covered by a redirect is not broken: GitBook resolves it.
19+
// (validate-redirects separately ensures every redirect target exists.)
20+
const sectionConfigPaths: Record<string, string> = {
21+
Guides: '.gitbook.yaml',
22+
'API Reference': join('docs', 'api-reference', '.gitbook.yaml'),
23+
'Brand Guides': join('docs', 'brand-guides', '.gitbook.yaml'),
24+
}
25+
26+
const sectionRedirects = new Map<string, Set<string>>()
27+
28+
function getSectionRedirects(sectionName: string): Set<string> {
29+
const cached = sectionRedirects.get(sectionName)
30+
if (cached != null) return cached
31+
32+
const redirectPaths = new Set<string>()
33+
const configPath = sectionConfigPaths[sectionName]
34+
if (configPath != null && existsSync(configPath)) {
35+
const config = YAML.parse(readFileSync(configPath, 'utf-8')) as {
36+
redirects?: Record<string, string>
37+
}
38+
for (const key of Object.keys(config.redirects ?? {})) {
39+
redirectPaths.add(key.replace(/^\//, ''))
40+
}
41+
}
42+
43+
sectionRedirects.set(sectionName, redirectPaths)
44+
return redirectPaths
45+
}
46+
1547
const absoluteUrlPattern = new RegExp(
1648
`${baseUrl.replaceAll('.', '\\.')}[^)"<>\\s]+`,
1749
'g',
@@ -83,6 +115,11 @@ function checkAbsoluteUrl(file: string, line: number, rawUrl: string): void {
83115
const targetMd = `${targetRoot}.md`
84116
const targetReadme = join(targetRoot, 'README.md')
85117

118+
// A URL covered by a .gitbook.yaml redirect resolves on the published site.
119+
if (getSectionRedirects(section.name).has(pagePath.replace(/^\//, ''))) {
120+
return
121+
}
122+
86123
if (!existsSync(targetMd) && !existsSync(targetReadme)) {
87124
brokenLinks.push({
88125
file,

0 commit comments

Comments
 (0)