diff --git a/docs/api/_report.md b/docs/api/_report.md
index 327adc94..750449c8 100644
--- a/docs/api/_report.md
+++ b/docs/api/_report.md
@@ -7,7 +7,6 @@
- `/access_codes`
- `/access_codes/simulate`
- `/access_codes/unmanaged`
-- `/action_attempts`
- `/bridges`
- `/client_sessions`
- `/connect_webviews`
diff --git a/docs/api/action_attempts/README.md b/docs/api/action_attempts/README.md
new file mode 100644
index 00000000..c711012e
--- /dev/null
+++ b/docs/api/action_attempts/README.md
@@ -0,0 +1,293 @@
+# Action Attempts
+
+## `action_attempt`
+
+Represents an attempt to perform an action against a device.
+
+### `action_attempt_id`
+
+Format: `UUID`
+
+ID of the action attempt.
+
+---
+
+### `status`
+
+Format: `Enum`
+
+Possible enum values:
+- `success`
+- `pending`
+- `error`
+
+---
+
+### `action_type`
+
+Format: `String`
+
+Type of the action attempt.
+
+Possible enum values:
+- `LOCK_DOOR`
+- `UNLOCK_DOOR`
+- `SCAN_CREDENTIAL`
+- `ENCODE_ACCESS_METHOD`
+- `ENCODE_CREDENTIAL`
+- `RESET_SANDBOX_WORKSPACE`
+- `SET_FAN_MODE`
+- `SET_HVAC_MODE`
+- `ACTIVATE_CLIMATE_PRESET`
+- `SIMULATE_KEYPAD_CODE_ENTRY`
+- `SIMULATE_MANUAL_LOCK_VIA_KEYPAD`
+- `PUSH_THERMOSTAT_PROGRAMS`
+- `SYNC_ACCESS_CODES`
+- `CREATE_ACCESS_CODE`
+- `DELETE_ACCESS_CODE`
+- `UPDATE_ACCESS_CODE`
+- `CREATE_NOISE_THRESHOLD`
+- `DELETE_NOISE_THRESHOLD`
+- `UPDATE_NOISE_THRESHOLD`
+
+---
+
+### `error`
+
+Format: `Object`
+
+Errors associated with the action attempt. Null for pending action attempts.
+
+---
+
+### `result`
+
+Format: `Object`
+
+Result of the action attempt. Null for pending action attempts.
+
+---
+
+## Endpoints
+
+
+---
+
+## Events
+
+### `action_attempt.lock_door.succeeded`
+
+A lock door [action attempt](../../core-concepts/action-attempts.md) succeeded.
+
+
+
+action_attempt_id
Format: UUID
+
+ID of the [action attempt](../../core-concepts/action-attempts.md).
+
+
+
+action_type
Format: String
+
+Type of action.
+
+
+
+created_at
Format: Datetime
+
+Date and time at which the event was created.
+
+
+
+event_id
Format: UUID
+
+ID of the event.
+
+
+
+event_type
Format: Enum
+
+Value: `action_attempt.lock_door.succeeded`
+
+
+
+occurred_at
Format: Datetime
+
+Date and time at which the event occurred.
+
+
+
+status
Format: String
+
+Status of the action.
+
+
+
+workspace_id
Format: UUID
+
+ID of the [workspace](../../core-concepts/workspaces/README.md).
+
+---
+
+### `action_attempt.lock_door.failed`
+
+A lock door [action attempt](../../core-concepts/action-attempts.md) failed.
+
+
+
+action_attempt_id
Format: UUID
+
+ID of the [action attempt](../../core-concepts/action-attempts.md).
+
+
+
+action_type
Format: String
+
+Type of action.
+
+
+
+created_at
Format: Datetime
+
+Date and time at which the event was created.
+
+
+
+event_id
Format: UUID
+
+ID of the event.
+
+
+
+event_type
Format: Enum
+
+Value: `action_attempt.lock_door.failed`
+
+
+
+occurred_at
Format: Datetime
+
+Date and time at which the event occurred.
+
+
+
+status
Format: String
+
+Status of the action.
+
+
+
+workspace_id
Format: UUID
+
+ID of the [workspace](../../core-concepts/workspaces/README.md).
+
+---
+
+### `action_attempt.unlock_door.succeeded`
+
+An unlock door [action attempt](../../core-concepts/action-attempts.md) succeeded.
+
+
+
+action_attempt_id
Format: UUID
+
+ID of the [action attempt](../../core-concepts/action-attempts.md).
+
+
+
+action_type
Format: String
+
+Type of action.
+
+
+
+created_at
Format: Datetime
+
+Date and time at which the event was created.
+
+
+
+event_id
Format: UUID
+
+ID of the event.
+
+
+
+event_type
Format: Enum
+
+Value: `action_attempt.unlock_door.succeeded`
+
+
+
+occurred_at
Format: Datetime
+
+Date and time at which the event occurred.
+
+
+
+status
Format: String
+
+Status of the action.
+
+
+
+workspace_id
Format: UUID
+
+ID of the [workspace](../../core-concepts/workspaces/README.md).
+
+---
+
+### `action_attempt.unlock_door.failed`
+
+An unlock door [action attempt](../../core-concepts/action-attempts.md) failed.
+
+
+
+action_attempt_id
Format: UUID
+
+ID of the [action attempt](../../core-concepts/action-attempts.md).
+
+
+
+action_type
Format: String
+
+Type of action.
+
+
+
+created_at
Format: Datetime
+
+Date and time at which the event was created.
+
+
+
+event_id
Format: UUID
+
+ID of the event.
+
+
+
+event_type
Format: Enum
+
+Value: `action_attempt.unlock_door.failed`
+
+
+
+occurred_at
Format: Datetime
+
+Date and time at which the event occurred.
+
+
+
+status
Format: String
+
+Status of the action.
+
+
+
+workspace_id
Format: UUID
+
+ID of the [workspace](../../core-concepts/workspaces/README.md).
+
+---
+
diff --git a/src/data/paths.yaml b/src/data/paths.yaml
index 4b9238aa..99af5a94 100644
--- a/src/data/paths.yaml
+++ b/src/data/paths.yaml
@@ -80,3 +80,8 @@
title: Enrollment Automations
resources:
- enrollment_automation
+
+/action_attempts:
+ title: Action Attempts
+ resources:
+ - action_attempt
diff --git a/src/lib/layout/action-attempt-resource.ts b/src/lib/layout/action-attempt-resource.ts
new file mode 100644
index 00000000..dfcca3ff
--- /dev/null
+++ b/src/lib/layout/action-attempt-resource.ts
@@ -0,0 +1,93 @@
+import type { Blueprint } from '@seamapi/blueprint'
+
+import {
+ type ApiError,
+ type ApiRouteEvent,
+ type ApiRouteProperty,
+ type ApiRouteResource,
+ type ApiWarning,
+ mapBlueprintPropertyToRouteProperty,
+ type ResourceSampleContext,
+} from './api-route.js'
+
+export function processActionAttemptResource(
+ blueprint: Blueprint,
+ resources: Array<
+ ApiRouteResource & {
+ warnings: ApiWarning[]
+ errors: ApiError[]
+ resourceSamples: ResourceSampleContext[]
+ }
+ >,
+ eventsByRoutePath: Map,
+): void {
+ const blueprintActionAttemptDef = blueprint.actionAttempts[0]
+ if (blueprintActionAttemptDef == null) {
+ throw new Error(
+ 'Cannot process action attempt resource: blueprint.actionAttempts is empty.',
+ )
+ }
+
+ const idPropKey = 'action_attempt_id'
+ const idPropDef = blueprintActionAttemptDef.properties.find(
+ (p) => p.name === idPropKey,
+ )
+ if (idPropDef == null) {
+ throw new Error(
+ `Blueprint action attempt is missing "${idPropKey}" property.`,
+ )
+ }
+
+ const statusPropKey = 'status'
+ const statusPropDef = blueprintActionAttemptDef.properties.find(
+ (p) => p.name === statusPropKey,
+ )
+ if (statusPropDef == null) {
+ throw new Error(
+ `Blueprint action attempt is missing "${statusPropKey}" property.`,
+ )
+ }
+
+ const actionTypes = blueprint.actionAttempts.map(
+ (attempt) => attempt.actionAttemptType,
+ )
+
+ const properties: ApiRouteProperty[] = [
+ mapBlueprintPropertyToRouteProperty(idPropDef),
+ mapBlueprintPropertyToRouteProperty(statusPropDef),
+ {
+ name: 'action_type',
+ description: 'Type of the action attempt.',
+ format: 'String',
+ isDeprecated: false,
+ deprecationMessage: '',
+ enumValues: actionTypes,
+ },
+ {
+ name: 'error',
+ description:
+ 'Errors associated with the action attempt. Null for pending action attempts.',
+ format: 'Object',
+ isDeprecated: false,
+ deprecationMessage: '',
+ },
+ {
+ name: 'result',
+ description:
+ 'Result of the action attempt. Null for pending action attempts.',
+ format: 'Object',
+ isDeprecated: false,
+ deprecationMessage: '',
+ },
+ ]
+
+ resources.push({
+ name: 'action_attempt',
+ description: 'Represents an attempt to perform an action against a device.',
+ properties,
+ errors: [],
+ warnings: [],
+ events: eventsByRoutePath.get('/action_attempts') ?? [],
+ resourceSamples: [],
+ })
+}
diff --git a/src/lib/layout/api-route.ts b/src/lib/layout/api-route.ts
index fd51d64c..f4b836dd 100644
--- a/src/lib/layout/api-route.ts
+++ b/src/lib/layout/api-route.ts
@@ -15,6 +15,8 @@ import type { ResourceSample } from 'node_modules/@seamapi/blueprint/lib/samples
import type { PathMetadata } from 'lib/path-metadata.js'
+import { processActionAttemptResource } from './action-attempt-resource.js'
+
export interface ApiRouteLayoutContext {
title: string
description: string
@@ -30,19 +32,19 @@ export interface ApiRouteLayoutContext {
events: ApiRouteEvent[]
}
-interface ApiRouteEvent {
+export interface ApiRouteEvent {
name: string
description: string
properties: ApiRouteProperty[]
}
-interface ResourceSampleContext {
+export interface ResourceSampleContext {
title: string
resourceData: string
resourceDataSyntax: SyntaxName
}
-type ApiRouteProperty = Pick<
+export type ApiRouteProperty = Pick<
Property,
'name' | 'description' | 'isDeprecated' | 'deprecationMessage'
> & {
@@ -68,11 +70,12 @@ export interface ApiRouteResource {
events: ApiRouteEvent[]
}
-interface ApiWarning {
+export interface ApiWarning {
name: string
description: string
}
-interface ApiError {
+
+export interface ApiError {
name: string
description: string
}
@@ -108,6 +111,11 @@ export function setApiRouteLayoutContext(
file.resources = []
for (const resourceType of metadata.resources) {
+ if (resourceType === 'action_attempt') {
+ processActionAttemptResource(blueprint, file.resources, eventsByRoutePath)
+ continue
+ }
+
const resource = blueprint.resources[resourceType]
if (resource == null) {
@@ -150,8 +158,6 @@ const groupEventsByRoutePath = (
const eventsByRoutePath = new Map()
for (const event of events) {
- if (event.routePath == null) continue
-
const routeEvents = eventsByRoutePath.get(event.routePath) ?? []
routeEvents.push({
name: event.eventType,
diff --git a/src/lib/reference.ts b/src/lib/reference.ts
index adae39a5..4cae12d1 100644
--- a/src/lib/reference.ts
+++ b/src/lib/reference.ts
@@ -57,7 +57,8 @@ export const reference = (
!route.path.startsWith('/acs') &&
!route.path.startsWith('/thermostats') &&
!route.path.startsWith('/phones') &&
- !route.path.startsWith('/user_identities')
+ !route.path.startsWith('/user_identities') &&
+ !route.path.startsWith('/action_attempts')
) {
continue
}