From 26bdd7b07aa560c205c4333427123ad5d381713f Mon Sep 17 00:00:00 2001 From: Claude Date: Thu, 9 Jul 2026 22:03:19 +0000 Subject: [PATCH] feat: link endpoint response schemas to their object pages (ENG-2240) Append a 'See [the `device` object](/api/devices/object).' sentence to every component schema that backs a generated object page, so Mintlify's inline-expanded endpoint responses point readers at the full resource reference. Scoped per-endpoint action_attempt schemas get the same link. Only resources actually rendered on an object page are linked: sampled resources at the page's own route, plus explicit resource_type pages (e.g. /action_attempts). Filtered views of a shared resource (/locks, /thermostats) don't claim the type. Co-Authored-By: Claude Fable 5 Claude-Session: https://claude.ai/code/session_01Y31Y4b3uGxyoeG7nVmum5S --- mintlify-codegen/lib/openapi.ts | 3 + mintlify-codegen/lib/transform-spec.ts | 96 +++++++++++++++++++++-- mintlify-docs/openapi.json | 101 +++++++++++++------------ 3 files changed, 144 insertions(+), 56 deletions(-) diff --git a/mintlify-codegen/lib/openapi.ts b/mintlify-codegen/lib/openapi.ts index 174ff3ff6..e539cabdb 100644 --- a/mintlify-codegen/lib/openapi.ts +++ b/mintlify-codegen/lib/openapi.ts @@ -3,6 +3,7 @@ import type { Blueprint } from '@seamapi/blueprint' import * as types from '@seamapi/types/connect' import type Metalsmith from 'metalsmith' +import { ObjectPageMetadataSchema } from './object-page-metadata.js' import { type PathMetadata, transformSpec, @@ -27,6 +28,7 @@ export const openapi: Metalsmith.Plugin = (files, metalsmith) => { const metadata = metalsmith.metadata() as { blueprint: Blueprint pathMetadata: PathMetadata + objectPages: unknown mintlify?: MintlifyMetadata } @@ -36,6 +38,7 @@ export const openapi: Metalsmith.Plugin = (files, metalsmith) => { rawSpec, metadata.blueprint, metadata.pathMetadata, + ObjectPageMetadataSchema.parse(metadata.objectPages), ) const orderedSpec = { diff --git a/mintlify-codegen/lib/transform-spec.ts b/mintlify-codegen/lib/transform-spec.ts index e2266675a..2a9532551 100644 --- a/mintlify-codegen/lib/transform-spec.ts +++ b/mintlify-codegen/lib/transform-spec.ts @@ -1,6 +1,7 @@ import type { Blueprint, Endpoint, SdkName } from '@seamapi/blueprint' import { supportedSdkOrder } from '../../codegen/lib/code-sample-tab-order.js' +import type { ObjectPageMetadata } from './object-page-metadata.js' export interface PathMetadataEntry { title: string @@ -137,13 +138,78 @@ function buildEndpointMap(blueprint: Blueprint): Map { return map } +/** A resource's object page: its href and the resource type it renders as + * (the noun in the page's "The Object" heading). */ +interface ObjectPageLink { + href: string + resourceType: string +} + +/** + * Map each blueprint resource type to its object page, so endpoint responses + * that reference a shared component schema can link to the resource's object + * page. Endpoint-listing and namespace pages are not object pages, and only + * resources with samples render on a default object page (mirroring + * reference.ts). Filtered views of a shared resource (e.g. /locks over + * device) don't claim the type because resources documented at their own + * route map first. + */ +function buildObjectPageLinks( + blueprint: Blueprint, + objectPages: ObjectPageMetadata, +): Map { + const links = new Map() + const add = (resourceType: string, routePath: string): void => { + if (links.has(resourceType)) return + links.set(resourceType, { + href: `/api${routePath}/object`, + resourceType, + }) + } + + for (const resource of blueprint.resources) { + const meta = objectPages[resource.routePath] + if (meta == null || meta.endpoints || meta.namespace) continue + if (meta.resource_type == null && resource.resourceSamples.length === 0) { + continue + } + if ( + meta.resource_type != null && + meta.resource_type !== resource.resourceType + ) { + continue + } + add(resource.resourceType, resource.routePath) + } + + // Pages backed by an explicit resource_type with no blueprint resource at + // their own route (e.g. /action_attempts). + for (const [routePath, meta] of Object.entries(objectPages)) { + if (meta.endpoints || meta.namespace || meta.resource_type == null) continue + add(meta.resource_type, routePath) + } + + return links +} + +/** The "See [the \`device\` object](/api/devices/object)." sentence appended + * to schema descriptions. */ +function objectPageSentence(link: ObjectPageLink): string { + return `See [the \`${link.resourceType}\` object](${link.href}).` +} + /** * Create a scoped action_attempt inline schema for an endpoint. */ -function createScopedActionAttempt(actionType: string): object { +function createScopedActionAttempt( + actionType: string, + objectPageLink?: ObjectPageLink, +): object { return { type: 'object', - description: `Tracks the progress of this operation. Poll using the action_attempt_id.`, + description: + `Tracks the progress of this operation. Poll using the action_attempt_id.` + + (objectPageLink ? ` ${objectPageSentence(objectPageLink)}` : ''), properties: { action_attempt_id: { type: 'string', @@ -345,8 +411,10 @@ export function transformSpec( spec: any, blueprint: Blueprint, _pathMetadata: PathMetadata, + objectPages: ObjectPageMetadata = {}, ): { spec: any; stats: TransformStats } { const endpointMap = buildEndpointMap(blueprint) + const objectPageLinks = buildObjectPageLinks(blueprint, objectPages) const stats: TransformStats = { totalEndpoints: 0, withCodeSamples: 0, @@ -435,8 +503,10 @@ export function transformSpec( (actionAttemptProp.$ref || (actionAttemptProp.oneOf && actionAttemptProp.oneOf.length > 3)) ) { - responseSchema.properties.action_attempt = - createScopedActionAttempt(actionType) + responseSchema.properties.action_attempt = createScopedActionAttempt( + actionType, + objectPageLinks.get('action_attempt'), + ) stats.withActionAttempts++ } } @@ -483,7 +553,7 @@ export function transformSpec( } // Transform component schemas - transformComponents(spec) + transformComponents(spec, objectPageLinks) // Recursively rewrite all description fields in the entire spec. // This catches deeply nested descriptions that the per-operation and @@ -497,7 +567,10 @@ export function transformSpec( /** * Clean up component schemas for Mintlify rendering. */ -function transformComponents(spec: any): void { +function transformComponents( + spec: any, + objectPageLinks: Map, +): void { const schemas = spec.components?.schemas if (!schemas) return @@ -562,6 +635,17 @@ function transformComponents(spec: any): void { } schema.required = ['action_attempt_id', 'action_type', 'status'] } + + // Link every schema that backs an object page to that page, so endpoint + // responses that reference the schema point readers at the full resource + // reference. Appended after truncation so the link is never cut off. + const link = objectPageLinks.get(name) + if (link != null) { + const sentence = objectPageSentence(link) + schema.description = schema.description + ? `${schema.description}\n\n${sentence}` + : sentence + } } } diff --git a/mintlify-docs/openapi.json b/mintlify-docs/openapi.json index d7ec61705..4475ae26f 100644 --- a/mintlify-docs/openapi.json +++ b/mintlify-docs/openapi.json @@ -12,7 +12,7 @@ "components": { "schemas": { "access_code": { - "description": "Represents a smart lock [access code](/low-level-apis/smart-locks/access-codes).", + "description": "Represents a smart lock [access code](/low-level-apis/smart-locks/access-codes).\n\nSee [the `access_code` object](/api/access_codes/object).", "properties": { "access_code_id": { "description": "Unique identifier for the access code.", @@ -519,7 +519,7 @@ "x-route-path": "/access_codes" }, "access_grant": { - "description": "Represents an Access Grant. Access Grants enable you to grant a user identity access to spaces, entrances, and devices through one or more access methods, such as mobile keys, plastic cards, and PIN codes. You can create an Access Grant for an existing user identity, or you can create a new user identity *while* creating the new Access Grant.", + "description": "Represents an Access Grant. Access Grants enable you to grant a user identity access to spaces, entrances, and devices through one or more access methods, such as mobile keys, plastic cards, and PIN codes. You can create an Access Grant for an existing user identity, or you can create a new user identity *while* creating the new Access Grant.\n\nSee [the `access_grant` object](/api/access_grants/object).", "properties": { "access_grant_id": { "description": "ID of the Access Grant.", @@ -915,7 +915,7 @@ "x-route-path": "/access_grants" }, "access_method": { - "description": "Represents an access method for an Access Grant. Access methods describe the modes of access, such as PIN codes, plastic cards, and mobile keys. For a mobile key, the access method also stores the URL for the associated Instant Key.", + "description": "Represents an access method for an Access Grant. Access methods describe the modes of access, such as PIN codes, plastic cards, and mobile keys. For a mobile key, the access method also stores the URL for the associated Instant Key.\n\nSee [the `access_method` object](/api/access_methods/object).", "properties": { "access_method_id": { "description": "ID of the access method.", @@ -1316,7 +1316,7 @@ "x-route-path": "/access_methods" }, "acs_access_group": { - "description": "Group that defines the entrances to which a set of users has access and, in some cases, the access schedule for these entrances and users.", + "description": "Group that defines the entrances to which a set of users has access and, in some cases, the access schedule for these entrances and users.\n\nSee [the `acs_access_group` object](/api/acs/access_groups/object).", "properties": { "access_group_type": { "deprecated": true, @@ -1918,7 +1918,7 @@ "x-route-path": "/acs/access_groups" }, "acs_credential": { - "description": "Means by which an [access control system user](/low-level-apis/access-systems/user-management) gains access at an [entrance](/low-level-apis/access-systems/retrieving-entrance-details). The `acs_credential` object represents a [credential](/low-level-apis/access-systems/managing-credentials) that provides an ACS user access within an [access control system](/low-level-apis/access-systems).", + "description": "Means by which an [access control system user](/low-level-apis/access-systems/user-management) gains access at an [entrance](/low-level-apis/access-systems/retrieving-entrance-details). The `acs_credential` object represents a [credential](/low-level-apis/access-systems/managing-credentials) that provides an ACS user access within an [access control system](/low-level-apis/access-systems).\n\nSee [the `acs_credential` object](/api/acs/credentials/object).", "properties": { "access_method": { "description": "Access method for the [credential](/low-level-apis/access-systems/managing-credentials). Supported values: `code`, `card`, `mobile_key`, `cloud_key`.", @@ -2290,7 +2290,7 @@ "x-undocumented": "Deprecated. Will be removed." }, "acs_encoder": { - "description": "Represents a hardware device that encodes [credential](/low-level-apis/access-systems/managing-credentials) data onto physical cards within an [access control system](/low-level-apis/access-systems).", + "description": "Represents a hardware device that encodes [credential](/low-level-apis/access-systems/managing-credentials) data onto physical cards within an [access control system](/low-level-apis/access-systems).\n\nSee [the `acs_encoder` object](/api/acs/encoders/object).", "properties": { "acs_encoder_id": { "description": "ID of the [encoder](/low-level-apis/access-systems/working-with-card-encoders-and-scanners).", @@ -2367,7 +2367,7 @@ "x-route-path": "/acs/encoders" }, "acs_entrance": { - "description": "Represents an [entrance](/low-level-apis/access-systems/retrieving-entrance-details) within an [access control system](/low-level-apis/access-systems).", + "description": "Represents an [entrance](/low-level-apis/access-systems/retrieving-entrance-details) within an [access control system](/low-level-apis/access-systems).\n\nSee [the `acs_entrance` object](/api/acs/entrances/object).", "properties": { "acs_entrance_id": { "description": "ID of the [entrance](/low-level-apis/access-systems/retrieving-entrance-details).", @@ -2885,7 +2885,7 @@ "x-route-path": "/acs/entrances" }, "acs_system": { - "description": "Represents an [access control system](/low-level-apis/access-systems).\n\nWithin an `acs_system`, create [`acs_user`s](/api/acs/users/object) and [`acs_credential`s](/api/acs/credentials/object) to grant access to the `acs_user`s.\n\nFor details about the resources associated with an access control system, see the [access control systems namespace](/api/acs/object).", + "description": "Represents an [access control system](/low-level-apis/access-systems).\n\nWithin an `acs_system`, create [`acs_user`s](/api/acs/users/object) and [`acs_credential`s](/api/acs/credentials/object) to grant access to the `acs_user`s.\n\nFor details about the resources associated with an access control system, see the [access control systems namespace](/api/acs/object).\n\nSee [the `acs_system` object](/api/acs/systems/object).", "properties": { "acs_access_group_count": { "description": "Number of access groups in the [access control system](/low-level-apis/access-systems).", @@ -3181,7 +3181,7 @@ "x-route-path": "/acs/systems" }, "acs_user": { - "description": "Represents a [user](/low-level-apis/access-systems/user-management) in an [access system](/low-level-apis/access-systems).", + "description": "Represents a [user](/low-level-apis/access-systems/user-management) in an [access system](/low-level-apis/access-systems).\n\nSee [the `acs_user` object](/api/acs/users/object).", "properties": { "access_schedule": { "description": "`starts_at` and `ends_at` timestamps for the [access system user's](/low-level-apis/access-systems/user-management) access.", @@ -3938,7 +3938,7 @@ "x-route-path": "/acs/users" }, "action_attempt": { - "description": "Represents an asynchronous action. Poll until status is success or error.", + "description": "Represents an asynchronous action. Poll until status is success or error.\n\nSee [the `action_attempt` object](/api/action_attempts/object).", "x-route-path": "/action_attempts", "type": "object", "properties": { @@ -4401,7 +4401,7 @@ "x-undocumented": "Seam Bridge client only." }, "client_session": { - "description": "Represents a [client session](/core-concepts/authentication/client-session-tokens). If you want to restrict your users' access to their own devices, use client sessions.", + "description": "Represents a [client session](/core-concepts/authentication/client-session-tokens). If you want to restrict your users' access to their own devices, use client sessions.\n\nSee [the `client_session` object](/api/client_sessions/object).", "properties": { "client_session_id": { "description": "ID of the client session.", @@ -4489,7 +4489,7 @@ "x-route-path": "/client_sessions" }, "connect_webview": { - "description": "Represents a [Connect Webview](/core-concepts/connect-webviews).", + "description": "Represents a [Connect Webview](/core-concepts/connect-webviews).\n\nSee [the `connect_webview` object](/api/connect_webviews/object).", "properties": { "accepted_capabilities": { "description": "High-level device capabilities that the Connect Webview can accept. When creating a Connect Webview, you can specify the types of devices that it can connect to Seam. If you do not set custom `accepted_capabilities`, Seam uses a default set of `accepted_capabilities` for each provider. For example, if you create a Connect Webview that accepts SmartThing devices, without specifying `accepted_capabilities`, Seam accepts only SmartThings locks. To connect SmartThings thermostats and locks to Seam, create a Connect Webview and include both `thermostat` and `lock` in the `accepted_capabilities`.", @@ -4656,7 +4656,7 @@ "x-route-path": "/connect_webviews" }, "connected_account": { - "description": "Represents a [connected account](/core-concepts/connected-accounts). A connected account is an external third-party account to which your user has authorized Seam to get access, for example, an August account with a list of door locks.", + "description": "Represents a [connected account](/core-concepts/connected-accounts). A connected account is an external third-party account to which your user has authorized Seam to get access, for example, an August account with a list of door locks.\n\nSee [the `connected_account` object](/api/connected_accounts/object).", "properties": { "accepted_capabilities": { "description": "List of capabilities that were accepted during the account connection process.", @@ -5033,7 +5033,7 @@ "x-undocumented": "Internal resource." }, "customer_portal": { - "description": "Represents a Customer Portal. Customer Portal is a hosted, customizable interface for managing device access. It enables you to embed secure, pre-authenticated access flows into your product—either by sharing a link with users or embedding a view in an iframe.", + "description": "Represents a Customer Portal. Customer Portal is a hosted, customizable interface for managing device access. It enables you to embed secure, pre-authenticated access flows into your product—either by sharing a link with users or embedding a view in an iframe.\n\nSee [the `customer_portal` object](/api/customers/object).", "properties": { "created_at": { "description": "Date and time at which the customer portal link was created.", @@ -5144,7 +5144,7 @@ "x-undocumented": "Unreleased." }, "device": { - "description": "Represents a [device](/core-concepts/devices) that has been connected to Seam.", + "description": "Represents a [device](/core-concepts/devices) that has been connected to Seam.\n\nSee [the `device` object](/api/devices/object).", "properties": { "can_configure_auto_lock": { "description": "Indicates whether the lock supports configuring automatic locking.", @@ -8485,7 +8485,8 @@ "provider_categories" ], "type": "object", - "x-route-path": "/devices" + "x-route-path": "/devices", + "description": "See [the `device_provider` object](/api/devices/object)." }, "enrollment_automation": { "description": "Represents an [enrollment automation](/capability-guides/mobile-access/issuing-mobile-credentials-from-an-access-control-system) within the [Seam mobile access solution](/capability-guides/mobile-access).", @@ -8528,7 +8529,7 @@ "x-undocumented": "Will be removed." }, "event": { - "description": "Represents an event. Events let you know when something interesting happens in your workspace. For example, when a lock is unlocked, Seam creates a `lock.unlocked` event. When a device's battery level is low, Seam creates a `device.battery_low` event.", + "description": "Represents an event. Events let you know when something interesting happens in your workspace. For example, when a lock is unlocked, Seam creates a `lock.unlocked` event. When a device's battery level is low, Seam creates a `device.battery_low` event.\n\nSee [the `event` object](/api/events/object).", "discriminator": { "propertyName": "event_type" }, @@ -18636,7 +18637,7 @@ "x-route-path": "/events" }, "instant_key": { - "description": "Represents a Seam Instant Key. For issuing Bluetooth mobile keys, Instant Keys are the fastest way to share access. With a single API call, you can create a mobile key and send it through text or email or embed it in your own app.", + "description": "Represents a Seam Instant Key. For issuing Bluetooth mobile keys, Instant Keys are the fastest way to share access. With a single API call, you can create a mobile key and send it through text or email or embed it in your own app.\n\nSee [the `instant_key` object](/api/instant_keys/object).", "properties": { "client_session_id": { "description": "ID of the client session associated with the Instant Key.", @@ -18750,7 +18751,7 @@ "x-undocumented": "Unreleased." }, "noise_threshold": { - "description": "Represents a [noise threshold](/capability-guides/noise-sensors/configure-noise-threshold-settings) for a [noise sensor](/capability-guides/noise-sensors). Thresholds represent the limits of noise tolerated at a property, which can be customized for each hour of the day. Each device has its own default thresholds, but you can use the Seam API to modify them.", + "description": "Represents a [noise threshold](/capability-guides/noise-sensors/configure-noise-threshold-settings) for a [noise sensor](/capability-guides/noise-sensors). Thresholds represent the limits of noise tolerated at a property, which can be customized for each hour of the day. Each device has its own default thresholds, but you can use the Seam API to modify them.\n\nSee [the `noise_threshold` object](/api/noise_sensors/noise_thresholds/object).", "properties": { "device_id": { "description": "Unique identifier for the device that contains the noise threshold.", @@ -18823,7 +18824,7 @@ "type": "object" }, "phone": { - "description": "Represents an app user's mobile phone.", + "description": "Represents an app user's mobile phone.\n\nSee [the `phone` object](/api/phones/object).", "properties": { "created_at": { "description": "Date and time at which the phone was created.\n ", @@ -20235,7 +20236,7 @@ "x-undocumented": "Seam Mobile SDK only." }, "space": { - "description": "Represents a space that is a logical grouping of devices and entrances. You can assign access to an entire space, thereby making granting access more efficient.", + "description": "Represents a space that is a logical grouping of devices and entrances. You can assign access to an entire space, thereby making granting access more efficient.\n\nSee [the `space` object](/api/spaces/object).", "properties": { "acs_entrance_count": { "description": "Number of entrances in the space.", @@ -20453,7 +20454,7 @@ "x-undocumented": "Internal resource for customer portals." }, "thermostat_daily_program": { - "description": "Represents a thermostat daily program, consisting of a set of periods, each of which has a starting time and the key that identifies the climate preset to apply at the starting time.", + "description": "Represents a thermostat daily program, consisting of a set of periods, each of which has a starting time and the key that identifies the climate preset to apply at the starting time.\n\nSee [the `thermostat_daily_program` object](/api/thermostats/daily_programs/object).", "properties": { "created_at": { "description": "Date and time at which the thermostat daily program was created.", @@ -20516,7 +20517,7 @@ "x-route-path": "/thermostats/daily_programs" }, "thermostat_schedule": { - "description": "Represents a [thermostat schedule](/capability-guides/thermostats/creating-and-managing-thermostat-schedules) that activates a configured [climate preset](/capability-guides/thermostats/creating-and-managing-climate-presets) on a [thermostat](/capability-guides/thermostats) at a specified starting time and deactivates the climate preset at a specified ending time.", + "description": "Represents a [thermostat schedule](/capability-guides/thermostats/creating-and-managing-thermostat-schedules) that activates a configured [climate preset](/capability-guides/thermostats/creating-and-managing-climate-presets) on a [thermostat](/capability-guides/thermostats) at a specified starting time and deactivates the climate preset at a specified ending time.\n\nSee [the `thermostat_schedule` object](/api/thermostats/schedules/object).", "properties": { "climate_preset_key": { "description": "Key of the [climate preset](/capability-guides/thermostats/creating-and-managing-climate-presets) to use for the [thermostat schedule](/capability-guides/thermostats/creating-and-managing-thermostat-schedules).", @@ -20605,7 +20606,7 @@ "x-route-path": "/thermostats/schedules" }, "unmanaged_access_code": { - "description": "Represents an [unmanaged smart lock access code](/low-level-apis/smart-locks/access-codes/migrating-existing-access-codes).", + "description": "Represents an [unmanaged smart lock access code](/low-level-apis/smart-locks/access-codes/migrating-existing-access-codes).\n\nSee [the `unmanaged_access_code` object](/api/access_codes/unmanaged/object).", "properties": { "access_code_id": { "description": "Unique identifier for the access code.", @@ -22440,7 +22441,7 @@ "x-undocumented": "Unreleased." }, "unmanaged_device": { - "description": "Represents an [unmanaged device](/core-concepts/devices/managed-and-unmanaged-devices). An unmanaged device has a limited set of visible properties and a subset of supported events. You cannot control an unmanaged device. Any [access codes](/low-level-apis/smart-locks/access-codes/migrating-existing-access-codes) on an unmanaged device are unmanaged.", + "description": "Represents an [unmanaged device](/core-concepts/devices/managed-and-unmanaged-devices). An unmanaged device has a limited set of visible properties and a subset of supported events. You cannot control an unmanaged device. Any [access codes](/low-level-apis/smart-locks/access-codes/migrating-existing-access-codes) on an unmanaged device are unmanaged.\n\nSee [the `unmanaged_device` object](/api/devices/unmanaged/object).", "properties": { "can_configure_auto_lock": { "description": "Indicates whether the lock supports configuring automatic locking.", @@ -22946,7 +22947,7 @@ "x-route-path": "/devices/unmanaged" }, "user_identity": { - "description": "Represents a [user identity](/capability-guides/mobile-access/managing-mobile-app-user-accounts-with-user-identities#what-is-a-user-identity) associated with an application user account.", + "description": "Represents a [user identity](/capability-guides/mobile-access/managing-mobile-app-user-accounts-with-user-identities#what-is-a-user-identity) associated with an application user account.\n\nSee [the `user_identity` object](/api/user_identities/object).", "properties": { "acs_user_ids": { "description": "Array of access system user IDs associated with the user identity.", @@ -23135,7 +23136,7 @@ "x-route-path": "/user_identities" }, "webhook": { - "description": "Represents a [webhook](/developer-tools/webhooks) that enables you to receive notifications of events. When you create a webhook, specify the endpoint URL at which you want to receive events and the set of event types that you want to receive.", + "description": "Represents a [webhook](/developer-tools/webhooks) that enables you to receive notifications of events. When you create a webhook, specify the endpoint URL at which you want to receive events and the set of event types that you want to receive.\n\nSee [the `webhook` object](/api/webhooks/object).", "properties": { "event_types": { "description": "Types of events that the [webhook](/developer-tools/webhooks) should receive.", @@ -23165,7 +23166,7 @@ "x-route-path": "/webhooks" }, "workspace": { - "description": "Represents a Seam [workspace](/core-concepts/workspaces). A workspace is a top-level entity that encompasses all other resources below it, such as devices, connected accounts, and Connect Webviews. Seam provides two types of workspaces. A [sandbox workspace](/core-concepts/workspaces#sandbox-workspaces) is a special type of workspace designed for testing code.", + "description": "Represents a Seam [workspace](/core-concepts/workspaces). A workspace is a top-level entity that encompasses all other resources below it, such as devices, connected accounts, and Connect Webviews. Seam provides two types of workspaces. A [sandbox workspace](/core-concepts/workspaces#sandbox-workspaces) is a special type of workspace designed for testing code.\n\nSee [the `workspace` object](/api/workspaces/object).", "properties": { "company_name": { "description": "Company name associated with the [workspace](/core-concepts/workspaces).", @@ -31658,7 +31659,7 @@ "properties": { "action_attempt": { "type": "object", - "description": "Tracks the progress of this operation. Poll using the action_attempt_id.", + "description": "Tracks the progress of this operation. Poll using the action_attempt_id. See [the `action_attempt` object](/api/action_attempts/object).", "properties": { "action_attempt_id": { "type": "string", @@ -31959,7 +31960,7 @@ "properties": { "action_attempt": { "type": "object", - "description": "Tracks the progress of this operation. Poll using the action_attempt_id.", + "description": "Tracks the progress of this operation. Poll using the action_attempt_id. See [the `action_attempt` object](/api/action_attempts/object).", "properties": { "action_attempt_id": { "type": "string", @@ -32829,7 +32830,7 @@ "properties": { "action_attempt": { "type": "object", - "description": "Tracks the progress of this operation. Poll using the action_attempt_id.", + "description": "Tracks the progress of this operation. Poll using the action_attempt_id. See [the `action_attempt` object](/api/action_attempts/object).", "properties": { "action_attempt_id": { "type": "string", @@ -37677,7 +37678,7 @@ "properties": { "action_attempt": { "type": "object", - "description": "Tracks the progress of this operation. Poll using the action_attempt_id.", + "description": "Tracks the progress of this operation. Poll using the action_attempt_id. See [the `action_attempt` object](/api/action_attempts/object).", "properties": { "action_attempt_id": { "type": "string", @@ -38250,7 +38251,7 @@ "properties": { "action_attempt": { "type": "object", - "description": "Tracks the progress of this operation. Poll using the action_attempt_id.", + "description": "Tracks the progress of this operation. Poll using the action_attempt_id. See [the `action_attempt` object](/api/action_attempts/object).", "properties": { "action_attempt_id": { "type": "string", @@ -38422,7 +38423,7 @@ "properties": { "action_attempt": { "type": "object", - "description": "Tracks the progress of this operation. Poll using the action_attempt_id.", + "description": "Tracks the progress of this operation. Poll using the action_attempt_id. See [the `action_attempt` object](/api/action_attempts/object).", "properties": { "action_attempt_id": { "type": "string", @@ -39920,7 +39921,7 @@ "properties": { "action_attempt": { "type": "object", - "description": "Tracks the progress of this operation. Poll using the action_attempt_id.", + "description": "Tracks the progress of this operation. Poll using the action_attempt_id. See [the `action_attempt` object](/api/action_attempts/object).", "properties": { "action_attempt_id": { "type": "string", @@ -55918,7 +55919,7 @@ "properties": { "action_attempt": { "type": "object", - "description": "Tracks the progress of this operation. Poll using the action_attempt_id.", + "description": "Tracks the progress of this operation. Poll using the action_attempt_id. See [the `action_attempt` object](/api/action_attempts/object).", "properties": { "action_attempt_id": { "type": "string", @@ -56941,7 +56942,7 @@ "properties": { "action_attempt": { "type": "object", - "description": "Tracks the progress of this operation. Poll using the action_attempt_id.", + "description": "Tracks the progress of this operation. Poll using the action_attempt_id. See [the `action_attempt` object](/api/action_attempts/object).", "properties": { "action_attempt_id": { "type": "string", @@ -57103,7 +57104,7 @@ "properties": { "action_attempt": { "type": "object", - "description": "Tracks the progress of this operation. Poll using the action_attempt_id.", + "description": "Tracks the progress of this operation. Poll using the action_attempt_id. See [the `action_attempt` object](/api/action_attempts/object).", "properties": { "action_attempt_id": { "type": "string", @@ -57254,7 +57255,7 @@ "properties": { "action_attempt": { "type": "object", - "description": "Tracks the progress of this operation. Poll using the action_attempt_id.", + "description": "Tracks the progress of this operation. Poll using the action_attempt_id. See [the `action_attempt` object](/api/action_attempts/object).", "properties": { "action_attempt_id": { "type": "string", @@ -57410,7 +57411,7 @@ "properties": { "action_attempt": { "type": "object", - "description": "Tracks the progress of this operation. Poll using the action_attempt_id.", + "description": "Tracks the progress of this operation. Poll using the action_attempt_id. See [the `action_attempt` object](/api/action_attempts/object).", "properties": { "action_attempt_id": { "type": "string", @@ -62439,7 +62440,7 @@ "properties": { "action_attempt": { "type": "object", - "description": "Tracks the progress of this operation. Poll using the action_attempt_id.", + "description": "Tracks the progress of this operation. Poll using the action_attempt_id. See [the `action_attempt` object](/api/action_attempts/object).", "properties": { "action_attempt_id": { "type": "string", @@ -62605,7 +62606,7 @@ "properties": { "action_attempt": { "type": "object", - "description": "Tracks the progress of this operation. Poll using the action_attempt_id.", + "description": "Tracks the progress of this operation. Poll using the action_attempt_id. See [the `action_attempt` object](/api/action_attempts/object).", "properties": { "action_attempt_id": { "type": "string", @@ -63397,7 +63398,7 @@ "properties": { "action_attempt": { "type": "object", - "description": "Tracks the progress of this operation. Poll using the action_attempt_id.", + "description": "Tracks the progress of this operation. Poll using the action_attempt_id. See [the `action_attempt` object](/api/action_attempts/object).", "properties": { "action_attempt_id": { "type": "string", @@ -63748,7 +63749,7 @@ "properties": { "action_attempt": { "type": "object", - "description": "Tracks the progress of this operation. Poll using the action_attempt_id.", + "description": "Tracks the progress of this operation. Poll using the action_attempt_id. See [the `action_attempt` object](/api/action_attempts/object).", "properties": { "action_attempt_id": { "type": "string", @@ -63927,7 +63928,7 @@ "properties": { "action_attempt": { "type": "object", - "description": "Tracks the progress of this operation. Poll using the action_attempt_id.", + "description": "Tracks the progress of this operation. Poll using the action_attempt_id. See [the `action_attempt` object](/api/action_attempts/object).", "properties": { "action_attempt_id": { "type": "string", @@ -64688,7 +64689,7 @@ "properties": { "action_attempt": { "type": "object", - "description": "Tracks the progress of this operation. Poll using the action_attempt_id.", + "description": "Tracks the progress of this operation. Poll using the action_attempt_id. See [the `action_attempt` object](/api/action_attempts/object).", "properties": { "action_attempt_id": { "type": "string", @@ -65880,7 +65881,7 @@ "properties": { "action_attempt": { "type": "object", - "description": "Tracks the progress of this operation. Poll using the action_attempt_id.", + "description": "Tracks the progress of this operation. Poll using the action_attempt_id. See [the `action_attempt` object](/api/action_attempts/object).", "properties": { "action_attempt_id": { "type": "string", @@ -66168,7 +66169,7 @@ "properties": { "action_attempt": { "type": "object", - "description": "Tracks the progress of this operation. Poll using the action_attempt_id.", + "description": "Tracks the progress of this operation. Poll using the action_attempt_id. See [the `action_attempt` object](/api/action_attempts/object).", "properties": { "action_attempt_id": { "type": "string", @@ -67270,7 +67271,7 @@ "properties": { "action_attempt": { "type": "object", - "description": "Tracks the progress of this operation. Poll using the action_attempt_id.", + "description": "Tracks the progress of this operation. Poll using the action_attempt_id. See [the `action_attempt` object](/api/action_attempts/object).", "properties": { "action_attempt_id": { "type": "string", @@ -72352,7 +72353,7 @@ "properties": { "action_attempt": { "type": "object", - "description": "Tracks the progress of this operation. Poll using the action_attempt_id.", + "description": "Tracks the progress of this operation. Poll using the action_attempt_id. See [the `action_attempt` object](/api/action_attempts/object).", "properties": { "action_attempt_id": { "type": "string",