---
id: AgentQLExtractionCompleted
name: AgentQL Extraction Completed
version: 1.0.0
owners:
- buildplan
summary: AgentQL returned extracted JSON.
badges:
- content: "Status: Implemented in api-v1"
backgroundColor: yellow
textColor: yellow
---
## Trigger
Emitted when AgentQL returns structured product data.
## Payload Shape
`sourceUrl`, `extracted`, `confidence`, `completedAt`.
## Consumers
`shared-scraping` transformer pipeline.
---
id: BrightDataPageFetched
name: BrightData Page Fetched
version: 1.0.0
owners:
- buildplan
summary: BrightData returned fetched page content.
badges:
- content: "Status: Implemented in api-v1"
backgroundColor: yellow
textColor: yellow
---
## Trigger
Emitted when BrightData returns hydrated HTML or dataset payloads.
## Payload Shape
`sourceUrl`, `statusCode`, `html`, `fetchedAt`.
## Consumers
`shared-scraping` extraction pipeline.
---
id: ClassParameterAllowedValuesReplaced
name: Class Parameter Allowed Values Replaced
version: 1.0.0
owners:
- buildplan
summary: The allowed-value set on a ClassParameter binding was replaced wholesale.
badges:
- content: "Status: Implemented in api-v1"
backgroundColor: yellow
textColor: yellow
---
## Purpose
Records a whole-set replacement of the [ParameterValue](/docs/entities/ParameterValue) options permitted on one [ClassParameter](/docs/entities/ClassParameter) binding — never an incremental add or remove. The set invariants (no duplicate value, at most one default) are validated *before* the no-change early return, so a replay that violates them still surfaces the error.
This is the only one of the four `ClassParameter*` events that does **not** reach the Revit projection side: changing which values are allowed does not change which object classes contribute to a projection's category set.
## Payload
Envelope is `{ name, occurredAt, payload }` (`apps/api-v1/src/modules/shared/domain/events/DomainEvent.ts`). All four `ClassParameter*` events share one payload type.
| Field | Type | Meaning |
| --- | --- | --- |
| `classParameterId` | `ClassParameterId` | The assignment whose values were replaced |
| `objectClassId` | `ObjectClassId` | Class holding the assignment |
| `parameterKeyId` | `ParameterKeyId` | Canonical key |
| `allowedValueCount` | `number` | Size of the new set. This is the only event that populates it |
## Raised by
- `ClassParameter.replaceAllowedValues` — `apps/api-v1/src/modules/bim-ontology/core/domain/class-parameter/ClassParameter.ts`, only when the new set actually differs from the current one
- `ReplaceClassParameterAllowedValuesUseCase.replaceClassParameterAllowedValues` — `apps/api-v1/src/modules/bim-ontology/core/application/use-cases/ClassParameterUseCases.ts`
- HTTP: [replaceClassParameterAllowedValues](/docs/services/api-v1/openapi/operations/replaceClassParameterAllowedValues). [validateClassParameterAllowedValues](/docs/services/api-v1/openapi/operations/validateClassParameterAllowedValues) runs the same invariants without writing, and emits nothing
## Consumers
`RenderedParamTemplatesSyncHandler` (`adapters/inbound/events/`) only, registered in `bim-ontology.composition.ts`. It calls `SyncObjectClassRenderedParamTemplatesUseCase` so inherited rows on descendant classes pick up the new allowed-value set.
`ClassParameterDerivedRevitCategoryHandler` is deliberately **not** registered for this event.
## Related
- [ClassParameter](/docs/entities/ClassParameter), [ParameterValue](/docs/entities/ParameterValue), [ParameterValueGroup](/docs/entities/ParameterValueGroup)
- [ClassParameterAssigned](/docs/events/ClassParameterAssigned), [ClassParameterAssignmentUpdated](/docs/events/ClassParameterAssignmentUpdated)
---
id: ClassParameterAssigned
name: Class Parameter Assigned
version: 1.0.0
owners:
- buildplan
summary: A ParameterKey was directly assigned to an ObjectClass.
badges:
- content: "Status: Implemented in api-v1"
backgroundColor: yellow
textColor: yellow
---
## Purpose
Records a new **direct** [ClassParameter](/docs/entities/ClassParameter) binding — a steward saying "this class has this parameter". `ClassParameter.bind` always creates with `assignmentOrigin: 'direct'`; rendered (inherited) rows are materialized by the template sync, not by this event.
Assignment changes which object classes contribute to a Revit projection's derived category set, which is why the projection side listens.
## Payload
Envelope is `{ name, occurredAt, payload }` (`apps/api-v1/src/modules/shared/domain/events/DomainEvent.ts`). All four `ClassParameter*` events share one payload type.
| Field | Type | Meaning |
| --- | --- | --- |
| `classParameterId` | `ClassParameterId` | The assignment record |
| `objectClassId` | `ObjectClassId` | Class the parameter was assigned to |
| `parameterKeyId` | `ParameterKeyId` | Canonical key being assigned |
| `allowedValueCount` | `number` (optional) | Only populated on [ClassParameterAllowedValuesReplaced](/docs/events/ClassParameterAllowedValuesReplaced) |
## Raised by
- `ClassParameter.bind` — `apps/api-v1/src/modules/bim-ontology/core/domain/class-parameter/ClassParameter.ts`
- `UpsertClassParameterUseCase.upsertClassParameter` — `apps/api-v1/src/modules/bim-ontology/core/application/use-cases/ClassParameterUseCases.ts`. `BulkAssignClassParametersUseCase` loops over the same use case
- HTTP: [upsertClassParameter](/docs/services/api-v1/openapi/operations/upsertClassParameter), [assignClassParameters](/docs/services/api-v1/openapi/operations/assignClassParameters)
## Consumers
Two handlers in `adapters/inbound/events/`, both registered in `bim-ontology.composition.ts`:
| Handler | Effect |
| --- | --- |
| `ClassParameterDerivedRevitCategoryHandler` | `PersistDerivedRevitCategorySetsUseCase` for the event's `parameterKeyId` — refreshes which classes contribute to the key's Revit projection |
| `RenderedParamTemplatesSyncHandler` | `SyncObjectClassRenderedParamTemplatesUseCase` for the affected class subtree — re-materializes inherited assignments |
## Related
- [ClassParameter](/docs/entities/ClassParameter), [ObjectClass](/docs/entities/ObjectClass), [ParameterKey](/docs/entities/ParameterKey)
- [ClassParameterAssignmentUpdated](/docs/events/ClassParameterAssignmentUpdated), [ClassParameterUnassigned](/docs/events/ClassParameterUnassigned)
---
id: ClassParameterAssignmentUpdated
name: Class Parameter Assignment Updated
version: 1.0.0
owners:
- buildplan
summary: An existing ClassParameter binding changed — requiredness, inheritance mode, child behavior, or promotion from rendered to direct.
badges:
- content: "Status: Implemented in api-v1"
backgroundColor: yellow
textColor: yellow
---
## Purpose
Records a revision of an existing [ClassParameter](/docs/entities/ClassParameter) binding rather than a new one. Emitted only when something actually changed — a no-op revision emits nothing and skips the Airtable write. Promotion of a rendered (inherited) row to `direct` also lands here, because the binding identity is unchanged.
## Payload
Envelope is `{ name, occurredAt, payload }` (`apps/api-v1/src/modules/shared/domain/events/DomainEvent.ts`). All four `ClassParameter*` events share one payload type.
| Field | Type | Meaning |
| --- | --- | --- |
| `classParameterId` | `ClassParameterId` | The assignment record |
| `objectClassId` | `ObjectClassId` | Class holding the assignment |
| `parameterKeyId` | `ParameterKeyId` | Canonical key |
| `allowedValueCount` | `number` (optional) | Not populated on this event |
## Raised by
`apps/api-v1/src/modules/bim-ontology/core/domain/class-parameter/ClassParameter.ts`:
- `reviseBinding` — requiredness plus inheritance mode (direct) or child behavior (rendered)
- `promoteToDirectAndReviseBinding` — rendered overlay promoted to a direct assignment in one step
- `promoteToDirect` — promotion alone; defined on the aggregate but not currently reached from any use case
Driven by `UpsertClassParameterUseCase.upsertClassParameter` (`core/application/use-cases/ClassParameterUseCases.ts`) — the same operation that raises [ClassParameterAssigned](/docs/events/ClassParameterAssigned) when the binding does not exist yet.
HTTP: [upsertClassParameter](/docs/services/api-v1/openapi/operations/upsertClassParameter), [assignClassParameters](/docs/services/api-v1/openapi/operations/assignClassParameters).
## Consumers
The same two handlers as `ClassParameterAssigned`, both registered in `bim-ontology.composition.ts`:
| Handler | Effect |
| --- | --- |
| `ClassParameterDerivedRevitCategoryHandler` | `PersistDerivedRevitCategorySetsUseCase` for the event's `parameterKeyId` |
| `RenderedParamTemplatesSyncHandler` | `SyncObjectClassRenderedParamTemplatesUseCase` for the affected class subtree |
## Related
- [ClassParameter](/docs/entities/ClassParameter), [ObjectClass](/docs/entities/ObjectClass), [ParameterKey](/docs/entities/ParameterKey)
- [ClassParameterAssigned](/docs/events/ClassParameterAssigned), [ClassParameterUnassigned](/docs/events/ClassParameterUnassigned)
---
id: ClassParameterUnassigned
name: Class Parameter Unassigned
version: 1.0.0
owners:
- buildplan
summary: A ClassParameter binding between an ObjectClass and a ParameterKey was removed.
badges:
- content: "Status: Implemented in api-v1"
backgroundColor: yellow
textColor: yellow
---
## Purpose
Records removal of a [ClassParameter](/docs/entities/ClassParameter) binding. `markUnassigned` only stages the event — the actual removal is the use case's job — so the payload still describes the binding that existed.
Removing a binding shrinks the set of object classes contributing to the key's Revit projection, so the projection side listens.
## Payload
Envelope is `{ name, occurredAt, payload }` (`apps/api-v1/src/modules/shared/domain/events/DomainEvent.ts`). All four `ClassParameter*` events share one payload type.
| Field | Type | Meaning |
| --- | --- | --- |
| `classParameterId` | `ClassParameterId` | The assignment that was removed |
| `objectClassId` | `ObjectClassId` | Class that no longer carries the parameter |
| `parameterKeyId` | `ParameterKeyId` | Canonical key that was unassigned |
| `allowedValueCount` | `number` (optional) | Not populated on this event |
## Raised by
- `ClassParameter.markUnassigned` — `apps/api-v1/src/modules/bim-ontology/core/domain/class-parameter/ClassParameter.ts`
- `UnassignClassParameterUseCase.unassignClassParameter` — `apps/api-v1/src/modules/bim-ontology/core/application/use-cases/ClassParameterUseCases.ts`
- HTTP: [unassignClassParameter](/docs/services/api-v1/openapi/operations/unassignClassParameter)
## Consumers
The same two handlers as [ClassParameterAssigned](/docs/events/ClassParameterAssigned), both registered in `bim-ontology.composition.ts`:
| Handler | Effect |
| --- | --- |
| `ClassParameterDerivedRevitCategoryHandler` | `PersistDerivedRevitCategorySetsUseCase` for the event's `parameterKeyId` |
| `RenderedParamTemplatesSyncHandler` | `SyncObjectClassRenderedParamTemplatesUseCase` for the affected class subtree, dropping inherited copies |
## Related
- [ClassParameter](/docs/entities/ClassParameter), [ObjectClass](/docs/entities/ObjectClass), [ParameterKey](/docs/entities/ParameterKey)
- [ClassParameterAssigned](/docs/events/ClassParameterAssigned), [ClassParameterAssignmentUpdated](/docs/events/ClassParameterAssignmentUpdated)
---
id: MedusaStripeWebhookReceived
name: Medusa Stripe Webhook Received
version: 1.0.0
owners:
- buildplan
summary: Medusa forwarded a Stripe commerce webhook to api-v1.
badges:
- content: "Status: Implemented in api-v1"
backgroundColor: yellow
textColor: yellow
---
## Trigger
Emitted when Medusa receives and forwards relevant Stripe events.
## Payload Shape
`eventId`, `eventType`, `orderId`, `receivedAt`.
## Consumers
`api-v1` webhook handlers and commerce sync workflows.
---
id: ObjectClassDemoted
name: Object Class Demoted
version: 1.0.0
owners:
- buildplan
summary: An approved ObjectClass returned to draft after it lost a required classification. Covering approved Revit projections are demoted to draft for re-review.
badges:
- content: "Status: Implemented in api-v1"
backgroundColor: yellow
textColor: yellow
---
## Purpose
Records `approved → draft` on [ObjectClass](/docs/entities/ObjectClass) when a required classification (Uniformat, Masterformat, ObjectClass code, or Revit category) no longer resolves on the class or its ancestry. Demotion is reversible re-review, not archival.
One event is recorded **per class**. A subtree that loses the same kind drains several events and they are dispatched together after persist.
## Payload
Envelope is `{ name, occurredAt, payload }` (`apps/api-v1/src/modules/shared/domain/events/DomainEvent.ts`).
| Field | Type | Meaning |
| --- | --- | --- |
| `objectClassId` | `string` | Class that was demoted |
| `reason` | `'lost_classification'` | Why demotion happened |
| `kind` | `ClassificationKind` (optional) | Required kind that was missing |
| `changedAt` | `Date` | Same `now` passed into `demoteToDraft` |
## Raised by
- `ObjectClass.demoteToDraft` — `apps/api-v1/src/modules/bim-ontology/core/domain/object-class/ObjectClass.ts`. No-op (no event) when already `draft`; refused when `archived`
- `DemoteObjectClassesThatLostClassificationUseCase.demoteWhenClassificationLost` drains `pullDomainEvents()` **after** `setLifecycleStatuses` succeeds
- HTTP: [syncObjectClassClassificationInheritance](/docs/services/api-v1/openapi/operations/syncObjectClassClassificationInheritance) (seeded). Graph mutations still emit [ObjectClassParentGraphChanged](/docs/events/ObjectClassParentGraphChanged); this event is the **downstream** reaction after classification refill
## Consumers
`DemoteRevitProjectionOnObjectClassDemotionHandler` (`adapters/inbound/events/`), registered in `bim-ontology.composition.ts` on `ObjectClassDemoted`. Thin: payload → `DemoteRevitProjectionsCoveringObjectClassUseCase`, which demotes covering **approved** projections to `draft` via `RevitParameterProjection.demoteToDraft` (emits [RevitParameterProjectionDemoted](/docs/events/RevitParameterProjectionDemoted)). Draft and archived projections are skipped.
The in-process dispatcher swallows handler errors, so GET `missingForApproval` / `objectClass.approved` remains the second signal if a covering projection stays approved.
## Related
- [ObjectClassParentGraphChanged](/docs/events/ObjectClassParentGraphChanged)
- [RevitParameterProjectionDemoted](/docs/events/RevitParameterProjectionDemoted)
- [Object Class Demotion Cascade](/docs/flows/object-class-demotion-cascade)
---
id: ObjectClassParentGraphChanged
name: Object Class Parent Graph Changed
version: 1.0.0
owners:
- buildplan
summary: Parent edges in the ObjectClass tree changed. Four handlers reconcile derived state for the affected subtrees.
badges:
- content: "Status: Implemented in api-v1"
backgroundColor: yellow
textColor: yellow
---
## Purpose
Published after the single-parent [ObjectClass](/docs/entities/ObjectClass) tree is restructured — create-root, add-child, move, merge, copy, or delete. Everything derived from tree position (hierarchy fields, classification inheritance, Revit categories, rendered parameter templates) is stale until the handlers run, so this event has more subscribers than any other in the module.
It is the real successor to `ObjectCategoryHierarchyChanged`, an event this catalog previously documented that was never implemented in `apps/api-v1`.
## Payload
Envelope is `{ name, occurredAt, payload }` (`apps/api-v1/src/modules/shared/domain/events/DomainEvent.ts`).
| Field | Type | Meaning |
| --- | --- | --- |
| `affectedObjectClassIds` | `readonly string[]` | De-duplicated `domain_id` seeds for the changed subtrees. Deleted seeds stay in the payload so handlers can skip missing rows instead of recomputing the whole tree |
| `changedAt` | `Date` | When the mutation happened; combined with the sorted ids to form each handler's idempotency key |
## Raised by
- Factory `objectClassParentGraphChanged` — `apps/api-v1/src/modules/bim-ontology/core/domain/object-class/ObjectClass.events.ts`, which de-duplicates the id list
- Dispatched by `ObjectClassWorkflowUseCases.dispatchParentGraphChanged` — `apps/api-v1/src/modules/bim-ontology/core/application/use-cases/ObjectClassWorkflowUseCases.ts` — reached from `createRoot` and `addChild` directly and from `runGraphMutation` for `delete`, `move`, `merge`, and `copy`
- HTTP: [createObjectClass](/docs/services/api-v1/openapi/operations/createObjectClass), [addObjectClassChild](/docs/services/api-v1/openapi/operations/addObjectClassChild), [moveObjectClasses](/docs/services/api-v1/openapi/operations/moveObjectClasses), [mergeObjectClasses](/docs/services/api-v1/openapi/operations/mergeObjectClasses), [copyObjectClasses](/docs/services/api-v1/openapi/operations/copyObjectClasses), [deleteObjectClasses](/docs/services/api-v1/openapi/operations/deleteObjectClasses)
## Consumers
Four handlers, all in `adapters/inbound/events/` and all registered in `bim-ontology.composition.ts`:
| Handler | Effect |
| --- | --- |
| `ObjectClassParentGraphChangedRevitCategoryHandler` | Re-runs `ReconcileObjectClassRevitCategoriesUseCase` for the affected subtrees, then `PersistDerivedRevitCategorySetsUseCase` to refresh derived projection category sets |
| `ObjectClassParentGraphChangedHierarchyHandler` | `syncHierarchy` — recomputes depth, path, `is_leaf`, and root object class |
| `ObjectClassParentGraphChangedClassificationHandler` | `syncClassificationInheritance` per affected id (Uniformat / Masterformat / ObjectClass code), then `DemoteObjectClassesThatLostClassificationUseCase` for approved nodes that lost a required kind. Demote records [ObjectClassDemoted](/docs/events/ObjectClassDemoted) after persist |
| `RenderedParamTemplatesSyncHandler` | `SyncObjectClassRenderedParamTemplatesUseCase` per surviving seed with `recomputeSubtree`, re-materializing inherited [ClassParameter](/docs/entities/ClassParameter) rows |
All four seed from `affectedObjectClassIds` rather than recomputing the whole tree (ADR-0047) — whole-tree recompute stays on the explicit `syncAll*` maintenance endpoints. Each is idempotent on sorted ids plus `changedAt`, skips not-found seeds, and records failures through `ForRecordingBimOntologyHandlerFailure` before rethrowing.
## Related
- [ObjectClass](/docs/entities/ObjectClass), [ClassParameter](/docs/entities/ClassParameter), [RevitParameterProjection](/docs/entities/RevitParameterProjection)
- [ObjectClassDemoted](/docs/events/ObjectClassDemoted)
- [BIM Ontology](/docs/domains/bim-ontology)
---
id: OrganizationCreated
name: Organization Created
version: 1.0.0
owners:
- buildplan
summary: A canonical party-directory organization was created.
badges:
- content: "Status: Implemented in api-v1"
backgroundColor: yellow
textColor: yellow
---
## Trigger
Emitted by party-directory after a new organization identity is persisted.
## Payload Shape
`organizationId`, `displayName`, `websiteUrl`, `createdAt`.
## Consumers
`api-v1` product-marketplace workflows that need manufacturer/vendor references.
---
id: OrganizationUpdated
name: Organization Updated
version: 1.0.0
owners:
- buildplan
summary: A canonical party-directory organization was updated.
badges:
- content: "Status: Implemented in api-v1"
backgroundColor: yellow
textColor: yellow
---
## Trigger
Emitted by party-directory after organization identity or contact details change.
## Payload Shape
`organizationId`, `changedFields`, `updatedAt`.
## Consumers
`api-v1` product-marketplace workflows and future CRM/service-marketplace projections.
---
id: ParameterGroupChanged
name: Parameter Group Changed
version: 1.0.0
owners:
- buildplan
summary: A ParameterGroup was created or renamed. Renaming a group resyncs derived Revit projection metadata.
badges:
- content: "Status: Implemented in api-v1"
backgroundColor: yellow
textColor: yellow
---
## Purpose
The only domain event `ParameterGroup` has. One event name covers both create and rename, discriminated by `change`. It matters downstream because group naming feeds derived Revit projection metadata, so renaming a group is not a cosmetic operation.
## Payload
Envelope is `{ name, occurredAt, payload }` (`apps/api-v1/src/modules/shared/domain/events/DomainEvent.ts`).
| Field | Type | Meaning |
| --- | --- | --- |
| `parameterGroupId` | `ParameterGroupId` | Group that changed |
| `change` | `'created' \| 'renamed'` | Which mutation happened |
| `name` | `string` | Group label after the change |
| `previousName` | `string \| null` | Prior label on `renamed`; `null` on `created` |
| `changedAt` | `Date` | When the change happened |
## Raised by
- `ParameterGroup.create` (`change: 'created'`) and `ParameterGroup.rename` (`change: 'renamed'`) — `apps/api-v1/src/modules/bim-ontology/core/domain/parameter-group/ParameterGroup.ts`
- `CreateParameterGroupUseCase` and `UpdateParameterGroupUseCase` — `apps/api-v1/src/modules/bim-ontology/core/application/use-cases/ParameterGroupAuthoringUseCases.ts`. A rename to the same name emits nothing and skips the Airtable write, so Airtable "when record updated" automations cannot loop
- HTTP: [createParameterGroup](/docs/services/api-v1/openapi/operations/createParameterGroup), [updateParameterGroup](/docs/services/api-v1/openapi/operations/updateParameterGroup)
## Consumers
`CanonicalProjectionMetadataChangedHandler` (`adapters/inbound/events/`) — the same instance that handles [ParameterKeyUpdated](/docs/events/ParameterKeyUpdated) — is registered in `bim-ontology.composition.ts` and calls `ResyncDerivedProjectionMetadataUseCase.resyncForParameterGroup`. That walks every projection derived from the group and refreshes its non-overridden metadata; projections whose `nameSource` is `override` are left alone.
A resync that changes shared-parameter-file output on an **approved** projection demotes it to `draft`.
## Related
- [ParameterGroup](/docs/entities/ParameterGroup), [RevitParameterProjection](/docs/entities/RevitParameterProjection)
- [RevitParameterProjectionResynced](/docs/events/RevitParameterProjectionResynced), [RevitParameterProjectionDemoted](/docs/events/RevitParameterProjectionDemoted)
---
id: ParameterKeyActivated
name: Parameter Key Activated
version: 1.0.0
owners:
- buildplan
summary: A ParameterKey moved `draft → active` — displayable but not yet vetted. Nothing subscribes to it.
badges:
- content: "Status: Implemented in api-v1"
backgroundColor: yellow
textColor: yellow
---
## Purpose
Marks the only transition out of `draft`. An `active` key is displayable-but-unvetted (ADR-0052): a scraped `product_spec` reaches the storefront immediately while still being flagged as needing steward mapping. Spec and control need not be complete yet, so both are nullable in the payload.
## Payload
Envelope is `{ name, occurredAt, payload }` (`apps/api-v1/src/modules/shared/domain/events/DomainEvent.ts`).
| Field | Type | Meaning |
| --- | --- | --- |
| `parameterKeyId` | `ParameterKeyId` | Key that was activated |
| `handle` | `string` | Normalized dictionary slug |
| `name` | `string` | Canonical label |
| `parameterSpecId` | `string \| null` | Value semantics, if already referenced |
| `parameterControlId` | `string \| null` | Presentation, if already referenced |
| `parameterGroupId` | `string \| null` | Authoring group, if any |
| `activatedAt` | `Date` | When the transition happened |
## Raised by
- `ParameterKey.activate` — `apps/api-v1/src/modules/bim-ontology/core/domain/parameter-key/ParameterKey.ts`
- `ActivateParameterKeyUseCase.activateParameterKey` — `apps/api-v1/src/modules/bim-ontology/core/application/use-cases/ParameterKeyAuthoringUseCases.ts`
- HTTP: [activateParameterKey](/docs/services/api-v1/openapi/operations/activateParameterKey)
## Consumers
**No subscriber in this module.** Activation has no registered handler in `bim-ontology.composition.ts`.
This is the single most commonly misremembered fact in the module. An earlier design auto-created the Revit projection on activation, and the only surviving trace is the deprecated `autoCreateProjectionOnParameterKeyActivation` method and its `AutoCreateProjectionOnParameterKeyActivationUseCase` subclass alias in `core/application/use-cases/RevitProjectionAuthoringUseCases.ts` — nothing registers or calls either. The projection ensure is wired to [ParameterKeyApproved](/docs/events/ParameterKeyApproved) alone. An `active` key is deliberately not yet in Revit.
## Related
- [ParameterKey](/docs/entities/ParameterKey) — why `active` exists
- [ParameterKeyApproved](/docs/events/ParameterKeyApproved)
- [BIM Ontology ubiquitous language](/docs/domains/bim-ontology/language)
---
id: ParameterKeyApproved
name: Parameter Key Approved
version: 1.0.0
owners:
- buildplan
summary: A ParameterKey passed its approve gate and became canonical. This is the event that triggers the automatic Revit projection.
badges:
- content: "Status: Implemented in api-v1"
backgroundColor: yellow
textColor: yellow
---
## Purpose
Marks `draft|active → approved`: the key is vetted and canonical. Approve requires a parameter spec and a parameter control, which is why `parameterSpecId` and `parameterControlId` are **non-nullable here** and nullable on every other `ParameterKey*` event. BIM usage is not required.
When the approved key carries `usage:bim_parameter`, this is the lifecycle event that mints Revit state. Catalog-only keys stay without a projection.
## Payload
Envelope is `{ name, occurredAt, payload }` (`apps/api-v1/src/modules/shared/domain/events/DomainEvent.ts`).
| Field | Type | Meaning |
| --- | --- | --- |
| `parameterKeyId` | `ParameterKeyId` | Key that was approved |
| `handle` | `string` | Normalized dictionary slug; also drives the built-in vs shared projection policy |
| `name` | `string` | Canonical label |
| `parameterSpecId` | `string` | Value semantics — required to approve |
| `parameterControlId` | `string` | Presentation — required to approve |
| `parameterGroupId` | `string \| null` | Authoring group; optional at every stage |
| `approvedAt` | `Date` | When approval happened; part of the handler's idempotency key |
## Raised by
- `ParameterKey.approve` — `apps/api-v1/src/modules/bim-ontology/core/domain/parameter-key/ParameterKey.ts`
- `ApproveParameterKeyUseCase.approveParameterKey` — `apps/api-v1/src/modules/bim-ontology/core/application/use-cases/ParameterKeyAuthoringUseCases.ts`
- HTTP: [approveParameterKey](/docs/services/api-v1/openapi/operations/approveParameterKey)
## Consumers
`ParameterKeyApprovedProjectionHandler` (`adapters/inbound/events/`), registered in `bim-ontology.composition.ts`, calls `EnsureRevitProjectionForParameterKeyUseCase.ensureRevitProjectionForParameterKey`. The ensure path:
- skips keys without `usage:bim_parameter`, so catalog-only keys never reach Revit;
- reuses an existing archived projection instead of minting a second one, preserving the shared parameter GUID;
- otherwise creates a `draft` projection — built-in or shared, per `CommonParameterProjectionPolicy` on the key's handle.
The handler is idempotent on `parameterKeyId + approvedAt` and records failures through `ForRecordingBimOntologyHandlerFailure` before rethrowing.
**Approval is the only trigger.** There is no registration for `ParameterKeyCreated` or `ParameterKeyActivated` anywhere in the module.
## Related
- [ParameterKey](/docs/entities/ParameterKey), [RevitParameterProjection](/docs/entities/RevitParameterProjection)
- [RevitParameterProjectionCreated](/docs/events/RevitParameterProjectionCreated), [RevitParameterProjectionReused](/docs/events/RevitParameterProjectionReused)
- [ParameterKeyUpdated](/docs/events/ParameterKeyUpdated) — the repair path for a missing projection
---
id: ParameterKeyArchived
name: Parameter Key Archived
version: 1.0.0
owners:
- buildplan
summary: A ParameterKey was archived; the linked Revit projection is archived with it, never deleted.
badges:
- content: "Status: Implemented in api-v1"
backgroundColor: yellow
textColor: yellow
---
## Purpose
Retires a key from `active` or `approved`. Archived has no forward transition — only `reinstate()` returns it to `draft`. The consequence that matters downstream is that the key's Revit projection is archived too, so the shared parameter GUID survives and can be reused if the key comes back.
## Payload
Envelope is `{ name, occurredAt, payload }` (`apps/api-v1/src/modules/shared/domain/events/DomainEvent.ts`).
| Field | Type | Meaning |
| --- | --- | --- |
| `parameterKeyId` | `ParameterKeyId` | Key that was archived |
| `archivedAt` | `Date` | When archival happened; part of the handler's idempotency key |
The smallest payload in the module — the handler re-reads whatever else it needs.
## Raised by
- `ParameterKey.archive` — `apps/api-v1/src/modules/bim-ontology/core/domain/parameter-key/ParameterKey.ts`. Idempotent: already-archived emits nothing
- `ArchiveParameterKeyUseCase.archiveParameterKey` — `apps/api-v1/src/modules/bim-ontology/core/application/use-cases/ParameterKeyAuthoringUseCases.ts`
- HTTP: [archiveParameterKey](/docs/services/api-v1/openapi/operations/archiveParameterKey)
## Consumers
`ArchiveRevitProjectionOnParameterKeyRetirementHandler` (`adapters/inbound/events/`), registered in `bim-ontology.composition.ts`, calls `ArchiveRevitParameterProjectionUseCase`. For this event the archive decision is unconditional — unlike the `ParameterKeyUpdated` path, the handler does not re-read the key's status first. It is a no-op when no projection is linked or the projection is already archived.
The same handler instance is registered for [ParameterKeyUpdated](/docs/events/ParameterKeyUpdated), where the decision *is* conditional.
## Related
- [ParameterKey](/docs/entities/ParameterKey), [RevitParameterProjection](/docs/entities/RevitParameterProjection)
- [RevitParameterProjectionArchived](/docs/events/RevitParameterProjectionArchived)
- [RevitParameterProjectionReused](/docs/events/RevitParameterProjectionReused) — how the archived projection comes back
---
id: ParameterKeyCreated
name: Parameter Key Created
version: 1.0.0
owners:
- buildplan
summary: A new canonical ParameterKey was authored in `draft`.
badges:
- content: "Status: Implemented in api-v1"
backgroundColor: yellow
textColor: yellow
---
## Purpose
Records that a new canonical parameter concept now exists in the dictionary. A created key is in `draft`: it is not displayable, not vetted, and has no Revit expression. Nothing downstream reacts to creation — the Revit projection waits for approval.
## Payload
Envelope is `{ name, occurredAt, payload }` (`apps/api-v1/src/modules/shared/domain/events/DomainEvent.ts`).
| Field | Type | Meaning |
| --- | --- | --- |
| `parameterKeyId` | `ParameterKeyId` | Stable `domain_id` of the new key |
| `handle` | `string` | Normalized slug used for dictionary lookup |
| `name` | `string` | Canonical human-readable label |
| `createdAt` | `Date` | When the key was created |
## Raised by
- `ParameterKey.create` — `apps/api-v1/src/modules/bim-ontology/core/domain/parameter-key/ParameterKey.ts`
- `CreateParameterKeyUseCase.createParameterKey` — `apps/api-v1/src/modules/bim-ontology/core/application/use-cases/ParameterKeyAuthoringUseCases.ts`
- HTTP: [createParameterKey](/docs/services/api-v1/openapi/operations/createParameterKey)
## Consumers
**No subscriber in this module.** `bim-ontology.composition.ts` registers no handler for `ParameterKeyCreated`; the only lifecycle events it subscribes to are `ParameterKeyUpdated`, `ParameterKeyApproved`, and `ParameterKeyArchived`. An older design that minted a Revit projection earlier in the lifecycle has been removed.
The event is still dispatched and picked up by the `'*'` `DomainEventAuditLogger`.
## Related
- [ParameterKey](/docs/entities/ParameterKey)
- [ParameterKeyActivated](/docs/events/ParameterKeyActivated) — the next step out of `draft`
- [ParameterKeyApproved](/docs/events/ParameterKeyApproved) — the event that actually triggers the Revit projection
---
id: ParameterKeyDeleted
name: Parameter Key Deleted
version: 1.0.0
owners:
- buildplan
summary: A ParameterKey was hard-deleted. The event is an audit record, not a lifecycle transition.
badges:
- content: "Status: Implemented in api-v1"
backgroundColor: yellow
textColor: yellow
---
## Purpose
Records a hard delete for audit. Deliberately **not** a lifecycle transition: `markDeleted` is allowed from any status and does not touch `lifecycleStatus`. Removing the record is the use case's job, and the retirement path stewards should normally use is [ParameterKeyArchived](/docs/events/ParameterKeyArchived).
## Payload
Envelope is `{ name, occurredAt, payload }` (`apps/api-v1/src/modules/shared/domain/events/DomainEvent.ts`).
| Field | Type | Meaning |
| --- | --- | --- |
| `parameterKeyId` | `ParameterKeyId` | Key that was deleted |
| `handle` | `string` | Normalized slug, captured before removal |
| `name` | `string` | Canonical label, captured before removal |
| `deletedAt` | `Date` | When deletion happened |
## Raised by
- `ParameterKey.markDeleted` — `apps/api-v1/src/modules/bim-ontology/core/domain/parameter-key/ParameterKey.ts`
- `DeleteParameterKeyUseCase.deleteParameterKey` — `apps/api-v1/src/modules/bim-ontology/core/application/use-cases/ParameterKeyAuthoringUseCases.ts`. It refuses the delete with `ParameterKeyHasClassParameterAssignmentsError` while any [ClassParameter](/docs/entities/ClassParameter) assignment still references the key, and cascade-deletes the linked Revit projection directly (returned as `cascadedRevitProjectionId`) rather than going through an event
- HTTP: [deleteParameterKey](/docs/services/api-v1/openapi/operations/deleteParameterKey)
## Consumers
**No subscriber in this module.** `bim-ontology.composition.ts` registers no handler for `ParameterKeyDeleted`; the `'*'` `DomainEventAuditLogger` records it and nothing else reacts. The projection cascade is **inline in the use case by design** (delete the key ⇒ delete the projection). Archive-and-reuse is the path that preserves the shared-parameter GUID. Testers unassign ClassParameter rows first (409 while assignments exist), then Delete from `ui_parameter_key_actions`.
## Related
- [ParameterKey](/docs/entities/ParameterKey)
- [ParameterKeyArchived](/docs/events/ParameterKeyArchived) — the reversible alternative
---
id: ParameterKeyUpdated
name: Parameter Key Updated
version: 1.0.0
owners:
- buildplan
summary: Canonical metadata, usages, or the system flag on a ParameterKey changed. The busiest event in the module — three handlers listen to it.
badges:
- content: "Status: Implemented in api-v1"
backgroundColor: yellow
textColor: yellow
---
## Purpose
Announces that something canonical about a key changed: its label, its spec/control/group references, its usage set, or its `isSystem` flag. It is emitted only when a value actually changed — a no-op update dispatches nothing, which keeps Airtable "when record updated" automations from looping.
## Payload
Envelope is `{ name, occurredAt, payload }` (`apps/api-v1/src/modules/shared/domain/events/DomainEvent.ts`).
| Field | Type | Meaning |
| --- | --- | --- |
| `parameterKeyId` | `ParameterKeyId` | Key that changed |
| `name` | `string` | Canonical label after the change |
| `parameterSpecId` | `string \| null` | Value-semantics reference after the change |
| `parameterControlId` | `string \| null` | Presentation reference after the change |
| `parameterGroupId` | `string \| null` | Authoring-group reference after the change |
| `updatedAt` | `Date` | When the change was applied |
The payload deliberately carries **neither `lifecycleStatus` nor `usage`**, so every handler that needs either re-reads the key from `ForStoringParameterKeys` rather than trusting the event.
## Raised by
`apps/api-v1/src/modules/bim-ontology/core/domain/parameter-key/ParameterKey.ts`:
- `updateCanonicalMetadata` — rename, re-describe, re-reference, re-usage
- `addUsages` — unions usages onto the key, idempotently
- `markSystem` — sets `isSystem` (requires `approved` plus `usage:bim_parameter`)
Use cases in `core/application/use-cases/ParameterKeyAuthoringUseCases.ts`: `UpdateParameterKeyUseCase.updateParameterKey`, `AssignParameterKeyToGroupUseCase.assignParameterKeyToGroup`.
HTTP: [updateParameterKey](/docs/services/api-v1/openapi/operations/updateParameterKey), [assignParameterKeyToGroup](/docs/services/api-v1/openapi/operations/assignParameterKeyToGroup).
## Consumers
Three handlers are registered in `apps/api-v1/src/modules/bim-ontology/composition/bim-ontology.composition.ts`, all under `adapters/inbound/events/`:
| Handler | Effect |
| --- | --- |
| `ParameterKeyUpdatedProjectionEnsureHandler` | **Guarded.** Reloads the key and acts only when it is already `approved` *and* carries `usage:bim_parameter`; then calls `EnsureRevitProjectionForParameterKeyUseCase` to repair a missing projection. Anything else is recorded as handled and skipped |
| `ArchiveRevitProjectionOnParameterKeyRetirementHandler` | Reloads the key and archives the linked projection when it is no longer `approved` or has lost `usage:bim_parameter`. Archives, never deletes |
| `CanonicalProjectionMetadataChangedHandler` | Calls `ResyncDerivedProjectionMetadataUseCase.resyncForParameterKey`, refreshing derived (non-overridden) Revit names |
The first two are effectively complementary: ensure runs for approved BIM keys, archive-on-retirement runs for everything else. All three key their idempotency off the event's own identity, never off projection state.
## Related
- [ParameterKey](/docs/entities/ParameterKey), [RevitParameterProjection](/docs/entities/RevitParameterProjection)
- [ParameterKeyApproved](/docs/events/ParameterKeyApproved) — the event that mints the projection in the first place
- [ParameterKeyArchived](/docs/events/ParameterKeyArchived) — the unconditional retirement path
---
id: ProductFromUrlRequested
name: Product From URL Requested
version: 1.0.0
owners:
- buildplan
summary: A product creation workflow accepted a source URL.
badges:
- content: "Status: Implemented in api-v1"
backgroundColor: yellow
textColor: yellow
---
## Trigger
Emitted after `POST /product-from-url` validates enough request context to queue work.
## Payload Shape
`productModelId`, `sourceUrl`, `requestedBy`, `requestedAt`.
## Consumers
`api-v1` product creation workflow and scrape job orchestration.
## Related Operations
- [createProductFromUrlAlias](/docs/services/api-v1/openapi/operations/createProductFromUrlAlias)
- [createProductModelFromUrl](/docs/services/api-v1/openapi/operations/createProductModelFromUrl)
---
id: ProductModelCreated
name: Product Model Created
version: 1.0.0
owners:
- buildplan
summary: A ProductModel aggregate was materialized in the catalog.
badges:
- content: "Status: Implemented in api-v1"
backgroundColor: yellow
textColor: yellow
---
## Trigger
Emitted after scraped or manually supplied data creates a durable product model.
## Payload Shape
`productModelId`, `name`, `vendorId`, `objectCategoryId`, `createdAt`.
## Consumers
Product image publishing, catalog read projections, and future api-v2 migration consumers.
## Related Operations
- [createProductModelFromUrl](/docs/services/api-v1/openapi/operations/createProductModelFromUrl)
---
id: ProductModelImagesPublished
name: Product Model Images Published
version: 1.0.0
owners:
- buildplan
summary: Product image records were published to persistent media URLs.
badges:
- content: "Status: Implemented in api-v1"
backgroundColor: yellow
textColor: yellow
---
## Trigger
Emitted after the image publishing workflow writes persistent URL and attachment state.
## Payload Shape
`productModelId`, `imageIds`, `publishedAt`, `storageProvider`.
## Consumers
Catalog presentation, design workflows, and future media projections.
## Related Operations
- [publishProductImages](/docs/services/api-v1/openapi/operations/publishProductImages)
- [publishProductVariantImages](/docs/services/api-v1/openapi/operations/publishProductVariantImages)
---
id: ProductScrapeCompleted
name: Product Scrape Completed
version: 1.0.0
owners:
- buildplan
summary: A product scrape completed with catalog-ready scraped data.
badges:
- content: "Status: Implemented in api-v1"
backgroundColor: yellow
textColor: yellow
---
## Trigger
Emitted when the scrape pipeline returns a successful result for a create, refresh, or reload workflow.
## Payload Shape
`scrapeRunId`, `productModelId`, `sourceUrl`, `vendorKey`, `completedAt`.
## Consumers
`api-v1` persistence use cases materialize product models, variants, media, docs, and status updates.
## Related Operations
- [runProductScrape](/docs/services/api-v1/openapi/operations/runProductScrape)
- [refreshProductModels](/docs/services/api-v1/openapi/operations/refreshProductModels)
- [reloadProductModels](/docs/services/api-v1/openapi/operations/reloadProductModels)
---
id: ProductScrapeFailed
name: Product Scrape Failed
version: 1.0.0
owners:
- buildplan
summary: A product scrape ended in a recoverable or terminal failure.
badges:
- content: "Status: Implemented in api-v1"
backgroundColor: yellow
textColor: yellow
---
## Trigger
Emitted when the scrape pipeline cannot produce a catalog-ready result.
## Payload Shape
`scrapeRunId`, `productModelId`, `sourceUrl`, `reason`, `failedAt`.
## Consumers
`api-v1` status adapters and operator-facing Retool surfaces consume this to expose failure state.
## Related Operations
- [runProductScrape](/docs/services/api-v1/openapi/operations/runProductScrape)
---
id: RevitParameterProjectionApproved
name: Revit Parameter Projection Approved
version: 1.0.0
owners:
- buildplan
summary: A steward promoted a Revit projection from `draft` to `approved`.
badges:
- content: "Status: Implemented in api-v1"
backgroundColor: yellow
textColor: yellow
---
## Purpose
Records steward promotion of a [RevitParameterProjection](/docs/entities/RevitParameterProjection) after `RevitProjectionApprovePolicy` passes. Approval is idempotent when already approved and refused when archived — an archived projection must be reused back into `draft` first. Approve-time invariants that `draft` is allowed to violate (complete primary-usage facts, at least one enabled application for non-built-in definitions) are enforced here.
## Payload
Envelope is `{ name, occurredAt, payload }` (`apps/api-v1/src/modules/shared/domain/events/DomainEvent.ts`). All seven `RevitParameterProjection*` events share one payload type; `occurredAt` appears both on the envelope and in the payload.
| Field | Type | Meaning |
| --- | --- | --- |
| `projectionId` | `string` | Projection `domain_id` |
| `parameterKeyId` | `string` | The one canonical key this projection expresses |
| `occurredAt` | `Date` | When approval happened |
## Raised by
- `RevitParameterProjection.approve` — `apps/api-v1/src/modules/bim-ontology/core/domain/revit/RevitParameterProjection.ts`
- `ApproveRevitParameterProjectionUseCase` — `apps/api-v1/src/modules/bim-ontology/core/application/use-cases/ApproveRevitParameterProjectionUseCase.ts`
- HTTP: [approveRevitParameterProjection](/docs/services/api-v1/openapi/operations/approveRevitParameterProjection)
## Consumers
**No subscriber in this module.** No handler is registered for any of the seven projection events in `bim-ontology.composition.ts`; only the `'*'` `DomainEventAuditLogger` sees them. Nothing is triggered by projection approval — the shared parameter file is generated on demand.
## Related
- [RevitParameterProjection](/docs/entities/RevitParameterProjection)
- [RevitParameterProjectionDemoted](/docs/events/RevitParameterProjectionDemoted) — the reverse transition
- [ObjectClass](/docs/entities/ObjectClass) — a class that lost its classification blocks projection approval
---
id: RevitParameterProjectionArchived
name: Revit Parameter Projection Archived
version: 1.0.0
owners:
- buildplan
summary: A Revit projection was archived and is retained for history — never deleted.
badges:
- content: "Status: Implemented in api-v1"
backgroundColor: yellow
textColor: yellow
---
## Purpose
Retires a [RevitParameterProjection](/docs/entities/RevitParameterProjection) while keeping the row. Archiving rather than deleting is deliberate: the shared parameter GUID survives so the projection can be reused if the key returns. An archived projection rejects edits and approval, and archiving is idempotent.
## Payload
Envelope is `{ name, occurredAt, payload }` (`apps/api-v1/src/modules/shared/domain/events/DomainEvent.ts`). All seven `RevitParameterProjection*` events share one payload type; `occurredAt` appears both on the envelope and in the payload.
| Field | Type | Meaning |
| --- | --- | --- |
| `projectionId` | `string` | Projection `domain_id` |
| `parameterKeyId` | `string` | The one canonical key this projection expresses |
| `occurredAt` | `Date` | When archival happened |
## Raised by
- `RevitParameterProjection.archive` — `apps/api-v1/src/modules/bim-ontology/core/domain/revit/RevitParameterProjection.ts`
- `ArchiveRevitParameterProjectionUseCase` — `apps/api-v1/src/modules/bim-ontology/core/application/use-cases/RevitProjectionAuthoringUseCases.ts`, reached either from the operation below or automatically from `ArchiveRevitProjectionOnParameterKeyRetirementHandler` on [ParameterKeyArchived](/docs/events/ParameterKeyArchived) and [ParameterKeyUpdated](/docs/events/ParameterKeyUpdated)
- HTTP: [archiveRevitParameterProjection](/docs/services/api-v1/openapi/operations/archiveRevitParameterProjection)
## Consumers
**No subscriber in this module.** No handler is registered for any of the seven projection events in `bim-ontology.composition.ts`; only the `'*'` `DomainEventAuditLogger` sees them. Note the direction: key retirement causes projection archival, not the other way round.
## Related
- [RevitParameterProjection](/docs/entities/RevitParameterProjection), [ParameterKey](/docs/entities/ParameterKey)
- [RevitParameterProjectionReused](/docs/events/RevitParameterProjectionReused), [ParameterKeyArchived](/docs/events/ParameterKeyArchived)
---
id: RevitParameterProjectionCreated
name: Revit Parameter Projection Created
version: 1.0.0
owners:
- buildplan
summary: A Revit-facing projection of an approved ParameterKey was created, normally in `draft`.
badges:
- content: "Status: Implemented in api-v1"
backgroundColor: yellow
textColor: yellow
---
## Purpose
Records the birth of a [RevitParameterProjection](/docs/entities/RevitParameterProjection) — one Revit expression of exactly one canonical [ParameterKey](/docs/entities/ParameterKey). Most projections are created automatically in `draft` by the ensure path after [ParameterKeyApproved](/docs/events/ParameterKeyApproved), then completed and approved by a steward.
## Payload
Envelope is `{ name, occurredAt, payload }` (`apps/api-v1/src/modules/shared/domain/events/DomainEvent.ts`). All seven `RevitParameterProjection*` events share one payload type; `occurredAt` appears both on the envelope and in the payload.
| Field | Type | Meaning |
| --- | --- | --- |
| `projectionId` | `string` | Projection `domain_id` |
| `parameterKeyId` | `string` | The one canonical key this projection expresses |
| `occurredAt` | `Date` | When the change happened |
## Raised by
- `RevitParameterProjection.create` — `apps/api-v1/src/modules/bim-ontology/core/domain/revit/RevitParameterProjection.ts`, only when a clock is supplied
- `CreateRevitParameterProjectionUseCase` — `apps/api-v1/src/modules/bim-ontology/core/application/use-cases/RevitProjectionAuthoringUseCases.ts`. `EnsureRevitProjectionForParameterKeyUseCase` reaches it through `ensureBuiltIn` / `ensureShared`, choosing the definition form from `CommonParameterProjectionPolicy` on the key's handle
- HTTP: [createRevitParameterProjection](/docs/services/api-v1/openapi/operations/createRevitParameterProjection)
## Consumers
**No subscriber in this module.** No handler is registered for any of the seven projection events in `bim-ontology.composition.ts`; only the `'*'` `DomainEventAuditLogger` sees them. Every automatic consequence around projections is driven by a `ParameterKey*` or `ParameterGroupChanged` event flowing *into* the projection side.
## Related
- [RevitParameterProjection](/docs/entities/RevitParameterProjection), [ParameterKey](/docs/entities/ParameterKey)
- [ParameterKeyApproved](/docs/events/ParameterKeyApproved) — what triggers the automatic create
- [RevitParameterProjectionApproved](/docs/events/RevitParameterProjectionApproved)
---
id: RevitParameterProjectionDemoted
name: Revit Parameter Projection Demoted
version: 1.0.0
owners:
- buildplan
summary: An approved projection returned to `draft` because its shared-parameter-file output changed.
badges:
- content: "Status: Implemented in api-v1"
backgroundColor: yellow
textColor: yellow
---
## Purpose
Records `approved → draft`. Demotion is not a steward action with its own endpoint — it is an automatic consequence of an edit or a metadata resync that would change what the projection contributes to the shared parameter file. Re-review is required before it is approved again. It is a no-op when already `draft` and refused when archived.
## Payload
Envelope is `{ name, occurredAt, payload }` (`apps/api-v1/src/modules/shared/domain/events/DomainEvent.ts`). All seven `RevitParameterProjection*` events share one payload type; `occurredAt` appears both on the envelope and in the payload.
| Field | Type | Meaning |
| --- | --- | --- |
| `projectionId` | `string` | Projection `domain_id` |
| `parameterKeyId` | `string` | The one canonical key this projection expresses |
| `occurredAt` | `Date` | When demotion happened |
## Raised by
- `RevitParameterProjection.demoteToDraft` — `apps/api-v1/src/modules/bim-ontology/core/domain/revit/RevitParameterProjection.ts`
- The `demoteApprovedIfSpfOutputChanged` helper (`core/application/use-cases/demoteApprovedIfSpfOutputChanged.ts`), called from `UpdateRevitParameterProjectionUseCase` (`RevitProjectionAuthoringUseCases.ts`) and from `ResyncDerivedProjectionMetadataUseCase` (`RevitProjectionDerivationUseCases.ts`)
- `DemoteRevitProjectionsCoveringObjectClassUseCase` after [ObjectClassDemoted](/docs/events/ObjectClassDemoted) — covering approved projections return to `draft` for re-review
- No operation demotes directly. It rides along with [updateRevitParameterProjection](/docs/services/api-v1/openapi/operations/updateRevitParameterProjection), the resync operations, a [ParameterGroupChanged](/docs/events/ParameterGroupChanged) / [ParameterKeyUpdated](/docs/events/ParameterKeyUpdated) resync, or an ObjectClass demotion cascade
## Consumers
**No subscriber in this module.** No handler is registered for any of the seven projection events in `bim-ontology.composition.ts`; only the `'*'` `DomainEventAuditLogger` sees them.
## Related
- [RevitParameterProjection](/docs/entities/RevitParameterProjection)
- [RevitParameterProjectionApproved](/docs/events/RevitParameterProjectionApproved), [RevitParameterProjectionResynced](/docs/events/RevitParameterProjectionResynced)
---
id: RevitParameterProjectionResynced
name: Revit Parameter Projection Resynced
version: 1.0.0
owners:
- buildplan
summary: A projection's derived Revit name was refreshed after the canonical key or group changed.
badges:
- content: "Status: Implemented in api-v1"
backgroundColor: yellow
textColor: yellow
---
## Purpose
Records that the non-authoritative stored Revit name was recomputed from canonical metadata. Authored facts and shared-definition identity are untouched. The resync is skipped — and no event is emitted — when `nameSource` is `override` or when the derived name already matches, so this event means the stored name genuinely changed.
## Payload
Envelope is `{ name, occurredAt, payload }` (`apps/api-v1/src/modules/shared/domain/events/DomainEvent.ts`). All seven `RevitParameterProjection*` events share one payload type; `occurredAt` appears both on the envelope and in the payload.
| Field | Type | Meaning |
| --- | --- | --- |
| `projectionId` | `string` | Projection `domain_id` |
| `parameterKeyId` | `string` | The one canonical key this projection expresses |
| `occurredAt` | `Date` | When the resync happened |
## Raised by
- `RevitParameterProjection.resyncDerivedName` — `apps/api-v1/src/modules/bim-ontology/core/domain/revit/RevitParameterProjection.ts`
- `ResyncDerivedProjectionMetadataUseCase` — `apps/api-v1/src/modules/bim-ontology/core/application/use-cases/RevitProjectionDerivationUseCases.ts`, invoked either by the operations below or automatically by `CanonicalProjectionMetadataChangedHandler` on [ParameterKeyUpdated](/docs/events/ParameterKeyUpdated) and [ParameterGroupChanged](/docs/events/ParameterGroupChanged)
- HTTP: [resyncDerivedRevitProjectionMetadata](/docs/services/api-v1/openapi/operations/resyncDerivedRevitProjectionMetadata), [resyncRevitProjectionsForParameterKey](/docs/services/api-v1/openapi/operations/resyncRevitProjectionsForParameterKey), [resyncRevitProjectionsForParameterGroup](/docs/services/api-v1/openapi/operations/resyncRevitProjectionsForParameterGroup)
## Consumers
**No subscriber in this module.** No handler is registered for any of the seven projection events in `bim-ontology.composition.ts`; only the `'*'` `DomainEventAuditLogger` sees them.
The resync itself can have a second effect in the same use case: if the new derived name changes shared-parameter-file output on an approved projection, it is demoted to `draft`.
## Related
- [RevitParameterProjection](/docs/entities/RevitParameterProjection), [ParameterGroup](/docs/entities/ParameterGroup)
- [RevitParameterProjectionDemoted](/docs/events/RevitParameterProjectionDemoted)
- [listRevitProjectionDerivationDrift](/docs/services/api-v1/openapi/operations/listRevitProjectionDerivationDrift) — projections whose stored name has drifted from the derived one
---
id: RevitParameterProjectionReused
name: Revit Parameter Projection Reused
version: 1.0.0
owners:
- buildplan
summary: An archived projection was restored to `draft`, keeping its identity and shared parameter GUID.
badges:
- content: "Status: Implemented in api-v1"
backgroundColor: yellow
textColor: yellow
---
## Purpose
The payoff of archiving instead of deleting. When a [ParameterKey](/docs/entities/ParameterKey) is approved again and an archived projection already exists for it, the ensure path restores that row rather than minting a new one — same `projectionId`, same shared parameter GUID, so Revit models that already reference the parameter stay valid. A no-op when the projection is not archived.
## Payload
Envelope is `{ name, occurredAt, payload }` (`apps/api-v1/src/modules/shared/domain/events/DomainEvent.ts`). All seven `RevitParameterProjection*` events share one payload type; `occurredAt` appears both on the envelope and in the payload.
| Field | Type | Meaning |
| --- | --- | --- |
| `projectionId` | `string` | Projection `domain_id` — unchanged by reuse |
| `parameterKeyId` | `string` | The one canonical key this projection expresses |
| `occurredAt` | `Date` | When the projection was restored |
## Raised by
- `RevitParameterProjection.reactivateFromArchive` — `apps/api-v1/src/modules/bim-ontology/core/domain/revit/RevitParameterProjection.ts`
- The `reuseArchivedProjection` helper inside `EnsureRevitProjectionForParameterKeyUseCase` — `apps/api-v1/src/modules/bim-ontology/core/application/use-cases/RevitProjectionAuthoringUseCases.ts`. Reuse requires the key to be `approved` and to carry `usage:bim_parameter`
- No operation reuses directly. It is reached through the ensure path fired by [ParameterKeyApproved](/docs/events/ParameterKeyApproved) or [ParameterKeyUpdated](/docs/events/ParameterKeyUpdated)
## Consumers
**No subscriber in this module.** No handler is registered for any of the seven projection events in `bim-ontology.composition.ts`; only the `'*'` `DomainEventAuditLogger` sees them.
## Related
- [RevitParameterProjection](/docs/entities/RevitParameterProjection)
- [RevitParameterProjectionArchived](/docs/events/RevitParameterProjectionArchived), [ParameterKeyApproved](/docs/events/ParameterKeyApproved)
---
id: RevitParameterProjectionUpdated
name: Revit Parameter Projection Updated
version: 1.0.0
owners:
- buildplan
summary: Steward-authored Revit facts on a projection changed.
badges:
- content: "Status: Implemented in api-v1"
backgroundColor: yellow
textColor: yellow
---
## Purpose
Records a change to the explicitly authored Revit facts of a [RevitParameterProjection](/docs/entities/RevitParameterProjection): definition, name and name source, family application, project binding, binding-kind and primary-usage preferences, spec type, storage type, visibility flags, discipline, tooltip. Archived projections are retained for history and reject edits, and a shared-definition GUID can never be removed or replaced.
## Payload
Envelope is `{ name, occurredAt, payload }` (`apps/api-v1/src/modules/shared/domain/events/DomainEvent.ts`). All seven `RevitParameterProjection*` events share one payload type; `occurredAt` appears both on the envelope and in the payload.
| Field | Type | Meaning |
| --- | --- | --- |
| `projectionId` | `string` | Projection `domain_id` |
| `parameterKeyId` | `string` | The one canonical key this projection expresses |
| `occurredAt` | `Date` | When the change happened |
The payload does not name the changed fields — read the projection to see the new state.
## Raised by
- `RevitParameterProjection.updateAuthoredFacts` — `apps/api-v1/src/modules/bim-ontology/core/domain/revit/RevitParameterProjection.ts`
- `UpdateRevitParameterProjectionUseCase` — `apps/api-v1/src/modules/bim-ontology/core/application/use-cases/RevitProjectionAuthoringUseCases.ts`. The same use case then runs `demoteApprovedIfSpfOutputChanged`, so an edit to an approved projection can also raise [RevitParameterProjectionDemoted](/docs/events/RevitParameterProjectionDemoted)
- HTTP: [updateRevitParameterProjection](/docs/services/api-v1/openapi/operations/updateRevitParameterProjection)
## Consumers
**No subscriber in this module.** No handler is registered for any of the seven projection events in `bim-ontology.composition.ts`; only the `'*'` `DomainEventAuditLogger` sees them.
## Related
- [RevitParameterProjection](/docs/entities/RevitParameterProjection)
- [RevitParameterProjectionDemoted](/docs/events/RevitParameterProjectionDemoted), [RevitParameterProjectionResynced](/docs/events/RevitParameterProjectionResynced)
---
id: ScrapeRunCompleted
name: Scrape Run Completed
version: 1.0.0
owners:
- buildplan
summary: A shared scraping run completed successfully.
badges:
- content: "Status: Implemented in api-v1"
backgroundColor: yellow
textColor: yellow
---
## Trigger
Emitted by shared-scraping after provider extraction returns normalized scrape data.
## Payload Shape
`scrapeRunId`, `sourceUrl`, `strategy`, `completedAt`.
## Consumers
`api-v1` product-marketplace workflows that transform scrape data into catalog records.
---
id: ScrapeRunFailed
name: Scrape Run Failed
version: 1.0.0
owners:
- buildplan
summary: A shared scraping run failed.
badges:
- content: "Status: Implemented in api-v1"
backgroundColor: yellow
textColor: yellow
---
## Trigger
Emitted by shared-scraping when provider, hydration, or extraction fails.
## Payload Shape
`scrapeRunId`, `sourceUrl`, `strategy`, `reason`, `failedAt`.
## Consumers
`api-v1` status adapters and retry/governor policies.
---
id: AgentQLExtractionRequested
name: AgentQL Extraction Requested
version: 1.0.0
owners:
- buildplan
summary: Request LLM-assisted extraction from a hydrated page.
badges:
- content: "Status: Implemented in api-v1"
backgroundColor: yellow
textColor: yellow
---
## Trigger
Sent by shared-scraping for Type B or hybrid product scrapers.
## Payload Shape
`sourceUrl`, `query`, `context`, `schemaHint`.
## Consumers
AgentQL API.
---
id: BrightDataPageFetchRequested
name: BrightData Page Fetch Requested
version: 1.0.0
owners:
- buildplan
summary: Request a BrightData-backed page fetch.
badges:
- content: "Status: Implemented in api-v1"
backgroundColor: yellow
textColor: yellow
---
## Trigger
Sent by shared-scraping when Web Unlocker or browser proxying is required.
## Payload Shape
`sourceUrl`, `proxyZone`, `browserMode`, `timeoutMs`.
## Consumers
BrightData Web Unlocker / browser infrastructure.
---
id: ExecuteScrapingPlan
name: Execute Scraping Plan
version: 1.0.0
owners:
- buildplan
summary: Run the selected scraping plan for a product source URL.
badges:
- content: "Status: Implemented in api-v1"
backgroundColor: yellow
textColor: yellow
---
## Trigger
Sent by api-v1 product creation, refresh, and reload workflows.
## Payload Shape
`scrapeRunId`, `sourceUrl`, `vendorKey`, `scope`, `capabilities`.
## Consumers
`shared-scraping` orchestration and outbound provider adapters.
---
id: FetchPage
name: Fetch Page
version: 1.0.0
owners:
- buildplan
summary: Fetch and hydrate a source page for scraping.
badges:
- content: "Status: Implemented in api-v1"
backgroundColor: yellow
textColor: yellow
---
## Trigger
Sent by shared-scraping orchestration when a deterministic or hybrid scraper needs page content.
## Payload Shape
`sourceUrl`, `strategy`, `timeoutMs`, `proxyRequired`.
## Consumers
BrightData and Playwright-backed page fetch adapters.
---
id: PersistPartyToAirtable
name: Persist Party To Airtable
version: 1.0.0
owners:
- buildplan
summary: Write party-directory records to Airtable.
badges:
- content: "Status: Implemented in api-v1"
backgroundColor: yellow
textColor: yellow
---
## Trigger
Sent by party-directory outbound persistence adapters.
## Payload Shape
`partyId`, `tableName`, `fields`, `correlationId`.
## Consumers
Airtable base and table APIs.
---
id: PublishCatalogToMedusa
name: Publish Catalog To Medusa
version: 1.0.0
owners:
- buildplan
summary: Publish selected product catalog data to the commerce engine.
badges:
- content: "Status: Implemented in api-v1"
backgroundColor: yellow
textColor: yellow
---
## Trigger
Sent by api-v1 when catalog entries need commerce availability.
## Payload Shape
`productModelId`, `variantIds`, `publishMode`, `correlationId`.
## Consumers
Medusa commerce backend.
---
id: ResolveManufacturerOrganization
name: Resolve Manufacturer Organization
version: 1.0.0
owners:
- buildplan
summary: Resolve or select the party-directory organization for a product manufacturer.
badges:
- content: "Status: Implemented in api-v1"
backgroundColor: yellow
textColor: yellow
---
## Trigger
Sent by api-v1 product scraping workflows when vendor/manufacturer identity must be normalized.
## Payload Shape
`sourceName`, `websiteUrl`, `sourceSystem`, optional `externalIds`.
## Consumers
`party-directory` lookup and matching use cases.
---
id: SendTransactionalEmail
name: Send Transactional Email
version: 1.0.0
owners:
- buildplan
summary: Send a transactional email from api-v1.
badges:
- content: "Status: Implemented in api-v1"
backgroundColor: yellow
textColor: yellow
---
## Trigger
Sent by api-v1 workflows that need email delivery.
## Payload Shape
`templateId`, `recipient`, `dynamicData`, `correlationId`.
## Consumers
Sendgrid Mail Send API.
---
id: SyncCatalogToAirtable
name: Sync Catalog To Airtable
version: 1.0.0
owners:
- buildplan
summary: Write product-marketplace catalog records to Airtable.
badges:
- content: "Status: Implemented in api-v1"
backgroundColor: yellow
textColor: yellow
---
## Trigger
Sent by api-v1 persistence adapters.
## Payload Shape
`tableName`, `records`, `operation`, `correlationId`.
## Consumers
Airtable base and table APIs.
---
id: UpsertOrganizationFromScrape
name: Upsert Organization From Scrape
version: 1.0.0
owners:
- buildplan
summary: Create or update an organization discovered during a product scrape.
badges:
- content: "Status: Implemented in api-v1"
backgroundColor: yellow
textColor: yellow
---
## Trigger
Sent by api-v1 after scrape extraction identifies manufacturer or brand organization data.
## Payload Shape
`name`, `websiteUrl`, `emails`, `phones`, `sourceUrl`.
## Consumers
`party-directory` persistence adapters and Airtable organization projections.
---
id: LookupOrganizationByDomain
name: Lookup Organization By Domain
version: 1.0.0
owners:
- buildplan
summary: Find an existing organization by website domain.
badges:
- content: "Status: Implemented in api-v1"
backgroundColor: yellow
textColor: yellow
---
## Trigger
Sent by api-v1 before creating a manufacturer organization from scraped product data.
## Payload Shape
`domain`, optional `organizationKind`.
## Consumers
`party-directory` read model.
---
id: PullAirtableSchema
name: Pull Airtable Schema
version: 1.0.0
owners:
- buildplan
summary: Read Airtable schema metadata.
badges:
- content: "Status: Implemented in api-v1"
backgroundColor: yellow
textColor: yellow
---
## Trigger
Sent by documentation and migration tooling when schema discovery is needed.
## Payload Shape
`baseId`, optional `tableIds`.
## Consumers
Airtable schema API.
---
id: RouteByVendor
name: Route By Vendor
version: 1.0.0
owners:
- buildplan
summary: Select a scraping strategy based on vendor or source URL.
badges:
- content: "Status: Implemented in api-v1"
backgroundColor: yellow
textColor: yellow
---
## Trigger
Sent by api-v1 before executing a product scrape.
## Payload Shape
`sourceUrl`, optional `vendorKey`, requested `scrapeScope`.
## Consumers
`shared-scraping` routing policy.
---
id: AgentQL
name: AgentQL
version: 1.0.0
owners:
- buildplan
summary: External LLM-assisted extraction provider used by shared-scraping.
externalSystem: true
receives:
- id: AgentQLExtractionRequested
version: 1.0.0
sends:
- id: AgentQLExtractionCompleted
version: 1.0.0
badges:
- content: External system
backgroundColor: gray
textColor: gray
---
## Role
AgentQL extracts structured product data when deterministic selectors are insufficient or a hybrid scrape needs AI assistance.
## Buildplan Integration Points
Shared-scraping owns AgentQL queries and validation. Product-marketplace consumes normalized scrape outcomes.
## Operational Notes
Rate limits, model behavior, and provider credentials stay behind shared-scraping outbound adapters.
---
id: Airtable
name: Airtable
version: 1.0.0
owners:
- buildplan
summary: External Airtable bases backing api-v1 product-marketplace and party-directory persistence.
externalSystem: true
receives:
- id: SyncCatalogToAirtable
version: 1.0.0
- id: PersistPartyToAirtable
version: 1.0.0
- id: PullAirtableSchema
version: 1.0.0
badges:
- content: External system
backgroundColor: gray
textColor: gray
---
## Role
Airtable is the current persistence substrate for api-v1 product-marketplace and selected shared party-directory records.
## Buildplan Integration Points
api-v1 writes product catalog records. party-directory writes canonical party records. Schema export is deferred to a future Azimutt/Airtable documentation round.
## Operational Notes
Base ids and tokens are environment-specific and should not appear in catalog content.
---
id: airtable-front-end
name: airtable-front-end
version: 1.0.0
owners:
- buildplan
summary: |
In-Airtable steward UI scripts (`apps/airtable-front-end`). Not a hosted process.
badges:
- content: Airtable steward UI scripts
backgroundColor: purple
textColor: purple
- content: "Status: Implemented in Airtable scripts"
backgroundColor: yellow
textColor: yellow
---
## Overview
JavaScript that runs inside **Airtable Interface automations**. Scripts call api-v1 HTTP endpoints; business logic stays in the backend. Source: `apps/airtable-front-end/current//`.
This is **not** a hosted Node process. Paste-deploy into the Airtable script editor. Mutation patterns: AT-A1, AT-A2, AT-B1, AT-C1 on the [platform language](/docs/domains/platform/language) page.
---
id: api-v1
name: api-v1 (current)
version: 1.0.0
owners:
- buildplan
summary: |
Active Airtable-backed backend for MVP and product workflows. Primary HTTP
surface for current development; OpenAPI contracts, domain discovery, and
longer-term migration context.
badges:
- content: Current
backgroundColor: blue
textColor: blue
- content: Deployable
backgroundColor: blue
textColor: blue
- content: "Status: Implemented in api-v1"
backgroundColor: yellow
textColor: yellow
specifications:
- type: openapi
path: openapi.json
name: Buildplan API v1
sends:
- id: ProductFromUrlRequested
version: 1.0.0
- id: ProductScrapeCompleted
version: 1.0.0
- id: ProductScrapeFailed
version: 1.0.0
- id: ProductModelCreated
version: 1.0.0
- id: ProductModelImagesPublished
version: 1.0.0
- id: ObjectClassParentGraphChanged
version: 1.0.0
- id: ObjectClassDemoted
version: 1.0.0
- id: ParameterKeyCreated
version: 1.0.0
- id: ParameterKeyUpdated
version: 1.0.0
- id: ParameterKeyActivated
version: 1.0.0
- id: ParameterKeyApproved
version: 1.0.0
- id: ParameterKeyArchived
version: 1.0.0
- id: ParameterKeyDeleted
version: 1.0.0
- id: ParameterGroupChanged
version: 1.0.0
- id: ClassParameterAssigned
version: 1.0.0
- id: ClassParameterAssignmentUpdated
version: 1.0.0
- id: ClassParameterAllowedValuesReplaced
version: 1.0.0
- id: ClassParameterUnassigned
version: 1.0.0
- id: RevitParameterProjectionCreated
version: 1.0.0
- id: RevitParameterProjectionUpdated
version: 1.0.0
- id: RevitParameterProjectionApproved
version: 1.0.0
- id: RevitParameterProjectionDemoted
version: 1.0.0
- id: RevitParameterProjectionReused
version: 1.0.0
- id: RevitParameterProjectionArchived
version: 1.0.0
- id: RevitParameterProjectionResynced
version: 1.0.0
- id: ExecuteScrapingPlan
version: 1.0.0
- id: FetchPage
version: 1.0.0
- id: RouteByVendor
version: 1.0.0
- id: ResolveManufacturerOrganization
version: 1.0.0
- id: UpsertOrganizationFromScrape
version: 1.0.0
- id: LookupOrganizationByDomain
version: 1.0.0
- id: SendTransactionalEmail
version: 1.0.0
- id: SyncCatalogToAirtable
version: 1.0.0
- id: PublishCatalogToMedusa
version: 1.0.0
receives:
- id: ScrapeRunCompleted
version: 1.0.0
- id: ScrapeRunFailed
version: 1.0.0
- id: OrganizationCreated
version: 1.0.0
- id: OrganizationUpdated
version: 1.0.0
- id: MedusaStripeWebhookReceived
version: 1.0.0
entities:
- id: ProductModel
version: 1.0.0
- id: ProductVariant
version: 1.0.0
- id: ModelParameter
version: 1.0.0
- id: ModelParameterAllowedValue
version: 1.0.0
- id: VariantParameter
version: 1.0.0
- id: PriceGroup
version: 1.0.0
- id: VendorProductCollection
version: 1.0.0
- id: CadAsset
version: 1.0.0
- id: Inventory
version: 1.0.0
- id: Material
version: 1.0.0
- id: ParameterValue
version: 1.0.0
- id: ParameterValueGroup
version: 1.0.0
- id: ObjectClass
version: 1.0.0
- id: ParameterKey
version: 1.0.0
- id: ClassParameter
version: 1.0.0
- id: ParameterGroup
version: 1.0.0
- id: RevitParameterProjection
version: 1.0.0
- id: ProductDescription
version: 1.0.0
- id: ProductDoc
version: 1.0.0
- id: ProductImage
version: 1.0.0
---
## Overview
This service is the **active** Airtable-backed backend for MVP work. See [Backend Platform](/docs/systems/backend-platform) for the api-v1 vs api-v2 rules.
The OpenAPI tab on this page renders the spec generated from Zod schemas in `apps/api-v1`. Stable published JSON: [/openapi/api-v1.json](/openapi/api-v1.json).
## Migration Status
- Domain behavior here is active; prefer extending api-v1 for MVP features.
- Migration to `api-v2` is planned after the MVP — do not treat this service as deprecated.
- Use this page for current implementation, HTTP contracts, and migration context.
- Do not duplicate detailed endpoint schemas in EventCatalog; use generated OpenAPI instead.
- Document migration-relevant behavior using the status labels in `CONVENTIONS.md`.
- Product Marketplace migration overview: [Product Marketplace Migration](/docs/domains/product-marketplace/migration).
## Domains
- [Product Marketplace](/docs/domains/product-marketplace)
## Raw Schema:openapi.json
{
"openapi": "3.1.0",
"info": {
"title": "Buildplan API v1",
"version": "1.0.0",
"description": "Formal HTTP API contract for the Airtable-backed Buildplan API v1. Generated from Zod route and DTO schemas; architecture context lives in EventCatalog."
},
"servers": [
{
"url": "/",
"description": "Current host"
},
{
"url": "https://api.mybuildplan.ai",
"description": "Production"
}
],
"tags": [
{
"name": "system-maintenance",
"description": "Admin and repair operations. Not part of the steward workflow. For Revit projections the normal path is: approved ParameterKey → auto-created draft → steward PATCH → approve."
}
],
"paths": {
"/api/v1/bim-ontology/object-classes": {
"get": {
"operationId": "listObjectClasses",
"tags": [
"BIM Ontology / Object Classes"
],
"summary": "List object classes for offline BIM classification",
"description": "Returns the ObjectClass single-parent tree (ADR-0046) for cacheable Revit classification. Public identities are domain_id values: items[].id and parentId never contain Airtable record IDs. Query view=flat|tree (default flat) controls representation only; root and maxDepth select the node set. root accepts domain_id or handle. maxDepth is relative to the selected root (or to ontology roots when root is omitted). Each item exposes parentId (string|null) and hasChildren (true when children exist in the full filtered graph, even if maxDepth truncates them). Tree view nests children[] recursively with deterministic id ordering. items contain no timestamps; generatedAt and ontologyVersion live outside items so clients can hash items deterministically. revitCategory is a singular 1:1 binding object (or null) with builtinName, builtinCategoryInt, and status. When status is not current, builtinName and builtinCategoryInt are null. This ObjectClass binding is separate from parameter-projection category scope (RevitCategoryScopeResolver / category-scope/resolve). When parent cycles or other graph corruption exist, the list still returns 200 with healthy classes in items[] and excluded classes named in diagnostics[].",
"security": [
{
"apiKeyAuth": []
}
],
"x-domain": {
"appVersion": "api-v1",
"boundedContext": "bim-ontology",
"service": "api-v1",
"aggregate": "ObjectClass",
"useCase": "ListObjectClassesUseCase",
"domainEvents": [],
"domainErrors": [
"ObjectClassGraphNodeIdentityMissingError",
"ObjectClassNotFoundError"
]
},
"parameters": [
{
"in": "query",
"name": "view",
"schema": {
"type": "string",
"enum": [
"flat",
"tree"
],
"default": "flat"
},
"description": "Response representation only. flat (default) returns a sorted array; tree nests children[] recursively."
},
{
"in": "query",
"name": "root",
"schema": {
"type": "string",
"minLength": 1
},
"description": "Optional traversal start (domain_id or handle). When omitted, traversal begins at ontology roots."
},
{
"in": "query",
"name": "maxDepth",
"schema": {
"type": "integer",
"minimum": 0
},
"description": "Optional relative depth bound from root (or from ontology roots when root is omitted). 0 returns only the start node(s)."
},
{
"in": "query",
"name": "includeInactive",
"schema": {
"type": "boolean",
"default": false
},
"description": "When true, include archived object classes; otherwise only approved classes are returned."
}
],
"responses": {
"200": {
"description": "Deterministic object-class list (flat or tree) returned",
"headers": {
"ETag": {
"description": "Same opaque value as ontologyVersion; changes when the returned class payload changes.",
"schema": {
"type": "string"
}
}
},
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"ontologyVersion": {
"type": "string"
},
"generatedAt": {
"type": "string",
"format": "date-time",
"pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$"
},
"count": {
"type": "integer",
"minimum": 0,
"maximum": 9007199254740991
},
"items": {
"type": "array",
"items": {
"$ref": "#/components/schemas/__schema0"
}
},
"diagnostics": {
"description": "Corrupt or cyclic ObjectClasses excluded from items so cold-start can proceed.",
"type": "array",
"items": {
"type": "object",
"properties": {
"objectClassId": {
"type": "string"
},
"message": {
"type": "string"
}
},
"required": [
"objectClassId",
"message"
],
"additionalProperties": false
}
}
},
"required": [
"ontologyVersion",
"generatedAt",
"count",
"items"
],
"additionalProperties": false
}
}
}
},
"304": {
"description": "Ontology payload unchanged for the supplied If-None-Match value"
},
"400": {
"description": "Invalid query parameter",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"error": {
"type": "string"
},
"message": {
"type": "string"
}
},
"additionalProperties": {}
}
}
}
},
"404": {
"description": "Unknown root object class",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"error": {
"type": "string"
},
"message": {
"type": "string"
}
},
"additionalProperties": {}
}
}
}
},
"500": {
"description": "ObjectClass list unavailable (e.g. missing graph identity). Cyclic classes degrade into diagnostics[] on 200.",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"error": {
"type": "string"
},
"message": {
"type": "string"
}
},
"additionalProperties": {}
}
}
}
}
}
},
"post": {
"operationId": "createObjectClass",
"tags": [
"BIM Ontology / Object Classes"
],
"summary": "Create a root object class (no parent)",
"description": "Creates a draft ObjectClass with no parent — a tree root (ADR-0046, ADR-0050). Child create remains POST /object-classes/actions/add-child; this endpoint rejects a supplied parent with HTTP 400. Optional singular classifications match Add Child (uniformat, masterformat, objectClassCode, revitCategory). Optional description and synonyms are steward text written on create (same as Add Child). Persists then emits ObjectClassParentGraphChanged so handlers fill depth 0, path, and is_leaf. Does not write root_object_class. Requires Idempotency-Key. Identifiers in the body are domain_id (ADR-0048), never rec…. Returns 202 Accepted immediately; processing continues and the outcome is written to script_log.",
"security": [
{
"apiKeyAuth": []
}
],
"x-domain": {
"appVersion": "api-v1",
"boundedContext": "bim-ontology",
"service": "api-v1",
"aggregate": "ObjectClass",
"useCase": "ObjectClassWorkflowUseCases",
"domainEvents": [
"ObjectClassParentGraphChanged"
],
"domainErrors": [
"ObjectClassWorkflowFailedError"
]
},
"parameters": [
{
"in": "header",
"name": "Idempotency-Key",
"required": true,
"schema": {
"type": "string",
"minLength": 1
}
}
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"name": {
"type": "string",
"minLength": 1
},
"userId": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
]
},
"revitCategory": {
"description": "Classification domain_id (preferred). Airtable rec… ids are deprecated during transition. Revit may also be builtinName.",
"anyOf": [
{
"type": "string",
"minLength": 1
},
{
"maxItems": 1,
"type": "array",
"items": {
"type": "string",
"minLength": 1
}
}
]
},
"uniformat": {
"description": "Classification domain_id (preferred). Airtable rec… ids are deprecated during transition. Revit may also be builtinName.",
"anyOf": [
{
"type": "string",
"minLength": 1
},
{
"maxItems": 1,
"type": "array",
"items": {
"type": "string",
"minLength": 1
}
}
]
},
"masterformat": {
"description": "Classification domain_id (preferred). Airtable rec… ids are deprecated during transition. Revit may also be builtinName.",
"anyOf": [
{
"type": "string",
"minLength": 1
},
{
"maxItems": 1,
"type": "array",
"items": {
"type": "string",
"minLength": 1
}
}
]
},
"objectClassCode": {
"description": "Classification domain_id (preferred). Airtable rec… ids are deprecated during transition. Revit may also be builtinName.",
"anyOf": [
{
"type": "string",
"minLength": 1
},
{
"maxItems": 1,
"type": "array",
"items": {
"type": "string",
"minLength": 1
}
}
]
},
"revitMakeUnique": {
"type": "boolean"
},
"uniformatMakeUnique": {
"type": "boolean"
},
"masterformatMakeUnique": {
"type": "boolean"
},
"objectClassCodeMakeUnique": {
"type": "boolean"
},
"description": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
]
},
"synonyms": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
]
},
"parentObjectClassIds": {},
"parentObjectClassId": {},
"parentId": {}
},
"required": [
"name"
],
"additionalProperties": false
}
}
}
},
"responses": {
"202": {
"description": "Create-root accepted; processing continues and the outcome is written to script_log.",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {},
"additionalProperties": {}
}
}
}
},
"400": {
"description": "Validation error, missing Idempotency-Key, or a parent was supplied",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"error": {
"type": "string"
},
"message": {
"type": "string"
}
},
"additionalProperties": {}
}
}
}
}
}
}
},
"/api/v1/bim-ontology/object-classes/sync-all": {
"post": {
"operationId": "syncAllObjectClasses",
"tags": [
"BIM Ontology / Object Classes"
],
"summary": "Sync the full object-class hierarchy",
"security": [
{
"apiKeyAuth": []
}
],
"x-domain": {
"appVersion": "api-v1",
"boundedContext": "bim-ontology",
"service": "api-v1",
"aggregate": "ObjectClass",
"useCase": "ObjectClassWorkflowUseCases",
"domainEvents": [],
"domainErrors": [
"ObjectClassDomainError"
]
},
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {},
"additionalProperties": {}
}
}
}
},
"responses": {
"200": {
"description": "Object-class hierarchy sync completed",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {},
"additionalProperties": {}
}
}
}
},
"400": {
"description": "Validation or workflow error",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"error": {
"type": "string"
},
"message": {
"type": "string"
}
},
"additionalProperties": {}
}
}
}
}
}
}
},
"/api/v1/bim-ontology/object-classes/{key}/actions/approve": {
"post": {
"operationId": "approveObjectClass",
"tags": [
"BIM Ontology / Object Classes"
],
"summary": "Approve an object class",
"description": "Type A lifecycle transition (ADR-0050). Requires every ancestor to be approved and Uniformat, Masterformat, ObjectClass code, and Revit category present (explicit or inherited). HTTP 409 names the blocking ancestor or missing classification. `:key` is domain_id or handle.",
"security": [
{
"apiKeyAuth": []
}
],
"x-domain": {
"appVersion": "api-v1",
"boundedContext": "bim-ontology",
"service": "api-v1",
"aggregate": "ObjectClass",
"useCase": "ApproveObjectClassUseCase",
"domainEvents": [],
"domainErrors": [
"ObjectClassActiveAncestorInvariantError",
"ObjectClassClassificationIncompleteError",
"InvalidObjectClassStatusTransitionError"
]
},
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {},
"additionalProperties": {}
}
}
}
},
"responses": {
"200": {
"description": "Object class approved",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {},
"additionalProperties": {}
}
}
}
},
"400": {
"description": "Validation or workflow error",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"error": {
"type": "string"
},
"message": {
"type": "string"
}
},
"additionalProperties": {}
}
}
}
},
"409": {
"description": "Conflict: a named ancestor is not approved, or required classification is incomplete (Uniformat, Masterformat, ObjectClass code, or Revit category missing on this class and its ancestry).",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"success": {
"type": "boolean",
"const": false
},
"error": {
"type": "object",
"properties": {
"code": {
"type": "string"
},
"message": {
"type": "string"
},
"details": {
"type": "object",
"properties": {
"objectClassId": {
"type": "string"
},
"objectClassName": {
"type": "string"
},
"blockingAncestor": {
"description": "Named ancestor that is not approved (HTTP 409).",
"type": "object",
"properties": {
"id": {
"type": "string"
},
"name": {
"type": "string"
},
"status": {
"type": "string",
"enum": [
"draft",
"approved",
"archived"
]
}
},
"required": [
"id",
"status"
],
"additionalProperties": false
},
"missingClassificationKind": {
"description": "Required classification missing on this class and its ancestry (HTTP 409).",
"type": "string",
"enum": [
"uniformat",
"masterformat",
"object_class_codes",
"revit_categories"
]
}
},
"additionalProperties": false
}
},
"required": [
"code",
"message"
],
"additionalProperties": false
}
},
"required": [
"success",
"error"
],
"additionalProperties": false
}
}
}
}
}
}
},
"/api/v1/bim-ontology/object-classes/{key}/actions/archive": {
"post": {
"operationId": "archiveObjectClass",
"tags": [
"BIM Ontology / Object Classes"
],
"summary": "Archive an object class (cascades to descendants)",
"description": "Type A lifecycle transition (ADR-0050). Archives the seed and its subtree. `:key` is domain_id or handle.",
"security": [
{
"apiKeyAuth": []
}
],
"x-domain": {
"appVersion": "api-v1",
"boundedContext": "bim-ontology",
"service": "api-v1",
"aggregate": "ObjectClass",
"useCase": "ArchiveObjectClassUseCase",
"domainEvents": [],
"domainErrors": [
"InvalidObjectClassStatusTransitionError",
"ObjectClassNotFoundError"
]
},
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {},
"additionalProperties": {}
}
}
}
},
"responses": {
"200": {
"description": "Object class archived",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {},
"additionalProperties": {}
}
}
}
},
"400": {
"description": "Validation or workflow error",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"error": {
"type": "string"
},
"message": {
"type": "string"
}
},
"additionalProperties": {}
}
}
}
}
}
}
},
"/api/v1/bim-ontology/object-classes/{key}/sync-hierarchy": {
"post": {
"operationId": "syncObjectClassHierarchy",
"tags": [
"BIM Ontology / Object Classes"
],
"summary": "Sync hierarchy state for an object class",
"description": "Type B recompute. `:key` is domain_id or handle (ADR-0048), never an Airtable rec id.",
"security": [
{
"apiKeyAuth": []
}
],
"x-domain": {
"appVersion": "api-v1",
"boundedContext": "bim-ontology",
"service": "api-v1",
"aggregate": "ObjectClass",
"useCase": "ObjectClassWorkflowUseCases",
"domainEvents": [],
"domainErrors": [
"ObjectClassDomainError"
]
},
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"userId": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
]
},
"recomputeWholeTree": {
"type": "boolean"
}
},
"additionalProperties": {}
}
}
}
},
"responses": {
"200": {
"description": "Object-class hierarchy sync completed",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {},
"additionalProperties": {}
}
}
}
},
"400": {
"description": "Validation or workflow error",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"error": {
"type": "string"
},
"message": {
"type": "string"
}
},
"additionalProperties": {}
}
}
}
}
}
}
},
"/api/v1/bim-ontology/object-classes/{key}/sync-rendered-param-templates": {
"post": {
"operationId": "syncObjectClassRenderedParamTemplates",
"tags": [
"BIM Ontology / Object Classes"
],
"summary": "Sync rendered parameter templates for an object class",
"description": "Type B recompute. `:key` is domain_id or handle (ADR-0048), never an Airtable rec id.",
"security": [
{
"apiKeyAuth": []
}
],
"x-domain": {
"appVersion": "api-v1",
"boundedContext": "bim-ontology",
"service": "api-v1",
"aggregate": "ObjectClass",
"useCase": "ObjectClassWorkflowUseCases",
"domainEvents": [],
"domainErrors": [
"ObjectClassDomainError"
]
},
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"userId": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
]
},
"recomputeSubtree": {
"type": "boolean"
}
},
"additionalProperties": {}
}
}
}
},
"responses": {
"200": {
"description": "Rendered parameter template sync completed",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {},
"additionalProperties": {}
}
}
}
},
"400": {
"description": "Validation or workflow error",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"error": {
"type": "string"
},
"message": {
"type": "string"
}
},
"additionalProperties": {}
}
}
}
}
}
}
},
"/api/v1/bim-ontology/object-classes/{key}/sync-classification-inheritance": {
"post": {
"operationId": "syncObjectClassClassificationInheritance",
"tags": [
"BIM Ontology / Object Classes"
],
"summary": "Sync classification push-overwrite for an object class",
"description": "Type B recompute (ADR-0050 / B6). Singular classifications (Uniformat, Masterformat, ObjectClass codes, Revit): infer make-unique then fill *_direct down until the next unique source. `:key` is domain_id or handle (ADR-0048). Seeded demote records ObjectClassDemoted per class that loses a required kind.",
"security": [
{
"apiKeyAuth": []
}
],
"x-domain": {
"appVersion": "api-v1",
"boundedContext": "bim-ontology",
"service": "api-v1",
"aggregate": "ObjectClass",
"useCase": "DemoteObjectClassesThatLostClassificationUseCase",
"domainEvents": [
"ObjectClassDemoted"
],
"domainErrors": [
"ObjectClassDomainError"
]
},
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"userId": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
]
},
"recomputeWholeTree": {
"type": "boolean"
}
},
"additionalProperties": {}
}
}
}
},
"responses": {
"200": {
"description": "Classification inheritance sync completed",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {},
"additionalProperties": {}
}
}
}
},
"400": {
"description": "Validation or workflow error",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"error": {
"type": "string"
},
"message": {
"type": "string"
}
},
"additionalProperties": {}
}
}
}
}
}
}
},
"/api/v1/bim-ontology/object-classes/{key}/revit-category": {
"put": {
"operationId": "setObjectClassRevitCategory",
"tags": [
"BIM Ontology / Object Classes"
],
"summary": "Set ObjectClass ↔ Revit category binding (1:1)",
"description": "Full replacement of the authored object_classes.revit_categories link (PUT semantics; at most one). Path key accepts ObjectClass domain_id or handle. Body revitCategory is a Revit category domain_id or builtinName (e.g. OST_PlumbingFixtures), or null to clear. Response data.revitCategory is the singular binding object. This authors BuildPlan classification binding only — it is not parameter-projection category scope and does not populate builtInCategories on projections. Requires Idempotency-Key.",
"security": [
{
"apiKeyAuth": []
}
],
"x-domain": {
"appVersion": "api-v1",
"boundedContext": "bim-ontology",
"service": "api-v1",
"aggregate": "ObjectClass",
"useCase": "SetObjectClassRevitCategoryUseCase",
"domainEvents": [],
"domainErrors": [
"ObjectClassRevitCategoryNotFoundError",
"RevitCategoryRefsNotFoundError"
]
},
"parameters": [
{
"in": "path",
"name": "key",
"required": true,
"schema": {
"type": "string",
"minLength": 1
},
"description": "ObjectClass domain_id or handle."
},
{
"in": "header",
"name": "Idempotency-Key",
"required": true,
"schema": {
"type": "string",
"minLength": 1
}
}
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"revitCategory": {
"anyOf": [
{
"type": "string",
"minLength": 1
},
{
"type": "null"
}
],
"description": "Revit category domain_id or builtinName (e.g. OST_PlumbingFixtures). null clears the authored link."
},
"makeUnique": {
"description": "When true, this class is the make-unique source for Revit category (C keep-assignment).",
"type": "boolean"
}
},
"required": [
"revitCategory"
],
"additionalProperties": false
}
}
}
},
"responses": {
"200": {
"description": "Revit category binding replaced",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"data": {
"type": "object",
"properties": {
"objectClassId": {
"type": "string"
},
"objectClassKey": {
"type": "string"
},
"revitCategory": {
"anyOf": [
{
"type": "object",
"properties": {
"builtinName": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
]
},
"builtinCategoryInt": {
"anyOf": [
{
"type": "integer",
"minimum": -9007199254740991,
"maximum": 9007199254740991
},
{
"type": "null"
}
]
},
"status": {
"type": "string",
"enum": [
"current",
"stale",
"needs_review",
"unresolved"
]
},
"source": {
"type": "string",
"enum": [
"authored",
"inherited"
]
},
"reviewReason": {
"type": "string"
}
},
"required": [
"builtinName",
"builtinCategoryInt",
"status"
],
"additionalProperties": false,
"description": "Singular 1:1 Revit category binding. When status !== current, builtinName and builtinCategoryInt are null."
},
{
"type": "null"
}
]
}
},
"required": [
"objectClassId",
"objectClassKey",
"revitCategory"
],
"additionalProperties": false
}
},
"required": [
"data"
],
"additionalProperties": false
}
}
}
},
"400": {
"description": "Validation error or missing Idempotency-Key",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"error": {
"type": "string"
},
"message": {
"type": "string"
}
},
"additionalProperties": {}
}
}
}
},
"404": {
"description": "ObjectClass or Revit category not found",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"error": {
"type": "string"
},
"message": {
"type": "string"
}
},
"additionalProperties": {}
}
}
}
}
}
}
},
"/api/v1/bim-ontology/object-classes/actions/delete": {
"post": {
"operationId": "deleteObjectClasses",
"tags": [
"BIM Ontology / Object Classes"
],
"summary": "Delete object-class nodes",
"description": "sourceObjectClassIds / targetObjectClassId / parentObjectClassIds are ObjectClass domain_id values (handle also accepted). Airtable rec… ids remain accepted during transition and are logged as deprecated. Returns 202 Accepted immediately; processing continues and the outcome is written to script_log.",
"security": [
{
"apiKeyAuth": []
}
],
"x-domain": {
"appVersion": "api-v1",
"boundedContext": "bim-ontology",
"service": "api-v1",
"aggregate": "ObjectClass",
"useCase": "ObjectClassWorkflowUseCases",
"domainEvents": [
"ObjectClassParentGraphChanged"
],
"domainErrors": [
"ObjectClassWorkflowFailedError",
"ObjectClassKeepDataRequiredError",
"ObjectClassParentIdentityMissingError"
]
},
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"sourceObjectClassIds": {
"maxItems": 500,
"type": "array",
"items": {
"type": "string",
"minLength": 1
},
"description": "ObjectClass domain_id values (preferred). Handle also accepted. Airtable rec… ids are deprecated during transition."
},
"includeChildren": {
"type": "boolean"
},
"userId": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
]
}
},
"required": [
"sourceObjectClassIds"
],
"additionalProperties": {}
}
}
}
},
"responses": {
"202": {
"description": "Object-class graph action completed. Accepted immediately; processing continues and the outcome is written to script_log / last_script_status.",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {},
"additionalProperties": {}
}
}
}
},
"400": {
"description": "Validation or workflow error",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"error": {
"type": "string"
},
"message": {
"type": "string"
}
},
"additionalProperties": {}
}
}
}
}
}
}
},
"/api/v1/bim-ontology/object-classes/actions/move": {
"post": {
"operationId": "moveObjectClasses",
"tags": [
"BIM Ontology / Object Classes"
],
"summary": "Move object-class nodes",
"description": "sourceObjectClassIds / targetObjectClassId / parentObjectClassIds are ObjectClass domain_id values (handle also accepted). Airtable rec… ids remain accepted during transition and are logged as deprecated. Returns 202 Accepted immediately; processing continues and the outcome is written to script_log.",
"security": [
{
"apiKeyAuth": []
}
],
"x-domain": {
"appVersion": "api-v1",
"boundedContext": "bim-ontology",
"service": "api-v1",
"aggregate": "ObjectClass",
"useCase": "ObjectClassWorkflowUseCases",
"domainEvents": [
"ObjectClassParentGraphChanged"
],
"domainErrors": [
"ObjectClassWorkflowFailedError",
"ObjectClassKeepDataRequiredError",
"ObjectClassParentIdentityMissingError"
]
},
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"sourceObjectClassIds": {
"type": "array",
"items": {
"type": "string",
"minLength": 1
},
"description": "ObjectClass domain_id values (preferred). Handle also accepted. Airtable rec… ids are deprecated during transition."
},
"targetObjectClassId": {
"type": "string",
"minLength": 1,
"description": "ObjectClass domain_id (preferred) or handle. Airtable rec… ids are deprecated during transition."
},
"includeChildren": {
"type": "boolean"
},
"makeUnique": {
"default": false,
"type": "boolean"
},
"userId": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
]
}
},
"required": [
"sourceObjectClassIds",
"targetObjectClassId"
],
"additionalProperties": {}
}
}
}
},
"responses": {
"202": {
"description": "Object-class graph action completed. Accepted immediately; processing continues and the outcome is written to script_log / last_script_status.",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {},
"additionalProperties": {}
}
}
}
},
"400": {
"description": "Validation or workflow error",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"error": {
"type": "string"
},
"message": {
"type": "string"
}
},
"additionalProperties": {}
}
}
}
}
}
}
},
"/api/v1/bim-ontology/object-classes/actions/merge": {
"post": {
"operationId": "mergeObjectClasses",
"tags": [
"BIM Ontology / Object Classes"
],
"summary": "Merge object-class nodes (keepData source|target required)",
"description": "sourceObjectClassIds / targetObjectClassId / parentObjectClassIds are ObjectClass domain_id values (handle also accepted). Airtable rec… ids remain accepted during transition and are logged as deprecated. Returns 202 Accepted immediately; processing continues and the outcome is written to script_log.",
"security": [
{
"apiKeyAuth": []
}
],
"x-domain": {
"appVersion": "api-v1",
"boundedContext": "bim-ontology",
"service": "api-v1",
"aggregate": "ObjectClass",
"useCase": "ObjectClassWorkflowUseCases",
"domainEvents": [
"ObjectClassParentGraphChanged"
],
"domainErrors": [
"ObjectClassWorkflowFailedError",
"ObjectClassKeepDataRequiredError",
"ObjectClassParentIdentityMissingError"
]
},
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"sourceObjectClassIds": {
"type": "array",
"items": {
"type": "string",
"minLength": 1
},
"description": "ObjectClass domain_id values (preferred). Handle also accepted. Airtable rec… ids are deprecated during transition."
},
"targetObjectClassId": {
"type": "string",
"minLength": 1,
"description": "ObjectClass domain_id (preferred) or handle. Airtable rec… ids are deprecated during transition."
},
"keepData": {
"type": "string",
"enum": [
"source",
"target"
]
},
"userId": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
]
}
},
"required": [
"sourceObjectClassIds",
"targetObjectClassId",
"keepData"
],
"additionalProperties": {}
}
}
}
},
"responses": {
"202": {
"description": "Object-class graph action completed. Accepted immediately; processing continues and the outcome is written to script_log / last_script_status.",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {},
"additionalProperties": {}
}
}
}
},
"400": {
"description": "Validation or workflow error",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"error": {
"type": "string"
},
"message": {
"type": "string"
}
},
"additionalProperties": {}
}
}
}
}
}
}
},
"/api/v1/bim-ontology/object-classes/actions/copy": {
"post": {
"operationId": "copyObjectClasses",
"tags": [
"BIM Ontology / Object Classes"
],
"summary": "Copy object-class nodes",
"description": "sourceObjectClassIds / targetObjectClassId / parentObjectClassIds are ObjectClass domain_id values (handle also accepted). Airtable rec… ids remain accepted during transition and are logged as deprecated. Returns 202 Accepted immediately; processing continues and the outcome is written to script_log.",
"security": [
{
"apiKeyAuth": []
}
],
"x-domain": {
"appVersion": "api-v1",
"boundedContext": "bim-ontology",
"service": "api-v1",
"aggregate": "ObjectClass",
"useCase": "ObjectClassWorkflowUseCases",
"domainEvents": [
"ObjectClassParentGraphChanged"
],
"domainErrors": [
"ObjectClassWorkflowFailedError",
"ObjectClassKeepDataRequiredError",
"ObjectClassParentIdentityMissingError"
]
},
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"sourceObjectClassIds": {
"type": "array",
"items": {
"type": "string",
"minLength": 1
},
"description": "ObjectClass domain_id values (preferred). Handle also accepted. Airtable rec… ids are deprecated during transition."
},
"targetObjectClassId": {
"type": "string",
"minLength": 1,
"description": "ObjectClass domain_id (preferred) or handle. Airtable rec… ids are deprecated during transition."
},
"includeChildren": {
"default": false,
"type": "boolean"
},
"makeUnique": {
"default": false,
"type": "boolean"
},
"userId": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
]
}
},
"required": [
"sourceObjectClassIds",
"targetObjectClassId"
],
"additionalProperties": {}
}
}
}
},
"responses": {
"202": {
"description": "Object-class graph action completed. Accepted immediately; processing continues and the outcome is written to script_log / last_script_status.",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {},
"additionalProperties": {}
}
}
}
},
"400": {
"description": "Validation or workflow error",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"error": {
"type": "string"
},
"message": {
"type": "string"
}
},
"additionalProperties": {}
}
}
}
}
}
}
},
"/api/v1/bim-ontology/object-classes/actions/add-child": {
"post": {
"operationId": "addObjectClassChild",
"tags": [
"BIM Ontology / Object Classes"
],
"summary": "Add a child object-class node",
"description": "Creates a draft child under the given parent. Requires Idempotency-Key. sourceObjectClassIds / targetObjectClassId / parentObjectClassIds are ObjectClass domain_id values (handle also accepted). Airtable rec… ids remain accepted during transition and are logged as deprecated. Classification refs prefer domain_id (rec… deprecated). Optional uniformat, masterformat, objectClassCode, revitCategory, description, and synonyms are authored on the new child. Returns 202 Accepted immediately; processing continues and the outcome is written to script_log.",
"security": [
{
"apiKeyAuth": []
}
],
"x-domain": {
"appVersion": "api-v1",
"boundedContext": "bim-ontology",
"service": "api-v1",
"aggregate": "ObjectClass",
"useCase": "ObjectClassWorkflowUseCases",
"domainEvents": [
"ObjectClassParentGraphChanged"
],
"domainErrors": [
"ObjectClassWorkflowFailedError",
"ObjectClassKeepDataRequiredError",
"ObjectClassParentIdentityMissingError"
]
},
"parameters": [
{
"in": "header",
"name": "Idempotency-Key",
"required": true,
"schema": {
"type": "string",
"minLength": 1
}
}
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"parentObjectClassIds": {
"type": "array",
"items": {
"type": "string",
"minLength": 1
},
"description": "ObjectClass domain_id values (preferred). Handle also accepted. Airtable rec… ids are deprecated during transition."
},
"childName": {
"type": "string"
},
"userId": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
]
},
"revitCategory": {
"description": "Classification domain_id (preferred). Airtable rec… ids are deprecated during transition. Revit may also be builtinName.",
"anyOf": [
{
"type": "string",
"minLength": 1
},
{
"maxItems": 1,
"type": "array",
"items": {
"type": "string",
"minLength": 1
}
}
]
},
"uniformat": {
"description": "Classification domain_id (preferred). Airtable rec… ids are deprecated during transition. Revit may also be builtinName.",
"anyOf": [
{
"type": "string",
"minLength": 1
},
{
"maxItems": 1,
"type": "array",
"items": {
"type": "string",
"minLength": 1
}
}
]
},
"masterformat": {
"description": "Classification domain_id (preferred). Airtable rec… ids are deprecated during transition. Revit may also be builtinName.",
"anyOf": [
{
"type": "string",
"minLength": 1
},
{
"maxItems": 1,
"type": "array",
"items": {
"type": "string",
"minLength": 1
}
}
]
},
"objectClassCode": {
"description": "Classification domain_id (preferred). Airtable rec… ids are deprecated during transition. Revit may also be builtinName.",
"anyOf": [
{
"type": "string",
"minLength": 1
},
{
"maxItems": 1,
"type": "array",
"items": {
"type": "string",
"minLength": 1
}
}
]
},
"revitMakeUnique": {
"type": "boolean"
},
"uniformatMakeUnique": {
"type": "boolean"
},
"masterformatMakeUnique": {
"type": "boolean"
},
"objectClassCodeMakeUnique": {
"type": "boolean"
},
"description": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
]
},
"synonyms": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
]
}
},
"required": [
"parentObjectClassIds"
],
"additionalProperties": false
}
}
}
},
"responses": {
"202": {
"description": "Add-child accepted. Accepted immediately; processing continues and the outcome is written to script_log / last_script_status.",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {},
"additionalProperties": {}
}
}
}
},
"400": {
"description": "Validation or workflow error",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"error": {
"type": "string"
},
"message": {
"type": "string"
}
},
"additionalProperties": {}
}
}
}
}
}
}
},
"/api/v1/bim-ontology/parameter-specs": {
"get": {
"operationId": "listParameterSpecs",
"tags": [
"BIM Ontology / Parameter Dictionary"
],
"summary": "List canonical parameter specifications",
"description": "revitSpecTypeIds are BuildPlan domain_id links into revit_spec_types. revitSpecForgeTypeIds are the version-pinned ForgeTypeId strings from those linked rows (forge_type_id). Empty revitSpecForgeTypeIds means non-Revit or forge_type_id not filled; multiple values may appear when multiple Revit versions are linked.",
"security": [
{
"apiKeyAuth": []
}
],
"x-domain": {
"appVersion": "api-v1",
"boundedContext": "bim-ontology",
"service": "api-v1",
"useCase": "ListBimOntologyReferencesUseCase",
"query": "Open question",
"domainEvents": [],
"domainErrors": [],
"aggregate": "ParameterSpec"
},
"responses": {
"200": {
"description": "ParameterSpec references returned",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"items": {
"type": "array",
"items": {
"type": "object",
"properties": {
"id": {
"type": "string"
},
"name": {
"type": "string"
},
"handle": {
"type": "string"
},
"dataType": {
"type": "string",
"enum": [
"string",
"number",
"boolean",
"string[]",
"number[]"
]
},
"isMeasurable": {
"type": "boolean"
},
"unitSystem": {
"type": "string",
"enum": [
"imperial",
"metric",
"both",
"na"
]
},
"revitSpecTypeIds": {
"type": "array",
"items": {
"type": "string"
},
"description": "Linked RevitSpecType domain_id values (BuildPlan identities)."
},
"revitSpecForgeTypeIds": {
"type": "array",
"items": {
"type": "string"
},
"description": "Version-pinned Revit ForgeTypeId strings from linked revit_spec_types.forge_type_id. Empty when non-Revit or unfilled."
}
},
"required": [
"id",
"name",
"handle",
"dataType",
"isMeasurable",
"unitSystem",
"revitSpecTypeIds",
"revitSpecForgeTypeIds"
],
"additionalProperties": false
}
},
"page": {
"type": "object",
"properties": {
"total": {
"type": "integer",
"minimum": 0,
"maximum": 9007199254740991
},
"page": {
"type": "number",
"const": 0
},
"limit": {
"type": "integer",
"minimum": 0,
"maximum": 9007199254740991
},
"hasNextPage": {
"type": "boolean",
"const": false
}
},
"required": [
"total",
"page",
"limit",
"hasNextPage"
],
"additionalProperties": false
}
},
"required": [
"items",
"page"
],
"additionalProperties": false
}
}
}
},
"500": {
"description": "BIM Ontology reference catalog unavailable",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"error": {
"type": "string"
}
},
"required": [
"error"
],
"additionalProperties": false
}
}
}
}
}
}
},
"/api/v1/bim-ontology/parameter-controls": {
"get": {
"operationId": "listParameterControls",
"tags": [
"BIM Ontology / Parameter Dictionary"
],
"summary": "List canonical parameter controls",
"security": [
{
"apiKeyAuth": []
}
],
"x-domain": {
"appVersion": "api-v1",
"boundedContext": "bim-ontology",
"service": "api-v1",
"useCase": "ListBimOntologyReferencesUseCase",
"query": "Open question",
"domainEvents": [],
"domainErrors": [],
"aggregate": "ParameterControl"
},
"responses": {
"200": {
"description": "ParameterControl references returned",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"items": {
"type": "array",
"items": {
"type": "object",
"properties": {
"id": {
"type": "string"
},
"name": {
"type": "string"
},
"handle": {
"type": "string"
},
"supportsMultiple": {
"type": "boolean"
},
"requiresOptions": {
"type": "boolean"
}
},
"required": [
"id",
"name",
"handle",
"supportsMultiple",
"requiresOptions"
],
"additionalProperties": false
}
},
"page": {
"type": "object",
"properties": {
"total": {
"type": "integer",
"minimum": 0,
"maximum": 9007199254740991
},
"page": {
"type": "number",
"const": 0
},
"limit": {
"type": "integer",
"minimum": 0,
"maximum": 9007199254740991
},
"hasNextPage": {
"type": "boolean",
"const": false
}
},
"required": [
"total",
"page",
"limit",
"hasNextPage"
],
"additionalProperties": false
}
},
"required": [
"items",
"page"
],
"additionalProperties": false
}
}
}
},
"500": {
"description": "BIM Ontology reference catalog unavailable",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"error": {
"type": "string"
}
},
"required": [
"error"
],
"additionalProperties": false
}
}
}
}
}
}
},
"/api/v1/bim-ontology/parameter-groups": {
"get": {
"operationId": "listParameterGroups",
"tags": [
"BIM Ontology / Parameter Dictionary"
],
"summary": "List canonical parameter groups",
"security": [
{
"apiKeyAuth": []
}
],
"x-domain": {
"appVersion": "api-v1",
"boundedContext": "bim-ontology",
"service": "api-v1",
"useCase": "ListBimOntologyReferencesUseCase",
"query": "Open question",
"domainEvents": [],
"domainErrors": [],
"aggregate": "ParameterGroup"
},
"responses": {
"200": {
"description": "ParameterGroup references returned",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"items": {
"type": "array",
"items": {
"type": "object",
"properties": {
"id": {
"type": "string"
},
"name": {
"type": "string"
}
},
"required": [
"id",
"name"
],
"additionalProperties": false
}
},
"page": {
"type": "object",
"properties": {
"total": {
"type": "integer",
"minimum": 0,
"maximum": 9007199254740991
},
"page": {
"type": "number",
"const": 0
},
"limit": {
"type": "integer",
"minimum": 0,
"maximum": 9007199254740991
},
"hasNextPage": {
"type": "boolean",
"const": false
}
},
"required": [
"total",
"page",
"limit",
"hasNextPage"
],
"additionalProperties": false
}
},
"required": [
"items",
"page"
],
"additionalProperties": false
}
}
}
},
"500": {
"description": "BIM Ontology reference catalog unavailable",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"error": {
"type": "string"
}
},
"required": [
"error"
],
"additionalProperties": false
}
}
}
}
}
},
"post": {
"operationId": "createParameterGroup",
"tags": [
"BIM Ontology / Parameter Dictionary"
],
"summary": "Create a canonical parameter group",
"security": [
{
"apiKeyAuth": []
}
],
"x-domain": {
"appVersion": "api-v1",
"boundedContext": "bim-ontology",
"service": "api-v1",
"aggregate": "ParameterGroup",
"useCase": "CreateParameterGroupUseCase",
"domainEvents": [
"ParameterGroupChanged"
],
"domainErrors": [
"ParameterKeyDomainError",
"ApplicationError"
]
},
"parameters": [
{
"in": "header",
"name": "Idempotency-Key",
"required": true,
"schema": {
"type": "string",
"minLength": 1
}
}
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"name": {
"type": "string",
"minLength": 1
}
},
"required": [
"name"
],
"additionalProperties": false
}
}
}
},
"responses": {
"201": {
"description": "Parameter group created",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"data": {
"type": "object",
"properties": {
"id": {
"type": "string"
},
"name": {
"type": "string"
}
},
"required": [
"id",
"name"
],
"additionalProperties": false
}
},
"required": [
"data"
],
"additionalProperties": false
}
}
}
},
"400": {
"description": "Invalid request",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"error": {
"type": "object",
"properties": {
"code": {
"type": "string"
},
"message": {
"type": "string"
},
"details": {}
},
"required": [
"code",
"message"
],
"additionalProperties": false
}
},
"required": [
"error"
],
"additionalProperties": false
}
}
}
},
"401": {
"description": "Authenticated caller required",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"error": {
"type": "object",
"properties": {
"code": {
"type": "string"
},
"message": {
"type": "string"
},
"details": {}
},
"required": [
"code",
"message"
],
"additionalProperties": false
}
},
"required": [
"error"
],
"additionalProperties": false
}
}
}
},
"404": {
"description": "Resource not found",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"error": {
"type": "object",
"properties": {
"code": {
"type": "string"
},
"message": {
"type": "string"
},
"details": {}
},
"required": [
"code",
"message"
],
"additionalProperties": false
}
},
"required": [
"error"
],
"additionalProperties": false
}
}
}
},
"409": {
"description": "Canonical identity conflict",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"error": {
"type": "object",
"properties": {
"code": {
"type": "string"
},
"message": {
"type": "string"
},
"details": {}
},
"required": [
"code",
"message"
],
"additionalProperties": false
}
},
"required": [
"error"
],
"additionalProperties": false
}
}
}
},
"422": {
"description": "Domain rule rejected the command",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"error": {
"type": "object",
"properties": {
"code": {
"type": "string"
},
"message": {
"type": "string"
},
"details": {}
},
"required": [
"code",
"message"
],
"additionalProperties": false
}
},
"required": [
"error"
],
"additionalProperties": false
}
}
}
},
"500": {
"description": "Parameter authoring unavailable",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"error": {
"type": "object",
"properties": {
"code": {
"type": "string"
},
"message": {
"type": "string"
},
"details": {}
},
"required": [
"code",
"message"
],
"additionalProperties": false
}
},
"required": [
"error"
],
"additionalProperties": false
}
}
}
}
}
}
},
"/api/v1/bim-ontology/units": {
"get": {
"operationId": "listUnits",
"tags": [
"BIM Ontology / Parameter Dictionary"
],
"summary": "List canonical BIM Ontology units",
"security": [
{
"apiKeyAuth": []
}
],
"x-domain": {
"appVersion": "api-v1",
"boundedContext": "bim-ontology",
"service": "api-v1",
"useCase": "ListBimOntologyReferencesUseCase",
"query": "Open question",
"domainEvents": [],
"domainErrors": [],
"aggregate": "Unit"
},
"responses": {
"200": {
"description": "Unit references returned",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"items": {
"type": "array",
"items": {
"type": "object",
"properties": {
"id": {
"type": "string"
},
"name": {
"type": "string"
},
"symbol": {
"type": "string"
}
},
"required": [
"id",
"name",
"symbol"
],
"additionalProperties": false
}
},
"page": {
"type": "object",
"properties": {
"total": {
"type": "integer",
"minimum": 0,
"maximum": 9007199254740991
},
"page": {
"type": "number",
"const": 0
},
"limit": {
"type": "integer",
"minimum": 0,
"maximum": 9007199254740991
},
"hasNextPage": {
"type": "boolean",
"const": false
}
},
"required": [
"total",
"page",
"limit",
"hasNextPage"
],
"additionalProperties": false
}
},
"required": [
"items",
"page"
],
"additionalProperties": false
}
}
}
},
"500": {
"description": "BIM Ontology reference catalog unavailable",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"error": {
"type": "string"
}
},
"required": [
"error"
],
"additionalProperties": false
}
}
}
}
}
}
},
"/api/v1/bim-ontology/parameter-specs/missing-complete-revit-spec-types": {
"get": {
"operationId": "listParameterSpecsMissingCompleteRevitSpecType",
"tags": [
"BIM Ontology / Parameter Dictionary",
"system-maintenance"
],
"summary": "List ParameterSpecs missing a complete Revit spec type",
"description": "System-maintenance coverage check. Returns every parameter_specs row with no revit_spec_types row that has both forge_type_id and storage_type. gap=unmapped when no revit_spec_types row links the spec; gap=incomplete when a linked row is missing forge_type_id and/or storage_type. Not a write path. Parameter specs are authored in Airtable (no POST /parameter-specs).",
"security": [
{
"apiKeyAuth": []
}
],
"x-internal": true,
"x-domain": {
"appVersion": "api-v1",
"boundedContext": "bim-ontology",
"service": "api-v1",
"useCase": "ListParameterSpecsMissingCompleteRevitSpecTypeUseCase",
"query": "Open question",
"domainEvents": [],
"domainErrors": [],
"aggregate": "ParameterSpec"
},
"responses": {
"200": {
"description": "ParameterSpecs missing a complete Revit spec type",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"items": {
"type": "array",
"items": {
"type": "object",
"properties": {
"id": {
"type": "string"
},
"name": {
"type": "string"
},
"handle": {
"type": "string"
},
"mappedRevitSpecTypeIds": {
"type": "array",
"items": {
"type": "string"
}
},
"gap": {
"type": "string",
"enum": [
"unmapped",
"incomplete"
]
}
},
"required": [
"id",
"name",
"handle",
"mappedRevitSpecTypeIds",
"gap"
],
"additionalProperties": false
}
},
"page": {
"type": "object",
"properties": {
"total": {
"type": "integer",
"minimum": 0,
"maximum": 9007199254740991
},
"page": {
"type": "number",
"const": 0
},
"limit": {
"type": "integer",
"minimum": 0,
"maximum": 9007199254740991
},
"hasNextPage": {
"type": "boolean",
"const": false
}
},
"required": [
"total",
"page",
"limit",
"hasNextPage"
],
"additionalProperties": false
}
},
"required": [
"items",
"page"
],
"additionalProperties": false
}
}
}
},
"401": {
"description": "Authenticated caller required",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"error": {
"type": "string"
}
},
"required": [
"error"
],
"additionalProperties": false
}
}
}
},
"500": {
"description": "BIM Ontology coverage check unavailable",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"error": {
"type": "string"
}
},
"required": [
"error"
],
"additionalProperties": false
}
}
}
}
}
}
},
"/api/v1/bim-ontology/parameter-keys": {
"get": {
"operationId": "listParameterKeys",
"tags": [
"BIM Ontology / Parameter Dictionary"
],
"summary": "List ParameterKeys",
"description": "Lists catalog ParameterKeys through the collection query kernel (limit/offset, eq filters, domainId sort). Operators beyond eq, select, and search are rejected by QueryPolicy. Filter lifecycleStatus=eq.approved for vetted merge targets. ObjectClass offerability is via class-parameters / effective-parameters — not parameter_keys.usages.",
"security": [
{
"apiKeyAuth": []
}
],
"x-domain": {
"appVersion": "api-v1",
"boundedContext": "bim-ontology",
"service": "api-v1",
"aggregate": "ParameterKey",
"useCase": "ListParameterKeysUseCase",
"domainEvents": [],
"domainErrors": [
"ParameterKeyDomainError",
"ApplicationError"
]
},
"parameters": [
{
"in": "query",
"name": "limit",
"schema": {
"type": "integer",
"minimum": 1,
"maximum": 200,
"default": 50
},
"description": "Page size (CollectionQuery)."
},
{
"in": "query",
"name": "offset",
"schema": {
"type": "integer",
"minimum": 0,
"default": 0
},
"description": "Numeric offset into the filtered candidate set."
},
{
"in": "query",
"name": "lifecycleStatus",
"schema": {
"type": "string"
},
"description": "PostgREST-style eq filter only, e.g. eq.approved. Duplicate filters AND together. Unknown operators are rejected by QueryPolicy."
},
{
"in": "query",
"name": "parameterSpecId",
"schema": {
"type": "string"
},
"description": "PostgREST-style eq filter only, e.g. eq.. Resolved to a linked record in the query adapter."
},
{
"in": "query",
"name": "order",
"schema": {
"type": "string",
"default": "domainId.asc"
},
"description": "Sort order. Allow-listed field: domainId (asc or desc). Default domainId.asc."
}
],
"responses": {
"200": {
"description": "Parameter keys returned",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"items": {
"type": "array",
"items": {
"type": "object",
"properties": {
"id": {
"type": "string"
},
"name": {
"type": "string"
},
"handle": {
"type": "string"
},
"description": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
]
},
"usages": {
"type": "array",
"items": {
"type": "string",
"enum": [
"variant_option",
"product_spec",
"bim_parameter",
"mfg_takeoff",
"qto_takeoff"
]
},
"description": "Legacy catalog usages on ParameterKey; may be empty. ObjectClass offerability is via class-parameters / effective-parameters, not usages."
},
"parameterSpecId": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
]
},
"parameterControlId": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
]
},
"parameterGroupId": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
]
},
"parameterValueGroupIds": {
"type": "array",
"items": {
"type": "string"
}
},
"lifecycleStatus": {
"type": "string",
"enum": [
"draft",
"active",
"approved",
"archived"
]
},
"isSystem": {
"type": "boolean"
}
},
"required": [
"id",
"name",
"handle",
"description",
"usages",
"parameterSpecId",
"parameterControlId",
"parameterGroupId",
"parameterValueGroupIds",
"lifecycleStatus",
"isSystem"
],
"additionalProperties": false
}
},
"page": {
"type": "object",
"properties": {
"total": {
"description": "Exact filtered count when the adapter finished scanning; omitted on partial scans.",
"type": "integer",
"minimum": 0,
"maximum": 9007199254740991
},
"page": {
"type": "integer",
"minimum": 0,
"maximum": 9007199254740991
},
"limit": {
"type": "integer",
"exclusiveMinimum": 0,
"maximum": 9007199254740991
},
"hasNextPage": {
"type": "boolean"
},
"nextOffset": {
"description": "Offset to request the next page. Prefer this over offset+limit when present.",
"type": "integer",
"minimum": 0,
"maximum": 9007199254740991
}
},
"required": [
"page",
"limit",
"hasNextPage"
],
"additionalProperties": false
},
"hydrateSkipCount": {
"description": "Candidates examined on this page that failed domain hydrate (soft-skipped).",
"type": "integer",
"minimum": 0,
"maximum": 9007199254740991
},
"diagnostics": {
"description": "Per-row hydrate skip reasons for this page (lifecycle invariant, unresolved links, etc.).",
"type": "array",
"items": {
"type": "object",
"properties": {
"parameterKeyId": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
]
},
"handle": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
]
},
"reason": {
"type": "string"
}
},
"required": [
"parameterKeyId",
"handle",
"reason"
],
"additionalProperties": false
}
}
},
"required": [
"items",
"page"
],
"additionalProperties": false,
"description": "ParameterKey catalog page. ObjectClass offerability is via class-parameters/effective-parameters; parameter_keys.usages may be empty."
}
}
}
},
"400": {
"description": "Invalid request",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"error": {
"type": "object",
"properties": {
"code": {
"type": "string"
},
"message": {
"type": "string"
},
"details": {}
},
"required": [
"code",
"message"
],
"additionalProperties": false
}
},
"required": [
"error"
],
"additionalProperties": false
}
}
}
},
"401": {
"description": "Authenticated caller required",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"error": {
"type": "object",
"properties": {
"code": {
"type": "string"
},
"message": {
"type": "string"
},
"details": {}
},
"required": [
"code",
"message"
],
"additionalProperties": false
}
},
"required": [
"error"
],
"additionalProperties": false
}
}
}
},
"404": {
"description": "Resource not found",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"error": {
"type": "object",
"properties": {
"code": {
"type": "string"
},
"message": {
"type": "string"
},
"details": {}
},
"required": [
"code",
"message"
],
"additionalProperties": false
}
},
"required": [
"error"
],
"additionalProperties": false
}
}
}
},
"409": {
"description": "Canonical identity conflict",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"error": {
"type": "object",
"properties": {
"code": {
"type": "string"
},
"message": {
"type": "string"
},
"details": {}
},
"required": [
"code",
"message"
],
"additionalProperties": false
}
},
"required": [
"error"
],
"additionalProperties": false
}
}
}
},
"422": {
"description": "Domain rule rejected the command",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"error": {
"type": "object",
"properties": {
"code": {
"type": "string"
},
"message": {
"type": "string"
},
"details": {}
},
"required": [
"code",
"message"
],
"additionalProperties": false
}
},
"required": [
"error"
],
"additionalProperties": false
}
}
}
},
"500": {
"description": "Parameter authoring unavailable",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"error": {
"type": "object",
"properties": {
"code": {
"type": "string"
},
"message": {
"type": "string"
},
"details": {}
},
"required": [
"code",
"message"
],
"additionalProperties": false
}
},
"required": [
"error"
],
"additionalProperties": false
}
}
}
}
}
},
"post": {
"operationId": "createParameterKey",
"tags": [
"BIM Ontology / Parameter Dictionary"
],
"summary": "Create a ParameterKey",
"description": "Creates a draft ParameterKey. Activate for visible unvetted use (e.g. scraped product_spec). Approve is the stewardship gate (spec + control required) and is legal from draft or active. ParameterKeyApproved ensures a linked draft RevitParameterProjection only when usages includes bim_parameter.",
"security": [
{
"apiKeyAuth": []
}
],
"x-domain": {
"appVersion": "api-v1",
"boundedContext": "bim-ontology",
"service": "api-v1",
"aggregate": "ParameterKey",
"useCase": "CreateParameterKeyUseCase",
"domainEvents": [
"ParameterKeyCreated"
],
"domainErrors": [
"ParameterKeyDomainError",
"ApplicationError"
]
},
"parameters": [
{
"in": "header",
"name": "Idempotency-Key",
"required": true,
"schema": {
"type": "string",
"minLength": 1
}
}
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"name": {
"type": "string",
"minLength": 1
},
"handle": {
"type": "string",
"minLength": 1
},
"description": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
]
},
"usages": {
"minItems": 1,
"type": "array",
"items": {
"type": "string",
"enum": [
"variant_option",
"product_spec",
"bim_parameter",
"mfg_takeoff",
"qto_takeoff"
]
}
},
"parameterSpecId": {
"anyOf": [
{
"type": "string",
"minLength": 1
},
{
"type": "null"
}
]
},
"parameterControlId": {
"anyOf": [
{
"type": "string",
"minLength": 1
},
{
"type": "null"
}
]
},
"parameterGroupId": {
"anyOf": [
{
"type": "string",
"minLength": 1
},
{
"type": "null"
}
]
},
"parameterValueGroupIds": {
"type": "array",
"items": {
"type": "string",
"minLength": 1
}
}
},
"required": [
"name"
],
"additionalProperties": false
}
}
}
},
"responses": {
"201": {
"description": "Parameter key created",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"data": {
"type": "object",
"properties": {
"id": {
"type": "string"
},
"name": {
"type": "string"
},
"handle": {
"type": "string"
},
"description": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
]
},
"usages": {
"type": "array",
"items": {
"type": "string",
"enum": [
"variant_option",
"product_spec",
"bim_parameter",
"mfg_takeoff",
"qto_takeoff"
]
},
"description": "Legacy catalog usages on ParameterKey; may be empty. ObjectClass offerability is via class-parameters / effective-parameters, not usages."
},
"parameterSpecId": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
]
},
"parameterControlId": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
]
},
"parameterGroupId": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
]
},
"parameterValueGroupIds": {
"type": "array",
"items": {
"type": "string"
}
},
"lifecycleStatus": {
"type": "string",
"enum": [
"draft",
"active",
"approved",
"archived"
]
},
"isSystem": {
"type": "boolean"
}
},
"required": [
"id",
"name",
"handle",
"description",
"usages",
"parameterSpecId",
"parameterControlId",
"parameterGroupId",
"parameterValueGroupIds",
"lifecycleStatus",
"isSystem"
],
"additionalProperties": false
}
},
"required": [
"data"
],
"additionalProperties": false
}
}
}
},
"400": {
"description": "Invalid request",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"error": {
"type": "object",
"properties": {
"code": {
"type": "string"
},
"message": {
"type": "string"
},
"details": {}
},
"required": [
"code",
"message"
],
"additionalProperties": false
}
},
"required": [
"error"
],
"additionalProperties": false
}
}
}
},
"401": {
"description": "Authenticated caller required",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"error": {
"type": "object",
"properties": {
"code": {
"type": "string"
},
"message": {
"type": "string"
},
"details": {}
},
"required": [
"code",
"message"
],
"additionalProperties": false
}
},
"required": [
"error"
],
"additionalProperties": false
}
}
}
},
"404": {
"description": "Resource not found",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"error": {
"type": "object",
"properties": {
"code": {
"type": "string"
},
"message": {
"type": "string"
},
"details": {}
},
"required": [
"code",
"message"
],
"additionalProperties": false
}
},
"required": [
"error"
],
"additionalProperties": false
}
}
}
},
"409": {
"description": "Canonical identity conflict",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"error": {
"type": "object",
"properties": {
"code": {
"type": "string"
},
"message": {
"type": "string"
},
"details": {}
},
"required": [
"code",
"message"
],
"additionalProperties": false
}
},
"required": [
"error"
],
"additionalProperties": false
}
}
}
},
"422": {
"description": "Domain rule rejected the command",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"error": {
"type": "object",
"properties": {
"code": {
"type": "string"
},
"message": {
"type": "string"
},
"details": {}
},
"required": [
"code",
"message"
],
"additionalProperties": false
}
},
"required": [
"error"
],
"additionalProperties": false
}
}
}
},
"500": {
"description": "Parameter authoring unavailable",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"error": {
"type": "object",
"properties": {
"code": {
"type": "string"
},
"message": {
"type": "string"
},
"details": {}
},
"required": [
"code",
"message"
],
"additionalProperties": false
}
},
"required": [
"error"
],
"additionalProperties": false
}
}
}
}
}
}
},
"/api/v1/bim-ontology/parameter-keys/{id}": {
"get": {
"operationId": "getParameterKey",
"tags": [
"BIM Ontology / Parameter Dictionary"
],
"summary": "Get a ParameterKey",
"security": [
{
"apiKeyAuth": []
}
],
"x-domain": {
"appVersion": "api-v1",
"boundedContext": "bim-ontology",
"service": "api-v1",
"aggregate": "ParameterKey",
"useCase": "GetParameterKeyUseCase",
"domainEvents": [],
"domainErrors": [
"ParameterKeyDomainError",
"ApplicationError"
]
},
"parameters": [
{
"in": "path",
"name": "id",
"required": true,
"schema": {
"type": "string"
}
}
],
"responses": {
"200": {
"description": "Parameter key returned",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"data": {
"type": "object",
"properties": {
"id": {
"type": "string"
},
"name": {
"type": "string"
},
"handle": {
"type": "string"
},
"description": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
]
},
"usages": {
"type": "array",
"items": {
"type": "string",
"enum": [
"variant_option",
"product_spec",
"bim_parameter",
"mfg_takeoff",
"qto_takeoff"
]
},
"description": "Legacy catalog usages on ParameterKey; may be empty. ObjectClass offerability is via class-parameters / effective-parameters, not usages."
},
"parameterSpecId": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
]
},
"parameterControlId": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
]
},
"parameterGroupId": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
]
},
"parameterValueGroupIds": {
"type": "array",
"items": {
"type": "string"
}
},
"lifecycleStatus": {
"type": "string",
"enum": [
"draft",
"active",
"approved",
"archived"
]
},
"isSystem": {
"type": "boolean"
}
},
"required": [
"id",
"name",
"handle",
"description",
"usages",
"parameterSpecId",
"parameterControlId",
"parameterGroupId",
"parameterValueGroupIds",
"lifecycleStatus",
"isSystem"
],
"additionalProperties": false
}
},
"required": [
"data"
],
"additionalProperties": false
}
}
}
},
"400": {
"description": "Invalid request",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"error": {
"type": "object",
"properties": {
"code": {
"type": "string"
},
"message": {
"type": "string"
},
"details": {}
},
"required": [
"code",
"message"
],
"additionalProperties": false
}
},
"required": [
"error"
],
"additionalProperties": false
}
}
}
},
"401": {
"description": "Authenticated caller required",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"error": {
"type": "object",
"properties": {
"code": {
"type": "string"
},
"message": {
"type": "string"
},
"details": {}
},
"required": [
"code",
"message"
],
"additionalProperties": false
}
},
"required": [
"error"
],
"additionalProperties": false
}
}
}
},
"404": {
"description": "Resource not found",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"error": {
"type": "object",
"properties": {
"code": {
"type": "string"
},
"message": {
"type": "string"
},
"details": {}
},
"required": [
"code",
"message"
],
"additionalProperties": false
}
},
"required": [
"error"
],
"additionalProperties": false
}
}
}
},
"409": {
"description": "Canonical identity conflict",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"error": {
"type": "object",
"properties": {
"code": {
"type": "string"
},
"message": {
"type": "string"
},
"details": {}
},
"required": [
"code",
"message"
],
"additionalProperties": false
}
},
"required": [
"error"
],
"additionalProperties": false
}
}
}
},
"422": {
"description": "Domain rule rejected the command",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"error": {
"type": "object",
"properties": {
"code": {
"type": "string"
},
"message": {
"type": "string"
},
"details": {}
},
"required": [
"code",
"message"
],
"additionalProperties": false
}
},
"required": [
"error"
],
"additionalProperties": false
}
}
}
},
"500": {
"description": "Parameter authoring unavailable",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"error": {
"type": "object",
"properties": {
"code": {
"type": "string"
},
"message": {
"type": "string"
},
"details": {}
},
"required": [
"code",
"message"
],
"additionalProperties": false
}
},
"required": [
"error"
],
"additionalProperties": false
}
}
}
}
}
},
"delete": {
"operationId": "deleteParameterKey",
"tags": [
"BIM Ontology / Parameter Dictionary"
],
"summary": "Delete a ParameterKey",
"description": "Hard-deletes a ParameterKey by domain_id (ADR-0048). Cascade-deletes the linked Revit parameter projection — the only path that deletes a projection rather than archiving it. Refuses with 409 when class_parameter assignments still exist unless deleteAssignments=true, which hard-deletes those assignment rows (direct and rendered) before the key. Idempotent: an already-deleted key returns 204.",
"security": [
{
"apiKeyAuth": []
}
],
"x-domain": {
"appVersion": "api-v1",
"boundedContext": "bim-ontology",
"service": "api-v1",
"aggregate": "ParameterKey",
"useCase": "DeleteParameterKeyUseCase",
"domainEvents": [
"ParameterKeyDeleted"
],
"domainErrors": [
"ParameterKeyDomainError",
"ApplicationError"
]
},
"parameters": [
{
"in": "path",
"name": "id",
"required": true,
"schema": {
"type": "string"
}
},
{
"in": "query",
"name": "deleteAssignments",
"required": false,
"schema": {
"type": "boolean",
"default": false
},
"description": "When true, hard-deletes blocking class_parameter rows before deleting the key. Default false refuses with 409."
}
],
"responses": {
"204": {
"description": "Parameter key deleted (idempotent if already gone)"
},
"400": {
"description": "Invalid request",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"error": {
"type": "object",
"properties": {
"code": {
"type": "string"
},
"message": {
"type": "string"
},
"details": {}
},
"required": [
"code",
"message"
],
"additionalProperties": false
}
},
"required": [
"error"
],
"additionalProperties": false
}
}
}
},
"401": {
"description": "Authenticated caller required",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"error": {
"type": "object",
"properties": {
"code": {
"type": "string"
},
"message": {
"type": "string"
},
"details": {}
},
"required": [
"code",
"message"
],
"additionalProperties": false
}
},
"required": [
"error"
],
"additionalProperties": false
}
}
}
},
"409": {
"description": "Blocked by class_parameter assignments; details.blockingAssignments lists domain_ids",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"error": {
"type": "object",
"properties": {
"code": {
"type": "string"
},
"message": {
"type": "string"
},
"details": {}
},
"required": [
"code",
"message"
],
"additionalProperties": false
}
},
"required": [
"error"
],
"additionalProperties": false
}
}
}
},
"422": {
"description": "Domain rule rejected the command",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"error": {
"type": "object",
"properties": {
"code": {
"type": "string"
},
"message": {
"type": "string"
},
"details": {}
},
"required": [
"code",
"message"
],
"additionalProperties": false
}
},
"required": [
"error"
],
"additionalProperties": false
}
}
}
},
"500": {
"description": "Parameter authoring unavailable",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"error": {
"type": "object",
"properties": {
"code": {
"type": "string"
},
"message": {
"type": "string"
},
"details": {}
},
"required": [
"code",
"message"
],
"additionalProperties": false
}
},
"required": [
"error"
],
"additionalProperties": false
}
}
}
}
}
}
},
"/api/v1/bim-ontology/parameter-keys/{id}/actions/update": {
"post": {
"operationId": "updateParameterKey",
"tags": [
"BIM Ontology / Parameter Dictionary"
],
"summary": "Update a canonical parameter key",
"security": [
{
"apiKeyAuth": []
}
],
"x-domain": {
"appVersion": "api-v1",
"boundedContext": "bim-ontology",
"service": "api-v1",
"aggregate": "ParameterKey",
"useCase": "UpdateParameterKeyUseCase",
"domainEvents": [
"ParameterKeyUpdated"
],
"domainErrors": [
"ParameterKeyDomainError",
"ApplicationError"
]
},
"parameters": [
{
"in": "path",
"name": "id",
"required": true,
"schema": {
"type": "string"
}
}
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"name": {
"type": "string",
"minLength": 1
},
"description": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
]
},
"parameterSpecId": {
"anyOf": [
{
"type": "string",
"minLength": 1
},
{
"type": "null"
}
]
},
"parameterControlId": {
"anyOf": [
{
"type": "string",
"minLength": 1
},
{
"type": "null"
}
]
},
"usages": {
"anyOf": [
{
"type": "array",
"items": {
"type": "string",
"enum": [
"variant_option",
"product_spec",
"bim_parameter",
"mfg_takeoff",
"qto_takeoff"
]
}
},
{
"type": "null"
}
]
}
},
"additionalProperties": false
}
}
}
},
"responses": {
"200": {
"description": "Parameter key updated",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"data": {
"type": "object",
"properties": {
"id": {
"type": "string"
},
"name": {
"type": "string"
},
"handle": {
"type": "string"
},
"description": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
]
},
"usages": {
"type": "array",
"items": {
"type": "string",
"enum": [
"variant_option",
"product_spec",
"bim_parameter",
"mfg_takeoff",
"qto_takeoff"
]
},
"description": "Legacy catalog usages on ParameterKey; may be empty. ObjectClass offerability is via class-parameters / effective-parameters, not usages."
},
"parameterSpecId": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
]
},
"parameterControlId": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
]
},
"parameterGroupId": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
]
},
"parameterValueGroupIds": {
"type": "array",
"items": {
"type": "string"
}
},
"lifecycleStatus": {
"type": "string",
"enum": [
"draft",
"active",
"approved",
"archived"
]
},
"isSystem": {
"type": "boolean"
}
},
"required": [
"id",
"name",
"handle",
"description",
"usages",
"parameterSpecId",
"parameterControlId",
"parameterGroupId",
"parameterValueGroupIds",
"lifecycleStatus",
"isSystem"
],
"additionalProperties": false
}
},
"required": [
"data"
],
"additionalProperties": false
}
}
}
},
"400": {
"description": "Invalid request",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"error": {
"type": "object",
"properties": {
"code": {
"type": "string"
},
"message": {
"type": "string"
},
"details": {}
},
"required": [
"code",
"message"
],
"additionalProperties": false
}
},
"required": [
"error"
],
"additionalProperties": false
}
}
}
},
"401": {
"description": "Authenticated caller required",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"error": {
"type": "object",
"properties": {
"code": {
"type": "string"
},
"message": {
"type": "string"
},
"details": {}
},
"required": [
"code",
"message"
],
"additionalProperties": false
}
},
"required": [
"error"
],
"additionalProperties": false
}
}
}
},
"404": {
"description": "Resource not found",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"error": {
"type": "object",
"properties": {
"code": {
"type": "string"
},
"message": {
"type": "string"
},
"details": {}
},
"required": [
"code",
"message"
],
"additionalProperties": false
}
},
"required": [
"error"
],
"additionalProperties": false
}
}
}
},
"409": {
"description": "Canonical identity conflict",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"error": {
"type": "object",
"properties": {
"code": {
"type": "string"
},
"message": {
"type": "string"
},
"details": {}
},
"required": [
"code",
"message"
],
"additionalProperties": false
}
},
"required": [
"error"
],
"additionalProperties": false
}
}
}
},
"422": {
"description": "Domain rule rejected the command",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"error": {
"type": "object",
"properties": {
"code": {
"type": "string"
},
"message": {
"type": "string"
},
"details": {}
},
"required": [
"code",
"message"
],
"additionalProperties": false
}
},
"required": [
"error"
],
"additionalProperties": false
}
}
}
},
"500": {
"description": "Parameter authoring unavailable",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"error": {
"type": "object",
"properties": {
"code": {
"type": "string"
},
"message": {
"type": "string"
},
"details": {}
},
"required": [
"code",
"message"
],
"additionalProperties": false
}
},
"required": [
"error"
],
"additionalProperties": false
}
}
}
}
}
}
},
"/api/v1/bim-ontology/parameter-keys/{id}/actions/activate": {
"post": {
"operationId": "activateParameterKey",
"tags": [
"BIM Ontology / Parameter Dictionary"
],
"summary": "Activate a canonical parameter key",
"security": [
{
"apiKeyAuth": []
}
],
"x-domain": {
"appVersion": "api-v1",
"boundedContext": "bim-ontology",
"service": "api-v1",
"aggregate": "ParameterKey",
"useCase": "ActivateParameterKeyUseCase",
"domainEvents": [
"ParameterKeyActivated"
],
"domainErrors": [
"ParameterKeyDomainError",
"ApplicationError"
]
},
"parameters": [
{
"in": "path",
"name": "id",
"required": true,
"schema": {
"type": "string"
}
}
],
"responses": {
"200": {
"description": "Parameter key activated",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"data": {
"type": "object",
"properties": {
"id": {
"type": "string"
},
"name": {
"type": "string"
},
"handle": {
"type": "string"
},
"description": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
]
},
"usages": {
"type": "array",
"items": {
"type": "string",
"enum": [
"variant_option",
"product_spec",
"bim_parameter",
"mfg_takeoff",
"qto_takeoff"
]
},
"description": "Legacy catalog usages on ParameterKey; may be empty. ObjectClass offerability is via class-parameters / effective-parameters, not usages."
},
"parameterSpecId": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
]
},
"parameterControlId": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
]
},
"parameterGroupId": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
]
},
"parameterValueGroupIds": {
"type": "array",
"items": {
"type": "string"
}
},
"lifecycleStatus": {
"type": "string",
"enum": [
"draft",
"active",
"approved",
"archived"
]
},
"isSystem": {
"type": "boolean"
}
},
"required": [
"id",
"name",
"handle",
"description",
"usages",
"parameterSpecId",
"parameterControlId",
"parameterGroupId",
"parameterValueGroupIds",
"lifecycleStatus",
"isSystem"
],
"additionalProperties": false
}
},
"required": [
"data"
],
"additionalProperties": false
}
}
}
},
"400": {
"description": "Invalid request",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"error": {
"type": "object",
"properties": {
"code": {
"type": "string"
},
"message": {
"type": "string"
},
"details": {}
},
"required": [
"code",
"message"
],
"additionalProperties": false
}
},
"required": [
"error"
],
"additionalProperties": false
}
}
}
},
"401": {
"description": "Authenticated caller required",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"error": {
"type": "object",
"properties": {
"code": {
"type": "string"
},
"message": {
"type": "string"
},
"details": {}
},
"required": [
"code",
"message"
],
"additionalProperties": false
}
},
"required": [
"error"
],
"additionalProperties": false
}
}
}
},
"404": {
"description": "Resource not found",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"error": {
"type": "object",
"properties": {
"code": {
"type": "string"
},
"message": {
"type": "string"
},
"details": {}
},
"required": [
"code",
"message"
],
"additionalProperties": false
}
},
"required": [
"error"
],
"additionalProperties": false
}
}
}
},
"409": {
"description": "Canonical identity conflict",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"error": {
"type": "object",
"properties": {
"code": {
"type": "string"
},
"message": {
"type": "string"
},
"details": {}
},
"required": [
"code",
"message"
],
"additionalProperties": false
}
},
"required": [
"error"
],
"additionalProperties": false
}
}
}
},
"422": {
"description": "Domain rule rejected the command",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"error": {
"type": "object",
"properties": {
"code": {
"type": "string"
},
"message": {
"type": "string"
},
"details": {}
},
"required": [
"code",
"message"
],
"additionalProperties": false
}
},
"required": [
"error"
],
"additionalProperties": false
}
}
}
},
"500": {
"description": "Parameter authoring unavailable",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"error": {
"type": "object",
"properties": {
"code": {
"type": "string"
},
"message": {
"type": "string"
},
"details": {}
},
"required": [
"code",
"message"
],
"additionalProperties": false
}
},
"required": [
"error"
],
"additionalProperties": false
}
}
}
}
}
}
},
"/api/v1/bim-ontology/parameter-keys/{id}/actions/approve": {
"post": {
"operationId": "approveParameterKey",
"tags": [
"BIM Ontology / Parameter Dictionary"
],
"summary": "Approve a canonical parameter key",
"description": "Stewardship gate: spec + control required. Legal from draft or active. Projection ensure runs only when usages includes bim_parameter.",
"security": [
{
"apiKeyAuth": []
}
],
"x-domain": {
"appVersion": "api-v1",
"boundedContext": "bim-ontology",
"service": "api-v1",
"aggregate": "ParameterKey",
"useCase": "ApproveParameterKeyUseCase",
"domainEvents": [
"ParameterKeyApproved"
],
"domainErrors": [
"ParameterKeyDomainError",
"ApplicationError"
]
},
"parameters": [
{
"in": "path",
"name": "id",
"required": true,
"schema": {
"type": "string"
}
}
],
"responses": {
"200": {
"description": "Parameter key approved",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"data": {
"type": "object",
"properties": {
"id": {
"type": "string"
},
"name": {
"type": "string"
},
"handle": {
"type": "string"
},
"description": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
]
},
"usages": {
"type": "array",
"items": {
"type": "string",
"enum": [
"variant_option",
"product_spec",
"bim_parameter",
"mfg_takeoff",
"qto_takeoff"
]
},
"description": "Legacy catalog usages on ParameterKey; may be empty. ObjectClass offerability is via class-parameters / effective-parameters, not usages."
},
"parameterSpecId": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
]
},
"parameterControlId": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
]
},
"parameterGroupId": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
]
},
"parameterValueGroupIds": {
"type": "array",
"items": {
"type": "string"
}
},
"lifecycleStatus": {
"type": "string",
"enum": [
"draft",
"active",
"approved",
"archived"
]
},
"isSystem": {
"type": "boolean"
}
},
"required": [
"id",
"name",
"handle",
"description",
"usages",
"parameterSpecId",
"parameterControlId",
"parameterGroupId",
"parameterValueGroupIds",
"lifecycleStatus",
"isSystem"
],
"additionalProperties": false
}
},
"required": [
"data"
],
"additionalProperties": false
}
}
}
},
"400": {
"description": "Invalid request",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"error": {
"type": "object",
"properties": {
"code": {
"type": "string"
},
"message": {
"type": "string"
},
"details": {}
},
"required": [
"code",
"message"
],
"additionalProperties": false
}
},
"required": [
"error"
],
"additionalProperties": false
}
}
}
},
"401": {
"description": "Authenticated caller required",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"error": {
"type": "object",
"properties": {
"code": {
"type": "string"
},
"message": {
"type": "string"
},
"details": {}
},
"required": [
"code",
"message"
],
"additionalProperties": false
}
},
"required": [
"error"
],
"additionalProperties": false
}
}
}
},
"404": {
"description": "Resource not found",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"error": {
"type": "object",
"properties": {
"code": {
"type": "string"
},
"message": {
"type": "string"
},
"details": {}
},
"required": [
"code",
"message"
],
"additionalProperties": false
}
},
"required": [
"error"
],
"additionalProperties": false
}
}
}
},
"409": {
"description": "Canonical identity conflict",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"error": {
"type": "object",
"properties": {
"code": {
"type": "string"
},
"message": {
"type": "string"
},
"details": {}
},
"required": [
"code",
"message"
],
"additionalProperties": false
}
},
"required": [
"error"
],
"additionalProperties": false
}
}
}
},
"422": {
"description": "Domain rule rejected the command",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"error": {
"type": "object",
"properties": {
"code": {
"type": "string"
},
"message": {
"type": "string"
},
"details": {}
},
"required": [
"code",
"message"
],
"additionalProperties": false
}
},
"required": [
"error"
],
"additionalProperties": false
}
}
}
},
"500": {
"description": "Parameter authoring unavailable",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"error": {
"type": "object",
"properties": {
"code": {
"type": "string"
},
"message": {
"type": "string"
},
"details": {}
},
"required": [
"code",
"message"
],
"additionalProperties": false
}
},
"required": [
"error"
],
"additionalProperties": false
}
}
}
}
}
}
},
"/api/v1/bim-ontology/parameter-keys/{id}/actions/archive": {
"post": {
"operationId": "archiveParameterKey",
"tags": [
"BIM Ontology / Parameter Dictionary"
],
"summary": "Archive a canonical parameter key",
"security": [
{
"apiKeyAuth": []
}
],
"x-domain": {
"appVersion": "api-v1",
"boundedContext": "bim-ontology",
"service": "api-v1",
"aggregate": "ParameterKey",
"useCase": "ArchiveParameterKeyUseCase",
"domainEvents": [
"ParameterKeyArchived"
],
"domainErrors": [
"ParameterKeyDomainError",
"ApplicationError"
]
},
"parameters": [
{
"in": "path",
"name": "id",
"required": true,
"schema": {
"type": "string"
}
}
],
"responses": {
"200": {
"description": "Parameter key archived",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"data": {
"type": "object",
"properties": {
"id": {
"type": "string"
},
"name": {
"type": "string"
},
"handle": {
"type": "string"
},
"description": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
]
},
"usages": {
"type": "array",
"items": {
"type": "string",
"enum": [
"variant_option",
"product_spec",
"bim_parameter",
"mfg_takeoff",
"qto_takeoff"
]
},
"description": "Legacy catalog usages on ParameterKey; may be empty. ObjectClass offerability is via class-parameters / effective-parameters, not usages."
},
"parameterSpecId": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
]
},
"parameterControlId": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
]
},
"parameterGroupId": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
]
},
"parameterValueGroupIds": {
"type": "array",
"items": {
"type": "string"
}
},
"lifecycleStatus": {
"type": "string",
"enum": [
"draft",
"active",
"approved",
"archived"
]
},
"isSystem": {
"type": "boolean"
}
},
"required": [
"id",
"name",
"handle",
"description",
"usages",
"parameterSpecId",
"parameterControlId",
"parameterGroupId",
"parameterValueGroupIds",
"lifecycleStatus",
"isSystem"
],
"additionalProperties": false
}
},
"required": [
"data"
],
"additionalProperties": false
}
}
}
},
"400": {
"description": "Invalid request",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"error": {
"type": "object",
"properties": {
"code": {
"type": "string"
},
"message": {
"type": "string"
},
"details": {}
},
"required": [
"code",
"message"
],
"additionalProperties": false
}
},
"required": [
"error"
],
"additionalProperties": false
}
}
}
},
"401": {
"description": "Authenticated caller required",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"error": {
"type": "object",
"properties": {
"code": {
"type": "string"
},
"message": {
"type": "string"
},
"details": {}
},
"required": [
"code",
"message"
],
"additionalProperties": false
}
},
"required": [
"error"
],
"additionalProperties": false
}
}
}
},
"404": {
"description": "Resource not found",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"error": {
"type": "object",
"properties": {
"code": {
"type": "string"
},
"message": {
"type": "string"
},
"details": {}
},
"required": [
"code",
"message"
],
"additionalProperties": false
}
},
"required": [
"error"
],
"additionalProperties": false
}
}
}
},
"409": {
"description": "Canonical identity conflict",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"error": {
"type": "object",
"properties": {
"code": {
"type": "string"
},
"message": {
"type": "string"
},
"details": {}
},
"required": [
"code",
"message"
],
"additionalProperties": false
}
},
"required": [
"error"
],
"additionalProperties": false
}
}
}
},
"422": {
"description": "Domain rule rejected the command",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"error": {
"type": "object",
"properties": {
"code": {
"type": "string"
},
"message": {
"type": "string"
},
"details": {}
},
"required": [
"code",
"message"
],
"additionalProperties": false
}
},
"required": [
"error"
],
"additionalProperties": false
}
}
}
},
"500": {
"description": "Parameter authoring unavailable",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"error": {
"type": "object",
"properties": {
"code": {
"type": "string"
},
"message": {
"type": "string"
},
"details": {}
},
"required": [
"code",
"message"
],
"additionalProperties": false
}
},
"required": [
"error"
],
"additionalProperties": false
}
}
}
}
}
}
},
"/api/v1/bim-ontology/parameter-keys/{id}/group": {
"put": {
"operationId": "assignParameterKeyToGroup",
"tags": [
"BIM Ontology / Parameter Dictionary"
],
"summary": "Assign a parameter key to its canonical group",
"security": [
{
"apiKeyAuth": []
}
],
"x-domain": {
"appVersion": "api-v1",
"boundedContext": "bim-ontology",
"service": "api-v1",
"aggregate": "ParameterKey",
"useCase": "AssignParameterKeyToGroupUseCase",
"domainEvents": [
"ParameterKeyUpdated"
],
"domainErrors": [
"ParameterKeyDomainError",
"ApplicationError"
]
},
"parameters": [
{
"in": "path",
"name": "id",
"required": true,
"schema": {
"type": "string"
}
}
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"parameterGroupId": {
"anyOf": [
{
"type": "string",
"minLength": 1
},
{
"type": "null"
}
]
}
},
"required": [
"parameterGroupId"
],
"additionalProperties": false
}
}
}
},
"responses": {
"200": {
"description": "Parameter group assignment updated",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"data": {
"type": "object",
"properties": {
"id": {
"type": "string"
},
"name": {
"type": "string"
},
"handle": {
"type": "string"
},
"description": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
]
},
"usages": {
"type": "array",
"items": {
"type": "string",
"enum": [
"variant_option",
"product_spec",
"bim_parameter",
"mfg_takeoff",
"qto_takeoff"
]
},
"description": "Legacy catalog usages on ParameterKey; may be empty. ObjectClass offerability is via class-parameters / effective-parameters, not usages."
},
"parameterSpecId": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
]
},
"parameterControlId": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
]
},
"parameterGroupId": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
]
},
"parameterValueGroupIds": {
"type": "array",
"items": {
"type": "string"
}
},
"lifecycleStatus": {
"type": "string",
"enum": [
"draft",
"active",
"approved",
"archived"
]
},
"isSystem": {
"type": "boolean"
}
},
"required": [
"id",
"name",
"handle",
"description",
"usages",
"parameterSpecId",
"parameterControlId",
"parameterGroupId",
"parameterValueGroupIds",
"lifecycleStatus",
"isSystem"
],
"additionalProperties": false
}
},
"required": [
"data"
],
"additionalProperties": false
}
}
}
},
"400": {
"description": "Invalid request",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"error": {
"type": "object",
"properties": {
"code": {
"type": "string"
},
"message": {
"type": "string"
},
"details": {}
},
"required": [
"code",
"message"
],
"additionalProperties": false
}
},
"required": [
"error"
],
"additionalProperties": false
}
}
}
},
"401": {
"description": "Authenticated caller required",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"error": {
"type": "object",
"properties": {
"code": {
"type": "string"
},
"message": {
"type": "string"
},
"details": {}
},
"required": [
"code",
"message"
],
"additionalProperties": false
}
},
"required": [
"error"
],
"additionalProperties": false
}
}
}
},
"404": {
"description": "Resource not found",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"error": {
"type": "object",
"properties": {
"code": {
"type": "string"
},
"message": {
"type": "string"
},
"details": {}
},
"required": [
"code",
"message"
],
"additionalProperties": false
}
},
"required": [
"error"
],
"additionalProperties": false
}
}
}
},
"409": {
"description": "Canonical identity conflict",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"error": {
"type": "object",
"properties": {
"code": {
"type": "string"
},
"message": {
"type": "string"
},
"details": {}
},
"required": [
"code",
"message"
],
"additionalProperties": false
}
},
"required": [
"error"
],
"additionalProperties": false
}
}
}
},
"422": {
"description": "Domain rule rejected the command",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"error": {
"type": "object",
"properties": {
"code": {
"type": "string"
},
"message": {
"type": "string"
},
"details": {}
},
"required": [
"code",
"message"
],
"additionalProperties": false
}
},
"required": [
"error"
],
"additionalProperties": false
}
}
}
},
"500": {
"description": "Parameter authoring unavailable",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"error": {
"type": "object",
"properties": {
"code": {
"type": "string"
},
"message": {
"type": "string"
},
"details": {}
},
"required": [
"code",
"message"
],
"additionalProperties": false
}
},
"required": [
"error"
],
"additionalProperties": false
}
}
}
}
}
}
},
"/api/v1/bim-ontology/parameter-groups/{id}/actions/update": {
"post": {
"operationId": "updateParameterGroup",
"tags": [
"BIM Ontology / Parameter Dictionary"
],
"summary": "Update a canonical parameter group",
"security": [
{
"apiKeyAuth": []
}
],
"x-domain": {
"appVersion": "api-v1",
"boundedContext": "bim-ontology",
"service": "api-v1",
"aggregate": "ParameterGroup",
"useCase": "UpdateParameterGroupUseCase",
"domainEvents": [
"ParameterGroupChanged"
],
"domainErrors": [
"ParameterKeyDomainError",
"ApplicationError"
]
},
"parameters": [
{
"in": "path",
"name": "id",
"required": true,
"schema": {
"type": "string"
}
}
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"name": {
"type": "string",
"minLength": 1
}
},
"required": [
"name"
],
"additionalProperties": false
}
}
}
},
"responses": {
"200": {
"description": "Parameter group updated",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"data": {
"type": "object",
"properties": {
"id": {
"type": "string"
},
"name": {
"type": "string"
}
},
"required": [
"id",
"name"
],
"additionalProperties": false
}
},
"required": [
"data"
],
"additionalProperties": false
}
}
}
},
"400": {
"description": "Invalid request",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"error": {
"type": "object",
"properties": {
"code": {
"type": "string"
},
"message": {
"type": "string"
},
"details": {}
},
"required": [
"code",
"message"
],
"additionalProperties": false
}
},
"required": [
"error"
],
"additionalProperties": false
}
}
}
},
"401": {
"description": "Authenticated caller required",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"error": {
"type": "object",
"properties": {
"code": {
"type": "string"
},
"message": {
"type": "string"
},
"details": {}
},
"required": [
"code",
"message"
],
"additionalProperties": false
}
},
"required": [
"error"
],
"additionalProperties": false
}
}
}
},
"404": {
"description": "Resource not found",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"error": {
"type": "object",
"properties": {
"code": {
"type": "string"
},
"message": {
"type": "string"
},
"details": {}
},
"required": [
"code",
"message"
],
"additionalProperties": false
}
},
"required": [
"error"
],
"additionalProperties": false
}
}
}
},
"409": {
"description": "Canonical identity conflict",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"error": {
"type": "object",
"properties": {
"code": {
"type": "string"
},
"message": {
"type": "string"
},
"details": {}
},
"required": [
"code",
"message"
],
"additionalProperties": false
}
},
"required": [
"error"
],
"additionalProperties": false
}
}
}
},
"422": {
"description": "Domain rule rejected the command",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"error": {
"type": "object",
"properties": {
"code": {
"type": "string"
},
"message": {
"type": "string"
},
"details": {}
},
"required": [
"code",
"message"
],
"additionalProperties": false
}
},
"required": [
"error"
],
"additionalProperties": false
}
}
}
},
"500": {
"description": "Parameter authoring unavailable",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"error": {
"type": "object",
"properties": {
"code": {
"type": "string"
},
"message": {
"type": "string"
},
"details": {}
},
"required": [
"code",
"message"
],
"additionalProperties": false
}
},
"required": [
"error"
],
"additionalProperties": false
}
}
}
}
}
}
},
"/api/v1/bim-ontology/parameter-values": {
"post": {
"operationId": "createParameterValue",
"tags": [
"BIM Ontology / Parameter Dictionary"
],
"summary": "Create a canonical parameter value",
"security": [
{
"apiKeyAuth": []
}
],
"x-domain": {
"appVersion": "api-v1",
"boundedContext": "bim-ontology",
"service": "api-v1",
"aggregate": "ParameterValue",
"useCase": "CreateParameterValueUseCase",
"domainEvents": [],
"domainErrors": [
"ParameterKeyDomainError",
"ApplicationError"
]
},
"parameters": [
{
"in": "header",
"name": "Idempotency-Key",
"required": true,
"schema": {
"type": "string",
"minLength": 1
}
}
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"parameterKeyId": {
"type": "string",
"minLength": 1
},
"name": {
"type": "string",
"minLength": 1
},
"code": {
"anyOf": [
{
"type": "string",
"minLength": 1
},
{
"type": "null"
}
]
},
"quantityValues": {
"type": "array",
"items": {
"type": "object",
"properties": {
"value": {
"type": "number"
},
"unitId": {
"type": "string",
"minLength": 1
},
"role": {
"type": "string",
"enum": [
"primary",
"primary min",
"primary max",
"alternate",
"alternate min",
"alternate max",
"modifier"
]
}
},
"required": [
"value",
"unitId",
"role"
],
"additionalProperties": false
}
}
},
"required": [
"parameterKeyId",
"name"
],
"additionalProperties": false
}
}
}
},
"responses": {
"201": {
"description": "Parameter value created",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"data": {
"type": "object",
"properties": {
"id": {
"type": "string"
},
"parameterKeyId": {
"type": "string"
},
"name": {
"type": "string"
},
"code": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
]
},
"quantityValues": {
"type": "array",
"items": {
"type": "object",
"properties": {
"value": {
"type": "number"
},
"unitId": {
"type": "string",
"minLength": 1
},
"role": {
"type": "string",
"enum": [
"primary",
"primary min",
"primary max",
"alternate",
"alternate min",
"alternate max",
"modifier"
]
}
},
"required": [
"value",
"unitId",
"role"
],
"additionalProperties": false
}
}
},
"required": [
"id",
"parameterKeyId",
"name",
"code",
"quantityValues"
],
"additionalProperties": false
}
},
"required": [
"data"
],
"additionalProperties": false
}
}
}
},
"400": {
"description": "Invalid request",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"error": {
"type": "object",
"properties": {
"code": {
"type": "string"
},
"message": {
"type": "string"
},
"details": {}
},
"required": [
"code",
"message"
],
"additionalProperties": false
}
},
"required": [
"error"
],
"additionalProperties": false
}
}
}
},
"401": {
"description": "Authenticated caller required",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"error": {
"type": "object",
"properties": {
"code": {
"type": "string"
},
"message": {
"type": "string"
},
"details": {}
},
"required": [
"code",
"message"
],
"additionalProperties": false
}
},
"required": [
"error"
],
"additionalProperties": false
}
}
}
},
"404": {
"description": "Resource not found",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"error": {
"type": "object",
"properties": {
"code": {
"type": "string"
},
"message": {
"type": "string"
},
"details": {}
},
"required": [
"code",
"message"
],
"additionalProperties": false
}
},
"required": [
"error"
],
"additionalProperties": false
}
}
}
},
"409": {
"description": "Canonical identity conflict",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"error": {
"type": "object",
"properties": {
"code": {
"type": "string"
},
"message": {
"type": "string"
},
"details": {}
},
"required": [
"code",
"message"
],
"additionalProperties": false
}
},
"required": [
"error"
],
"additionalProperties": false
}
}
}
},
"422": {
"description": "Domain rule rejected the command",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"error": {
"type": "object",
"properties": {
"code": {
"type": "string"
},
"message": {
"type": "string"
},
"details": {}
},
"required": [
"code",
"message"
],
"additionalProperties": false
}
},
"required": [
"error"
],
"additionalProperties": false
}
}
}
},
"500": {
"description": "Parameter authoring unavailable",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"error": {
"type": "object",
"properties": {
"code": {
"type": "string"
},
"message": {
"type": "string"
},
"details": {}
},
"required": [
"code",
"message"
],
"additionalProperties": false
}
},
"required": [
"error"
],
"additionalProperties": false
}
}
}
}
}
}
},
"/api/v1/bim-ontology/parameter-values/{id}": {
"put": {
"operationId": "updateParameterValue",
"tags": [
"BIM Ontology / Parameter Dictionary"
],
"summary": "Replace authored parameter value fields",
"security": [
{
"apiKeyAuth": []
}
],
"x-domain": {
"appVersion": "api-v1",
"boundedContext": "bim-ontology",
"service": "api-v1",
"aggregate": "ParameterValue",
"useCase": "UpdateParameterValueUseCase",
"domainEvents": [],
"domainErrors": [
"ParameterKeyDomainError",
"ApplicationError"
]
},
"parameters": [
{
"in": "path",
"name": "id",
"required": true,
"schema": {
"type": "string"
}
}
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"parameterKeyId": {
"type": "string",
"minLength": 1
},
"name": {
"type": "string",
"minLength": 1
},
"code": {
"anyOf": [
{
"type": "string",
"minLength": 1
},
{
"type": "null"
}
]
},
"quantityValues": {
"type": "array",
"items": {
"type": "object",
"properties": {
"value": {
"type": "number"
},
"unitId": {
"type": "string",
"minLength": 1
},
"role": {
"type": "string",
"enum": [
"primary",
"primary min",
"primary max",
"alternate",
"alternate min",
"alternate max",
"modifier"
]
}
},
"required": [
"value",
"unitId",
"role"
],
"additionalProperties": false
}
}
},
"required": [
"parameterKeyId",
"name"
],
"additionalProperties": false
}
}
}
},
"responses": {
"200": {
"description": "Parameter value updated",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"data": {
"type": "object",
"properties": {
"id": {
"type": "string"
},
"parameterKeyId": {
"type": "string"
},
"name": {
"type": "string"
},
"code": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
]
},
"quantityValues": {
"type": "array",
"items": {
"type": "object",
"properties": {
"value": {
"type": "number"
},
"unitId": {
"type": "string",
"minLength": 1
},
"role": {
"type": "string",
"enum": [
"primary",
"primary min",
"primary max",
"alternate",
"alternate min",
"alternate max",
"modifier"
]
}
},
"required": [
"value",
"unitId",
"role"
],
"additionalProperties": false
}
}
},
"required": [
"id",
"parameterKeyId",
"name",
"code",
"quantityValues"
],
"additionalProperties": false
}
},
"required": [
"data"
],
"additionalProperties": false
}
}
}
},
"400": {
"description": "Invalid request",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"error": {
"type": "object",
"properties": {
"code": {
"type": "string"
},
"message": {
"type": "string"
},
"details": {}
},
"required": [
"code",
"message"
],
"additionalProperties": false
}
},
"required": [
"error"
],
"additionalProperties": false
}
}
}
},
"401": {
"description": "Authenticated caller required",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"error": {
"type": "object",
"properties": {
"code": {
"type": "string"
},
"message": {
"type": "string"
},
"details": {}
},
"required": [
"code",
"message"
],
"additionalProperties": false
}
},
"required": [
"error"
],
"additionalProperties": false
}
}
}
},
"404": {
"description": "Resource not found",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"error": {
"type": "object",
"properties": {
"code": {
"type": "string"
},
"message": {
"type": "string"
},
"details": {}
},
"required": [
"code",
"message"
],
"additionalProperties": false
}
},
"required": [
"error"
],
"additionalProperties": false
}
}
}
},
"409": {
"description": "Canonical identity conflict",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"error": {
"type": "object",
"properties": {
"code": {
"type": "string"
},
"message": {
"type": "string"
},
"details": {}
},
"required": [
"code",
"message"
],
"additionalProperties": false
}
},
"required": [
"error"
],
"additionalProperties": false
}
}
}
},
"422": {
"description": "Domain rule rejected the command",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"error": {
"type": "object",
"properties": {
"code": {
"type": "string"
},
"message": {
"type": "string"
},
"details": {}
},
"required": [
"code",
"message"
],
"additionalProperties": false
}
},
"required": [
"error"
],
"additionalProperties": false
}
}
}
},
"500": {
"description": "Parameter authoring unavailable",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"error": {
"type": "object",
"properties": {
"code": {
"type": "string"
},
"message": {
"type": "string"
},
"details": {}
},
"required": [
"code",
"message"
],
"additionalProperties": false
}
},
"required": [
"error"
],
"additionalProperties": false
}
}
}
}
}
}
},
"/api/v1/bim-ontology/object-classes/{key}/parameters": {
"get": {
"operationId": "listClassParameters",
"tags": [
"BIM Ontology / Class Parameters"
],
"summary": "List direct, inherited, or subtree class parameters",
"description": "The key path parameter is ObjectClass.handle, not domain_id. Default (`extent=self`, `includeInherited=false`) returns authored assignments at this class only (governance/edit). `includeInherited=true` adds ancestor assignments in a simpler list shape; it overlaps the self+ancestors coverage of `GET /object-classes/{key}/effective-parameters`, which is the canonical plugin target for resolved self+ancestors (inheritance path, conflicts, revitProjectionReadiness). Prefer effective-parameters for that UX. `extent=subtree` returns direct assignments on this class and all descendants (`includeInherited` is ignored).",
"security": [
{
"apiKeyAuth": []
}
],
"x-domain": {
"appVersion": "api-v1",
"boundedContext": "bim-ontology",
"service": "api-v1",
"aggregate": "ClassParameter",
"useCase": "ListClassParametersUseCase",
"domainEvents": [],
"domainErrors": [
"ParameterKeyNotBindableError",
"InheritedClassParameterReadOnlyError",
"RenderedClassParameterReadOnlyError",
"LegacyUnknownClassParameterReadOnlyError",
"ClassParameterInheritanceMirrorConflictError",
"ParameterValueNotFoundError",
"ParameterValueDoesNotBelongToKeyError"
]
},
"parameters": [
{
"in": "path",
"name": "key",
"required": true,
"schema": {
"type": "string",
"minLength": 1
}
},
{
"in": "query",
"name": "includeInherited",
"schema": {
"type": "boolean",
"default": false
},
"description": "When extent=self, include ancestor assignments. Overlaps effective-parameters for self+ancestors; prefer effective-parameters for plugin dropdowns."
},
{
"in": "query",
"name": "extent",
"schema": {
"type": "string",
"enum": [
"self",
"subtree"
],
"default": "self"
},
"description": "self = this class (optionally + ancestors via includeInherited). subtree = direct assignments on this class and all descendants."
}
],
"responses": {
"200": {
"description": "Class parameters returned",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"items": {
"type": "array",
"items": {
"type": "object",
"properties": {
"id": {
"type": "string"
},
"objectClassId": {
"type": "string"
},
"objectClassKey": {
"type": "string"
},
"parameterKeyId": {
"type": "string"
},
"parameterKey": {
"anyOf": [
{
"type": "object",
"properties": {
"id": {
"type": "string"
},
"handle": {
"type": "string"
},
"name": {
"type": "string"
},
"description": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
]
}
},
"required": [
"id",
"handle",
"name",
"description"
],
"additionalProperties": false
},
{
"type": "null"
}
]
},
"isRequired": {
"type": "boolean"
},
"inheritanceMode": {
"anyOf": [
{
"type": "string",
"enum": [
"unique",
"apply_to_descendants"
]
},
{
"type": "null"
}
]
},
"childBehavior": {
"anyOf": [
{
"type": "string",
"enum": [
"inherit",
"override",
"suppress"
]
},
{
"type": "null"
}
]
},
"provenance": {
"type": "string",
"enum": [
"direct",
"rendered",
"legacy_unknown",
"inherited"
],
"description": "Persisted assignment origin, or inherited for a computed ancestor assignment.",
"readOnly": true
},
"readOnly": {
"type": "boolean",
"description": "Only direct assignments permit DELETE and allowed-value mutation.",
"readOnly": true
},
"inheritedFromObjectClassId": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
]
},
"allowedValues": {
"type": "array",
"items": {
"type": "object",
"properties": {
"id": {
"type": "string"
},
"parameterValueId": {
"type": "string"
},
"isDefault": {
"type": "boolean"
},
"isNominal": {
"type": "boolean"
},
"sortOrder": {
"anyOf": [
{
"type": "integer",
"minimum": -9007199254740991,
"maximum": 9007199254740991
},
{
"type": "null"
}
]
}
},
"required": [
"id",
"parameterValueId",
"isDefault",
"isNominal",
"sortOrder"
],
"additionalProperties": false
}
}
},
"required": [
"id",
"objectClassId",
"objectClassKey",
"parameterKeyId",
"parameterKey",
"isRequired",
"inheritanceMode",
"childBehavior",
"provenance",
"readOnly",
"inheritedFromObjectClassId",
"allowedValues"
],
"additionalProperties": false
}
},
"page": {
"type": "object",
"properties": {
"total": {
"type": "integer",
"minimum": 0,
"maximum": 9007199254740991
},
"page": {
"type": "integer",
"minimum": 0,
"maximum": 9007199254740991
},
"limit": {
"type": "integer",
"minimum": 0,
"maximum": 9007199254740991
},
"hasNextPage": {
"type": "boolean"
}
},
"required": [
"total",
"page",
"limit",
"hasNextPage"
],
"additionalProperties": false
}
},
"required": [
"items",
"page"
],
"additionalProperties": false
}
}
}
},
"400": {
"description": "Invalid request",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"error": {
"type": "object",
"properties": {
"code": {
"type": "string"
},
"message": {
"type": "string"
},
"details": {}
},
"required": [
"code",
"message"
],
"additionalProperties": false
}
},
"required": [
"error"
],
"additionalProperties": false
}
}
}
},
"401": {
"description": "Authenticated caller required",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"error": {
"type": "object",
"properties": {
"code": {
"type": "string"
},
"message": {
"type": "string"
},
"details": {}
},
"required": [
"code",
"message"
],
"additionalProperties": false
}
},
"required": [
"error"
],
"additionalProperties": false
}
}
}
},
"404": {
"description": "Object class, key, or assignment not found",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"error": {
"type": "object",
"properties": {
"code": {
"type": "string"
},
"message": {
"type": "string"
},
"details": {}
},
"required": [
"code",
"message"
],
"additionalProperties": false
}
},
"required": [
"error"
],
"additionalProperties": false
}
}
}
},
"409": {
"description": "Assignment or allowed-value conflict",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"error": {
"type": "object",
"properties": {
"code": {
"type": "string"
},
"message": {
"type": "string"
},
"details": {}
},
"required": [
"code",
"message"
],
"additionalProperties": false
}
},
"required": [
"error"
],
"additionalProperties": false
}
}
}
},
"422": {
"description": "Assignment rule rejected the command",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"error": {
"type": "object",
"properties": {
"code": {
"type": "string"
},
"message": {
"type": "string"
},
"details": {}
},
"required": [
"code",
"message"
],
"additionalProperties": false
}
},
"required": [
"error"
],
"additionalProperties": false
}
}
}
},
"500": {
"description": "Class parameter authoring unavailable",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"error": {
"type": "object",
"properties": {
"code": {
"type": "string"
},
"message": {
"type": "string"
},
"details": {}
},
"required": [
"code",
"message"
],
"additionalProperties": false
}
},
"required": [
"error"
],
"additionalProperties": false
}
}
}
}
}
}
},
"/api/v1/bim-ontology/object-classes/{key}/parameters/{parameterKeyId}": {
"put": {
"operationId": "upsertClassParameter",
"tags": [
"BIM Ontology / Class Parameters"
],
"summary": "Create, replace, or explicitly promote a class parameter to direct",
"security": [
{
"apiKeyAuth": []
}
],
"x-domain": {
"appVersion": "api-v1",
"boundedContext": "bim-ontology",
"service": "api-v1",
"aggregate": "ClassParameter",
"useCase": "UpsertClassParameterUseCase",
"domainEvents": [
"ClassParameterAssigned",
"ClassParameterAssignmentUpdated"
],
"domainErrors": [
"ParameterKeyNotBindableError",
"InheritedClassParameterReadOnlyError",
"RenderedClassParameterReadOnlyError",
"LegacyUnknownClassParameterReadOnlyError",
"ClassParameterInheritanceMirrorConflictError",
"ParameterValueNotFoundError",
"ParameterValueDoesNotBelongToKeyError"
]
},
"parameters": [
{
"in": "path",
"name": "key",
"required": true,
"schema": {
"type": "string",
"minLength": 1
}
},
{
"in": "path",
"name": "parameterKeyId",
"required": true,
"schema": {
"type": "string",
"minLength": 1
}
},
{
"in": "header",
"name": "Idempotency-Key",
"required": true,
"schema": {
"type": "string",
"minLength": 1
}
}
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"isRequired": {
"type": "boolean"
},
"inheritanceMode": {
"type": "string",
"enum": [
"unique",
"apply_to_descendants"
]
},
"childBehavior": {
"anyOf": [
{
"type": "string",
"enum": [
"inherit",
"override",
"suppress"
]
},
{
"type": "null"
}
]
},
"userId": {
"type": "string",
"minLength": 1
}
},
"required": [
"isRequired",
"inheritanceMode"
],
"additionalProperties": false
}
}
}
},
"responses": {
"200": {
"description": "Class parameter replaced",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"data": {
"type": "object",
"properties": {
"id": {
"type": "string"
},
"objectClassId": {
"type": "string"
},
"objectClassKey": {
"type": "string"
},
"parameterKeyId": {
"type": "string"
},
"parameterKey": {
"anyOf": [
{
"type": "object",
"properties": {
"id": {
"type": "string"
},
"handle": {
"type": "string"
},
"name": {
"type": "string"
},
"description": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
]
}
},
"required": [
"id",
"handle",
"name",
"description"
],
"additionalProperties": false
},
{
"type": "null"
}
]
},
"isRequired": {
"type": "boolean"
},
"inheritanceMode": {
"anyOf": [
{
"type": "string",
"enum": [
"unique",
"apply_to_descendants"
]
},
{
"type": "null"
}
]
},
"childBehavior": {
"anyOf": [
{
"type": "string",
"enum": [
"inherit",
"override",
"suppress"
]
},
{
"type": "null"
}
]
},
"provenance": {
"type": "string",
"enum": [
"direct",
"rendered",
"legacy_unknown",
"inherited"
],
"description": "Persisted assignment origin, or inherited for a computed ancestor assignment.",
"readOnly": true
},
"readOnly": {
"type": "boolean",
"description": "Only direct assignments permit DELETE and allowed-value mutation.",
"readOnly": true
},
"inheritedFromObjectClassId": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
]
},
"allowedValues": {
"type": "array",
"items": {
"type": "object",
"properties": {
"id": {
"type": "string"
},
"parameterValueId": {
"type": "string"
},
"isDefault": {
"type": "boolean"
},
"isNominal": {
"type": "boolean"
},
"sortOrder": {
"anyOf": [
{
"type": "integer",
"minimum": -9007199254740991,
"maximum": 9007199254740991
},
{
"type": "null"
}
]
}
},
"required": [
"id",
"parameterValueId",
"isDefault",
"isNominal",
"sortOrder"
],
"additionalProperties": false
}
}
},
"required": [
"id",
"objectClassId",
"objectClassKey",
"parameterKeyId",
"parameterKey",
"isRequired",
"inheritanceMode",
"childBehavior",
"provenance",
"readOnly",
"inheritedFromObjectClassId",
"allowedValues"
],
"additionalProperties": false
}
},
"required": [
"data"
],
"additionalProperties": false
}
}
}
},
"201": {
"description": "Class parameter created at the requested URI",
"headers": {
"Location": {
"description": "URI of the newly created class-parameter assignment",
"schema": {
"type": "string"
}
}
},
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"data": {
"type": "object",
"properties": {
"id": {
"type": "string"
},
"objectClassId": {
"type": "string"
},
"objectClassKey": {
"type": "string"
},
"parameterKeyId": {
"type": "string"
},
"parameterKey": {
"anyOf": [
{
"type": "object",
"properties": {
"id": {
"type": "string"
},
"handle": {
"type": "string"
},
"name": {
"type": "string"
},
"description": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
]
}
},
"required": [
"id",
"handle",
"name",
"description"
],
"additionalProperties": false
},
{
"type": "null"
}
]
},
"isRequired": {
"type": "boolean"
},
"inheritanceMode": {
"anyOf": [
{
"type": "string",
"enum": [
"unique",
"apply_to_descendants"
]
},
{
"type": "null"
}
]
},
"childBehavior": {
"anyOf": [
{
"type": "string",
"enum": [
"inherit",
"override",
"suppress"
]
},
{
"type": "null"
}
]
},
"provenance": {
"type": "string",
"enum": [
"direct",
"rendered",
"legacy_unknown",
"inherited"
],
"description": "Persisted assignment origin, or inherited for a computed ancestor assignment.",
"readOnly": true
},
"readOnly": {
"type": "boolean",
"description": "Only direct assignments permit DELETE and allowed-value mutation.",
"readOnly": true
},
"inheritedFromObjectClassId": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
]
},
"allowedValues": {
"type": "array",
"items": {
"type": "object",
"properties": {
"id": {
"type": "string"
},
"parameterValueId": {
"type": "string"
},
"isDefault": {
"type": "boolean"
},
"isNominal": {
"type": "boolean"
},
"sortOrder": {
"anyOf": [
{
"type": "integer",
"minimum": -9007199254740991,
"maximum": 9007199254740991
},
{
"type": "null"
}
]
}
},
"required": [
"id",
"parameterValueId",
"isDefault",
"isNominal",
"sortOrder"
],
"additionalProperties": false
}
}
},
"required": [
"id",
"objectClassId",
"objectClassKey",
"parameterKeyId",
"parameterKey",
"isRequired",
"inheritanceMode",
"childBehavior",
"provenance",
"readOnly",
"inheritedFromObjectClassId",
"allowedValues"
],
"additionalProperties": false
}
},
"required": [
"data"
],
"additionalProperties": false
}
}
}
},
"400": {
"description": "Invalid request",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"error": {
"type": "object",
"properties": {
"code": {
"type": "string"
},
"message": {
"type": "string"
},
"details": {}
},
"required": [
"code",
"message"
],
"additionalProperties": false
}
},
"required": [
"error"
],
"additionalProperties": false
}
}
}
},
"401": {
"description": "Authenticated caller required",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"error": {
"type": "object",
"properties": {
"code": {
"type": "string"
},
"message": {
"type": "string"
},
"details": {}
},
"required": [
"code",
"message"
],
"additionalProperties": false
}
},
"required": [
"error"
],
"additionalProperties": false
}
}
}
},
"404": {
"description": "Object class, key, or assignment not found",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"error": {
"type": "object",
"properties": {
"code": {
"type": "string"
},
"message": {
"type": "string"
},
"details": {}
},
"required": [
"code",
"message"
],
"additionalProperties": false
}
},
"required": [
"error"
],
"additionalProperties": false
}
}
}
},
"409": {
"description": "Assignment or allowed-value conflict",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"error": {
"type": "object",
"properties": {
"code": {
"type": "string"
},
"message": {
"type": "string"
},
"details": {}
},
"required": [
"code",
"message"
],
"additionalProperties": false
}
},
"required": [
"error"
],
"additionalProperties": false
}
}
}
},
"422": {
"description": "Assignment rule rejected the command",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"error": {
"type": "object",
"properties": {
"code": {
"type": "string"
},
"message": {
"type": "string"
},
"details": {}
},
"required": [
"code",
"message"
],
"additionalProperties": false
}
},
"required": [
"error"
],
"additionalProperties": false
}
}
}
},
"500": {
"description": "Class parameter authoring unavailable",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"error": {
"type": "object",
"properties": {
"code": {
"type": "string"
},
"message": {
"type": "string"
},
"details": {}
},
"required": [
"code",
"message"
],
"additionalProperties": false
}
},
"required": [
"error"
],
"additionalProperties": false
}
}
}
}
}
},
"delete": {
"operationId": "unassignClassParameter",
"tags": [
"BIM Ontology / Class Parameters"
],
"summary": "Remove a direct class-parameter assignment",
"security": [
{
"apiKeyAuth": []
}
],
"x-domain": {
"appVersion": "api-v1",
"boundedContext": "bim-ontology",
"service": "api-v1",
"aggregate": "ClassParameter",
"useCase": "UnassignClassParameterUseCase",
"domainEvents": [
"ClassParameterUnassigned"
],
"domainErrors": [
"ParameterKeyNotBindableError",
"InheritedClassParameterReadOnlyError",
"RenderedClassParameterReadOnlyError",
"LegacyUnknownClassParameterReadOnlyError",
"ClassParameterInheritanceMirrorConflictError",
"ParameterValueNotFoundError",
"ParameterValueDoesNotBelongToKeyError"
]
},
"parameters": [
{
"in": "path",
"name": "key",
"required": true,
"schema": {
"type": "string",
"minLength": 1
}
},
{
"in": "path",
"name": "parameterKeyId",
"required": true,
"schema": {
"type": "string",
"minLength": 1
}
}
],
"responses": {
"204": {
"description": "Direct assignment removed"
},
"400": {
"description": "Invalid request",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"error": {
"type": "object",
"properties": {
"code": {
"type": "string"
},
"message": {
"type": "string"
},
"details": {}
},
"required": [
"code",
"message"
],
"additionalProperties": false
}
},
"required": [
"error"
],
"additionalProperties": false
}
}
}
},
"401": {
"description": "Authenticated caller required",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"error": {
"type": "object",
"properties": {
"code": {
"type": "string"
},
"message": {
"type": "string"
},
"details": {}
},
"required": [
"code",
"message"
],
"additionalProperties": false
}
},
"required": [
"error"
],
"additionalProperties": false
}
}
}
},
"404": {
"description": "Object class, key, or assignment not found",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"error": {
"type": "object",
"properties": {
"code": {
"type": "string"
},
"message": {
"type": "string"
},
"details": {}
},
"required": [
"code",
"message"
],
"additionalProperties": false
}
},
"required": [
"error"
],
"additionalProperties": false
}
}
}
},
"409": {
"description": "Assignment or allowed-value conflict",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"error": {
"type": "object",
"properties": {
"code": {
"type": "string"
},
"message": {
"type": "string"
},
"details": {}
},
"required": [
"code",
"message"
],
"additionalProperties": false
}
},
"required": [
"error"
],
"additionalProperties": false
}
}
}
},
"422": {
"description": "Assignment rule rejected the command",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"error": {
"type": "object",
"properties": {
"code": {
"type": "string"
},
"message": {
"type": "string"
},
"details": {}
},
"required": [
"code",
"message"
],
"additionalProperties": false
}
},
"required": [
"error"
],
"additionalProperties": false
}
}
}
},
"500": {
"description": "Class parameter authoring unavailable",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"error": {
"type": "object",
"properties": {
"code": {
"type": "string"
},
"message": {
"type": "string"
},
"details": {}
},
"required": [
"code",
"message"
],
"additionalProperties": false
}
},
"required": [
"error"
],
"additionalProperties": false
}
}
}
}
}
}
},
"/api/v1/bim-ontology/object-classes/actions/assign-parameters": {
"post": {
"operationId": "assignClassParameters",
"tags": [
"BIM Ontology / Class Parameters"
],
"summary": "Assign parameter keys to object classes in bulk",
"description": "Creates or updates every ParameterKey × ObjectClass pair through the canonical single-binding authoring workflow.",
"security": [
{
"apiKeyAuth": []
}
],
"x-domain": {
"appVersion": "api-v1",
"boundedContext": "bim-ontology",
"service": "api-v1",
"aggregate": "ClassParameter",
"useCase": "BulkAssignClassParametersUseCase",
"domainEvents": [
"ClassParameterAssigned",
"ClassParameterAssignmentUpdated"
],
"domainErrors": [
"ParameterKeyNotBindableError",
"InheritedClassParameterReadOnlyError",
"RenderedClassParameterReadOnlyError",
"LegacyUnknownClassParameterReadOnlyError",
"ClassParameterInheritanceMirrorConflictError",
"ParameterValueNotFoundError",
"ParameterValueDoesNotBelongToKeyError"
]
},
"parameters": [
{
"in": "header",
"name": "Idempotency-Key",
"required": true,
"schema": {
"type": "string",
"minLength": 1
}
}
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"parameterKeyIds": {
"minItems": 1,
"maxItems": 500,
"type": "array",
"items": {
"type": "string",
"minLength": 1
}
},
"objectClassKeys": {
"minItems": 1,
"maxItems": 500,
"type": "array",
"items": {
"type": "string",
"minLength": 1
}
},
"isRequired": {
"default": false,
"type": "boolean"
},
"inheritanceMode": {
"type": "string",
"enum": [
"unique",
"apply_to_descendants"
]
},
"userId": {
"type": "string",
"minLength": 1
}
},
"required": [
"parameterKeyIds",
"objectClassKeys",
"inheritanceMode"
],
"additionalProperties": false
}
}
}
},
"responses": {
"200": {
"description": "Class parameters assigned",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"data": {
"type": "object",
"properties": {
"items": {
"type": "array",
"items": {
"type": "object",
"properties": {
"id": {
"type": "string"
},
"objectClassId": {
"type": "string"
},
"objectClassKey": {
"type": "string"
},
"parameterKeyId": {
"type": "string"
},
"parameterKey": {
"anyOf": [
{
"type": "object",
"properties": {
"id": {
"type": "string"
},
"handle": {
"type": "string"
},
"name": {
"type": "string"
},
"description": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
]
}
},
"required": [
"id",
"handle",
"name",
"description"
],
"additionalProperties": false
},
{
"type": "null"
}
]
},
"isRequired": {
"type": "boolean"
},
"inheritanceMode": {
"anyOf": [
{
"type": "string",
"enum": [
"unique",
"apply_to_descendants"
]
},
{
"type": "null"
}
]
},
"childBehavior": {
"anyOf": [
{
"type": "string",
"enum": [
"inherit",
"override",
"suppress"
]
},
{
"type": "null"
}
]
},
"provenance": {
"type": "string",
"enum": [
"direct",
"rendered",
"legacy_unknown",
"inherited"
],
"description": "Persisted assignment origin, or inherited for a computed ancestor assignment.",
"readOnly": true
},
"readOnly": {
"type": "boolean",
"description": "Only direct assignments permit DELETE and allowed-value mutation.",
"readOnly": true
},
"inheritedFromObjectClassId": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
]
},
"allowedValues": {
"type": "array",
"items": {
"type": "object",
"properties": {
"id": {
"type": "string"
},
"parameterValueId": {
"type": "string"
},
"isDefault": {
"type": "boolean"
},
"isNominal": {
"type": "boolean"
},
"sortOrder": {
"anyOf": [
{
"type": "integer",
"minimum": -9007199254740991,
"maximum": 9007199254740991
},
{
"type": "null"
}
]
}
},
"required": [
"id",
"parameterValueId",
"isDefault",
"isNominal",
"sortOrder"
],
"additionalProperties": false
}
}
},
"required": [
"id",
"objectClassId",
"objectClassKey",
"parameterKeyId",
"parameterKey",
"isRequired",
"inheritanceMode",
"childBehavior",
"provenance",
"readOnly",
"inheritedFromObjectClassId",
"allowedValues"
],
"additionalProperties": false
}
},
"createdCount": {
"type": "integer",
"minimum": 0,
"maximum": 9007199254740991
},
"updatedCount": {
"type": "integer",
"minimum": 0,
"maximum": 9007199254740991
}
},
"required": [
"items",
"createdCount",
"updatedCount"
],
"additionalProperties": false
}
},
"required": [
"data"
],
"additionalProperties": false
}
}
}
},
"400": {
"description": "Invalid request",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"error": {
"type": "object",
"properties": {
"code": {
"type": "string"
},
"message": {
"type": "string"
},
"details": {}
},
"required": [
"code",
"message"
],
"additionalProperties": false
}
},
"required": [
"error"
],
"additionalProperties": false
}
}
}
},
"401": {
"description": "Authenticated caller required",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"error": {
"type": "object",
"properties": {
"code": {
"type": "string"
},
"message": {
"type": "string"
},
"details": {}
},
"required": [
"code",
"message"
],
"additionalProperties": false
}
},
"required": [
"error"
],
"additionalProperties": false
}
}
}
},
"404": {
"description": "Object class, key, or assignment not found",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"error": {
"type": "object",
"properties": {
"code": {
"type": "string"
},
"message": {
"type": "string"
},
"details": {}
},
"required": [
"code",
"message"
],
"additionalProperties": false
}
},
"required": [
"error"
],
"additionalProperties": false
}
}
}
},
"409": {
"description": "Assignment or allowed-value conflict",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"error": {
"type": "object",
"properties": {
"code": {
"type": "string"
},
"message": {
"type": "string"
},
"details": {}
},
"required": [
"code",
"message"
],
"additionalProperties": false
}
},
"required": [
"error"
],
"additionalProperties": false
}
}
}
},
"422": {
"description": "Assignment rule rejected the command",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"error": {
"type": "object",
"properties": {
"code": {
"type": "string"
},
"message": {
"type": "string"
},
"details": {}
},
"required": [
"code",
"message"
],
"additionalProperties": false
}
},
"required": [
"error"
],
"additionalProperties": false
}
}
}
},
"500": {
"description": "Class parameter authoring unavailable",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"error": {
"type": "object",
"properties": {
"code": {
"type": "string"
},
"message": {
"type": "string"
},
"details": {}
},
"required": [
"code",
"message"
],
"additionalProperties": false
}
},
"required": [
"error"
],
"additionalProperties": false
}
}
}
}
}
}
},
"/api/v1/bim-ontology/object-classes/{key}/parameters/{parameterKeyId}/allowed-values": {
"put": {
"operationId": "replaceClassParameterAllowedValues",
"tags": [
"BIM Ontology / Class Parameters"
],
"summary": "Fully replace allowed values for a direct class parameter",
"security": [
{
"apiKeyAuth": []
}
],
"x-domain": {
"appVersion": "api-v1",
"boundedContext": "bim-ontology",
"service": "api-v1",
"aggregate": "ClassParameter",
"useCase": "ReplaceClassParameterAllowedValuesUseCase",
"domainEvents": [
"ClassParameterAllowedValuesReplaced"
],
"domainErrors": [
"ParameterKeyNotBindableError",
"InheritedClassParameterReadOnlyError",
"RenderedClassParameterReadOnlyError",
"LegacyUnknownClassParameterReadOnlyError",
"ClassParameterInheritanceMirrorConflictError",
"ParameterValueNotFoundError",
"ParameterValueDoesNotBelongToKeyError"
]
},
"parameters": [
{
"in": "path",
"name": "key",
"required": true,
"schema": {
"type": "string",
"minLength": 1
}
},
{
"in": "path",
"name": "parameterKeyId",
"required": true,
"schema": {
"type": "string",
"minLength": 1
}
}
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"allowedValues": {
"type": "array",
"items": {
"type": "object",
"properties": {
"parameterValueId": {
"type": "string",
"minLength": 1
},
"isDefault": {
"type": "boolean"
},
"isNominal": {
"type": "boolean"
},
"sortOrder": {
"anyOf": [
{
"type": "integer",
"minimum": -9007199254740991,
"maximum": 9007199254740991
},
{
"type": "null"
}
]
}
},
"required": [
"parameterValueId",
"isDefault",
"isNominal",
"sortOrder"
],
"additionalProperties": false
}
}
},
"required": [
"allowedValues"
],
"additionalProperties": false
}
}
}
},
"responses": {
"200": {
"description": "Allowed values replaced",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"data": {
"type": "object",
"properties": {
"id": {
"type": "string"
},
"objectClassId": {
"type": "string"
},
"objectClassKey": {
"type": "string"
},
"parameterKeyId": {
"type": "string"
},
"parameterKey": {
"anyOf": [
{
"type": "object",
"properties": {
"id": {
"type": "string"
},
"handle": {
"type": "string"
},
"name": {
"type": "string"
},
"description": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
]
}
},
"required": [
"id",
"handle",
"name",
"description"
],
"additionalProperties": false
},
{
"type": "null"
}
]
},
"isRequired": {
"type": "boolean"
},
"inheritanceMode": {
"anyOf": [
{
"type": "string",
"enum": [
"unique",
"apply_to_descendants"
]
},
{
"type": "null"
}
]
},
"childBehavior": {
"anyOf": [
{
"type": "string",
"enum": [
"inherit",
"override",
"suppress"
]
},
{
"type": "null"
}
]
},
"provenance": {
"type": "string",
"enum": [
"direct",
"rendered",
"legacy_unknown",
"inherited"
],
"description": "Persisted assignment origin, or inherited for a computed ancestor assignment.",
"readOnly": true
},
"readOnly": {
"type": "boolean",
"description": "Only direct assignments permit DELETE and allowed-value mutation.",
"readOnly": true
},
"inheritedFromObjectClassId": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
]
},
"allowedValues": {
"type": "array",
"items": {
"type": "object",
"properties": {
"id": {
"type": "string"
},
"parameterValueId": {
"type": "string"
},
"isDefault": {
"type": "boolean"
},
"isNominal": {
"type": "boolean"
},
"sortOrder": {
"anyOf": [
{
"type": "integer",
"minimum": -9007199254740991,
"maximum": 9007199254740991
},
{
"type": "null"
}
]
}
},
"required": [
"id",
"parameterValueId",
"isDefault",
"isNominal",
"sortOrder"
],
"additionalProperties": false
}
}
},
"required": [
"id",
"objectClassId",
"objectClassKey",
"parameterKeyId",
"parameterKey",
"isRequired",
"inheritanceMode",
"childBehavior",
"provenance",
"readOnly",
"inheritedFromObjectClassId",
"allowedValues"
],
"additionalProperties": false
}
},
"required": [
"data"
],
"additionalProperties": false
}
}
}
},
"400": {
"description": "Invalid request",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"error": {
"type": "object",
"properties": {
"code": {
"type": "string"
},
"message": {
"type": "string"
},
"details": {}
},
"required": [
"code",
"message"
],
"additionalProperties": false
}
},
"required": [
"error"
],
"additionalProperties": false
}
}
}
},
"401": {
"description": "Authenticated caller required",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"error": {
"type": "object",
"properties": {
"code": {
"type": "string"
},
"message": {
"type": "string"
},
"details": {}
},
"required": [
"code",
"message"
],
"additionalProperties": false
}
},
"required": [
"error"
],
"additionalProperties": false
}
}
}
},
"404": {
"description": "Object class, key, or assignment not found",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"error": {
"type": "object",
"properties": {
"code": {
"type": "string"
},
"message": {
"type": "string"
},
"details": {}
},
"required": [
"code",
"message"
],
"additionalProperties": false
}
},
"required": [
"error"
],
"additionalProperties": false
}
}
}
},
"409": {
"description": "Assignment or allowed-value conflict",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"error": {
"type": "object",
"properties": {
"code": {
"type": "string"
},
"message": {
"type": "string"
},
"details": {}
},
"required": [
"code",
"message"
],
"additionalProperties": false
}
},
"required": [
"error"
],
"additionalProperties": false
}
}
}
},
"422": {
"description": "Assignment rule rejected the command",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"error": {
"type": "object",
"properties": {
"code": {
"type": "string"
},
"message": {
"type": "string"
},
"details": {}
},
"required": [
"code",
"message"
],
"additionalProperties": false
}
},
"required": [
"error"
],
"additionalProperties": false
}
}
}
},
"500": {
"description": "Class parameter authoring unavailable",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"error": {
"type": "object",
"properties": {
"code": {
"type": "string"
},
"message": {
"type": "string"
},
"details": {}
},
"required": [
"code",
"message"
],
"additionalProperties": false
}
},
"required": [
"error"
],
"additionalProperties": false
}
}
}
}
}
}
},
"/api/v1/bim-ontology/object-classes/{key}/parameters/{parameterKeyId}/actions/validate-allowed-values": {
"post": {
"operationId": "validateClassParameterAllowedValues",
"tags": [
"BIM Ontology / Class Parameters"
],
"summary": "Validate persisted class-parameter allowed values without writing",
"description": "Type B B3 (ADR-0047). `:key` is ObjectClass.handle; `:parameterKeyId` is ParameterKey domain_id. Validates the assignment already stored on this ObjectClass × ParameterKey pair.",
"security": [
{
"apiKeyAuth": []
}
],
"x-domain": {
"appVersion": "api-v1",
"boundedContext": "bim-ontology",
"service": "api-v1",
"aggregate": "ClassParameter",
"useCase": "ValidateClassParameterAllowedValuesUseCase",
"domainEvents": [],
"domainErrors": [
"ParameterKeyNotBindableError",
"InheritedClassParameterReadOnlyError",
"RenderedClassParameterReadOnlyError",
"LegacyUnknownClassParameterReadOnlyError",
"ClassParameterInheritanceMirrorConflictError",
"ParameterValueNotFoundError",
"ParameterValueDoesNotBelongToKeyError"
]
},
"parameters": [
{
"in": "path",
"name": "key",
"required": true,
"schema": {
"type": "string",
"minLength": 1
}
},
{
"in": "path",
"name": "parameterKeyId",
"required": true,
"schema": {
"type": "string",
"minLength": 1
}
}
],
"responses": {
"200": {
"description": "Allowed values are valid",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"data": {
"type": "object",
"properties": {
"classParameterId": {
"type": "string"
},
"valid": {
"type": "boolean",
"const": true
},
"allowedValueCount": {
"type": "integer",
"minimum": 0,
"maximum": 9007199254740991
}
},
"required": [
"classParameterId",
"valid",
"allowedValueCount"
],
"additionalProperties": false
}
},
"required": [
"data"
],
"additionalProperties": false
}
}
}
},
"400": {
"description": "Invalid request",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"error": {
"type": "object",
"properties": {
"code": {
"type": "string"
},
"message": {
"type": "string"
},
"details": {}
},
"required": [
"code",
"message"
],
"additionalProperties": false
}
},
"required": [
"error"
],
"additionalProperties": false
}
}
}
},
"401": {
"description": "Authenticated caller required",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"error": {
"type": "object",
"properties": {
"code": {
"type": "string"
},
"message": {
"type": "string"
},
"details": {}
},
"required": [
"code",
"message"
],
"additionalProperties": false
}
},
"required": [
"error"
],
"additionalProperties": false
}
}
}
},
"404": {
"description": "Object class, key, or assignment not found",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"error": {
"type": "object",
"properties": {
"code": {
"type": "string"
},
"message": {
"type": "string"
},
"details": {}
},
"required": [
"code",
"message"
],
"additionalProperties": false
}
},
"required": [
"error"
],
"additionalProperties": false
}
}
}
},
"409": {
"description": "Assignment or allowed-value conflict",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"error": {
"type": "object",
"properties": {
"code": {
"type": "string"
},
"message": {
"type": "string"
},
"details": {}
},
"required": [
"code",
"message"
],
"additionalProperties": false
}
},
"required": [
"error"
],
"additionalProperties": false
}
}
}
},
"422": {
"description": "Assignment rule rejected the command",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"error": {
"type": "object",
"properties": {
"code": {
"type": "string"
},
"message": {
"type": "string"
},
"details": {}
},
"required": [
"code",
"message"
],
"additionalProperties": false
}
},
"required": [
"error"
],
"additionalProperties": false
}
}
}
},
"500": {
"description": "Class parameter authoring unavailable",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"error": {
"type": "object",
"properties": {
"code": {
"type": "string"
},
"message": {
"type": "string"
},
"details": {}
},
"required": [
"code",
"message"
],
"additionalProperties": false
}
},
"required": [
"error"
],
"additionalProperties": false
}
}
}
}
}
}
},
"/api/v1/bim-ontology/object-classes/{key}/effective-parameters": {
"get": {
"operationId": "getEffectiveParameterSet",
"tags": [
"BIM Ontology / Class Parameters"
],
"summary": "Resolve effective parameters for one object class",
"description": "Canonical self+ancestors read for the Revit plugin (resolution, inheritance path, conflicts, revitProjectionReadiness). The key path parameter is ObjectClass.handle, not domain_id. Only includeArchived and explain are accepted; unknown query parameters return 400. Overlaps `GET /object-classes/{key}/parameters?includeInherited=true` (simpler list shape); prefer this endpoint for plugin target dropdowns. For descendant coverage use `GET /object-classes/{key}/parameters?extent=subtree`.",
"security": [
{
"apiKeyAuth": []
}
],
"x-domain": {
"appVersion": "api-v1",
"boundedContext": "bim-ontology",
"service": "api-v1",
"aggregate": "ClassParameter",
"useCase": "ResolveEffectiveParameterSetUseCase",
"domainEvents": [],
"domainErrors": [
"EffectiveParameterGraphCycleError",
"EffectiveParameterObjectClassNotFoundError",
"InvalidCategoryParameterInheritanceConfigurationError",
"ObjectClassGraphNodeIdentityMissingError",
"ObjectClassParentIdentityMissingError"
]
},
"parameters": [
{
"in": "path",
"name": "key",
"required": true,
"schema": {
"type": "string",
"minLength": 1
}
},
{
"in": "query",
"name": "includeArchived",
"schema": {
"type": "boolean",
"default": false
},
"description": "Include archived ParameterKeys for audit reads; drafts remain excluded."
},
{
"in": "query",
"name": "explain",
"schema": {
"type": "boolean",
"default": false
},
"description": "Include per-key resolution and suppression explanations."
}
],
"responses": {
"200": {
"description": "Effective parameter resolution returned",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"data": {
"type": "object",
"properties": {
"objectClassId": {
"type": "string"
},
"objectClassKey": {
"type": "string"
},
"parameters": {
"type": "array",
"items": {
"type": "object",
"properties": {
"parameterKey": {
"type": "object",
"properties": {
"id": {
"type": "string"
},
"handle": {
"type": "string"
},
"name": {
"type": "string"
},
"description": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
]
},
"lifecycleStatus": {
"type": "string",
"enum": [
"draft",
"active",
"approved",
"archived"
]
},
"isApproved": {
"type": "boolean"
},
"parameterSpecId": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
]
},
"parameterControlId": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
]
}
},
"required": [
"id",
"handle",
"name",
"description",
"lifecycleStatus",
"isApproved",
"parameterSpecId",
"parameterControlId"
],
"additionalProperties": false
},
"binding": {
"type": "object",
"properties": {
"id": {
"type": "string"
},
"isRequired": {
"type": "boolean"
},
"inheritanceMode": {
"type": "string",
"enum": [
"unique",
"apply_to_descendants"
]
},
"childBehavior": {
"type": "string",
"enum": [
"inherit",
"override",
"suppress"
]
}
},
"required": [
"id",
"isRequired",
"inheritanceMode",
"childBehavior"
],
"additionalProperties": false
},
"allowedValues": {
"type": "array",
"items": {
"type": "object",
"properties": {
"parameterValueId": {
"type": "string"
},
"value": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
]
},
"isDefault": {
"type": "boolean"
},
"isNominal": {
"type": "boolean"
},
"sortOrder": {
"anyOf": [
{
"type": "integer",
"minimum": -9007199254740991,
"maximum": 9007199254740991
},
{
"type": "null"
}
]
}
},
"required": [
"parameterValueId",
"value",
"isDefault",
"isNominal",
"sortOrder"
],
"additionalProperties": false
}
},
"group": {
"anyOf": [
{
"type": "object",
"properties": {
"id": {
"type": "string"
},
"name": {
"type": "string"
}
},
"required": [
"id",
"name"
],
"additionalProperties": false
},
{
"type": "null"
}
]
},
"revitProjection": {
"anyOf": [
{
"type": "object",
"properties": {
"id": {
"type": "string"
},
"status": {
"type": "string",
"enum": [
"draft",
"approved",
"archived"
]
},
"definition": {
"type": "object",
"properties": {
"form": {
"type": "string",
"enum": [
"shared",
"family_local",
"project_local",
"built_in",
"legacy_unknown"
]
},
"sharedGuid": {
"anyOf": [
{
"type": "string",
"format": "uuid",
"pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"
},
{
"type": "null"
}
]
},
"builtInParameterName": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
]
}
},
"required": [
"form",
"sharedGuid",
"builtInParameterName"
],
"additionalProperties": false
},
"familyApplication": {
"anyOf": [
{
"type": "object",
"properties": {
"bindingKind": {
"type": "string",
"enum": [
"instance",
"type"
]
}
},
"required": [
"bindingKind"
],
"additionalProperties": false
},
{
"type": "null"
}
]
},
"projectBinding": {
"anyOf": [
{
"type": "object",
"properties": {
"bindingKind": {
"type": "string",
"enum": [
"instance",
"type"
]
}
},
"required": [
"bindingKind"
],
"additionalProperties": false
},
{
"type": "null"
}
]
}
},
"required": [
"id",
"status",
"definition",
"familyApplication",
"projectBinding"
],
"additionalProperties": false
},
{
"type": "null"
}
]
},
"inheritanceSource": {
"type": "object",
"properties": {
"objectClassId": {
"type": "string"
},
"objectClassKey": {
"type": "string"
},
"pathObjectClassIds": {
"type": "array",
"items": {
"type": "string"
}
},
"pathObjectClassKeys": {
"type": "array",
"items": {
"type": "string"
}
},
"distance": {
"type": "integer",
"minimum": 0,
"maximum": 9007199254740991
}
},
"required": [
"objectClassId",
"objectClassKey",
"pathObjectClassIds",
"pathObjectClassKeys",
"distance"
],
"additionalProperties": false
},
"resolution": {
"type": "string",
"enum": [
"direct",
"inherited",
"override"
]
},
"explanation": {
"type": "string"
},
"revitProjectionReadiness": {
"type": "object",
"properties": {
"ready": {
"type": "boolean"
},
"reasons": {
"type": "array",
"items": {
"type": "string",
"enum": [
"KEY_NOT_ACTIVE",
"KEY_NOT_APPROVED",
"KEY_NOT_BIM_PARAMETER",
"MISSING_SPEC",
"MISSING_CONTROL",
"PARAMETER_PROJECTION_MISSING",
"MULTIPLE_PARAMETER_PROJECTIONS",
"PARAMETER_PROJECTION_NOT_APPROVED"
]
}
}
},
"required": [
"ready",
"reasons"
],
"additionalProperties": false
}
},
"required": [
"parameterKey",
"binding",
"allowedValues",
"group",
"revitProjection",
"inheritanceSource",
"resolution",
"explanation",
"revitProjectionReadiness"
],
"additionalProperties": false
}
},
"explanations": {
"type": "array",
"items": {
"type": "object",
"properties": {
"parameterKeyId": {
"type": "string"
},
"resolution": {
"type": "string",
"enum": [
"direct",
"inherited",
"override",
"suppressed"
]
},
"winningBindingId": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
]
},
"sourceObjectClassId": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
]
},
"sourceObjectClassKey": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
]
},
"pathObjectClassKeys": {
"type": "array",
"items": {
"type": "string"
}
},
"suppressedByBindingId": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
]
},
"explanation": {
"type": "string"
}
},
"required": [
"parameterKeyId",
"resolution",
"winningBindingId",
"sourceObjectClassId",
"sourceObjectClassKey",
"pathObjectClassKeys",
"suppressedByBindingId",
"explanation"
],
"additionalProperties": false
}
}
},
"required": [
"objectClassId",
"objectClassKey",
"parameters",
"explanations"
],
"additionalProperties": false
}
},
"required": [
"data"
],
"additionalProperties": false
}
}
}
},
"400": {
"description": "Invalid request",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"error": {
"type": "object",
"properties": {
"code": {
"type": "string"
},
"message": {
"type": "string"
},
"details": {}
},
"required": [
"code",
"message"
],
"additionalProperties": false
}
},
"required": [
"error"
],
"additionalProperties": false
}
}
}
},
"401": {
"description": "Authenticated caller required",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"error": {
"type": "object",
"properties": {
"code": {
"type": "string"
},
"message": {
"type": "string"
},
"details": {}
},
"required": [
"code",
"message"
],
"additionalProperties": false
}
},
"required": [
"error"
],
"additionalProperties": false
}
}
}
},
"404": {
"description": "Object class not found",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"error": {
"type": "object",
"properties": {
"code": {
"type": "string"
},
"message": {
"type": "string"
},
"details": {}
},
"required": [
"code",
"message"
],
"additionalProperties": false
}
},
"required": [
"error"
],
"additionalProperties": false
}
}
}
},
"409": {
"description": "Ontology graph or persisted binding configuration conflict",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"error": {
"type": "object",
"properties": {
"code": {
"type": "string"
},
"message": {
"type": "string"
},
"details": {}
},
"required": [
"code",
"message"
],
"additionalProperties": false
}
},
"required": [
"error"
],
"additionalProperties": false
}
}
}
},
"422": {
"description": "Malformed ObjectClass graph or persisted inheritance configuration",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"error": {
"type": "object",
"properties": {
"code": {
"type": "string"
},
"message": {
"type": "string"
},
"details": {}
},
"required": [
"code",
"message"
],
"additionalProperties": false
}
},
"required": [
"error"
],
"additionalProperties": false
}
}
}
},
"500": {
"description": "Effective parameter resolution unavailable",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"error": {
"type": "object",
"properties": {
"code": {
"type": "string"
},
"message": {
"type": "string"
},
"details": {}
},
"required": [
"code",
"message"
],
"additionalProperties": false
}
},
"required": [
"error"
],
"additionalProperties": false
}
}
}
}
}
}
},
"/api/v1/bim-ontology/object-classes/effective-parameters/resolve": {
"post": {
"operationId": "resolveEffectiveParameterSets",
"tags": [
"BIM Ontology / Class Parameters"
],
"summary": "Resolve per-class effective sets and a conflict-safe merged union",
"description": "Bulk endpoint for resolving expected/effective parameters for multiple classes. objectClassKeys contains ObjectClass.handle values, not domain_id values.",
"security": [
{
"apiKeyAuth": []
}
],
"x-domain": {
"appVersion": "api-v1",
"boundedContext": "bim-ontology",
"service": "api-v1",
"aggregate": "ClassParameter",
"useCase": "ResolveEffectiveParameterSetsUseCase",
"domainEvents": [],
"domainErrors": [
"EffectiveParameterGraphCycleError",
"EffectiveParameterObjectClassNotFoundError",
"InvalidCategoryParameterInheritanceConfigurationError",
"ObjectClassGraphNodeIdentityMissingError",
"ObjectClassParentIdentityMissingError"
]
},
"parameters": [],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"objectClassKeys": {
"minItems": 1,
"maxItems": 500,
"type": "array",
"items": {
"type": "string",
"minLength": 1
}
},
"includeArchived": {
"default": false,
"type": "boolean"
},
"explain": {
"default": false,
"type": "boolean"
}
},
"required": [
"objectClassKeys"
],
"additionalProperties": false
}
}
}
},
"responses": {
"200": {
"description": "Effective parameter resolution returned",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"data": {
"type": "object",
"properties": {
"perClass": {
"type": "array",
"items": {
"type": "object",
"properties": {
"objectClassId": {
"type": "string"
},
"objectClassKey": {
"type": "string"
},
"parameters": {
"type": "array",
"items": {
"type": "object",
"properties": {
"parameterKey": {
"type": "object",
"properties": {
"id": {
"type": "string"
},
"handle": {
"type": "string"
},
"name": {
"type": "string"
},
"description": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
]
},
"lifecycleStatus": {
"type": "string",
"enum": [
"draft",
"active",
"approved",
"archived"
]
},
"isApproved": {
"type": "boolean"
},
"parameterSpecId": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
]
},
"parameterControlId": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
]
}
},
"required": [
"id",
"handle",
"name",
"description",
"lifecycleStatus",
"isApproved",
"parameterSpecId",
"parameterControlId"
],
"additionalProperties": false
},
"binding": {
"type": "object",
"properties": {
"id": {
"type": "string"
},
"isRequired": {
"type": "boolean"
},
"inheritanceMode": {
"type": "string",
"enum": [
"unique",
"apply_to_descendants"
]
},
"childBehavior": {
"type": "string",
"enum": [
"inherit",
"override",
"suppress"
]
}
},
"required": [
"id",
"isRequired",
"inheritanceMode",
"childBehavior"
],
"additionalProperties": false
},
"allowedValues": {
"type": "array",
"items": {
"type": "object",
"properties": {
"parameterValueId": {
"type": "string"
},
"value": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
]
},
"isDefault": {
"type": "boolean"
},
"isNominal": {
"type": "boolean"
},
"sortOrder": {
"anyOf": [
{
"type": "integer",
"minimum": -9007199254740991,
"maximum": 9007199254740991
},
{
"type": "null"
}
]
}
},
"required": [
"parameterValueId",
"value",
"isDefault",
"isNominal",
"sortOrder"
],
"additionalProperties": false
}
},
"group": {
"anyOf": [
{
"type": "object",
"properties": {
"id": {
"type": "string"
},
"name": {
"type": "string"
}
},
"required": [
"id",
"name"
],
"additionalProperties": false
},
{
"type": "null"
}
]
},
"revitProjection": {
"anyOf": [
{
"type": "object",
"properties": {
"id": {
"type": "string"
},
"status": {
"type": "string",
"enum": [
"draft",
"approved",
"archived"
]
},
"definition": {
"type": "object",
"properties": {
"form": {
"type": "string",
"enum": [
"shared",
"family_local",
"project_local",
"built_in",
"legacy_unknown"
]
},
"sharedGuid": {
"anyOf": [
{
"type": "string",
"format": "uuid",
"pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"
},
{
"type": "null"
}
]
},
"builtInParameterName": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
]
}
},
"required": [
"form",
"sharedGuid",
"builtInParameterName"
],
"additionalProperties": false
},
"familyApplication": {
"anyOf": [
{
"type": "object",
"properties": {
"bindingKind": {
"type": "string",
"enum": [
"instance",
"type"
]
}
},
"required": [
"bindingKind"
],
"additionalProperties": false
},
{
"type": "null"
}
]
},
"projectBinding": {
"anyOf": [
{
"type": "object",
"properties": {
"bindingKind": {
"type": "string",
"enum": [
"instance",
"type"
]
}
},
"required": [
"bindingKind"
],
"additionalProperties": false
},
{
"type": "null"
}
]
}
},
"required": [
"id",
"status",
"definition",
"familyApplication",
"projectBinding"
],
"additionalProperties": false
},
{
"type": "null"
}
]
},
"inheritanceSource": {
"type": "object",
"properties": {
"objectClassId": {
"type": "string"
},
"objectClassKey": {
"type": "string"
},
"pathObjectClassIds": {
"type": "array",
"items": {
"type": "string"
}
},
"pathObjectClassKeys": {
"type": "array",
"items": {
"type": "string"
}
},
"distance": {
"type": "integer",
"minimum": 0,
"maximum": 9007199254740991
}
},
"required": [
"objectClassId",
"objectClassKey",
"pathObjectClassIds",
"pathObjectClassKeys",
"distance"
],
"additionalProperties": false
},
"resolution": {
"type": "string",
"enum": [
"direct",
"inherited",
"override"
]
},
"explanation": {
"type": "string"
},
"revitProjectionReadiness": {
"type": "object",
"properties": {
"ready": {
"type": "boolean"
},
"reasons": {
"type": "array",
"items": {
"type": "string",
"enum": [
"KEY_NOT_ACTIVE",
"KEY_NOT_APPROVED",
"KEY_NOT_BIM_PARAMETER",
"MISSING_SPEC",
"MISSING_CONTROL",
"PARAMETER_PROJECTION_MISSING",
"MULTIPLE_PARAMETER_PROJECTIONS",
"PARAMETER_PROJECTION_NOT_APPROVED"
]
}
}
},
"required": [
"ready",
"reasons"
],
"additionalProperties": false
}
},
"required": [
"parameterKey",
"binding",
"allowedValues",
"group",
"revitProjection",
"inheritanceSource",
"resolution",
"explanation",
"revitProjectionReadiness"
],
"additionalProperties": false
}
},
"explanations": {
"type": "array",
"items": {
"type": "object",
"properties": {
"parameterKeyId": {
"type": "string"
},
"resolution": {
"type": "string",
"enum": [
"direct",
"inherited",
"override",
"suppressed"
]
},
"winningBindingId": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
]
},
"sourceObjectClassId": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
]
},
"sourceObjectClassKey": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
]
},
"pathObjectClassKeys": {
"type": "array",
"items": {
"type": "string"
}
},
"suppressedByBindingId": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
]
},
"explanation": {
"type": "string"
}
},
"required": [
"parameterKeyId",
"resolution",
"winningBindingId",
"sourceObjectClassId",
"sourceObjectClassKey",
"pathObjectClassKeys",
"suppressedByBindingId",
"explanation"
],
"additionalProperties": false
}
}
},
"required": [
"objectClassId",
"objectClassKey",
"parameters",
"explanations"
],
"additionalProperties": false
}
},
"mergedUnion": {
"type": "array",
"items": {
"type": "object",
"properties": {
"parameterKey": {
"type": "object",
"properties": {
"id": {
"type": "string"
},
"handle": {
"type": "string"
},
"name": {
"type": "string"
},
"description": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
]
},
"lifecycleStatus": {
"type": "string",
"enum": [
"draft",
"active",
"approved",
"archived"
]
},
"isApproved": {
"type": "boolean"
},
"parameterSpecId": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
]
},
"parameterControlId": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
]
}
},
"required": [
"id",
"handle",
"name",
"description",
"lifecycleStatus",
"isApproved",
"parameterSpecId",
"parameterControlId"
],
"additionalProperties": false
},
"binding": {
"type": "object",
"properties": {
"id": {
"type": "string"
},
"isRequired": {
"type": "boolean"
},
"inheritanceMode": {
"type": "string",
"enum": [
"unique",
"apply_to_descendants"
]
},
"childBehavior": {
"type": "string",
"enum": [
"inherit",
"override",
"suppress"
]
}
},
"required": [
"id",
"isRequired",
"inheritanceMode",
"childBehavior"
],
"additionalProperties": false
},
"allowedValues": {
"type": "array",
"items": {
"type": "object",
"properties": {
"parameterValueId": {
"type": "string"
},
"value": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
]
},
"isDefault": {
"type": "boolean"
},
"isNominal": {
"type": "boolean"
},
"sortOrder": {
"anyOf": [
{
"type": "integer",
"minimum": -9007199254740991,
"maximum": 9007199254740991
},
{
"type": "null"
}
]
}
},
"required": [
"parameterValueId",
"value",
"isDefault",
"isNominal",
"sortOrder"
],
"additionalProperties": false
}
},
"group": {
"anyOf": [
{
"type": "object",
"properties": {
"id": {
"type": "string"
},
"name": {
"type": "string"
}
},
"required": [
"id",
"name"
],
"additionalProperties": false
},
{
"type": "null"
}
]
},
"revitProjection": {
"anyOf": [
{
"type": "object",
"properties": {
"id": {
"type": "string"
},
"status": {
"type": "string",
"enum": [
"draft",
"approved",
"archived"
]
},
"definition": {
"type": "object",
"properties": {
"form": {
"type": "string",
"enum": [
"shared",
"family_local",
"project_local",
"built_in",
"legacy_unknown"
]
},
"sharedGuid": {
"anyOf": [
{
"type": "string",
"format": "uuid",
"pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"
},
{
"type": "null"
}
]
},
"builtInParameterName": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
]
}
},
"required": [
"form",
"sharedGuid",
"builtInParameterName"
],
"additionalProperties": false
},
"familyApplication": {
"anyOf": [
{
"type": "object",
"properties": {
"bindingKind": {
"type": "string",
"enum": [
"instance",
"type"
]
}
},
"required": [
"bindingKind"
],
"additionalProperties": false
},
{
"type": "null"
}
]
},
"projectBinding": {
"anyOf": [
{
"type": "object",
"properties": {
"bindingKind": {
"type": "string",
"enum": [
"instance",
"type"
]
}
},
"required": [
"bindingKind"
],
"additionalProperties": false
},
{
"type": "null"
}
]
}
},
"required": [
"id",
"status",
"definition",
"familyApplication",
"projectBinding"
],
"additionalProperties": false
},
{
"type": "null"
}
]
},
"inheritanceSource": {
"type": "object",
"properties": {
"objectClassId": {
"type": "string"
},
"objectClassKey": {
"type": "string"
},
"pathObjectClassIds": {
"type": "array",
"items": {
"type": "string"
}
},
"pathObjectClassKeys": {
"type": "array",
"items": {
"type": "string"
}
},
"distance": {
"type": "integer",
"minimum": 0,
"maximum": 9007199254740991
}
},
"required": [
"objectClassId",
"objectClassKey",
"pathObjectClassIds",
"pathObjectClassKeys",
"distance"
],
"additionalProperties": false
},
"resolution": {
"type": "string",
"enum": [
"direct",
"inherited",
"override"
]
},
"explanation": {
"type": "string"
},
"revitProjectionReadiness": {
"type": "object",
"properties": {
"ready": {
"type": "boolean"
},
"reasons": {
"type": "array",
"items": {
"type": "string",
"enum": [
"KEY_NOT_ACTIVE",
"KEY_NOT_APPROVED",
"KEY_NOT_BIM_PARAMETER",
"MISSING_SPEC",
"MISSING_CONTROL",
"PARAMETER_PROJECTION_MISSING",
"MULTIPLE_PARAMETER_PROJECTIONS",
"PARAMETER_PROJECTION_NOT_APPROVED"
]
}
}
},
"required": [
"ready",
"reasons"
],
"additionalProperties": false
}
},
"required": [
"parameterKey",
"binding",
"allowedValues",
"group",
"revitProjection",
"inheritanceSource",
"resolution",
"explanation",
"revitProjectionReadiness"
],
"additionalProperties": false
}
},
"conflicts": {
"type": "array",
"items": {
"type": "object",
"properties": {
"parameterKeyId": {
"type": "string"
},
"objectClassKeys": {
"type": "array",
"items": {
"type": "string"
}
},
"explanations": {
"type": "array",
"items": {
"type": "string"
}
}
},
"required": [
"parameterKeyId",
"objectClassKeys",
"explanations"
],
"additionalProperties": false
}
}
},
"required": [
"perClass",
"mergedUnion",
"conflicts"
],
"additionalProperties": false
}
},
"required": [
"data"
],
"additionalProperties": false
}
}
}
},
"400": {
"description": "Invalid request",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"error": {
"type": "object",
"properties": {
"code": {
"type": "string"
},
"message": {
"type": "string"
},
"details": {}
},
"required": [
"code",
"message"
],
"additionalProperties": false
}
},
"required": [
"error"
],
"additionalProperties": false
}
}
}
},
"401": {
"description": "Authenticated caller required",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"error": {
"type": "object",
"properties": {
"code": {
"type": "string"
},
"message": {
"type": "string"
},
"details": {}
},
"required": [
"code",
"message"
],
"additionalProperties": false
}
},
"required": [
"error"
],
"additionalProperties": false
}
}
}
},
"404": {
"description": "Object class not found",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"error": {
"type": "object",
"properties": {
"code": {
"type": "string"
},
"message": {
"type": "string"
},
"details": {}
},
"required": [
"code",
"message"
],
"additionalProperties": false
}
},
"required": [
"error"
],
"additionalProperties": false
}
}
}
},
"409": {
"description": "Ontology graph or persisted binding configuration conflict",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"error": {
"type": "object",
"properties": {
"code": {
"type": "string"
},
"message": {
"type": "string"
},
"details": {}
},
"required": [
"code",
"message"
],
"additionalProperties": false
}
},
"required": [
"error"
],
"additionalProperties": false
}
}
}
},
"422": {
"description": "Malformed ObjectClass graph or persisted inheritance configuration",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"error": {
"type": "object",
"properties": {
"code": {
"type": "string"
},
"message": {
"type": "string"
},
"details": {}
},
"required": [
"code",
"message"
],
"additionalProperties": false
}
},
"required": [
"error"
],
"additionalProperties": false
}
}
}
},
"500": {
"description": "Effective parameter resolution unavailable",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"error": {
"type": "object",
"properties": {
"code": {
"type": "string"
},
"message": {
"type": "string"
},
"details": {}
},
"required": [
"code",
"message"
],
"additionalProperties": false
}
},
"required": [
"error"
],
"additionalProperties": false
}
}
}
}
}
}
},
"/api/v1/bim-ontology/revit/parameter-projections": {
"get": {
"operationId": "listRevitParameterProjections",
"tags": [
"BIM Ontology / Revit Parameter Projection"
],
"summary": "List Revit parameter projections",
"description": "Steward collection read through the query kernel (parseCollectionQuery → QueryPolicy → ForQueryingCollections). Filter projectionStatus=eq.draft for the draft queue; parameterKeyId=eq. and discipline=eq. are also allowed. Sort domainId asc/desc. Offset pagination via limit/offset. Envelope { items, page } (total optional). Linked parameterKeyId is resolved in the query adapter so rec ids never leave the adapter (ADR-0048). Not a system-maintenance endpoint.",
"security": [
{
"apiKeyAuth": []
}
],
"x-domain": {
"appVersion": "api-v1",
"boundedContext": "bim-ontology",
"service": "api-v1",
"aggregate": "RevitParameterProjection",
"useCase": "ListRevitParameterProjectionsUseCase",
"domainEvents": [],
"domainErrors": [
"QueryValidationError",
"QueryRuntimeError"
]
},
"parameters": [
{
"in": "query",
"name": "limit",
"schema": {
"type": "integer",
"minimum": 1,
"maximum": 200,
"default": 50
},
"description": "Page size (CollectionQuery)."
},
{
"in": "query",
"name": "offset",
"schema": {
"type": "integer",
"minimum": 0,
"default": 0
},
"description": "Numeric offset into the filtered candidate set."
},
{
"in": "query",
"name": "projectionStatus",
"schema": {
"type": "string"
},
"description": "PostgREST-style eq filter only, e.g. eq.draft. Duplicate filters AND together."
},
{
"in": "query",
"name": "parameterKeyId",
"schema": {
"type": "string"
},
"description": "PostgREST-style eq filter only, e.g. eq.. Resolved to a linked record in the query adapter."
},
{
"in": "query",
"name": "discipline",
"schema": {
"type": "string"
},
"description": "PostgREST-style eq filter only, e.g. eq.Common."
},
{
"in": "query",
"name": "order",
"schema": {
"type": "string",
"default": "domainId.asc"
},
"description": "Sort order. Allow-listed field: domainId (asc or desc). Default domainId.asc."
}
],
"responses": {
"200": {
"description": "Paged steward projection list",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"items": {
"type": "array",
"items": {
"type": "object",
"properties": {
"id": {
"type": "string"
},
"parameterKeyId": {
"type": "string"
},
"projectionStatus": {
"type": "string",
"enum": [
"draft",
"approved",
"archived"
]
},
"discipline": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
]
},
"guid": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
]
},
"revitName": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
]
}
},
"required": [
"id",
"parameterKeyId",
"projectionStatus",
"discipline",
"guid",
"revitName"
],
"additionalProperties": false
}
},
"page": {
"type": "object",
"properties": {
"total": {
"type": "integer",
"minimum": 0,
"maximum": 9007199254740991
},
"page": {
"type": "integer",
"minimum": 0,
"maximum": 9007199254740991
},
"limit": {
"type": "integer",
"exclusiveMinimum": 0,
"maximum": 9007199254740991
},
"hasNextPage": {
"type": "boolean"
},
"nextOffset": {
"type": "integer",
"minimum": 0,
"maximum": 9007199254740991
}
},
"required": [
"page",
"limit",
"hasNextPage"
],
"additionalProperties": false
}
},
"required": [
"items",
"page"
],
"additionalProperties": false
}
}
}
},
"400": {
"description": "Invalid query",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"error": {
"type": "object",
"properties": {
"code": {
"type": "string"
},
"message": {
"type": "string"
},
"field": {
"type": "string"
},
"details": {},
"unmetRequirements": {
"type": "array",
"items": {
"type": "object",
"properties": {
"code": {
"type": "string"
},
"message": {
"type": "string"
},
"objectClassKey": {
"type": "string"
},
"objectClassId": {
"type": "string"
}
},
"required": [
"code",
"message"
],
"additionalProperties": false
}
}
},
"required": [
"code",
"message"
],
"additionalProperties": false
}
},
"required": [
"error"
],
"additionalProperties": false
}
}
}
},
"401": {
"description": "Authenticated caller required",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"error": {
"type": "object",
"properties": {
"code": {
"type": "string"
},
"message": {
"type": "string"
},
"field": {
"type": "string"
},
"details": {},
"unmetRequirements": {
"type": "array",
"items": {
"type": "object",
"properties": {
"code": {
"type": "string"
},
"message": {
"type": "string"
},
"objectClassKey": {
"type": "string"
},
"objectClassId": {
"type": "string"
}
},
"required": [
"code",
"message"
],
"additionalProperties": false
}
}
},
"required": [
"code",
"message"
],
"additionalProperties": false
}
},
"required": [
"error"
],
"additionalProperties": false
}
}
}
},
"500": {
"description": "Projection list unavailable",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"error": {
"type": "object",
"properties": {
"code": {
"type": "string"
},
"message": {
"type": "string"
},
"field": {
"type": "string"
},
"details": {},
"unmetRequirements": {
"type": "array",
"items": {
"type": "object",
"properties": {
"code": {
"type": "string"
},
"message": {
"type": "string"
},
"objectClassKey": {
"type": "string"
},
"objectClassId": {
"type": "string"
}
},
"required": [
"code",
"message"
],
"additionalProperties": false
}
}
},
"required": [
"code",
"message"
],
"additionalProperties": false
}
},
"required": [
"error"
],
"additionalProperties": false
}
}
}
}
}
},
"post": {
"operationId": "createRevitParameterProjection",
"tags": [
"BIM Ontology / Revit Parameter Projection",
"system-maintenance"
],
"summary": "Create a Revit parameter projection",
"description": "Not part of the steward workflow. The normal path is: approved ParameterKey → auto-created draft → steward PATCH → approve. Maintenance / drift-repair path for Revit parameter projections. Primary creation path is ParameterKey approve: ParameterKeyApproved handlers call EnsureRevitProjectionForParameterKeyUseCase and create a draft projection. Create write enum is shared | family_local | built_in. project_local and legacy_unknown are read-only (not creatable). `global` is not accepted — use response scope.kind. Optional sharedGuid on shared create seeds a known GUID (409 SHARED_GUID_CONFLICT on duplicate). built_in requires builtInParameterName and null familyApplication/projectBinding. Soft-idempotent: when a projection already exists for the ParameterKey and the requested identity matches, returns 200 with the existing row. When an existing projection identity mismatches (definitionForm / sharedGuid / builtInParameterName), returns 409 REVIT_PROJECTION_IDENTITY_CONFLICT. Response includes definitionForm (alias of definition.form), definition.builtInParameterName, top-level revitUiGroup.groupTypeId (ForgeTypeId `autodesk.parameter.group:…`, fallback `autodesk.parameter.group:data-1.0.0`), scope.kind, and builtInCategories (null until a Revit-availability source exists).",
"security": [
{
"apiKeyAuth": []
}
],
"x-internal": true,
"x-domain": {
"appVersion": "api-v1",
"boundedContext": "bim-ontology",
"service": "api-v1",
"aggregate": "RevitParameterProjection",
"useCase": "CreateRevitParameterProjectionUseCase",
"domainEvents": [
"RevitParameterProjectionCreated"
],
"domainErrors": [
"RevitParameterProjectionInvariantError",
"RevitProjectionAuthoringError",
"ObjectClassMissingRevitCategoryError",
"ProjectParameterCategoryScopeEmptyError",
"MalformedRevitCategoryDataError"
]
},
"parameters": [
{
"in": "header",
"name": "Idempotency-Key",
"required": true,
"schema": {
"type": "string",
"minLength": 1
}
}
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"parameterKeyId": {
"type": "string",
"minLength": 1
},
"definitionForm": {
"type": "string",
"enum": [
"shared",
"family_local",
"built_in"
]
},
"revitSpecTypeId": {
"type": "string",
"minLength": 1
},
"storageType": {
"type": "string",
"enum": [
"Double",
"Integer",
"String",
"ElementId",
"None"
]
},
"familyApplication": {
"anyOf": [
{
"type": "object",
"properties": {
"bindingKind": {
"type": "string",
"enum": [
"instance",
"type"
]
}
},
"additionalProperties": false
},
{
"type": "null"
}
]
},
"projectBinding": {
"anyOf": [
{
"type": "object",
"properties": {
"bindingKind": {
"type": "string",
"enum": [
"instance",
"type"
]
}
},
"additionalProperties": false
},
{
"type": "null"
}
]
},
"bindingKindPreference": {
"anyOf": [
{
"type": "string",
"enum": [
"instance",
"type"
]
},
{
"type": "null"
}
]
},
"revitPrimaryUsage": {
"anyOf": [
{
"type": "string",
"enum": [
"family",
"project"
]
},
{
"type": "null"
}
]
},
"isSystem": {
"type": "boolean"
},
"visibilityFlags": {
"type": "object",
"properties": {
"isVisible": {
"type": "boolean"
},
"isUserModifiable": {
"type": "boolean"
},
"hidesWhenNoValue": {
"type": "boolean"
}
},
"required": [
"isVisible",
"isUserModifiable",
"hidesWhenNoValue"
],
"additionalProperties": false
},
"discipline": {
"anyOf": [
{
"type": "string",
"enum": [
"Common",
"Structural",
"Electrical",
"HVAC",
"Infrastructure",
"Piping",
"Energy"
]
},
{
"type": "null"
}
]
},
"tooltipDescription": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
]
},
"revitNameOverride": {
"anyOf": [
{
"type": "string",
"minLength": 1
},
{
"type": "null"
}
]
},
"sharedGuid": {
"type": "string",
"format": "uuid",
"pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"
},
"builtInParameterName": {
"type": "string",
"pattern": "^[A-Z][A-Z0-9_]+$"
}
},
"required": [
"parameterKeyId",
"definitionForm",
"revitSpecTypeId",
"storageType",
"familyApplication",
"projectBinding",
"visibilityFlags"
],
"additionalProperties": false
}
}
}
},
"responses": {
"200": {
"description": "Existing projection returned (soft-idempotent identity match)",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"data": {
"type": "object",
"properties": {
"id": {
"type": "string"
},
"parameterKeyId": {
"type": "string"
},
"definition": {
"type": "object",
"properties": {
"form": {
"type": "string",
"enum": [
"shared",
"family_local",
"project_local",
"built_in",
"legacy_unknown"
]
},
"guid": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
]
},
"builtInParameterName": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
]
}
},
"required": [
"form",
"guid",
"builtInParameterName"
],
"additionalProperties": false
},
"definitionForm": {
"type": "string",
"enum": [
"shared",
"family_local",
"project_local",
"built_in",
"legacy_unknown"
],
"description": "Alias of definition.form for clients that pin the top-level field. Always equals definition.form."
},
"revitName": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
]
},
"revitNameSource": {
"type": "string",
"enum": [
"derived",
"override"
]
},
"revitNameOverridden": {
"type": "boolean"
},
"sharedParameterFileGroup": {
"type": "object",
"properties": {
"parameterGroupId": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
]
},
"name": {
"type": "string"
},
"sortOrder": {
"anyOf": [
{
"type": "number"
},
{
"type": "null"
}
]
}
},
"required": [
"parameterGroupId",
"name",
"sortOrder"
],
"additionalProperties": false
},
"revitUiGroup": {
"type": "object",
"properties": {
"parameterGroupId": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
]
},
"groupTypeId": {
"type": "string",
"description": "Revit UI property-palette groupTypeId in ForgeTypeId format `autodesk.parameter.group:…` (e.g. `autodesk.parameter.group:identityData-1.0.0`). When the canonical ParameterGroup has no valid Revit UI groupTypeId, the response uses the pinned fallback `autodesk.parameter.group:data-1.0.0` and sets usedFallback=true. Top-level on projection read shapes; also may appear nested under package familyApplication/projectBinding."
},
"usedFallback": {
"type": "boolean"
}
},
"required": [
"parameterGroupId",
"groupTypeId",
"usedFallback"
],
"additionalProperties": false,
"description": "Derived Revit UI parameter group. groupTypeId is the stable Forge group identity used by the plugin."
},
"familyApplication": {
"anyOf": [
{
"type": "object",
"properties": {
"bindingKind": {
"type": "string",
"enum": [
"instance",
"type"
]
}
},
"required": [
"bindingKind"
],
"additionalProperties": false
},
{
"type": "null"
}
]
},
"projectBinding": {
"anyOf": [
{
"type": "object",
"properties": {
"bindingKind": {
"type": "string",
"enum": [
"instance",
"type"
]
}
},
"required": [
"bindingKind"
],
"additionalProperties": false
},
{
"type": "null"
}
]
},
"bindingKindPreference": {
"anyOf": [
{
"type": "string",
"enum": [
"instance",
"type"
]
},
{
"type": "null"
}
]
},
"revitPrimaryUsage": {
"anyOf": [
{
"type": "string",
"enum": [
"family",
"project"
]
},
{
"type": "null"
}
]
},
"isSystem": {
"type": "boolean"
},
"resolvedCategoryScope": {
"anyOf": [
{
"type": "object",
"properties": {
"projectionId": {
"type": "string"
},
"parameterKeyId": {
"type": "string"
},
"sharedGuid": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
]
},
"categories": {
"type": "array",
"items": {
"type": "object",
"properties": {
"builtinName": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
]
},
"builtinCategoryInt": {
"anyOf": [
{
"type": "integer",
"minimum": -9007199254740991,
"maximum": 9007199254740991
},
{
"type": "null"
}
]
},
"forgeTypeId": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
]
},
"displayName": {
"type": "string"
}
},
"required": [
"builtinName",
"builtinCategoryInt",
"forgeTypeId",
"displayName"
],
"additionalProperties": false
}
},
"contributingObjectClasses": {
"type": "array",
"items": {
"type": "object",
"properties": {
"objectClassId": {
"type": "string"
},
"objectClassKey": {
"type": "string"
},
"categories": {
"type": "array",
"items": {
"type": "object",
"properties": {
"builtinName": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
]
},
"builtinCategoryInt": {
"anyOf": [
{
"type": "integer",
"minimum": -9007199254740991,
"maximum": 9007199254740991
},
{
"type": "null"
}
]
},
"forgeTypeId": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
]
},
"displayName": {
"type": "string"
}
},
"required": [
"builtinName",
"builtinCategoryInt",
"forgeTypeId",
"displayName"
],
"additionalProperties": false
}
}
},
"required": [
"objectClassId",
"objectClassKey",
"categories"
],
"additionalProperties": false
}
},
"diagnostics": {
"type": "array",
"items": {
"type": "string"
}
}
},
"required": [
"projectionId",
"parameterKeyId",
"sharedGuid",
"categories",
"contributingObjectClasses",
"diagnostics"
],
"additionalProperties": false
},
{
"type": "null"
}
]
},
"scope": {
"type": "object",
"properties": {
"kind": {
"type": "string",
"enum": [
"derived",
"unscoped"
],
"description": "Project-binding category-scope kind. Always derived from ObjectClass assignments when a project binding exists. `unscoped` when there is no project binding / no resolved scope."
}
},
"required": [
"kind"
],
"additionalProperties": false
},
"builtInCategories": {
"anyOf": [
{
"type": "array",
"items": {
"type": "object",
"properties": {
"builtinName": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
]
},
"builtinCategoryInt": {
"anyOf": [
{
"type": "integer",
"minimum": -9007199254740991,
"maximum": 9007199254740991
},
{
"type": "null"
}
]
},
"forgeTypeId": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
]
},
"displayName": {
"type": "string"
}
},
"required": [
"builtinName",
"builtinCategoryInt",
"forgeTypeId",
"displayName"
],
"additionalProperties": false
}
},
{
"type": "null"
}
],
"description": "Revit-native categories where a built_in parameter exists. Null when no Revit-availability source or explicit projection data is present. Never derived from ObjectClass affinity links."
},
"specTypeId": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
]
},
"storageType": {
"anyOf": [
{
"type": "string",
"enum": [
"Double",
"Integer",
"String",
"ElementId",
"None"
]
},
{
"type": "null"
}
]
},
"visibilityFlags": {
"anyOf": [
{
"type": "object",
"properties": {
"isVisible": {
"type": "boolean"
},
"isUserModifiable": {
"type": "boolean"
},
"hidesWhenNoValue": {
"type": "boolean"
}
},
"required": [
"isVisible",
"isUserModifiable",
"hidesWhenNoValue"
],
"additionalProperties": false
},
{
"type": "null"
}
]
},
"discipline": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
]
},
"tooltipDescription": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
]
},
"projectionStatus": {
"type": "string",
"enum": [
"draft",
"approved",
"archived"
]
},
"lifecycleStatus": {
"anyOf": [
{
"type": "string",
"enum": [
"draft",
"active",
"approved",
"archived"
]
},
{
"type": "null"
}
]
},
"isApproved": {
"type": "boolean"
}
},
"required": [
"id",
"parameterKeyId",
"definition",
"definitionForm",
"revitName",
"revitNameSource",
"revitNameOverridden",
"sharedParameterFileGroup",
"revitUiGroup",
"familyApplication",
"projectBinding",
"bindingKindPreference",
"revitPrimaryUsage",
"isSystem",
"resolvedCategoryScope",
"scope",
"builtInCategories",
"specTypeId",
"storageType",
"visibilityFlags",
"discipline",
"tooltipDescription",
"projectionStatus",
"lifecycleStatus",
"isApproved"
],
"additionalProperties": false
}
},
"required": [
"data"
],
"additionalProperties": false
}
}
}
},
"201": {
"description": "Revit parameter projection created",
"headers": {
"Location": {
"description": "URI of the newly created projection",
"schema": {
"type": "string"
}
}
},
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"data": {
"type": "object",
"properties": {
"id": {
"type": "string"
},
"parameterKeyId": {
"type": "string"
},
"definition": {
"type": "object",
"properties": {
"form": {
"type": "string",
"enum": [
"shared",
"family_local",
"project_local",
"built_in",
"legacy_unknown"
]
},
"guid": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
]
},
"builtInParameterName": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
]
}
},
"required": [
"form",
"guid",
"builtInParameterName"
],
"additionalProperties": false
},
"definitionForm": {
"type": "string",
"enum": [
"shared",
"family_local",
"project_local",
"built_in",
"legacy_unknown"
],
"description": "Alias of definition.form for clients that pin the top-level field. Always equals definition.form."
},
"revitName": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
]
},
"revitNameSource": {
"type": "string",
"enum": [
"derived",
"override"
]
},
"revitNameOverridden": {
"type": "boolean"
},
"sharedParameterFileGroup": {
"type": "object",
"properties": {
"parameterGroupId": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
]
},
"name": {
"type": "string"
},
"sortOrder": {
"anyOf": [
{
"type": "number"
},
{
"type": "null"
}
]
}
},
"required": [
"parameterGroupId",
"name",
"sortOrder"
],
"additionalProperties": false
},
"revitUiGroup": {
"type": "object",
"properties": {
"parameterGroupId": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
]
},
"groupTypeId": {
"type": "string",
"description": "Revit UI property-palette groupTypeId in ForgeTypeId format `autodesk.parameter.group:…` (e.g. `autodesk.parameter.group:identityData-1.0.0`). When the canonical ParameterGroup has no valid Revit UI groupTypeId, the response uses the pinned fallback `autodesk.parameter.group:data-1.0.0` and sets usedFallback=true. Top-level on projection read shapes; also may appear nested under package familyApplication/projectBinding."
},
"usedFallback": {
"type": "boolean"
}
},
"required": [
"parameterGroupId",
"groupTypeId",
"usedFallback"
],
"additionalProperties": false,
"description": "Derived Revit UI parameter group. groupTypeId is the stable Forge group identity used by the plugin."
},
"familyApplication": {
"anyOf": [
{
"type": "object",
"properties": {
"bindingKind": {
"type": "string",
"enum": [
"instance",
"type"
]
}
},
"required": [
"bindingKind"
],
"additionalProperties": false
},
{
"type": "null"
}
]
},
"projectBinding": {
"anyOf": [
{
"type": "object",
"properties": {
"bindingKind": {
"type": "string",
"enum": [
"instance",
"type"
]
}
},
"required": [
"bindingKind"
],
"additionalProperties": false
},
{
"type": "null"
}
]
},
"bindingKindPreference": {
"anyOf": [
{
"type": "string",
"enum": [
"instance",
"type"
]
},
{
"type": "null"
}
]
},
"revitPrimaryUsage": {
"anyOf": [
{
"type": "string",
"enum": [
"family",
"project"
]
},
{
"type": "null"
}
]
},
"isSystem": {
"type": "boolean"
},
"resolvedCategoryScope": {
"anyOf": [
{
"type": "object",
"properties": {
"projectionId": {
"type": "string"
},
"parameterKeyId": {
"type": "string"
},
"sharedGuid": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
]
},
"categories": {
"type": "array",
"items": {
"type": "object",
"properties": {
"builtinName": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
]
},
"builtinCategoryInt": {
"anyOf": [
{
"type": "integer",
"minimum": -9007199254740991,
"maximum": 9007199254740991
},
{
"type": "null"
}
]
},
"forgeTypeId": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
]
},
"displayName": {
"type": "string"
}
},
"required": [
"builtinName",
"builtinCategoryInt",
"forgeTypeId",
"displayName"
],
"additionalProperties": false
}
},
"contributingObjectClasses": {
"type": "array",
"items": {
"type": "object",
"properties": {
"objectClassId": {
"type": "string"
},
"objectClassKey": {
"type": "string"
},
"categories": {
"type": "array",
"items": {
"type": "object",
"properties": {
"builtinName": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
]
},
"builtinCategoryInt": {
"anyOf": [
{
"type": "integer",
"minimum": -9007199254740991,
"maximum": 9007199254740991
},
{
"type": "null"
}
]
},
"forgeTypeId": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
]
},
"displayName": {
"type": "string"
}
},
"required": [
"builtinName",
"builtinCategoryInt",
"forgeTypeId",
"displayName"
],
"additionalProperties": false
}
}
},
"required": [
"objectClassId",
"objectClassKey",
"categories"
],
"additionalProperties": false
}
},
"diagnostics": {
"type": "array",
"items": {
"type": "string"
}
}
},
"required": [
"projectionId",
"parameterKeyId",
"sharedGuid",
"categories",
"contributingObjectClasses",
"diagnostics"
],
"additionalProperties": false
},
{
"type": "null"
}
]
},
"scope": {
"type": "object",
"properties": {
"kind": {
"type": "string",
"enum": [
"derived",
"unscoped"
],
"description": "Project-binding category-scope kind. Always derived from ObjectClass assignments when a project binding exists. `unscoped` when there is no project binding / no resolved scope."
}
},
"required": [
"kind"
],
"additionalProperties": false
},
"builtInCategories": {
"anyOf": [
{
"type": "array",
"items": {
"type": "object",
"properties": {
"builtinName": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
]
},
"builtinCategoryInt": {
"anyOf": [
{
"type": "integer",
"minimum": -9007199254740991,
"maximum": 9007199254740991
},
{
"type": "null"
}
]
},
"forgeTypeId": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
]
},
"displayName": {
"type": "string"
}
},
"required": [
"builtinName",
"builtinCategoryInt",
"forgeTypeId",
"displayName"
],
"additionalProperties": false
}
},
{
"type": "null"
}
],
"description": "Revit-native categories where a built_in parameter exists. Null when no Revit-availability source or explicit projection data is present. Never derived from ObjectClass affinity links."
},
"specTypeId": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
]
},
"storageType": {
"anyOf": [
{
"type": "string",
"enum": [
"Double",
"Integer",
"String",
"ElementId",
"None"
]
},
{
"type": "null"
}
]
},
"visibilityFlags": {
"anyOf": [
{
"type": "object",
"properties": {
"isVisible": {
"type": "boolean"
},
"isUserModifiable": {
"type": "boolean"
},
"hidesWhenNoValue": {
"type": "boolean"
}
},
"required": [
"isVisible",
"isUserModifiable",
"hidesWhenNoValue"
],
"additionalProperties": false
},
{
"type": "null"
}
]
},
"discipline": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
]
},
"tooltipDescription": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
]
},
"projectionStatus": {
"type": "string",
"enum": [
"draft",
"approved",
"archived"
]
},
"lifecycleStatus": {
"anyOf": [
{
"type": "string",
"enum": [
"draft",
"active",
"approved",
"archived"
]
},
{
"type": "null"
}
]
},
"isApproved": {
"type": "boolean"
}
},
"required": [
"id",
"parameterKeyId",
"definition",
"definitionForm",
"revitName",
"revitNameSource",
"revitNameOverridden",
"sharedParameterFileGroup",
"revitUiGroup",
"familyApplication",
"projectBinding",
"bindingKindPreference",
"revitPrimaryUsage",
"isSystem",
"resolvedCategoryScope",
"scope",
"builtInCategories",
"specTypeId",
"storageType",
"visibilityFlags",
"discipline",
"tooltipDescription",
"projectionStatus",
"lifecycleStatus",
"isApproved"
],
"additionalProperties": false
}
},
"required": [
"data"
],
"additionalProperties": false
}
}
}
},
"400": {
"description": "Invalid request",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"error": {
"type": "object",
"properties": {
"code": {
"type": "string"
},
"message": {
"type": "string"
},
"field": {
"type": "string"
},
"details": {},
"unmetRequirements": {
"type": "array",
"items": {
"type": "object",
"properties": {
"code": {
"type": "string"
},
"message": {
"type": "string"
},
"objectClassKey": {
"type": "string"
},
"objectClassId": {
"type": "string"
}
},
"required": [
"code",
"message"
],
"additionalProperties": false
}
}
},
"required": [
"code",
"message"
],
"additionalProperties": false
}
},
"required": [
"error"
],
"additionalProperties": false
}
}
}
},
"401": {
"description": "Authenticated caller required",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"error": {
"type": "object",
"properties": {
"code": {
"type": "string"
},
"message": {
"type": "string"
},
"field": {
"type": "string"
},
"details": {},
"unmetRequirements": {
"type": "array",
"items": {
"type": "object",
"properties": {
"code": {
"type": "string"
},
"message": {
"type": "string"
},
"objectClassKey": {
"type": "string"
},
"objectClassId": {
"type": "string"
}
},
"required": [
"code",
"message"
],
"additionalProperties": false
}
}
},
"required": [
"code",
"message"
],
"additionalProperties": false
}
},
"required": [
"error"
],
"additionalProperties": false
}
}
}
},
"404": {
"description": "Resource not found",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"error": {
"type": "object",
"properties": {
"code": {
"type": "string"
},
"message": {
"type": "string"
},
"field": {
"type": "string"
},
"details": {},
"unmetRequirements": {
"type": "array",
"items": {
"type": "object",
"properties": {
"code": {
"type": "string"
},
"message": {
"type": "string"
},
"objectClassKey": {
"type": "string"
},
"objectClassId": {
"type": "string"
}
},
"required": [
"code",
"message"
],
"additionalProperties": false
}
}
},
"required": [
"code",
"message"
],
"additionalProperties": false
}
},
"required": [
"error"
],
"additionalProperties": false
}
}
}
},
"409": {
"description": "GUID / built-in / projection identity conflict (SHARED_GUID_CONFLICT, BUILT_IN_PARAMETER_NAME_CONFLICT, or REVIT_PROJECTION_IDENTITY_CONFLICT)",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"error": {
"type": "object",
"properties": {
"code": {
"type": "string"
},
"message": {
"type": "string"
},
"field": {
"type": "string"
},
"details": {},
"unmetRequirements": {
"type": "array",
"items": {
"type": "object",
"properties": {
"code": {
"type": "string"
},
"message": {
"type": "string"
},
"objectClassKey": {
"type": "string"
},
"objectClassId": {
"type": "string"
}
},
"required": [
"code",
"message"
],
"additionalProperties": false
}
}
},
"required": [
"code",
"message"
],
"additionalProperties": false
}
},
"required": [
"error"
],
"additionalProperties": false
}
}
}
},
"422": {
"description": "Projection invariant rejected the command",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"error": {
"type": "object",
"properties": {
"code": {
"type": "string"
},
"message": {
"type": "string"
},
"field": {
"type": "string"
},
"details": {},
"unmetRequirements": {
"type": "array",
"items": {
"type": "object",
"properties": {
"code": {
"type": "string"
},
"message": {
"type": "string"
},
"objectClassKey": {
"type": "string"
},
"objectClassId": {
"type": "string"
}
},
"required": [
"code",
"message"
],
"additionalProperties": false
}
}
},
"required": [
"code",
"message"
],
"additionalProperties": false
}
},
"required": [
"error"
],
"additionalProperties": false
}
}
}
},
"500": {
"description": "Projection authoring unavailable",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"error": {
"type": "object",
"properties": {
"code": {
"type": "string"
},
"message": {
"type": "string"
},
"field": {
"type": "string"
},
"details": {},
"unmetRequirements": {
"type": "array",
"items": {
"type": "object",
"properties": {
"code": {
"type": "string"
},
"message": {
"type": "string"
},
"objectClassKey": {
"type": "string"
},
"objectClassId": {
"type": "string"
}
},
"required": [
"code",
"message"
],
"additionalProperties": false
}
}
},
"required": [
"code",
"message"
],
"additionalProperties": false
}
},
"required": [
"error"
],
"additionalProperties": false
}
}
}
}
}
}
},
"/api/v1/bim-ontology/revit/parameter-projections/{key}": {
"get": {
"operationId": "getRevitParameterProjection",
"tags": [
"BIM Ontology / Revit Parameter Projection"
],
"summary": "Get a Revit parameter projection",
"description": "Steward detail read. `:key` is the projection domain_id (ADR-0048). Includes identity, GUID, status, discipline, spec type, derived name, applications and binding kinds, primary usage, derived categories (S19 applicable-class set), ParameterKey summary, and missingForApproval from RevitProjectionApprovePolicy (empty when complete). Not a system-maintenance endpoint.",
"security": [
{
"apiKeyAuth": []
}
],
"x-domain": {
"appVersion": "api-v1",
"boundedContext": "bim-ontology",
"service": "api-v1",
"aggregate": "RevitParameterProjection",
"useCase": "GetRevitParameterProjectionUseCase",
"domainEvents": [],
"domainErrors": [
"RevitParameterProjectionInvariantError",
"RevitProjectionAuthoringError",
"ObjectClassMissingRevitCategoryError",
"ProjectParameterCategoryScopeEmptyError",
"MalformedRevitCategoryDataError"
]
},
"parameters": [
{
"in": "path",
"name": "key",
"required": true,
"schema": {
"type": "string"
},
"description": "RevitParameterProjection domain_id (ADR-0048)"
}
],
"responses": {
"200": {
"description": "Revit parameter projection detail",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"data": {
"type": "object",
"properties": {
"id": {
"type": "string"
},
"parameterKeyId": {
"type": "string"
},
"guid": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
]
},
"projectionStatus": {
"type": "string",
"enum": [
"draft",
"approved",
"archived"
]
},
"discipline": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
]
},
"specTypeId": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
]
},
"revitName": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
]
},
"familyApplication": {
"anyOf": [
{
"type": "object",
"properties": {
"bindingKind": {
"type": "string",
"enum": [
"instance",
"type"
]
}
},
"required": [
"bindingKind"
],
"additionalProperties": false
},
{
"type": "null"
}
]
},
"projectBinding": {
"anyOf": [
{
"type": "object",
"properties": {
"bindingKind": {
"type": "string",
"enum": [
"instance",
"type"
]
}
},
"required": [
"bindingKind"
],
"additionalProperties": false
},
{
"type": "null"
}
]
},
"bindingKindPreference": {
"anyOf": [
{
"type": "string",
"enum": [
"instance",
"type"
]
},
{
"type": "null"
}
]
},
"revitPrimaryUsage": {
"anyOf": [
{
"type": "string",
"enum": [
"family",
"project"
]
},
{
"type": "null"
}
]
},
"categories": {
"type": "array",
"items": {
"type": "object",
"properties": {
"builtinName": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
]
},
"builtinCategoryInt": {
"anyOf": [
{
"type": "integer",
"minimum": -9007199254740991,
"maximum": 9007199254740991
},
{
"type": "null"
}
]
},
"forgeTypeId": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
]
},
"displayName": {
"type": "string"
}
},
"required": [
"builtinName",
"builtinCategoryInt",
"forgeTypeId",
"displayName"
],
"additionalProperties": false
}
},
"parameterKey": {
"anyOf": [
{
"type": "object",
"properties": {
"id": {
"type": "string"
},
"name": {
"type": "string"
},
"handle": {
"type": "string"
},
"lifecycleStatus": {
"type": "string"
},
"usages": {
"type": "array",
"items": {
"type": "string"
}
}
},
"required": [
"id",
"name",
"handle",
"lifecycleStatus",
"usages"
],
"additionalProperties": false
},
{
"type": "null"
}
]
},
"missingForApproval": {
"type": "array",
"items": {
"type": "object",
"properties": {
"code": {
"type": "string"
},
"message": {
"type": "string"
},
"objectClassKey": {
"type": "string"
},
"objectClassId": {
"type": "string"
}
},
"required": [
"code",
"message"
],
"additionalProperties": false
}
}
},
"required": [
"id",
"parameterKeyId",
"guid",
"projectionStatus",
"discipline",
"specTypeId",
"revitName",
"familyApplication",
"projectBinding",
"bindingKindPreference",
"revitPrimaryUsage",
"categories",
"parameterKey",
"missingForApproval"
],
"additionalProperties": false
}
},
"required": [
"data"
],
"additionalProperties": false
}
}
}
},
"400": {
"description": "Invalid request",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"error": {
"type": "object",
"properties": {
"code": {
"type": "string"
},
"message": {
"type": "string"
},
"field": {
"type": "string"
},
"details": {},
"unmetRequirements": {
"type": "array",
"items": {
"type": "object",
"properties": {
"code": {
"type": "string"
},
"message": {
"type": "string"
},
"objectClassKey": {
"type": "string"
},
"objectClassId": {
"type": "string"
}
},
"required": [
"code",
"message"
],
"additionalProperties": false
}
}
},
"required": [
"code",
"message"
],
"additionalProperties": false
}
},
"required": [
"error"
],
"additionalProperties": false
}
}
}
},
"401": {
"description": "Authenticated caller required",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"error": {
"type": "object",
"properties": {
"code": {
"type": "string"
},
"message": {
"type": "string"
},
"field": {
"type": "string"
},
"details": {},
"unmetRequirements": {
"type": "array",
"items": {
"type": "object",
"properties": {
"code": {
"type": "string"
},
"message": {
"type": "string"
},
"objectClassKey": {
"type": "string"
},
"objectClassId": {
"type": "string"
}
},
"required": [
"code",
"message"
],
"additionalProperties": false
}
}
},
"required": [
"code",
"message"
],
"additionalProperties": false
}
},
"required": [
"error"
],
"additionalProperties": false
}
}
}
},
"404": {
"description": "Resource not found",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"error": {
"type": "object",
"properties": {
"code": {
"type": "string"
},
"message": {
"type": "string"
},
"field": {
"type": "string"
},
"details": {},
"unmetRequirements": {
"type": "array",
"items": {
"type": "object",
"properties": {
"code": {
"type": "string"
},
"message": {
"type": "string"
},
"objectClassKey": {
"type": "string"
},
"objectClassId": {
"type": "string"
}
},
"required": [
"code",
"message"
],
"additionalProperties": false
}
}
},
"required": [
"code",
"message"
],
"additionalProperties": false
}
},
"required": [
"error"
],
"additionalProperties": false
}
}
}
},
"409": {
"description": "Conflict: GUID / built-in identity, or approve preconditions unmet (REVIT_PROJECTION_APPROVE_PRECONDITION_FAILED lists every unmet item)",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"error": {
"type": "object",
"properties": {
"code": {
"type": "string"
},
"message": {
"type": "string"
},
"field": {
"type": "string"
},
"details": {},
"unmetRequirements": {
"type": "array",
"items": {
"type": "object",
"properties": {
"code": {
"type": "string"
},
"message": {
"type": "string"
},
"objectClassKey": {
"type": "string"
},
"objectClassId": {
"type": "string"
}
},
"required": [
"code",
"message"
],
"additionalProperties": false
}
}
},
"required": [
"code",
"message"
],
"additionalProperties": false
}
},
"required": [
"error"
],
"additionalProperties": false
}
}
}
},
"422": {
"description": "Projection invariant rejected the command",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"error": {
"type": "object",
"properties": {
"code": {
"type": "string"
},
"message": {
"type": "string"
},
"field": {
"type": "string"
},
"details": {},
"unmetRequirements": {
"type": "array",
"items": {
"type": "object",
"properties": {
"code": {
"type": "string"
},
"message": {
"type": "string"
},
"objectClassKey": {
"type": "string"
},
"objectClassId": {
"type": "string"
}
},
"required": [
"code",
"message"
],
"additionalProperties": false
}
}
},
"required": [
"code",
"message"
],
"additionalProperties": false
}
},
"required": [
"error"
],
"additionalProperties": false
}
}
}
},
"500": {
"description": "Projection authoring unavailable",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"error": {
"type": "object",
"properties": {
"code": {
"type": "string"
},
"message": {
"type": "string"
},
"field": {
"type": "string"
},
"details": {},
"unmetRequirements": {
"type": "array",
"items": {
"type": "object",
"properties": {
"code": {
"type": "string"
},
"message": {
"type": "string"
},
"objectClassKey": {
"type": "string"
},
"objectClassId": {
"type": "string"
}
},
"required": [
"code",
"message"
],
"additionalProperties": false
}
}
},
"required": [
"code",
"message"
],
"additionalProperties": false
}
},
"required": [
"error"
],
"additionalProperties": false
}
}
}
}
}
},
"patch": {
"operationId": "updateRevitParameterProjection",
"tags": [
"BIM Ontology / Revit Parameter Projection"
],
"summary": "Patch steward Revit projection facts",
"description": "Steward write surface for Revit-specific facts on a draft or approved projection. Accepts binding kind preference, Revit primary usage, family application enable/disable and binding kind, project binding enable/disable and binding kind, and visibility / user-modifiable flags. Derived fields (parameterKeyId, discipline, spec type, derived name, categories, GUID, status) are rejected with HTTP 400 naming the field. A PATCH that changes binding, usage, or visibility facts on an approved projection demotes it to draft so the steward must re-approve (SPF/package output would change). A no-op PATCH stays approved. The normal path is: approved ParameterKey → auto-created draft → steward PATCH → approve.",
"security": [
{
"apiKeyAuth": []
}
],
"x-domain": {
"appVersion": "api-v1",
"boundedContext": "bim-ontology",
"service": "api-v1",
"aggregate": "RevitParameterProjection",
"useCase": "UpdateRevitParameterProjectionUseCase",
"domainEvents": [
"RevitParameterProjectionUpdated"
],
"domainErrors": [
"RevitParameterProjectionInvariantError",
"RevitProjectionAuthoringError",
"ObjectClassMissingRevitCategoryError",
"ProjectParameterCategoryScopeEmptyError",
"MalformedRevitCategoryDataError"
]
},
"parameters": [
{
"in": "path",
"name": "key",
"required": true,
"schema": {
"type": "string"
},
"description": "RevitParameterProjection domain_id (ADR-0048)"
}
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"bindingKindPreference": {
"anyOf": [
{
"type": "string",
"enum": [
"instance",
"type"
]
},
{
"type": "null"
}
]
},
"revitPrimaryUsage": {
"anyOf": [
{
"type": "string",
"enum": [
"family",
"project"
]
},
{
"type": "null"
}
]
},
"familyApplication": {
"anyOf": [
{
"type": "object",
"properties": {
"bindingKind": {
"type": "string",
"enum": [
"instance",
"type"
]
}
},
"required": [
"bindingKind"
],
"additionalProperties": false
},
{
"type": "null"
}
]
},
"projectBinding": {
"anyOf": [
{
"type": "object",
"properties": {
"bindingKind": {
"type": "string",
"enum": [
"instance",
"type"
]
}
},
"required": [
"bindingKind"
],
"additionalProperties": false
},
{
"type": "null"
}
]
},
"visibilityFlags": {
"type": "object",
"properties": {
"isVisible": {
"type": "boolean"
},
"isUserModifiable": {
"type": "boolean"
},
"hidesWhenNoValue": {
"type": "boolean"
}
},
"required": [
"isVisible",
"isUserModifiable",
"hidesWhenNoValue"
],
"additionalProperties": false
}
},
"additionalProperties": false
}
}
}
},
"responses": {
"200": {
"description": "Revit parameter projection updated",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"data": {
"type": "object",
"properties": {
"id": {
"type": "string"
},
"parameterKeyId": {
"type": "string"
},
"definition": {
"type": "object",
"properties": {
"form": {
"type": "string",
"enum": [
"shared",
"family_local",
"project_local",
"built_in",
"legacy_unknown"
]
},
"guid": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
]
},
"builtInParameterName": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
]
}
},
"required": [
"form",
"guid",
"builtInParameterName"
],
"additionalProperties": false
},
"definitionForm": {
"type": "string",
"enum": [
"shared",
"family_local",
"project_local",
"built_in",
"legacy_unknown"
],
"description": "Alias of definition.form for clients that pin the top-level field. Always equals definition.form."
},
"revitName": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
]
},
"revitNameSource": {
"type": "string",
"enum": [
"derived",
"override"
]
},
"revitNameOverridden": {
"type": "boolean"
},
"sharedParameterFileGroup": {
"type": "object",
"properties": {
"parameterGroupId": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
]
},
"name": {
"type": "string"
},
"sortOrder": {
"anyOf": [
{
"type": "number"
},
{
"type": "null"
}
]
}
},
"required": [
"parameterGroupId",
"name",
"sortOrder"
],
"additionalProperties": false
},
"revitUiGroup": {
"type": "object",
"properties": {
"parameterGroupId": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
]
},
"groupTypeId": {
"type": "string",
"description": "Revit UI property-palette groupTypeId in ForgeTypeId format `autodesk.parameter.group:…` (e.g. `autodesk.parameter.group:identityData-1.0.0`). When the canonical ParameterGroup has no valid Revit UI groupTypeId, the response uses the pinned fallback `autodesk.parameter.group:data-1.0.0` and sets usedFallback=true. Top-level on projection read shapes; also may appear nested under package familyApplication/projectBinding."
},
"usedFallback": {
"type": "boolean"
}
},
"required": [
"parameterGroupId",
"groupTypeId",
"usedFallback"
],
"additionalProperties": false,
"description": "Derived Revit UI parameter group. groupTypeId is the stable Forge group identity used by the plugin."
},
"familyApplication": {
"anyOf": [
{
"type": "object",
"properties": {
"bindingKind": {
"type": "string",
"enum": [
"instance",
"type"
]
}
},
"required": [
"bindingKind"
],
"additionalProperties": false
},
{
"type": "null"
}
]
},
"projectBinding": {
"anyOf": [
{
"type": "object",
"properties": {
"bindingKind": {
"type": "string",
"enum": [
"instance",
"type"
]
}
},
"required": [
"bindingKind"
],
"additionalProperties": false
},
{
"type": "null"
}
]
},
"bindingKindPreference": {
"anyOf": [
{
"type": "string",
"enum": [
"instance",
"type"
]
},
{
"type": "null"
}
]
},
"revitPrimaryUsage": {
"anyOf": [
{
"type": "string",
"enum": [
"family",
"project"
]
},
{
"type": "null"
}
]
},
"isSystem": {
"type": "boolean"
},
"resolvedCategoryScope": {
"anyOf": [
{
"type": "object",
"properties": {
"projectionId": {
"type": "string"
},
"parameterKeyId": {
"type": "string"
},
"sharedGuid": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
]
},
"categories": {
"type": "array",
"items": {
"type": "object",
"properties": {
"builtinName": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
]
},
"builtinCategoryInt": {
"anyOf": [
{
"type": "integer",
"minimum": -9007199254740991,
"maximum": 9007199254740991
},
{
"type": "null"
}
]
},
"forgeTypeId": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
]
},
"displayName": {
"type": "string"
}
},
"required": [
"builtinName",
"builtinCategoryInt",
"forgeTypeId",
"displayName"
],
"additionalProperties": false
}
},
"contributingObjectClasses": {
"type": "array",
"items": {
"type": "object",
"properties": {
"objectClassId": {
"type": "string"
},
"objectClassKey": {
"type": "string"
},
"categories": {
"type": "array",
"items": {
"type": "object",
"properties": {
"builtinName": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
]
},
"builtinCategoryInt": {
"anyOf": [
{
"type": "integer",
"minimum": -9007199254740991,
"maximum": 9007199254740991
},
{
"type": "null"
}
]
},
"forgeTypeId": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
]
},
"displayName": {
"type": "string"
}
},
"required": [
"builtinName",
"builtinCategoryInt",
"forgeTypeId",
"displayName"
],
"additionalProperties": false
}
}
},
"required": [
"objectClassId",
"objectClassKey",
"categories"
],
"additionalProperties": false
}
},
"diagnostics": {
"type": "array",
"items": {
"type": "string"
}
}
},
"required": [
"projectionId",
"parameterKeyId",
"sharedGuid",
"categories",
"contributingObjectClasses",
"diagnostics"
],
"additionalProperties": false
},
{
"type": "null"
}
]
},
"scope": {
"type": "object",
"properties": {
"kind": {
"type": "string",
"enum": [
"derived",
"unscoped"
],
"description": "Project-binding category-scope kind. Always derived from ObjectClass assignments when a project binding exists. `unscoped` when there is no project binding / no resolved scope."
}
},
"required": [
"kind"
],
"additionalProperties": false
},
"builtInCategories": {
"anyOf": [
{
"type": "array",
"items": {
"type": "object",
"properties": {
"builtinName": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
]
},
"builtinCategoryInt": {
"anyOf": [
{
"type": "integer",
"minimum": -9007199254740991,
"maximum": 9007199254740991
},
{
"type": "null"
}
]
},
"forgeTypeId": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
]
},
"displayName": {
"type": "string"
}
},
"required": [
"builtinName",
"builtinCategoryInt",
"forgeTypeId",
"displayName"
],
"additionalProperties": false
}
},
{
"type": "null"
}
],
"description": "Revit-native categories where a built_in parameter exists. Null when no Revit-availability source or explicit projection data is present. Never derived from ObjectClass affinity links."
},
"specTypeId": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
]
},
"storageType": {
"anyOf": [
{
"type": "string",
"enum": [
"Double",
"Integer",
"String",
"ElementId",
"None"
]
},
{
"type": "null"
}
]
},
"visibilityFlags": {
"anyOf": [
{
"type": "object",
"properties": {
"isVisible": {
"type": "boolean"
},
"isUserModifiable": {
"type": "boolean"
},
"hidesWhenNoValue": {
"type": "boolean"
}
},
"required": [
"isVisible",
"isUserModifiable",
"hidesWhenNoValue"
],
"additionalProperties": false
},
{
"type": "null"
}
]
},
"discipline": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
]
},
"tooltipDescription": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
]
},
"projectionStatus": {
"type": "string",
"enum": [
"draft",
"approved",
"archived"
]
},
"lifecycleStatus": {
"anyOf": [
{
"type": "string",
"enum": [
"draft",
"active",
"approved",
"archived"
]
},
{
"type": "null"
}
]
},
"isApproved": {
"type": "boolean"
}
},
"required": [
"id",
"parameterKeyId",
"definition",
"definitionForm",
"revitName",
"revitNameSource",
"revitNameOverridden",
"sharedParameterFileGroup",
"revitUiGroup",
"familyApplication",
"projectBinding",
"bindingKindPreference",
"revitPrimaryUsage",
"isSystem",
"resolvedCategoryScope",
"scope",
"builtInCategories",
"specTypeId",
"storageType",
"visibilityFlags",
"discipline",
"tooltipDescription",
"projectionStatus",
"lifecycleStatus",
"isApproved"
],
"additionalProperties": false
}
},
"required": [
"data"
],
"additionalProperties": false
}
}
}
},
"400": {
"description": "Invalid request",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"error": {
"type": "object",
"properties": {
"code": {
"type": "string"
},
"message": {
"type": "string"
},
"field": {
"type": "string"
},
"details": {},
"unmetRequirements": {
"type": "array",
"items": {
"type": "object",
"properties": {
"code": {
"type": "string"
},
"message": {
"type": "string"
},
"objectClassKey": {
"type": "string"
},
"objectClassId": {
"type": "string"
}
},
"required": [
"code",
"message"
],
"additionalProperties": false
}
}
},
"required": [
"code",
"message"
],
"additionalProperties": false
}
},
"required": [
"error"
],
"additionalProperties": false
}
}
}
},
"401": {
"description": "Authenticated caller required",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"error": {
"type": "object",
"properties": {
"code": {
"type": "string"
},
"message": {
"type": "string"
},
"field": {
"type": "string"
},
"details": {},
"unmetRequirements": {
"type": "array",
"items": {
"type": "object",
"properties": {
"code": {
"type": "string"
},
"message": {
"type": "string"
},
"objectClassKey": {
"type": "string"
},
"objectClassId": {
"type": "string"
}
},
"required": [
"code",
"message"
],
"additionalProperties": false
}
}
},
"required": [
"code",
"message"
],
"additionalProperties": false
}
},
"required": [
"error"
],
"additionalProperties": false
}
}
}
},
"404": {
"description": "Resource not found",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"error": {
"type": "object",
"properties": {
"code": {
"type": "string"
},
"message": {
"type": "string"
},
"field": {
"type": "string"
},
"details": {},
"unmetRequirements": {
"type": "array",
"items": {
"type": "object",
"properties": {
"code": {
"type": "string"
},
"message": {
"type": "string"
},
"objectClassKey": {
"type": "string"
},
"objectClassId": {
"type": "string"
}
},
"required": [
"code",
"message"
],
"additionalProperties": false
}
}
},
"required": [
"code",
"message"
],
"additionalProperties": false
}
},
"required": [
"error"
],
"additionalProperties": false
}
}
}
},
"409": {
"description": "Conflict: GUID / built-in identity, or approve preconditions unmet (REVIT_PROJECTION_APPROVE_PRECONDITION_FAILED lists every unmet item)",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"error": {
"type": "object",
"properties": {
"code": {
"type": "string"
},
"message": {
"type": "string"
},
"field": {
"type": "string"
},
"details": {},
"unmetRequirements": {
"type": "array",
"items": {
"type": "object",
"properties": {
"code": {
"type": "string"
},
"message": {
"type": "string"
},
"objectClassKey": {
"type": "string"
},
"objectClassId": {
"type": "string"
}
},
"required": [
"code",
"message"
],
"additionalProperties": false
}
}
},
"required": [
"code",
"message"
],
"additionalProperties": false
}
},
"required": [
"error"
],
"additionalProperties": false
}
}
}
},
"422": {
"description": "Projection invariant rejected the command",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"error": {
"type": "object",
"properties": {
"code": {
"type": "string"
},
"message": {
"type": "string"
},
"field": {
"type": "string"
},
"details": {},
"unmetRequirements": {
"type": "array",
"items": {
"type": "object",
"properties": {
"code": {
"type": "string"
},
"message": {
"type": "string"
},
"objectClassKey": {
"type": "string"
},
"objectClassId": {
"type": "string"
}
},
"required": [
"code",
"message"
],
"additionalProperties": false
}
}
},
"required": [
"code",
"message"
],
"additionalProperties": false
}
},
"required": [
"error"
],
"additionalProperties": false
}
}
}
},
"500": {
"description": "Projection authoring unavailable",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"error": {
"type": "object",
"properties": {
"code": {
"type": "string"
},
"message": {
"type": "string"
},
"field": {
"type": "string"
},
"details": {},
"unmetRequirements": {
"type": "array",
"items": {
"type": "object",
"properties": {
"code": {
"type": "string"
},
"message": {
"type": "string"
},
"objectClassKey": {
"type": "string"
},
"objectClassId": {
"type": "string"
}
},
"required": [
"code",
"message"
],
"additionalProperties": false
}
}
},
"required": [
"code",
"message"
],
"additionalProperties": false
}
},
"required": [
"error"
],
"additionalProperties": false
}
}
}
}
}
}
},
"/api/v1/bim-ontology/revit/parameter-projections/{key}/actions/approve": {
"post": {
"operationId": "approveRevitParameterProjection",
"tags": [
"BIM Ontology / Revit Parameter Projection"
],
"summary": "Approve a Revit parameter projection",
"description": "Steward promotion of a draft projection after Revit-specific facts are complete. `:key` is the projection domain_id (ADR-0048). HTTP 409 lists every unmet precondition at once (ParameterKey approved + bim_parameter, discipline, spec type, enabled application, binding kind preference, primary usage, shared GUID, every applicable ObjectClass approved, categories when the definition requires them). Not a system-maintenance endpoint. The normal path is: approved ParameterKey → auto-created draft → steward PATCH → approve.",
"security": [
{
"apiKeyAuth": []
}
],
"x-domain": {
"appVersion": "api-v1",
"boundedContext": "bim-ontology",
"service": "api-v1",
"aggregate": "RevitParameterProjection",
"useCase": "ApproveRevitParameterProjectionUseCase",
"domainEvents": [
"RevitParameterProjectionApproved"
],
"domainErrors": [
"RevitParameterProjectionInvariantError",
"RevitProjectionAuthoringError",
"ObjectClassMissingRevitCategoryError",
"ProjectParameterCategoryScopeEmptyError",
"MalformedRevitCategoryDataError"
]
},
"parameters": [
{
"in": "path",
"name": "key",
"required": true,
"schema": {
"type": "string"
},
"description": "RevitParameterProjection domain_id (ADR-0048)"
}
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {},
"additionalProperties": {}
}
}
}
},
"responses": {
"200": {
"description": "Revit parameter projection approved",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"data": {
"type": "object",
"properties": {
"id": {
"type": "string"
},
"parameterKeyId": {
"type": "string"
},
"definition": {
"type": "object",
"properties": {
"form": {
"type": "string",
"enum": [
"shared",
"family_local",
"project_local",
"built_in",
"legacy_unknown"
]
},
"guid": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
]
},
"builtInParameterName": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
]
}
},
"required": [
"form",
"guid",
"builtInParameterName"
],
"additionalProperties": false
},
"definitionForm": {
"type": "string",
"enum": [
"shared",
"family_local",
"project_local",
"built_in",
"legacy_unknown"
],
"description": "Alias of definition.form for clients that pin the top-level field. Always equals definition.form."
},
"revitName": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
]
},
"revitNameSource": {
"type": "string",
"enum": [
"derived",
"override"
]
},
"revitNameOverridden": {
"type": "boolean"
},
"sharedParameterFileGroup": {
"type": "object",
"properties": {
"parameterGroupId": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
]
},
"name": {
"type": "string"
},
"sortOrder": {
"anyOf": [
{
"type": "number"
},
{
"type": "null"
}
]
}
},
"required": [
"parameterGroupId",
"name",
"sortOrder"
],
"additionalProperties": false
},
"revitUiGroup": {
"type": "object",
"properties": {
"parameterGroupId": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
]
},
"groupTypeId": {
"type": "string",
"description": "Revit UI property-palette groupTypeId in ForgeTypeId format `autodesk.parameter.group:…` (e.g. `autodesk.parameter.group:identityData-1.0.0`). When the canonical ParameterGroup has no valid Revit UI groupTypeId, the response uses the pinned fallback `autodesk.parameter.group:data-1.0.0` and sets usedFallback=true. Top-level on projection read shapes; also may appear nested under package familyApplication/projectBinding."
},
"usedFallback": {
"type": "boolean"
}
},
"required": [
"parameterGroupId",
"groupTypeId",
"usedFallback"
],
"additionalProperties": false,
"description": "Derived Revit UI parameter group. groupTypeId is the stable Forge group identity used by the plugin."
},
"familyApplication": {
"anyOf": [
{
"type": "object",
"properties": {
"bindingKind": {
"type": "string",
"enum": [
"instance",
"type"
]
}
},
"required": [
"bindingKind"
],
"additionalProperties": false
},
{
"type": "null"
}
]
},
"projectBinding": {
"anyOf": [
{
"type": "object",
"properties": {
"bindingKind": {
"type": "string",
"enum": [
"instance",
"type"
]
}
},
"required": [
"bindingKind"
],
"additionalProperties": false
},
{
"type": "null"
}
]
},
"bindingKindPreference": {
"anyOf": [
{
"type": "string",
"enum": [
"instance",
"type"
]
},
{
"type": "null"
}
]
},
"revitPrimaryUsage": {
"anyOf": [
{
"type": "string",
"enum": [
"family",
"project"
]
},
{
"type": "null"
}
]
},
"isSystem": {
"type": "boolean"
},
"resolvedCategoryScope": {
"anyOf": [
{
"type": "object",
"properties": {
"projectionId": {
"type": "string"
},
"parameterKeyId": {
"type": "string"
},
"sharedGuid": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
]
},
"categories": {
"type": "array",
"items": {
"type": "object",
"properties": {
"builtinName": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
]
},
"builtinCategoryInt": {
"anyOf": [
{
"type": "integer",
"minimum": -9007199254740991,
"maximum": 9007199254740991
},
{
"type": "null"
}
]
},
"forgeTypeId": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
]
},
"displayName": {
"type": "string"
}
},
"required": [
"builtinName",
"builtinCategoryInt",
"forgeTypeId",
"displayName"
],
"additionalProperties": false
}
},
"contributingObjectClasses": {
"type": "array",
"items": {
"type": "object",
"properties": {
"objectClassId": {
"type": "string"
},
"objectClassKey": {
"type": "string"
},
"categories": {
"type": "array",
"items": {
"type": "object",
"properties": {
"builtinName": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
]
},
"builtinCategoryInt": {
"anyOf": [
{
"type": "integer",
"minimum": -9007199254740991,
"maximum": 9007199254740991
},
{
"type": "null"
}
]
},
"forgeTypeId": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
]
},
"displayName": {
"type": "string"
}
},
"required": [
"builtinName",
"builtinCategoryInt",
"forgeTypeId",
"displayName"
],
"additionalProperties": false
}
}
},
"required": [
"objectClassId",
"objectClassKey",
"categories"
],
"additionalProperties": false
}
},
"diagnostics": {
"type": "array",
"items": {
"type": "string"
}
}
},
"required": [
"projectionId",
"parameterKeyId",
"sharedGuid",
"categories",
"contributingObjectClasses",
"diagnostics"
],
"additionalProperties": false
},
{
"type": "null"
}
]
},
"scope": {
"type": "object",
"properties": {
"kind": {
"type": "string",
"enum": [
"derived",
"unscoped"
],
"description": "Project-binding category-scope kind. Always derived from ObjectClass assignments when a project binding exists. `unscoped` when there is no project binding / no resolved scope."
}
},
"required": [
"kind"
],
"additionalProperties": false
},
"builtInCategories": {
"anyOf": [
{
"type": "array",
"items": {
"type": "object",
"properties": {
"builtinName": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
]
},
"builtinCategoryInt": {
"anyOf": [
{
"type": "integer",
"minimum": -9007199254740991,
"maximum": 9007199254740991
},
{
"type": "null"
}
]
},
"forgeTypeId": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
]
},
"displayName": {
"type": "string"
}
},
"required": [
"builtinName",
"builtinCategoryInt",
"forgeTypeId",
"displayName"
],
"additionalProperties": false
}
},
{
"type": "null"
}
],
"description": "Revit-native categories where a built_in parameter exists. Null when no Revit-availability source or explicit projection data is present. Never derived from ObjectClass affinity links."
},
"specTypeId": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
]
},
"storageType": {
"anyOf": [
{
"type": "string",
"enum": [
"Double",
"Integer",
"String",
"ElementId",
"None"
]
},
{
"type": "null"
}
]
},
"visibilityFlags": {
"anyOf": [
{
"type": "object",
"properties": {
"isVisible": {
"type": "boolean"
},
"isUserModifiable": {
"type": "boolean"
},
"hidesWhenNoValue": {
"type": "boolean"
}
},
"required": [
"isVisible",
"isUserModifiable",
"hidesWhenNoValue"
],
"additionalProperties": false
},
{
"type": "null"
}
]
},
"discipline": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
]
},
"tooltipDescription": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
]
},
"projectionStatus": {
"type": "string",
"enum": [
"draft",
"approved",
"archived"
]
},
"lifecycleStatus": {
"anyOf": [
{
"type": "string",
"enum": [
"draft",
"active",
"approved",
"archived"
]
},
{
"type": "null"
}
]
},
"isApproved": {
"type": "boolean"
}
},
"required": [
"id",
"parameterKeyId",
"definition",
"definitionForm",
"revitName",
"revitNameSource",
"revitNameOverridden",
"sharedParameterFileGroup",
"revitUiGroup",
"familyApplication",
"projectBinding",
"bindingKindPreference",
"revitPrimaryUsage",
"isSystem",
"resolvedCategoryScope",
"scope",
"builtInCategories",
"specTypeId",
"storageType",
"visibilityFlags",
"discipline",
"tooltipDescription",
"projectionStatus",
"lifecycleStatus",
"isApproved"
],
"additionalProperties": false
}
},
"required": [
"data"
],
"additionalProperties": false
}
}
}
},
"400": {
"description": "Invalid request",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"error": {
"type": "object",
"properties": {
"code": {
"type": "string"
},
"message": {
"type": "string"
},
"field": {
"type": "string"
},
"details": {},
"unmetRequirements": {
"type": "array",
"items": {
"type": "object",
"properties": {
"code": {
"type": "string"
},
"message": {
"type": "string"
},
"objectClassKey": {
"type": "string"
},
"objectClassId": {
"type": "string"
}
},
"required": [
"code",
"message"
],
"additionalProperties": false
}
}
},
"required": [
"code",
"message"
],
"additionalProperties": false
}
},
"required": [
"error"
],
"additionalProperties": false
}
}
}
},
"401": {
"description": "Authenticated caller required",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"error": {
"type": "object",
"properties": {
"code": {
"type": "string"
},
"message": {
"type": "string"
},
"field": {
"type": "string"
},
"details": {},
"unmetRequirements": {
"type": "array",
"items": {
"type": "object",
"properties": {
"code": {
"type": "string"
},
"message": {
"type": "string"
},
"objectClassKey": {
"type": "string"
},
"objectClassId": {
"type": "string"
}
},
"required": [
"code",
"message"
],
"additionalProperties": false
}
}
},
"required": [
"code",
"message"
],
"additionalProperties": false
}
},
"required": [
"error"
],
"additionalProperties": false
}
}
}
},
"404": {
"description": "Resource not found",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"error": {
"type": "object",
"properties": {
"code": {
"type": "string"
},
"message": {
"type": "string"
},
"field": {
"type": "string"
},
"details": {},
"unmetRequirements": {
"type": "array",
"items": {
"type": "object",
"properties": {
"code": {
"type": "string"
},
"message": {
"type": "string"
},
"objectClassKey": {
"type": "string"
},
"objectClassId": {
"type": "string"
}
},
"required": [
"code",
"message"
],
"additionalProperties": false
}
}
},
"required": [
"code",
"message"
],
"additionalProperties": false
}
},
"required": [
"error"
],
"additionalProperties": false
}
}
}
},
"409": {
"description": "Conflict: GUID / built-in identity, or approve preconditions unmet (REVIT_PROJECTION_APPROVE_PRECONDITION_FAILED lists every unmet item)",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"error": {
"type": "object",
"properties": {
"code": {
"type": "string"
},
"message": {
"type": "string"
},
"field": {
"type": "string"
},
"details": {},
"unmetRequirements": {
"type": "array",
"items": {
"type": "object",
"properties": {
"code": {
"type": "string"
},
"message": {
"type": "string"
},
"objectClassKey": {
"type": "string"
},
"objectClassId": {
"type": "string"
}
},
"required": [
"code",
"message"
],
"additionalProperties": false
}
}
},
"required": [
"code",
"message"
],
"additionalProperties": false
}
},
"required": [
"error"
],
"additionalProperties": false
}
}
}
},
"422": {
"description": "Projection invariant rejected the command",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"error": {
"type": "object",
"properties": {
"code": {
"type": "string"
},
"message": {
"type": "string"
},
"field": {
"type": "string"
},
"details": {},
"unmetRequirements": {
"type": "array",
"items": {
"type": "object",
"properties": {
"code": {
"type": "string"
},
"message": {
"type": "string"
},
"objectClassKey": {
"type": "string"
},
"objectClassId": {
"type": "string"
}
},
"required": [
"code",
"message"
],
"additionalProperties": false
}
}
},
"required": [
"code",
"message"
],
"additionalProperties": false
}
},
"required": [
"error"
],
"additionalProperties": false
}
}
}
},
"500": {
"description": "Projection authoring unavailable",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"error": {
"type": "object",
"properties": {
"code": {
"type": "string"
},
"message": {
"type": "string"
},
"field": {
"type": "string"
},
"details": {},
"unmetRequirements": {
"type": "array",
"items": {
"type": "object",
"properties": {
"code": {
"type": "string"
},
"message": {
"type": "string"
},
"objectClassKey": {
"type": "string"
},
"objectClassId": {
"type": "string"
}
},
"required": [
"code",
"message"
],
"additionalProperties": false
}
}
},
"required": [
"code",
"message"
],
"additionalProperties": false
}
},
"required": [
"error"
],
"additionalProperties": false
}
}
}
}
}
}
},
"/api/v1/bim-ontology/revit/parameter-projections/{id}/archive": {
"post": {
"operationId": "archiveRevitParameterProjection",
"tags": [
"BIM Ontology / Revit Parameter Projection",
"system-maintenance"
],
"summary": "Archive a Revit parameter projection",
"description": "Not part of the steward workflow. The normal path is: approved ParameterKey → auto-created draft → steward PATCH → approve.",
"security": [
{
"apiKeyAuth": []
}
],
"x-internal": true,
"x-domain": {
"appVersion": "api-v1",
"boundedContext": "bim-ontology",
"service": "api-v1",
"aggregate": "RevitParameterProjection",
"useCase": "ArchiveRevitParameterProjectionUseCase",
"domainEvents": [
"RevitParameterProjectionArchived"
],
"domainErrors": [
"RevitParameterProjectionInvariantError",
"RevitProjectionAuthoringError",
"ObjectClassMissingRevitCategoryError",
"ProjectParameterCategoryScopeEmptyError",
"MalformedRevitCategoryDataError"
]
},
"parameters": [
{
"in": "path",
"name": "id",
"required": true,
"schema": {
"type": "string"
},
"description": "RevitParameterProjection domain_id (ADR-0048)"
}
],
"responses": {
"200": {
"description": "Revit parameter projection archived",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"data": {
"type": "object",
"properties": {
"id": {
"type": "string"
},
"parameterKeyId": {
"type": "string"
},
"definition": {
"type": "object",
"properties": {
"form": {
"type": "string",
"enum": [
"shared",
"family_local",
"project_local",
"built_in",
"legacy_unknown"
]
},
"guid": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
]
},
"builtInParameterName": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
]
}
},
"required": [
"form",
"guid",
"builtInParameterName"
],
"additionalProperties": false
},
"definitionForm": {
"type": "string",
"enum": [
"shared",
"family_local",
"project_local",
"built_in",
"legacy_unknown"
],
"description": "Alias of definition.form for clients that pin the top-level field. Always equals definition.form."
},
"revitName": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
]
},
"revitNameSource": {
"type": "string",
"enum": [
"derived",
"override"
]
},
"revitNameOverridden": {
"type": "boolean"
},
"sharedParameterFileGroup": {
"type": "object",
"properties": {
"parameterGroupId": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
]
},
"name": {
"type": "string"
},
"sortOrder": {
"anyOf": [
{
"type": "number"
},
{
"type": "null"
}
]
}
},
"required": [
"parameterGroupId",
"name",
"sortOrder"
],
"additionalProperties": false
},
"revitUiGroup": {
"type": "object",
"properties": {
"parameterGroupId": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
]
},
"groupTypeId": {
"type": "string",
"description": "Revit UI property-palette groupTypeId in ForgeTypeId format `autodesk.parameter.group:…` (e.g. `autodesk.parameter.group:identityData-1.0.0`). When the canonical ParameterGroup has no valid Revit UI groupTypeId, the response uses the pinned fallback `autodesk.parameter.group:data-1.0.0` and sets usedFallback=true. Top-level on projection read shapes; also may appear nested under package familyApplication/projectBinding."
},
"usedFallback": {
"type": "boolean"
}
},
"required": [
"parameterGroupId",
"groupTypeId",
"usedFallback"
],
"additionalProperties": false,
"description": "Derived Revit UI parameter group. groupTypeId is the stable Forge group identity used by the plugin."
},
"familyApplication": {
"anyOf": [
{
"type": "object",
"properties": {
"bindingKind": {
"type": "string",
"enum": [
"instance",
"type"
]
}
},
"required": [
"bindingKind"
],
"additionalProperties": false
},
{
"type": "null"
}
]
},
"projectBinding": {
"anyOf": [
{
"type": "object",
"properties": {
"bindingKind": {
"type": "string",
"enum": [
"instance",
"type"
]
}
},
"required": [
"bindingKind"
],
"additionalProperties": false
},
{
"type": "null"
}
]
},
"bindingKindPreference": {
"anyOf": [
{
"type": "string",
"enum": [
"instance",
"type"
]
},
{
"type": "null"
}
]
},
"revitPrimaryUsage": {
"anyOf": [
{
"type": "string",
"enum": [
"family",
"project"
]
},
{
"type": "null"
}
]
},
"isSystem": {
"type": "boolean"
},
"resolvedCategoryScope": {
"anyOf": [
{
"type": "object",
"properties": {
"projectionId": {
"type": "string"
},
"parameterKeyId": {
"type": "string"
},
"sharedGuid": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
]
},
"categories": {
"type": "array",
"items": {
"type": "object",
"properties": {
"builtinName": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
]
},
"builtinCategoryInt": {
"anyOf": [
{
"type": "integer",
"minimum": -9007199254740991,
"maximum": 9007199254740991
},
{
"type": "null"
}
]
},
"forgeTypeId": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
]
},
"displayName": {
"type": "string"
}
},
"required": [
"builtinName",
"builtinCategoryInt",
"forgeTypeId",
"displayName"
],
"additionalProperties": false
}
},
"contributingObjectClasses": {
"type": "array",
"items": {
"type": "object",
"properties": {
"objectClassId": {
"type": "string"
},
"objectClassKey": {
"type": "string"
},
"categories": {
"type": "array",
"items": {
"type": "object",
"properties": {
"builtinName": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
]
},
"builtinCategoryInt": {
"anyOf": [
{
"type": "integer",
"minimum": -9007199254740991,
"maximum": 9007199254740991
},
{
"type": "null"
}
]
},
"forgeTypeId": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
]
},
"displayName": {
"type": "string"
}
},
"required": [
"builtinName",
"builtinCategoryInt",
"forgeTypeId",
"displayName"
],
"additionalProperties": false
}
}
},
"required": [
"objectClassId",
"objectClassKey",
"categories"
],
"additionalProperties": false
}
},
"diagnostics": {
"type": "array",
"items": {
"type": "string"
}
}
},
"required": [
"projectionId",
"parameterKeyId",
"sharedGuid",
"categories",
"contributingObjectClasses",
"diagnostics"
],
"additionalProperties": false
},
{
"type": "null"
}
]
},
"scope": {
"type": "object",
"properties": {
"kind": {
"type": "string",
"enum": [
"derived",
"unscoped"
],
"description": "Project-binding category-scope kind. Always derived from ObjectClass assignments when a project binding exists. `unscoped` when there is no project binding / no resolved scope."
}
},
"required": [
"kind"
],
"additionalProperties": false
},
"builtInCategories": {
"anyOf": [
{
"type": "array",
"items": {
"type": "object",
"properties": {
"builtinName": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
]
},
"builtinCategoryInt": {
"anyOf": [
{
"type": "integer",
"minimum": -9007199254740991,
"maximum": 9007199254740991
},
{
"type": "null"
}
]
},
"forgeTypeId": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
]
},
"displayName": {
"type": "string"
}
},
"required": [
"builtinName",
"builtinCategoryInt",
"forgeTypeId",
"displayName"
],
"additionalProperties": false
}
},
{
"type": "null"
}
],
"description": "Revit-native categories where a built_in parameter exists. Null when no Revit-availability source or explicit projection data is present. Never derived from ObjectClass affinity links."
},
"specTypeId": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
]
},
"storageType": {
"anyOf": [
{
"type": "string",
"enum": [
"Double",
"Integer",
"String",
"ElementId",
"None"
]
},
{
"type": "null"
}
]
},
"visibilityFlags": {
"anyOf": [
{
"type": "object",
"properties": {
"isVisible": {
"type": "boolean"
},
"isUserModifiable": {
"type": "boolean"
},
"hidesWhenNoValue": {
"type": "boolean"
}
},
"required": [
"isVisible",
"isUserModifiable",
"hidesWhenNoValue"
],
"additionalProperties": false
},
{
"type": "null"
}
]
},
"discipline": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
]
},
"tooltipDescription": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
]
},
"projectionStatus": {
"type": "string",
"enum": [
"draft",
"approved",
"archived"
]
},
"lifecycleStatus": {
"anyOf": [
{
"type": "string",
"enum": [
"draft",
"active",
"approved",
"archived"
]
},
{
"type": "null"
}
]
},
"isApproved": {
"type": "boolean"
}
},
"required": [
"id",
"parameterKeyId",
"definition",
"definitionForm",
"revitName",
"revitNameSource",
"revitNameOverridden",
"sharedParameterFileGroup",
"revitUiGroup",
"familyApplication",
"projectBinding",
"bindingKindPreference",
"revitPrimaryUsage",
"isSystem",
"resolvedCategoryScope",
"scope",
"builtInCategories",
"specTypeId",
"storageType",
"visibilityFlags",
"discipline",
"tooltipDescription",
"projectionStatus",
"lifecycleStatus",
"isApproved"
],
"additionalProperties": false
}
},
"required": [
"data"
],
"additionalProperties": false
}
}
}
},
"400": {
"description": "Invalid request",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"error": {
"type": "object",
"properties": {
"code": {
"type": "string"
},
"message": {
"type": "string"
},
"field": {
"type": "string"
},
"details": {},
"unmetRequirements": {
"type": "array",
"items": {
"type": "object",
"properties": {
"code": {
"type": "string"
},
"message": {
"type": "string"
},
"objectClassKey": {
"type": "string"
},
"objectClassId": {
"type": "string"
}
},
"required": [
"code",
"message"
],
"additionalProperties": false
}
}
},
"required": [
"code",
"message"
],
"additionalProperties": false
}
},
"required": [
"error"
],
"additionalProperties": false
}
}
}
},
"401": {
"description": "Authenticated caller required",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"error": {
"type": "object",
"properties": {
"code": {
"type": "string"
},
"message": {
"type": "string"
},
"field": {
"type": "string"
},
"details": {},
"unmetRequirements": {
"type": "array",
"items": {
"type": "object",
"properties": {
"code": {
"type": "string"
},
"message": {
"type": "string"
},
"objectClassKey": {
"type": "string"
},
"objectClassId": {
"type": "string"
}
},
"required": [
"code",
"message"
],
"additionalProperties": false
}
}
},
"required": [
"code",
"message"
],
"additionalProperties": false
}
},
"required": [
"error"
],
"additionalProperties": false
}
}
}
},
"404": {
"description": "Resource not found",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"error": {
"type": "object",
"properties": {
"code": {
"type": "string"
},
"message": {
"type": "string"
},
"field": {
"type": "string"
},
"details": {},
"unmetRequirements": {
"type": "array",
"items": {
"type": "object",
"properties": {
"code": {
"type": "string"
},
"message": {
"type": "string"
},
"objectClassKey": {
"type": "string"
},
"objectClassId": {
"type": "string"
}
},
"required": [
"code",
"message"
],
"additionalProperties": false
}
}
},
"required": [
"code",
"message"
],
"additionalProperties": false
}
},
"required": [
"error"
],
"additionalProperties": false
}
}
}
},
"409": {
"description": "Conflict: GUID / built-in identity, or approve preconditions unmet (REVIT_PROJECTION_APPROVE_PRECONDITION_FAILED lists every unmet item)",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"error": {
"type": "object",
"properties": {
"code": {
"type": "string"
},
"message": {
"type": "string"
},
"field": {
"type": "string"
},
"details": {},
"unmetRequirements": {
"type": "array",
"items": {
"type": "object",
"properties": {
"code": {
"type": "string"
},
"message": {
"type": "string"
},
"objectClassKey": {
"type": "string"
},
"objectClassId": {
"type": "string"
}
},
"required": [
"code",
"message"
],
"additionalProperties": false
}
}
},
"required": [
"code",
"message"
],
"additionalProperties": false
}
},
"required": [
"error"
],
"additionalProperties": false
}
}
}
},
"422": {
"description": "Projection invariant rejected the command",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"error": {
"type": "object",
"properties": {
"code": {
"type": "string"
},
"message": {
"type": "string"
},
"field": {
"type": "string"
},
"details": {},
"unmetRequirements": {
"type": "array",
"items": {
"type": "object",
"properties": {
"code": {
"type": "string"
},
"message": {
"type": "string"
},
"objectClassKey": {
"type": "string"
},
"objectClassId": {
"type": "string"
}
},
"required": [
"code",
"message"
],
"additionalProperties": false
}
}
},
"required": [
"code",
"message"
],
"additionalProperties": false
}
},
"required": [
"error"
],
"additionalProperties": false
}
}
}
},
"500": {
"description": "Projection authoring unavailable",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"error": {
"type": "object",
"properties": {
"code": {
"type": "string"
},
"message": {
"type": "string"
},
"field": {
"type": "string"
},
"details": {},
"unmetRequirements": {
"type": "array",
"items": {
"type": "object",
"properties": {
"code": {
"type": "string"
},
"message": {
"type": "string"
},
"objectClassKey": {
"type": "string"
},
"objectClassId": {
"type": "string"
}
},
"required": [
"code",
"message"
],
"additionalProperties": false
}
}
},
"required": [
"code",
"message"
],
"additionalProperties": false
}
},
"required": [
"error"
],
"additionalProperties": false
}
}
}
}
}
}
},
"/api/v1/bim-ontology/revit/parameter-projections/{id}/resync": {
"post": {
"operationId": "resyncDerivedRevitProjectionMetadata",
"tags": [
"BIM Ontology / Revit Parameter Projection",
"system-maintenance"
],
"summary": "Resync derived Revit projection metadata",
"description": "Not part of the steward workflow. The normal path is: approved ParameterKey → auto-created draft → steward PATCH → approve.",
"security": [
{
"apiKeyAuth": []
}
],
"x-internal": true,
"x-domain": {
"appVersion": "api-v1",
"boundedContext": "bim-ontology",
"service": "api-v1",
"aggregate": "RevitParameterProjection",
"useCase": "ResyncDerivedProjectionMetadataUseCase",
"domainEvents": [
"RevitParameterProjectionResynced"
],
"domainErrors": [
"RevitParameterProjectionInvariantError",
"RevitProjectionAuthoringError",
"ObjectClassMissingRevitCategoryError",
"ProjectParameterCategoryScopeEmptyError",
"MalformedRevitCategoryDataError"
]
},
"parameters": [
{
"in": "path",
"name": "id",
"required": true,
"schema": {
"type": "string"
},
"description": "RevitParameterProjection domain_id (ADR-0048)"
}
],
"responses": {
"200": {
"description": "Projection derivation checked and resynced",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"data": {
"type": "object",
"properties": {
"metadata": {
"type": "object",
"properties": {
"projectionId": {
"type": "string"
},
"parameterKeyId": {
"type": "string"
},
"revitName": {
"type": "string"
},
"revitNameSource": {
"type": "string",
"enum": [
"derived",
"override"
]
},
"sharedParameterFileGroup": {
"type": "object",
"properties": {
"parameterGroupId": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
]
},
"name": {
"type": "string"
},
"sortOrder": {
"anyOf": [
{
"type": "number"
},
{
"type": "null"
}
]
}
},
"required": [
"parameterGroupId",
"name",
"sortOrder"
],
"additionalProperties": false
},
"revitUiGroup": {
"type": "object",
"properties": {
"parameterGroupId": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
]
},
"groupTypeId": {
"type": "string"
},
"usedFallback": {
"type": "boolean"
}
},
"required": [
"parameterGroupId",
"groupTypeId",
"usedFallback"
],
"additionalProperties": false
},
"lifecycleStatus": {
"anyOf": [
{
"type": "string",
"enum": [
"draft",
"active",
"approved",
"archived"
]
},
{
"type": "null"
}
]
},
"isApproved": {
"type": "boolean"
}
},
"required": [
"projectionId",
"parameterKeyId",
"revitName",
"revitNameSource",
"sharedParameterFileGroup",
"revitUiGroup",
"lifecycleStatus",
"isApproved"
],
"additionalProperties": false
},
"changed": {
"type": "boolean"
}
},
"required": [
"metadata",
"changed"
],
"additionalProperties": false
}
},
"required": [
"data"
],
"additionalProperties": false
}
}
}
},
"400": {
"description": "Invalid request",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"error": {
"type": "object",
"properties": {
"code": {
"type": "string"
},
"message": {
"type": "string"
},
"field": {
"type": "string"
},
"details": {},
"unmetRequirements": {
"type": "array",
"items": {
"type": "object",
"properties": {
"code": {
"type": "string"
},
"message": {
"type": "string"
},
"objectClassKey": {
"type": "string"
},
"objectClassId": {
"type": "string"
}
},
"required": [
"code",
"message"
],
"additionalProperties": false
}
}
},
"required": [
"code",
"message"
],
"additionalProperties": false
}
},
"required": [
"error"
],
"additionalProperties": false
}
}
}
},
"401": {
"description": "Authenticated caller required",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"error": {
"type": "object",
"properties": {
"code": {
"type": "string"
},
"message": {
"type": "string"
},
"field": {
"type": "string"
},
"details": {},
"unmetRequirements": {
"type": "array",
"items": {
"type": "object",
"properties": {
"code": {
"type": "string"
},
"message": {
"type": "string"
},
"objectClassKey": {
"type": "string"
},
"objectClassId": {
"type": "string"
}
},
"required": [
"code",
"message"
],
"additionalProperties": false
}
}
},
"required": [
"code",
"message"
],
"additionalProperties": false
}
},
"required": [
"error"
],
"additionalProperties": false
}
}
}
},
"404": {
"description": "Resource not found",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"error": {
"type": "object",
"properties": {
"code": {
"type": "string"
},
"message": {
"type": "string"
},
"field": {
"type": "string"
},
"details": {},
"unmetRequirements": {
"type": "array",
"items": {
"type": "object",
"properties": {
"code": {
"type": "string"
},
"message": {
"type": "string"
},
"objectClassKey": {
"type": "string"
},
"objectClassId": {
"type": "string"
}
},
"required": [
"code",
"message"
],
"additionalProperties": false
}
}
},
"required": [
"code",
"message"
],
"additionalProperties": false
}
},
"required": [
"error"
],
"additionalProperties": false
}
}
}
},
"409": {
"description": "Conflict: GUID / built-in identity, or approve preconditions unmet (REVIT_PROJECTION_APPROVE_PRECONDITION_FAILED lists every unmet item)",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"error": {
"type": "object",
"properties": {
"code": {
"type": "string"
},
"message": {
"type": "string"
},
"field": {
"type": "string"
},
"details": {},
"unmetRequirements": {
"type": "array",
"items": {
"type": "object",
"properties": {
"code": {
"type": "string"
},
"message": {
"type": "string"
},
"objectClassKey": {
"type": "string"
},
"objectClassId": {
"type": "string"
}
},
"required": [
"code",
"message"
],
"additionalProperties": false
}
}
},
"required": [
"code",
"message"
],
"additionalProperties": false
}
},
"required": [
"error"
],
"additionalProperties": false
}
}
}
},
"422": {
"description": "Projection invariant rejected the command",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"error": {
"type": "object",
"properties": {
"code": {
"type": "string"
},
"message": {
"type": "string"
},
"field": {
"type": "string"
},
"details": {},
"unmetRequirements": {
"type": "array",
"items": {
"type": "object",
"properties": {
"code": {
"type": "string"
},
"message": {
"type": "string"
},
"objectClassKey": {
"type": "string"
},
"objectClassId": {
"type": "string"
}
},
"required": [
"code",
"message"
],
"additionalProperties": false
}
}
},
"required": [
"code",
"message"
],
"additionalProperties": false
}
},
"required": [
"error"
],
"additionalProperties": false
}
}
}
},
"500": {
"description": "Projection authoring unavailable",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"error": {
"type": "object",
"properties": {
"code": {
"type": "string"
},
"message": {
"type": "string"
},
"field": {
"type": "string"
},
"details": {},
"unmetRequirements": {
"type": "array",
"items": {
"type": "object",
"properties": {
"code": {
"type": "string"
},
"message": {
"type": "string"
},
"objectClassKey": {
"type": "string"
},
"objectClassId": {
"type": "string"
}
},
"required": [
"code",
"message"
],
"additionalProperties": false
}
}
},
"required": [
"code",
"message"
],
"additionalProperties": false
}
},
"required": [
"error"
],
"additionalProperties": false
}
}
}
}
}
}
},
"/api/v1/bim-ontology/revit/parameter-projections/by-parameter-key/{key}/resync": {
"post": {
"operationId": "resyncRevitProjectionsForParameterKey",
"tags": [
"BIM Ontology / Revit Parameter Projection",
"system-maintenance"
],
"summary": "Resync projections derived from a ParameterKey",
"description": "Not part of the steward workflow. The normal path is: approved ParameterKey → auto-created draft → steward PATCH → approve.",
"security": [
{
"apiKeyAuth": []
}
],
"x-internal": true,
"x-domain": {
"appVersion": "api-v1",
"boundedContext": "bim-ontology",
"service": "api-v1",
"aggregate": "RevitParameterProjection",
"useCase": "ResyncDerivedProjectionMetadataUseCase.resyncForParameterKey",
"domainEvents": [
"RevitParameterProjectionResynced"
],
"domainErrors": [
"RevitParameterProjectionInvariantError",
"RevitProjectionAuthoringError",
"ObjectClassMissingRevitCategoryError",
"ProjectParameterCategoryScopeEmptyError",
"MalformedRevitCategoryDataError"
]
},
"parameters": [
{
"in": "path",
"name": "key",
"required": true,
"schema": {
"type": "string"
},
"description": "ParameterKey domain_id (ADR-0048)"
}
],
"responses": {
"200": {
"description": "ParameterKey projection resync result",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"data": {
"type": "object",
"properties": {
"resynced": {
"type": "boolean"
}
},
"required": [
"resynced"
],
"additionalProperties": false
}
},
"required": [
"data"
],
"additionalProperties": false
}
}
}
},
"400": {
"description": "Invalid request",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"error": {
"type": "object",
"properties": {
"code": {
"type": "string"
},
"message": {
"type": "string"
},
"field": {
"type": "string"
},
"details": {},
"unmetRequirements": {
"type": "array",
"items": {
"type": "object",
"properties": {
"code": {
"type": "string"
},
"message": {
"type": "string"
},
"objectClassKey": {
"type": "string"
},
"objectClassId": {
"type": "string"
}
},
"required": [
"code",
"message"
],
"additionalProperties": false
}
}
},
"required": [
"code",
"message"
],
"additionalProperties": false
}
},
"required": [
"error"
],
"additionalProperties": false
}
}
}
},
"401": {
"description": "Authenticated caller required",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"error": {
"type": "object",
"properties": {
"code": {
"type": "string"
},
"message": {
"type": "string"
},
"field": {
"type": "string"
},
"details": {},
"unmetRequirements": {
"type": "array",
"items": {
"type": "object",
"properties": {
"code": {
"type": "string"
},
"message": {
"type": "string"
},
"objectClassKey": {
"type": "string"
},
"objectClassId": {
"type": "string"
}
},
"required": [
"code",
"message"
],
"additionalProperties": false
}
}
},
"required": [
"code",
"message"
],
"additionalProperties": false
}
},
"required": [
"error"
],
"additionalProperties": false
}
}
}
},
"404": {
"description": "Resource not found",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"error": {
"type": "object",
"properties": {
"code": {
"type": "string"
},
"message": {
"type": "string"
},
"field": {
"type": "string"
},
"details": {},
"unmetRequirements": {
"type": "array",
"items": {
"type": "object",
"properties": {
"code": {
"type": "string"
},
"message": {
"type": "string"
},
"objectClassKey": {
"type": "string"
},
"objectClassId": {
"type": "string"
}
},
"required": [
"code",
"message"
],
"additionalProperties": false
}
}
},
"required": [
"code",
"message"
],
"additionalProperties": false
}
},
"required": [
"error"
],
"additionalProperties": false
}
}
}
},
"409": {
"description": "Conflict: GUID / built-in identity, or approve preconditions unmet (REVIT_PROJECTION_APPROVE_PRECONDITION_FAILED lists every unmet item)",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"error": {
"type": "object",
"properties": {
"code": {
"type": "string"
},
"message": {
"type": "string"
},
"field": {
"type": "string"
},
"details": {},
"unmetRequirements": {
"type": "array",
"items": {
"type": "object",
"properties": {
"code": {
"type": "string"
},
"message": {
"type": "string"
},
"objectClassKey": {
"type": "string"
},
"objectClassId": {
"type": "string"
}
},
"required": [
"code",
"message"
],
"additionalProperties": false
}
}
},
"required": [
"code",
"message"
],
"additionalProperties": false
}
},
"required": [
"error"
],
"additionalProperties": false
}
}
}
},
"422": {
"description": "Projection invariant rejected the command",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"error": {
"type": "object",
"properties": {
"code": {
"type": "string"
},
"message": {
"type": "string"
},
"field": {
"type": "string"
},
"details": {},
"unmetRequirements": {
"type": "array",
"items": {
"type": "object",
"properties": {
"code": {
"type": "string"
},
"message": {
"type": "string"
},
"objectClassKey": {
"type": "string"
},
"objectClassId": {
"type": "string"
}
},
"required": [
"code",
"message"
],
"additionalProperties": false
}
}
},
"required": [
"code",
"message"
],
"additionalProperties": false
}
},
"required": [
"error"
],
"additionalProperties": false
}
}
}
},
"500": {
"description": "Projection authoring unavailable",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"error": {
"type": "object",
"properties": {
"code": {
"type": "string"
},
"message": {
"type": "string"
},
"field": {
"type": "string"
},
"details": {},
"unmetRequirements": {
"type": "array",
"items": {
"type": "object",
"properties": {
"code": {
"type": "string"
},
"message": {
"type": "string"
},
"objectClassKey": {
"type": "string"
},
"objectClassId": {
"type": "string"
}
},
"required": [
"code",
"message"
],
"additionalProperties": false
}
}
},
"required": [
"code",
"message"
],
"additionalProperties": false
}
},
"required": [
"error"
],
"additionalProperties": false
}
}
}
}
}
}
},
"/api/v1/bim-ontology/revit/parameter-projections/by-parameter-group/{key}/resync": {
"post": {
"operationId": "resyncRevitProjectionsForParameterGroup",
"tags": [
"BIM Ontology / Revit Parameter Projection",
"system-maintenance"
],
"summary": "Resync projections derived from a ParameterGroup",
"description": "Not part of the steward workflow. The normal path is: approved ParameterKey → auto-created draft → steward PATCH → approve.",
"security": [
{
"apiKeyAuth": []
}
],
"x-internal": true,
"x-domain": {
"appVersion": "api-v1",
"boundedContext": "bim-ontology",
"service": "api-v1",
"aggregate": "RevitParameterProjection",
"useCase": "ResyncDerivedProjectionMetadataUseCase.resyncForParameterGroup",
"domainEvents": [
"RevitParameterProjectionResynced"
],
"domainErrors": [
"RevitParameterProjectionInvariantError",
"RevitProjectionAuthoringError",
"ObjectClassMissingRevitCategoryError",
"ProjectParameterCategoryScopeEmptyError",
"MalformedRevitCategoryDataError"
]
},
"parameters": [
{
"in": "path",
"name": "key",
"required": true,
"schema": {
"type": "string"
},
"description": "ParameterGroup domain_id (ADR-0048)"
}
],
"responses": {
"200": {
"description": "ParameterGroup projection resync result",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"data": {
"type": "object",
"properties": {
"resynced": {
"type": "boolean"
}
},
"required": [
"resynced"
],
"additionalProperties": false
}
},
"required": [
"data"
],
"additionalProperties": false
}
}
}
},
"400": {
"description": "Invalid request",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"error": {
"type": "object",
"properties": {
"code": {
"type": "string"
},
"message": {
"type": "string"
},
"field": {
"type": "string"
},
"details": {},
"unmetRequirements": {
"type": "array",
"items": {
"type": "object",
"properties": {
"code": {
"type": "string"
},
"message": {
"type": "string"
},
"objectClassKey": {
"type": "string"
},
"objectClassId": {
"type": "string"
}
},
"required": [
"code",
"message"
],
"additionalProperties": false
}
}
},
"required": [
"code",
"message"
],
"additionalProperties": false
}
},
"required": [
"error"
],
"additionalProperties": false
}
}
}
},
"401": {
"description": "Authenticated caller required",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"error": {
"type": "object",
"properties": {
"code": {
"type": "string"
},
"message": {
"type": "string"
},
"field": {
"type": "string"
},
"details": {},
"unmetRequirements": {
"type": "array",
"items": {
"type": "object",
"properties": {
"code": {
"type": "string"
},
"message": {
"type": "string"
},
"objectClassKey": {
"type": "string"
},
"objectClassId": {
"type": "string"
}
},
"required": [
"code",
"message"
],
"additionalProperties": false
}
}
},
"required": [
"code",
"message"
],
"additionalProperties": false
}
},
"required": [
"error"
],
"additionalProperties": false
}
}
}
},
"404": {
"description": "Resource not found",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"error": {
"type": "object",
"properties": {
"code": {
"type": "string"
},
"message": {
"type": "string"
},
"field": {
"type": "string"
},
"details": {},
"unmetRequirements": {
"type": "array",
"items": {
"type": "object",
"properties": {
"code": {
"type": "string"
},
"message": {
"type": "string"
},
"objectClassKey": {
"type": "string"
},
"objectClassId": {
"type": "string"
}
},
"required": [
"code",
"message"
],
"additionalProperties": false
}
}
},
"required": [
"code",
"message"
],
"additionalProperties": false
}
},
"required": [
"error"
],
"additionalProperties": false
}
}
}
},
"409": {
"description": "Conflict: GUID / built-in identity, or approve preconditions unmet (REVIT_PROJECTION_APPROVE_PRECONDITION_FAILED lists every unmet item)",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"error": {
"type": "object",
"properties": {
"code": {
"type": "string"
},
"message": {
"type": "string"
},
"field": {
"type": "string"
},
"details": {},
"unmetRequirements": {
"type": "array",
"items": {
"type": "object",
"properties": {
"code": {
"type": "string"
},
"message": {
"type": "string"
},
"objectClassKey": {
"type": "string"
},
"objectClassId": {
"type": "string"
}
},
"required": [
"code",
"message"
],
"additionalProperties": false
}
}
},
"required": [
"code",
"message"
],
"additionalProperties": false
}
},
"required": [
"error"
],
"additionalProperties": false
}
}
}
},
"422": {
"description": "Projection invariant rejected the command",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"error": {
"type": "object",
"properties": {
"code": {
"type": "string"
},
"message": {
"type": "string"
},
"field": {
"type": "string"
},
"details": {},
"unmetRequirements": {
"type": "array",
"items": {
"type": "object",
"properties": {
"code": {
"type": "string"
},
"message": {
"type": "string"
},
"objectClassKey": {
"type": "string"
},
"objectClassId": {
"type": "string"
}
},
"required": [
"code",
"message"
],
"additionalProperties": false
}
}
},
"required": [
"code",
"message"
],
"additionalProperties": false
}
},
"required": [
"error"
],
"additionalProperties": false
}
}
}
},
"500": {
"description": "Projection authoring unavailable",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"error": {
"type": "object",
"properties": {
"code": {
"type": "string"
},
"message": {
"type": "string"
},
"field": {
"type": "string"
},
"details": {},
"unmetRequirements": {
"type": "array",
"items": {
"type": "object",
"properties": {
"code": {
"type": "string"
},
"message": {
"type": "string"
},
"objectClassKey": {
"type": "string"
},
"objectClassId": {
"type": "string"
}
},
"required": [
"code",
"message"
],
"additionalProperties": false
}
}
},
"required": [
"code",
"message"
],
"additionalProperties": false
}
},
"required": [
"error"
],
"additionalProperties": false
}
}
}
}
}
}
},
"/api/v1/bim-ontology/revit/parameter-projections/{id}/category-scope/resolve": {
"post": {
"operationId": "resolveRevitParameterCategoryScope",
"tags": [
"BIM Ontology / Revit Parameter Projection"
],
"summary": "Resolve Revit project-binding category scope",
"description": "Side-effect-free computation. Derives current category scope from every ObjectClass effective parameter set and performs no projection writes.",
"security": [
{
"apiKeyAuth": []
}
],
"x-domain": {
"appVersion": "api-v1",
"boundedContext": "bim-ontology",
"service": "api-v1",
"aggregate": "RevitParameterProjection",
"useCase": "ResolveRevitCategoryScopeUseCase",
"domainEvents": [],
"domainErrors": [
"RevitParameterProjectionInvariantError",
"RevitProjectionAuthoringError",
"ObjectClassMissingRevitCategoryError",
"ProjectParameterCategoryScopeEmptyError",
"MalformedRevitCategoryDataError"
]
},
"parameters": [
{
"in": "path",
"name": "id",
"required": true,
"schema": {
"type": "string"
},
"description": "RevitParameterProjection domain_id (ADR-0048)"
}
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {},
"additionalProperties": false
}
}
}
},
"responses": {
"200": {
"description": "Current project-binding category scope",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"data": {
"type": "object",
"properties": {
"scope": {
"anyOf": [
{
"type": "object",
"properties": {
"projectionId": {
"type": "string"
},
"parameterKeyId": {
"type": "string"
},
"sharedGuid": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
]
},
"categories": {
"type": "array",
"items": {
"type": "object",
"properties": {
"builtinName": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
]
},
"builtinCategoryInt": {
"anyOf": [
{
"type": "integer",
"minimum": -9007199254740991,
"maximum": 9007199254740991
},
{
"type": "null"
}
]
},
"forgeTypeId": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
]
},
"displayName": {
"type": "string"
}
},
"required": [
"builtinName",
"builtinCategoryInt",
"forgeTypeId",
"displayName"
],
"additionalProperties": false
}
},
"contributingObjectClasses": {
"type": "array",
"items": {
"type": "object",
"properties": {
"objectClassId": {
"type": "string"
},
"objectClassKey": {
"type": "string"
},
"categories": {
"type": "array",
"items": {
"type": "object",
"properties": {
"builtinName": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
]
},
"builtinCategoryInt": {
"anyOf": [
{
"type": "integer",
"minimum": -9007199254740991,
"maximum": 9007199254740991
},
{
"type": "null"
}
]
},
"forgeTypeId": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
]
},
"displayName": {
"type": "string"
}
},
"required": [
"builtinName",
"builtinCategoryInt",
"forgeTypeId",
"displayName"
],
"additionalProperties": false
}
}
},
"required": [
"objectClassId",
"objectClassKey",
"categories"
],
"additionalProperties": false
}
},
"diagnostics": {
"type": "array",
"items": {
"type": "string"
}
}
},
"required": [
"projectionId",
"parameterKeyId",
"sharedGuid",
"categories",
"contributingObjectClasses",
"diagnostics"
],
"additionalProperties": false
},
{
"type": "null"
}
]
}
},
"required": [
"scope"
],
"additionalProperties": false
}
},
"required": [
"data"
],
"additionalProperties": false
}
}
}
},
"400": {
"description": "Invalid request",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"error": {
"type": "object",
"properties": {
"code": {
"type": "string"
},
"message": {
"type": "string"
},
"field": {
"type": "string"
},
"details": {},
"unmetRequirements": {
"type": "array",
"items": {
"type": "object",
"properties": {
"code": {
"type": "string"
},
"message": {
"type": "string"
},
"objectClassKey": {
"type": "string"
},
"objectClassId": {
"type": "string"
}
},
"required": [
"code",
"message"
],
"additionalProperties": false
}
}
},
"required": [
"code",
"message"
],
"additionalProperties": false
}
},
"required": [
"error"
],
"additionalProperties": false
}
}
}
},
"401": {
"description": "Authenticated caller required",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"error": {
"type": "object",
"properties": {
"code": {
"type": "string"
},
"message": {
"type": "string"
},
"field": {
"type": "string"
},
"details": {},
"unmetRequirements": {
"type": "array",
"items": {
"type": "object",
"properties": {
"code": {
"type": "string"
},
"message": {
"type": "string"
},
"objectClassKey": {
"type": "string"
},
"objectClassId": {
"type": "string"
}
},
"required": [
"code",
"message"
],
"additionalProperties": false
}
}
},
"required": [
"code",
"message"
],
"additionalProperties": false
}
},
"required": [
"error"
],
"additionalProperties": false
}
}
}
},
"404": {
"description": "Resource not found",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"error": {
"type": "object",
"properties": {
"code": {
"type": "string"
},
"message": {
"type": "string"
},
"field": {
"type": "string"
},
"details": {},
"unmetRequirements": {
"type": "array",
"items": {
"type": "object",
"properties": {
"code": {
"type": "string"
},
"message": {
"type": "string"
},
"objectClassKey": {
"type": "string"
},
"objectClassId": {
"type": "string"
}
},
"required": [
"code",
"message"
],
"additionalProperties": false
}
}
},
"required": [
"code",
"message"
],
"additionalProperties": false
}
},
"required": [
"error"
],
"additionalProperties": false
}
}
}
},
"409": {
"description": "Conflict: GUID / built-in identity, or approve preconditions unmet (REVIT_PROJECTION_APPROVE_PRECONDITION_FAILED lists every unmet item)",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"error": {
"type": "object",
"properties": {
"code": {
"type": "string"
},
"message": {
"type": "string"
},
"field": {
"type": "string"
},
"details": {},
"unmetRequirements": {
"type": "array",
"items": {
"type": "object",
"properties": {
"code": {
"type": "string"
},
"message": {
"type": "string"
},
"objectClassKey": {
"type": "string"
},
"objectClassId": {
"type": "string"
}
},
"required": [
"code",
"message"
],
"additionalProperties": false
}
}
},
"required": [
"code",
"message"
],
"additionalProperties": false
}
},
"required": [
"error"
],
"additionalProperties": false
}
}
}
},
"422": {
"description": "Projection invariant rejected the command",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"error": {
"type": "object",
"properties": {
"code": {
"type": "string"
},
"message": {
"type": "string"
},
"field": {
"type": "string"
},
"details": {},
"unmetRequirements": {
"type": "array",
"items": {
"type": "object",
"properties": {
"code": {
"type": "string"
},
"message": {
"type": "string"
},
"objectClassKey": {
"type": "string"
},
"objectClassId": {
"type": "string"
}
},
"required": [
"code",
"message"
],
"additionalProperties": false
}
}
},
"required": [
"code",
"message"
],
"additionalProperties": false
}
},
"required": [
"error"
],
"additionalProperties": false
}
}
}
},
"500": {
"description": "Projection authoring unavailable",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"error": {
"type": "object",
"properties": {
"code": {
"type": "string"
},
"message": {
"type": "string"
},
"field": {
"type": "string"
},
"details": {},
"unmetRequirements": {
"type": "array",
"items": {
"type": "object",
"properties": {
"code": {
"type": "string"
},
"message": {
"type": "string"
},
"objectClassKey": {
"type": "string"
},
"objectClassId": {
"type": "string"
}
},
"required": [
"code",
"message"
],
"additionalProperties": false
}
}
},
"required": [
"code",
"message"
],
"additionalProperties": false
}
},
"required": [
"error"
],
"additionalProperties": false
}
}
}
}
}
}
},
"/api/v1/bim-ontology/revit/parameter-projections/lookup": {
"post": {
"operationId": "lookupRevitParameterProjections",
"tags": [
"BIM Ontology / Revit Parameter Projection"
],
"summary": "Lookup Revit parameter projections by GUID or built-in name",
"description": "Batch reverse lookup for Revit parameter projections. Provide sharedGuids and/or builtInParameterNames (at least one non-empty). Response always includes explicit found and notFound collections — misses are never silent. Each array is capped at 100 processed keys; truncation is disclosed via batch.*.truncated and batch.*.omitted (never silent).",
"security": [
{
"apiKeyAuth": []
}
],
"x-domain": {
"appVersion": "api-v1",
"boundedContext": "bim-ontology",
"service": "api-v1",
"aggregate": "RevitParameterProjection",
"useCase": "LookupRevitParameterProjectionsUseCase",
"domainEvents": [],
"domainErrors": [
"RevitParameterProjectionInvariantError",
"RevitProjectionAuthoringError",
"ObjectClassMissingRevitCategoryError",
"ProjectParameterCategoryScopeEmptyError",
"MalformedRevitCategoryDataError"
]
},
"parameters": [],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"sharedGuids": {
"maxItems": 500,
"type": "array",
"items": {
"type": "string",
"minLength": 1
}
},
"builtInParameterNames": {
"maxItems": 500,
"type": "array",
"items": {
"type": "string",
"minLength": 1
}
}
},
"additionalProperties": false
}
}
}
},
"responses": {
"200": {
"description": "Projection lookup result with found, notFound, and batch disclosure",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"data": {
"type": "object",
"properties": {
"found": {
"type": "object",
"properties": {
"bySharedGuid": {
"type": "array",
"items": {
"type": "object",
"properties": {
"sharedGuid": {
"type": "string"
},
"projection": {
"type": "object",
"properties": {
"id": {
"type": "string"
},
"parameterKeyId": {
"type": "string"
},
"lifecycleStatus": {
"anyOf": [
{
"type": "string",
"enum": [
"draft",
"active",
"approved",
"archived"
]
},
{
"type": "null"
}
]
},
"isApproved": {
"type": "boolean"
},
"definitionForm": {
"type": "string",
"enum": [
"shared",
"family_local",
"project_local",
"built_in",
"legacy_unknown"
]
},
"sharedGuid": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
]
},
"builtInParameterName": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
]
},
"revitName": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
]
},
"projectionStatus": {
"type": "string",
"enum": [
"draft",
"approved",
"archived"
]
}
},
"required": [
"id",
"parameterKeyId",
"lifecycleStatus",
"isApproved",
"definitionForm",
"sharedGuid",
"builtInParameterName",
"revitName",
"projectionStatus"
],
"additionalProperties": false
}
},
"required": [
"sharedGuid",
"projection"
],
"additionalProperties": false
}
},
"byBuiltInParameterName": {
"type": "array",
"items": {
"type": "object",
"properties": {
"builtInParameterName": {
"type": "string"
},
"projection": {
"type": "object",
"properties": {
"id": {
"type": "string"
},
"parameterKeyId": {
"type": "string"
},
"lifecycleStatus": {
"anyOf": [
{
"type": "string",
"enum": [
"draft",
"active",
"approved",
"archived"
]
},
{
"type": "null"
}
]
},
"isApproved": {
"type": "boolean"
},
"definitionForm": {
"type": "string",
"enum": [
"shared",
"family_local",
"project_local",
"built_in",
"legacy_unknown"
]
},
"sharedGuid": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
]
},
"builtInParameterName": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
]
},
"revitName": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
]
},
"projectionStatus": {
"type": "string",
"enum": [
"draft",
"approved",
"archived"
]
}
},
"required": [
"id",
"parameterKeyId",
"lifecycleStatus",
"isApproved",
"definitionForm",
"sharedGuid",
"builtInParameterName",
"revitName",
"projectionStatus"
],
"additionalProperties": false
}
},
"required": [
"builtInParameterName",
"projection"
],
"additionalProperties": false
}
}
},
"required": [
"bySharedGuid",
"byBuiltInParameterName"
],
"additionalProperties": false
},
"notFound": {
"type": "object",
"properties": {
"sharedGuids": {
"type": "array",
"items": {
"type": "string"
}
},
"builtInParameterNames": {
"type": "array",
"items": {
"type": "string"
}
}
},
"required": [
"sharedGuids",
"builtInParameterNames"
],
"additionalProperties": false
},
"batch": {
"type": "object",
"properties": {
"sharedGuids": {
"type": "object",
"properties": {
"limit": {
"type": "integer",
"minimum": -9007199254740991,
"maximum": 9007199254740991,
"description": "Max keys processed per array (100). Excess keys are listed in omitted — never silently dropped."
},
"requested": {
"type": "integer",
"minimum": -9007199254740991,
"maximum": 9007199254740991
},
"processed": {
"type": "integer",
"minimum": -9007199254740991,
"maximum": 9007199254740991
},
"truncated": {
"type": "boolean"
},
"omitted": {
"type": "array",
"items": {
"type": "string"
}
}
},
"required": [
"limit",
"requested",
"processed",
"truncated",
"omitted"
],
"additionalProperties": false
},
"builtInParameterNames": {
"type": "object",
"properties": {
"limit": {
"type": "integer",
"minimum": -9007199254740991,
"maximum": 9007199254740991,
"description": "Max keys processed per array (100). Excess keys are listed in omitted — never silently dropped."
},
"requested": {
"type": "integer",
"minimum": -9007199254740991,
"maximum": 9007199254740991
},
"processed": {
"type": "integer",
"minimum": -9007199254740991,
"maximum": 9007199254740991
},
"truncated": {
"type": "boolean"
},
"omitted": {
"type": "array",
"items": {
"type": "string"
}
}
},
"required": [
"limit",
"requested",
"processed",
"truncated",
"omitted"
],
"additionalProperties": false
}
},
"required": [
"sharedGuids",
"builtInParameterNames"
],
"additionalProperties": false
}
},
"required": [
"found",
"notFound",
"batch"
],
"additionalProperties": false
}
},
"required": [
"data"
],
"additionalProperties": false
}
}
}
},
"400": {
"description": "Invalid request",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"error": {
"type": "object",
"properties": {
"code": {
"type": "string"
},
"message": {
"type": "string"
},
"field": {
"type": "string"
},
"details": {},
"unmetRequirements": {
"type": "array",
"items": {
"type": "object",
"properties": {
"code": {
"type": "string"
},
"message": {
"type": "string"
},
"objectClassKey": {
"type": "string"
},
"objectClassId": {
"type": "string"
}
},
"required": [
"code",
"message"
],
"additionalProperties": false
}
}
},
"required": [
"code",
"message"
],
"additionalProperties": false
}
},
"required": [
"error"
],
"additionalProperties": false
}
}
}
},
"401": {
"description": "Authenticated caller required",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"error": {
"type": "object",
"properties": {
"code": {
"type": "string"
},
"message": {
"type": "string"
},
"field": {
"type": "string"
},
"details": {},
"unmetRequirements": {
"type": "array",
"items": {
"type": "object",
"properties": {
"code": {
"type": "string"
},
"message": {
"type": "string"
},
"objectClassKey": {
"type": "string"
},
"objectClassId": {
"type": "string"
}
},
"required": [
"code",
"message"
],
"additionalProperties": false
}
}
},
"required": [
"code",
"message"
],
"additionalProperties": false
}
},
"required": [
"error"
],
"additionalProperties": false
}
}
}
},
"404": {
"description": "Resource not found",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"error": {
"type": "object",
"properties": {
"code": {
"type": "string"
},
"message": {
"type": "string"
},
"field": {
"type": "string"
},
"details": {},
"unmetRequirements": {
"type": "array",
"items": {
"type": "object",
"properties": {
"code": {
"type": "string"
},
"message": {
"type": "string"
},
"objectClassKey": {
"type": "string"
},
"objectClassId": {
"type": "string"
}
},
"required": [
"code",
"message"
],
"additionalProperties": false
}
}
},
"required": [
"code",
"message"
],
"additionalProperties": false
}
},
"required": [
"error"
],
"additionalProperties": false
}
}
}
},
"409": {
"description": "Conflict: GUID / built-in identity, or approve preconditions unmet (REVIT_PROJECTION_APPROVE_PRECONDITION_FAILED lists every unmet item)",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"error": {
"type": "object",
"properties": {
"code": {
"type": "string"
},
"message": {
"type": "string"
},
"field": {
"type": "string"
},
"details": {},
"unmetRequirements": {
"type": "array",
"items": {
"type": "object",
"properties": {
"code": {
"type": "string"
},
"message": {
"type": "string"
},
"objectClassKey": {
"type": "string"
},
"objectClassId": {
"type": "string"
}
},
"required": [
"code",
"message"
],
"additionalProperties": false
}
}
},
"required": [
"code",
"message"
],
"additionalProperties": false
}
},
"required": [
"error"
],
"additionalProperties": false
}
}
}
},
"422": {
"description": "Projection invariant rejected the command",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"error": {
"type": "object",
"properties": {
"code": {
"type": "string"
},
"message": {
"type": "string"
},
"field": {
"type": "string"
},
"details": {},
"unmetRequirements": {
"type": "array",
"items": {
"type": "object",
"properties": {
"code": {
"type": "string"
},
"message": {
"type": "string"
},
"objectClassKey": {
"type": "string"
},
"objectClassId": {
"type": "string"
}
},
"required": [
"code",
"message"
],
"additionalProperties": false
}
}
},
"required": [
"code",
"message"
],
"additionalProperties": false
}
},
"required": [
"error"
],
"additionalProperties": false
}
}
}
},
"500": {
"description": "Projection authoring unavailable",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"error": {
"type": "object",
"properties": {
"code": {
"type": "string"
},
"message": {
"type": "string"
},
"field": {
"type": "string"
},
"details": {},
"unmetRequirements": {
"type": "array",
"items": {
"type": "object",
"properties": {
"code": {
"type": "string"
},
"message": {
"type": "string"
},
"objectClassKey": {
"type": "string"
},
"objectClassId": {
"type": "string"
}
},
"required": [
"code",
"message"
],
"additionalProperties": false
}
}
},
"required": [
"code",
"message"
],
"additionalProperties": false
}
},
"required": [
"error"
],
"additionalProperties": false
}
}
}
}
}
}
},
"/api/v1/bim-ontology/revit/parameter-projections/derivation-drift": {
"get": {
"operationId": "listRevitProjectionDerivationDrift",
"tags": [
"BIM Ontology / Revit Parameter Projection"
],
"summary": "List Revit projection derivation drift",
"security": [
{
"apiKeyAuth": []
}
],
"x-domain": {
"appVersion": "api-v1",
"boundedContext": "bim-ontology",
"service": "api-v1",
"aggregate": "RevitParameterProjection",
"useCase": "ListProjectionDerivationDriftUseCase",
"domainEvents": [],
"domainErrors": [
"RevitParameterProjectionInvariantError",
"RevitProjectionAuthoringError",
"ObjectClassMissingRevitCategoryError",
"ProjectParameterCategoryScopeEmptyError",
"MalformedRevitCategoryDataError"
]
},
"parameters": [],
"responses": {
"200": {
"description": "Projection derivation drift",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"data": {
"type": "array",
"items": {
"type": "object",
"properties": {
"category": {
"type": "string",
"enum": [
"name_override",
"name_stale",
"revit_ui_group_missing_or_invalid",
"missing_group",
"projection_missing"
]
},
"projectionId": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
]
},
"parameterKeyId": {
"type": "string"
},
"message": {
"type": "string"
}
},
"required": [
"category",
"projectionId",
"parameterKeyId",
"message"
],
"additionalProperties": false
}
}
},
"required": [
"data"
],
"additionalProperties": false
}
}
}
},
"400": {
"description": "Invalid request",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"error": {
"type": "object",
"properties": {
"code": {
"type": "string"
},
"message": {
"type": "string"
},
"field": {
"type": "string"
},
"details": {},
"unmetRequirements": {
"type": "array",
"items": {
"type": "object",
"properties": {
"code": {
"type": "string"
},
"message": {
"type": "string"
},
"objectClassKey": {
"type": "string"
},
"objectClassId": {
"type": "string"
}
},
"required": [
"code",
"message"
],
"additionalProperties": false
}
}
},
"required": [
"code",
"message"
],
"additionalProperties": false
}
},
"required": [
"error"
],
"additionalProperties": false
}
}
}
},
"401": {
"description": "Authenticated caller required",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"error": {
"type": "object",
"properties": {
"code": {
"type": "string"
},
"message": {
"type": "string"
},
"field": {
"type": "string"
},
"details": {},
"unmetRequirements": {
"type": "array",
"items": {
"type": "object",
"properties": {
"code": {
"type": "string"
},
"message": {
"type": "string"
},
"objectClassKey": {
"type": "string"
},
"objectClassId": {
"type": "string"
}
},
"required": [
"code",
"message"
],
"additionalProperties": false
}
}
},
"required": [
"code",
"message"
],
"additionalProperties": false
}
},
"required": [
"error"
],
"additionalProperties": false
}
}
}
},
"404": {
"description": "Resource not found",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"error": {
"type": "object",
"properties": {
"code": {
"type": "string"
},
"message": {
"type": "string"
},
"field": {
"type": "string"
},
"details": {},
"unmetRequirements": {
"type": "array",
"items": {
"type": "object",
"properties": {
"code": {
"type": "string"
},
"message": {
"type": "string"
},
"objectClassKey": {
"type": "string"
},
"objectClassId": {
"type": "string"
}
},
"required": [
"code",
"message"
],
"additionalProperties": false
}
}
},
"required": [
"code",
"message"
],
"additionalProperties": false
}
},
"required": [
"error"
],
"additionalProperties": false
}
}
}
},
"409": {
"description": "Conflict: GUID / built-in identity, or approve preconditions unmet (REVIT_PROJECTION_APPROVE_PRECONDITION_FAILED lists every unmet item)",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"error": {
"type": "object",
"properties": {
"code": {
"type": "string"
},
"message": {
"type": "string"
},
"field": {
"type": "string"
},
"details": {},
"unmetRequirements": {
"type": "array",
"items": {
"type": "object",
"properties": {
"code": {
"type": "string"
},
"message": {
"type": "string"
},
"objectClassKey": {
"type": "string"
},
"objectClassId": {
"type": "string"
}
},
"required": [
"code",
"message"
],
"additionalProperties": false
}
}
},
"required": [
"code",
"message"
],
"additionalProperties": false
}
},
"required": [
"error"
],
"additionalProperties": false
}
}
}
},
"422": {
"description": "Projection invariant rejected the command",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"error": {
"type": "object",
"properties": {
"code": {
"type": "string"
},
"message": {
"type": "string"
},
"field": {
"type": "string"
},
"details": {},
"unmetRequirements": {
"type": "array",
"items": {
"type": "object",
"properties": {
"code": {
"type": "string"
},
"message": {
"type": "string"
},
"objectClassKey": {
"type": "string"
},
"objectClassId": {
"type": "string"
}
},
"required": [
"code",
"message"
],
"additionalProperties": false
}
}
},
"required": [
"code",
"message"
],
"additionalProperties": false
}
},
"required": [
"error"
],
"additionalProperties": false
}
}
}
},
"500": {
"description": "Projection authoring unavailable",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"error": {
"type": "object",
"properties": {
"code": {
"type": "string"
},
"message": {
"type": "string"
},
"field": {
"type": "string"
},
"details": {},
"unmetRequirements": {
"type": "array",
"items": {
"type": "object",
"properties": {
"code": {
"type": "string"
},
"message": {
"type": "string"
},
"objectClassKey": {
"type": "string"
},
"objectClassId": {
"type": "string"
}
},
"required": [
"code",
"message"
],
"additionalProperties": false
}
}
},
"required": [
"code",
"message"
],
"additionalProperties": false
}
},
"required": [
"error"
],
"additionalProperties": false
}
}
}
}
}
}
},
"/api/v1/bim-ontology/revit/parameter-packages/resolve": {
"post": {
"operationId": "resolveRevitParameterPackage",
"tags": [
"BIM Ontology / Revit Parameter Package"
],
"summary": "Resolve an ObjectClass-filtered Revit package",
"description": "Side-effect-free deterministic manifest generation. The hash excludes generatedAt and packageHash. objectClassKeys and objectClassIds accept ObjectClass handles and/or domain ids (paste-friendly). Effective parameters expand self + ancestors via ClassParameter inheritance. SPF *GROUP rows use the root of the single-parent ObjectClass lineage (not BuildPlan parameter_groups); revitUiGroup remains the Revit property-palette group from parameter_groups. Built-in projections stay in manifest entries; DEFINITION_NOT_SHARED excludes them from the SPF text only.",
"security": [
{
"apiKeyAuth": []
}
],
"x-domain": {
"appVersion": "api-v1",
"boundedContext": "bim-ontology",
"service": "api-v1",
"aggregate": "RevitParameterPackage",
"useCase": "ResolveRevitParameterPackageUseCase",
"domainEvents": [],
"domainErrors": [
"ObjectClassSetEmptyError",
"EffectiveParameterObjectClassNotFoundError",
"RevitParameterPackageError",
"ObjectClassMissingRevitCategoryError",
"ProjectParameterCategoryScopeEmptyError"
]
},
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"objectClassKeys": {
"default": [],
"type": "array",
"items": {
"type": "string",
"minLength": 1
}
},
"objectClassIds": {
"default": [],
"type": "array",
"items": {
"type": "string",
"minLength": 1
}
},
"projectId": {
"default": null,
"anyOf": [
{
"type": "string",
"minLength": 1
},
{
"type": "null"
}
]
},
"revitVersion": {
"default": null,
"anyOf": [
{
"type": "integer",
"exclusiveMinimum": 0,
"maximum": 9007199254740991
},
{
"type": "null"
}
]
},
"includeArchived": {
"default": false,
"type": "boolean"
},
"format": {
"default": "manifest",
"type": "string",
"enum": [
"manifest",
"shared_parameter_file",
"both"
]
}
},
"additionalProperties": false
}
}
}
},
"responses": {
"200": {
"description": "Deterministic Revit package manifest",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"data": {
"type": "object",
"properties": {
"manifest": {
"type": "object",
"properties": {
"schemaVersion": {
"type": "string",
"const": "1.0.0"
},
"generatedAt": {
"type": "string"
},
"packageHash": {
"type": "string"
},
"hashAlgorithm": {
"type": "string",
"const": "sha256"
},
"hashExcludes": {
"type": "array",
"prefixItems": [
{
"type": "string",
"const": "generatedAt"
},
{
"type": "string",
"const": "packageHash"
}
]
},
"request": {
"type": "object",
"properties": {
"objectClassKeys": {
"type": "array",
"items": {
"type": "string"
}
},
"projectId": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
]
},
"revitVersion": {
"anyOf": [
{
"type": "integer",
"exclusiveMinimum": 0,
"maximum": 9007199254740991
},
{
"type": "null"
}
]
},
"includeArchived": {
"type": "boolean"
}
},
"required": [
"objectClassKeys",
"projectId",
"revitVersion",
"includeArchived"
],
"additionalProperties": false
},
"entries": {
"type": "array",
"items": {
"type": "object",
"properties": {
"parameterKey": {
"type": "object",
"properties": {
"id": {
"type": "string"
},
"handle": {
"type": "string"
},
"canonicalName": {
"type": "string"
},
"description": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
]
},
"lifecycleStatus": {
"type": "string",
"enum": [
"draft",
"active",
"approved",
"archived"
]
},
"isApproved": {
"type": "boolean"
}
},
"required": [
"id",
"handle",
"canonicalName",
"description",
"lifecycleStatus"
],
"additionalProperties": false
},
"projectionId": {
"type": "string"
},
"projectionStatus": {
"type": "string",
"enum": [
"draft",
"approved",
"archived"
]
},
"definitionForm": {
"type": "string",
"enum": [
"shared",
"family_local",
"project_local",
"built_in",
"legacy_unknown"
],
"description": "Alias of definition.form for clients that pin top-level definitionForm"
},
"definition": {
"type": "object",
"properties": {
"form": {
"type": "string",
"enum": [
"shared",
"family_local",
"project_local",
"built_in",
"legacy_unknown"
]
},
"sharedGuid": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
]
},
"builtInParameterName": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
]
},
"effectiveName": {
"type": "string"
},
"effectiveNameSource": {
"type": "string",
"enum": [
"derived",
"override"
]
},
"specForgeTypeId": {
"type": "string"
},
"storageType": {
"type": "string",
"enum": [
"Double",
"Integer",
"String",
"ElementId",
"None"
]
},
"visibility": {
"type": "object",
"properties": {
"isVisible": {
"type": "boolean"
},
"isUserModifiable": {
"type": "boolean"
},
"hidesWhenNoValue": {
"type": "boolean"
}
},
"required": [
"isVisible",
"isUserModifiable",
"hidesWhenNoValue"
],
"additionalProperties": false
},
"discipline": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
]
},
"tooltipDescription": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
]
}
},
"required": [
"form",
"sharedGuid",
"builtInParameterName",
"effectiveName",
"effectiveNameSource",
"specForgeTypeId",
"storageType",
"visibility",
"discipline",
"tooltipDescription"
],
"additionalProperties": false
},
"sharedParameterFileGroup": {
"type": "object",
"properties": {
"parameterGroupId": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
]
},
"name": {
"type": "string"
},
"sortOrder": {
"anyOf": [
{
"type": "number"
},
{
"type": "null"
}
]
}
},
"required": [
"parameterGroupId",
"name",
"sortOrder"
],
"additionalProperties": false
},
"revitUiGroup": {
"type": "object",
"properties": {
"parameterGroupId": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
]
},
"groupTypeId": {
"type": "string",
"description": "Revit UI property-palette groupTypeId in ForgeTypeId format `autodesk.parameter.group:…` (e.g. `autodesk.parameter.group:identityData-1.0.0`). When the canonical ParameterGroup has no valid Revit UI groupTypeId, the response uses the pinned fallback `autodesk.parameter.group:data-1.0.0` and sets usedFallback=true."
},
"usedFallback": {
"type": "boolean"
}
},
"required": [
"parameterGroupId",
"groupTypeId",
"usedFallback"
],
"additionalProperties": false,
"description": "Top-level derived Revit UI group for the projection entry. Nested copies may also appear under familyApplication/projectBinding."
},
"familyApplication": {
"anyOf": [
{
"type": "object",
"properties": {
"bindingKind": {
"type": "string",
"enum": [
"instance",
"type"
]
},
"revitUiGroup": {
"type": "object",
"properties": {
"parameterGroupId": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
]
},
"groupTypeId": {
"type": "string",
"description": "Revit UI property-palette groupTypeId in ForgeTypeId format `autodesk.parameter.group:…` (e.g. `autodesk.parameter.group:identityData-1.0.0`). When the canonical ParameterGroup has no valid Revit UI groupTypeId, the response uses the pinned fallback `autodesk.parameter.group:data-1.0.0` and sets usedFallback=true."
},
"usedFallback": {
"type": "boolean"
}
},
"required": [
"parameterGroupId",
"groupTypeId",
"usedFallback"
],
"additionalProperties": false
}
},
"required": [
"bindingKind",
"revitUiGroup"
],
"additionalProperties": false
},
{
"type": "null"
}
]
},
"projectBinding": {
"anyOf": [
{
"type": "object",
"properties": {
"bindingKind": {
"type": "string",
"enum": [
"instance",
"type"
]
},
"revitUiGroup": {
"type": "object",
"properties": {
"parameterGroupId": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
]
},
"groupTypeId": {
"type": "string",
"description": "Revit UI property-palette groupTypeId in ForgeTypeId format `autodesk.parameter.group:…` (e.g. `autodesk.parameter.group:identityData-1.0.0`). When the canonical ParameterGroup has no valid Revit UI groupTypeId, the response uses the pinned fallback `autodesk.parameter.group:data-1.0.0` and sets usedFallback=true."
},
"usedFallback": {
"type": "boolean"
}
},
"required": [
"parameterGroupId",
"groupTypeId",
"usedFallback"
],
"additionalProperties": false
},
"categoryScope": {
"type": "object",
"properties": {
"categories": {
"type": "array",
"items": {
"type": "object",
"properties": {
"builtinName": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
]
},
"builtinCategoryInt": {
"anyOf": [
{
"type": "integer",
"minimum": -9007199254740991,
"maximum": 9007199254740991
},
{
"type": "null"
}
]
},
"forgeTypeId": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
]
},
"displayName": {
"type": "string"
}
},
"required": [
"builtinName",
"builtinCategoryInt",
"forgeTypeId",
"displayName"
],
"additionalProperties": false
}
},
"contributingObjectClasses": {
"type": "array",
"items": {
"type": "object",
"properties": {
"objectClassId": {
"type": "string"
},
"objectClassKey": {
"type": "string"
},
"categories": {
"type": "array",
"items": {
"type": "object",
"properties": {
"builtinName": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
]
},
"builtinCategoryInt": {
"anyOf": [
{
"type": "integer",
"minimum": -9007199254740991,
"maximum": 9007199254740991
},
{
"type": "null"
}
]
},
"forgeTypeId": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
]
},
"displayName": {
"type": "string"
}
},
"required": [
"builtinName",
"builtinCategoryInt",
"forgeTypeId",
"displayName"
],
"additionalProperties": false
}
}
},
"required": [
"objectClassId",
"objectClassKey",
"categories"
],
"additionalProperties": false
}
},
"diagnostics": {
"type": "array",
"items": {
"type": "string"
}
}
},
"required": [
"categories",
"contributingObjectClasses",
"diagnostics"
],
"additionalProperties": false
}
},
"required": [
"bindingKind",
"revitUiGroup",
"categoryScope"
],
"additionalProperties": false
},
{
"type": "null"
}
]
},
"applicableObjectClasses": {
"type": "array",
"items": {
"type": "object",
"properties": {
"objectClassId": {
"type": "string"
},
"objectClassKey": {
"type": "string"
},
"resolution": {
"type": "string",
"enum": [
"direct",
"inherited",
"override"
]
},
"inheritanceSource": {
"type": "object",
"properties": {
"objectClassId": {
"type": "string"
},
"objectClassKey": {
"type": "string"
},
"pathObjectClassIds": {
"type": "array",
"items": {
"type": "string"
}
},
"pathObjectClassKeys": {
"type": "array",
"items": {
"type": "string"
}
},
"distance": {
"type": "integer",
"minimum": 0,
"maximum": 9007199254740991
}
},
"required": [
"objectClassId",
"objectClassKey",
"pathObjectClassIds",
"pathObjectClassKeys",
"distance"
],
"additionalProperties": false
}
},
"required": [
"objectClassId",
"objectClassKey",
"resolution",
"inheritanceSource"
],
"additionalProperties": false
}
}
},
"required": [
"parameterKey",
"projectionId",
"projectionStatus",
"definitionForm",
"definition",
"sharedParameterFileGroup",
"revitUiGroup",
"familyApplication",
"projectBinding",
"applicableObjectClasses"
],
"additionalProperties": false
}
},
"exclusions": {
"type": "array",
"items": {
"type": "object",
"properties": {
"parameterKeyId": {
"type": "string"
},
"projectionId": {
"type": "string"
},
"output": {
"type": "string",
"enum": [
"shared_parameter_file",
"projection_list"
]
},
"reason": {
"type": "string",
"enum": [
"DEFINITION_NOT_SHARED",
"SPEC_MISSING_SPF_DATATYPE",
"OBJECT_CLASS_GRAPH_CYCLE",
"OBJECT_CLASS_NOT_FOUND",
"PARAMETER_PROJECTION_MISSING",
"PARAMETER_PROJECTION_MULTIPLE",
"PARAMETER_PROJECTION_INCOMPLETE",
"PARAMETER_PROJECTION_INACTIVE",
"PROJECT_LOCAL_PARAMETER_NOT_PACKAGEABLE",
"PACKAGE_UNSUPPORTED_REVIT_VERSION",
"PROJECT_PARAMETER_CATEGORY_SCOPE_EMPTY"
]
},
"definitionForm": {
"type": "string",
"enum": [
"shared",
"family_local",
"project_local",
"built_in",
"legacy_unknown",
"unknown"
]
}
},
"required": [
"parameterKeyId",
"projectionId",
"output",
"reason",
"definitionForm"
],
"additionalProperties": false
}
}
},
"required": [
"schemaVersion",
"generatedAt",
"packageHash",
"hashAlgorithm",
"hashExcludes",
"request",
"entries",
"exclusions"
],
"additionalProperties": false
},
"sharedParameterFileText": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
]
}
},
"required": [
"manifest",
"sharedParameterFileText"
],
"additionalProperties": false
}
},
"required": [
"data"
],
"additionalProperties": false
}
}
}
},
"400": {
"description": "Invalid or empty ObjectClass set",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"error": {
"type": "object",
"properties": {
"code": {
"type": "string"
},
"message": {
"type": "string"
},
"details": {}
},
"required": [
"code",
"message"
],
"additionalProperties": false
}
},
"required": [
"error"
],
"additionalProperties": false
}
}
}
},
"401": {
"description": "Authenticated caller required",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"error": {
"type": "object",
"properties": {
"code": {
"type": "string"
},
"message": {
"type": "string"
},
"details": {}
},
"required": [
"code",
"message"
],
"additionalProperties": false
}
},
"required": [
"error"
],
"additionalProperties": false
}
}
}
},
"404": {
"description": "ObjectClass not found",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"error": {
"type": "object",
"properties": {
"code": {
"type": "string"
},
"message": {
"type": "string"
},
"details": {}
},
"required": [
"code",
"message"
],
"additionalProperties": false
}
},
"required": [
"error"
],
"additionalProperties": false
}
}
}
},
"422": {
"description": "Package cannot be generated honestly",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"error": {
"type": "object",
"properties": {
"code": {
"type": "string"
},
"message": {
"type": "string"
},
"details": {}
},
"required": [
"code",
"message"
],
"additionalProperties": false
}
},
"required": [
"error"
],
"additionalProperties": false
}
}
}
},
"500": {
"description": "Package resolution unavailable",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"error": {
"type": "object",
"properties": {
"code": {
"type": "string"
},
"message": {
"type": "string"
},
"details": {}
},
"required": [
"code",
"message"
],
"additionalProperties": false
}
},
"required": [
"error"
],
"additionalProperties": false
}
}
}
}
}
}
},
"/api/v1/bim-ontology/revit/parameter-packages/shared-parameter-file": {
"post": {
"operationId": "generateRevitSharedParameterFile",
"tags": [
"BIM Ontology / Revit Parameter Package"
],
"summary": "Generate a filtered Revit shared parameter file",
"description": "Returns deterministic CRLF and tab-delimited text containing shared definitions only. Performs no writes. objectClassKeys / objectClassIds accept handles or domain ids; parameters include self + ancestors. *GROUP names are top-level ObjectClasses on each parameter's primary-parent lineage. Built-ins never emit PARAM rows.",
"security": [
{
"apiKeyAuth": []
}
],
"x-domain": {
"appVersion": "api-v1",
"boundedContext": "bim-ontology",
"service": "api-v1",
"aggregate": "RevitParameterPackage",
"useCase": "ResolveRevitParameterPackageUseCase",
"domainEvents": [],
"domainErrors": [
"ObjectClassSetEmptyError",
"EffectiveParameterObjectClassNotFoundError",
"RevitParameterPackageError",
"ObjectClassMissingRevitCategoryError",
"ProjectParameterCategoryScopeEmptyError"
]
},
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"objectClassKeys": {
"default": [],
"type": "array",
"items": {
"type": "string",
"minLength": 1
}
},
"objectClassIds": {
"default": [],
"type": "array",
"items": {
"type": "string",
"minLength": 1
}
},
"projectId": {
"default": null,
"anyOf": [
{
"type": "string",
"minLength": 1
},
{
"type": "null"
}
]
},
"revitVersion": {
"default": null,
"anyOf": [
{
"type": "integer",
"exclusiveMinimum": 0,
"maximum": 9007199254740991
},
{
"type": "null"
}
]
},
"includeArchived": {
"default": false,
"type": "boolean"
},
"format": {
"default": "manifest",
"type": "string",
"enum": [
"manifest",
"shared_parameter_file",
"both"
]
}
},
"additionalProperties": false
}
}
}
},
"responses": {
"200": {
"description": "Revit shared parameter definition file",
"content": {
"text/plain": {
"schema": {
"type": "string"
}
}
}
},
"400": {
"description": "Invalid or empty ObjectClass set",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"error": {
"type": "object",
"properties": {
"code": {
"type": "string"
},
"message": {
"type": "string"
},
"details": {}
},
"required": [
"code",
"message"
],
"additionalProperties": false
}
},
"required": [
"error"
],
"additionalProperties": false
}
}
}
},
"401": {
"description": "Authenticated caller required",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"error": {
"type": "object",
"properties": {
"code": {
"type": "string"
},
"message": {
"type": "string"
},
"details": {}
},
"required": [
"code",
"message"
],
"additionalProperties": false
}
},
"required": [
"error"
],
"additionalProperties": false
}
}
}
},
"404": {
"description": "ObjectClass not found",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"error": {
"type": "object",
"properties": {
"code": {
"type": "string"
},
"message": {
"type": "string"
},
"details": {}
},
"required": [
"code",
"message"
],
"additionalProperties": false
}
},
"required": [
"error"
],
"additionalProperties": false
}
}
}
},
"422": {
"description": "Package cannot be generated honestly",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"error": {
"type": "object",
"properties": {
"code": {
"type": "string"
},
"message": {
"type": "string"
},
"details": {}
},
"required": [
"code",
"message"
],
"additionalProperties": false
}
},
"required": [
"error"
],
"additionalProperties": false
}
}
}
},
"500": {
"description": "Package resolution unavailable",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"error": {
"type": "object",
"properties": {
"code": {
"type": "string"
},
"message": {
"type": "string"
},
"details": {}
},
"required": [
"code",
"message"
],
"additionalProperties": false
}
},
"required": [
"error"
],
"additionalProperties": false
}
}
}
}
}
}
},
"/api/v1/bim-ontology/revit/common-project-parameters": {
"get": {
"operationId": "listCommonProjectParameters",
"tags": [
"BIM Ontology / Revit Parameter Package"
],
"summary": "List common project parameters for Revit",
"description": "Returns installable common shared project parameters for the Revit client. items[] include only definitionForm=shared projections whose ParameterKey is lifecycleStatus=approved with bim_parameter usage. Built-ins are omitted (already in Revit). revitCategories are derived from ObjectClass assignments, else the architectural_model_objects RevitCategorySet; sheet/view/annotation categories are denylisted. Empty category sets are omitted from items and listed in diagnostics.",
"security": [
{
"apiKeyAuth": []
}
],
"x-domain": {
"appVersion": "api-v1",
"boundedContext": "bim-ontology",
"service": "api-v1",
"aggregate": "RevitParameterProjection",
"useCase": "ListCommonProjectParametersUseCase",
"domainEvents": [],
"domainErrors": [
"EffectiveParameterObjectClassNotFoundError",
"EffectiveParameterGraphCycleError",
"ListCommonProjectParametersError"
]
},
"responses": {
"200": {
"description": "Common project parameters with non-empty category sets",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"data": {
"type": "object",
"properties": {
"items": {
"type": "array",
"items": {
"type": "object",
"properties": {
"parameterKeyId": {
"type": "string",
"minLength": 1
},
"lifecycleStatus": {
"type": "string",
"enum": [
"draft",
"active",
"approved",
"archived"
]
},
"isApproved": {
"type": "boolean",
"const": true
},
"definitionForm": {
"type": "string",
"const": "shared"
},
"sharedGuid": {
"anyOf": [
{
"type": "string",
"minLength": 1
},
{
"type": "null"
}
]
},
"builtInParameterName": {
"type": "null"
},
"revitName": {
"type": "string",
"minLength": 1
},
"revitCategories": {
"minItems": 1,
"type": "array",
"items": {
"type": "object",
"properties": {
"builtinName": {
"type": "string",
"minLength": 1
},
"builtinCategoryInt": {
"type": "integer",
"minimum": -9007199254740991,
"maximum": 9007199254740991
}
},
"required": [
"builtinName",
"builtinCategoryInt"
],
"additionalProperties": false
}
},
"bindingKindPreference": {
"anyOf": [
{
"type": "string",
"enum": [
"instance",
"type"
]
},
{
"type": "null"
}
]
},
"revitUiGroup": {
"type": "object",
"properties": {
"parameterGroupId": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
]
},
"groupTypeId": {
"type": "string",
"description": "Revit UI property-palette groupTypeId in ForgeTypeId format `autodesk.parameter.group:…`. Fallback when unset/invalid: `autodesk.parameter.group:data-1.0.0` (usedFallback=true)."
},
"usedFallback": {
"type": "boolean"
}
},
"required": [
"parameterGroupId",
"groupTypeId",
"usedFallback"
],
"additionalProperties": false
},
"specForgeTypeId": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
]
},
"storageType": {
"anyOf": [
{
"type": "string",
"enum": [
"Double",
"Integer",
"String",
"ElementId",
"None"
]
},
{
"type": "null"
}
]
}
},
"required": [
"parameterKeyId",
"lifecycleStatus",
"isApproved",
"definitionForm",
"sharedGuid",
"builtInParameterName",
"revitName",
"revitCategories",
"bindingKindPreference",
"revitUiGroup",
"specForgeTypeId",
"storageType"
],
"additionalProperties": false
}
},
"diagnostics": {
"type": "array",
"items": {
"type": "object",
"properties": {
"parameterKeyId": {
"type": "string",
"minLength": 1
},
"projectionId": {
"type": "string",
"minLength": 1
},
"definitionForm": {
"type": "string",
"enum": [
"shared",
"built_in"
]
},
"sharedGuid": {
"anyOf": [
{
"type": "string",
"minLength": 1
},
{
"type": "null"
}
]
},
"builtInParameterName": {
"anyOf": [
{
"type": "string",
"minLength": 1
},
{
"type": "null"
}
]
},
"reason": {
"type": "string",
"enum": [
"EMPTY_REVIT_CATEGORIES",
"MISSING_REVIT_NAME",
"NOT_INSTALLABLE_SHARED",
"PARAMETER_KEY_NOT_APPROVED_BIM"
]
},
"message": {
"type": "string",
"minLength": 1
}
},
"required": [
"parameterKeyId",
"projectionId",
"definitionForm",
"sharedGuid",
"builtInParameterName",
"reason",
"message"
],
"additionalProperties": false
}
}
},
"required": [
"items",
"diagnostics"
],
"additionalProperties": false
}
},
"required": [
"data"
],
"additionalProperties": false
}
}
}
},
"401": {
"description": "Authenticated caller required",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"error": {
"type": "object",
"properties": {
"code": {
"type": "string"
},
"message": {
"type": "string"
},
"details": {}
},
"required": [
"code",
"message"
],
"additionalProperties": false
}
},
"required": [
"error"
],
"additionalProperties": false
}
}
}
},
"404": {
"description": "ObjectClass not found",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"error": {
"type": "object",
"properties": {
"code": {
"type": "string"
},
"message": {
"type": "string"
},
"details": {}
},
"required": [
"code",
"message"
],
"additionalProperties": false
}
},
"required": [
"error"
],
"additionalProperties": false
}
}
}
},
"422": {
"description": "Effective parameter graph cannot be resolved",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"error": {
"type": "object",
"properties": {
"code": {
"type": "string"
},
"message": {
"type": "string"
},
"details": {}
},
"required": [
"code",
"message"
],
"additionalProperties": false
}
},
"required": [
"error"
],
"additionalProperties": false
}
}
}
},
"500": {
"description": "Common project parameters unavailable",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"error": {
"type": "object",
"properties": {
"code": {
"type": "string"
},
"message": {
"type": "string"
},
"details": {}
},
"required": [
"code",
"message"
],
"additionalProperties": false
}
},
"required": [
"error"
],
"additionalProperties": false
}
}
}
}
}
}
},
"/api/v1/product-marketplace/product-from-url": {
"post": {
"operationId": "createProductModelFromUrl",
"tags": [
"Product Marketplace / Product Models"
],
"summary": "Create a product model from a source URL",
"security": [
{
"apiKeyAuth": []
}
],
"x-domain": {
"appVersion": "api-v1",
"boundedContext": "product-marketplace",
"service": "api-v1",
"aggregate": "ProductModel",
"useCase": "CreateProductModelFromUrlUseCase",
"command": "CreateProductFromUrlCommand",
"domainEvents": [
"Open question"
],
"domainErrors": [
"ApplicationError"
]
},
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"uiRequestId": {
"type": "string"
},
"uiRecordId": {
"type": "string"
},
"url": {
"type": "string"
},
"normalizedUrl": {
"type": "string"
},
"companyId": {
"type": "string"
},
"objectCategoryId": {
"type": "string"
},
"uiScrapeAction": {
"type": "string"
},
"requestedByUserId": {
"type": "string"
},
"scriptLogId": {
"type": "string"
}
},
"additionalProperties": {}
}
}
}
},
"responses": {
"200": {
"description": "Product creation workflow queued",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {},
"additionalProperties": {}
}
}
}
},
"400": {
"description": "Validation or workflow error"
}
}
}
},
"/api/v1/product-marketplace/product-models": {
"get": {
"operationId": "listProductModels",
"tags": [
"Product Marketplace / Product Models"
],
"summary": "(stub) List product models",
"security": [
{
"apiKeyAuth": []
}
],
"x-domain": {
"appVersion": "api-v1",
"boundedContext": "product-marketplace",
"service": "api-v1",
"aggregate": "ProductModel",
"useCase": "Open question",
"domainEvents": [
"Open question"
],
"domainErrors": [
"ApplicationError"
]
},
"responses": {
"501": {
"description": "Mounted legacy stub; not implemented"
}
}
},
"post": {
"operationId": "createProductModel",
"tags": [
"Product Marketplace / Product Models"
],
"summary": "(stub) Create a product model",
"security": [
{
"apiKeyAuth": []
}
],
"x-domain": {
"appVersion": "api-v1",
"boundedContext": "product-marketplace",
"service": "api-v1",
"aggregate": "ProductModel",
"useCase": "Open question",
"domainEvents": [
"Open question"
],
"domainErrors": [
"ApplicationError"
]
},
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"userId": {
"type": "string"
},
"requestedByUserId": {
"type": "string"
}
},
"additionalProperties": {}
}
}
}
},
"responses": {
"501": {
"description": "Mounted legacy stub; not implemented"
}
}
}
},
"/api/v1/product-marketplace/product-models/{id}": {
"get": {
"operationId": "getProductModelById",
"tags": [
"Product Marketplace / Product Models"
],
"summary": "(stub) Get a product model by ID",
"security": [
{
"apiKeyAuth": []
}
],
"x-domain": {
"appVersion": "api-v1",
"boundedContext": "product-marketplace",
"service": "api-v1",
"aggregate": "ProductModel",
"useCase": "ReadProductModelByIdUseCase",
"domainEvents": [
"Open question"
],
"domainErrors": [
"ApplicationError"
]
},
"responses": {
"501": {
"description": "Mounted legacy stub; not implemented"
}
}
},
"put": {
"operationId": "updateProductModel",
"tags": [
"Product Marketplace / Product Models"
],
"summary": "(stub) Update a product model",
"security": [
{
"apiKeyAuth": []
}
],
"x-domain": {
"appVersion": "api-v1",
"boundedContext": "product-marketplace",
"service": "api-v1",
"aggregate": "ProductModel",
"useCase": "Open question",
"domainEvents": [
"Open question"
],
"domainErrors": [
"ApplicationError"
]
},
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"userId": {
"type": "string"
},
"requestedByUserId": {
"type": "string"
}
},
"additionalProperties": {}
}
}
}
},
"responses": {
"501": {
"description": "Mounted legacy stub; not implemented"
}
}
},
"delete": {
"operationId": "deleteProductModel",
"tags": [
"Product Marketplace / Product Models"
],
"summary": "(stub) Delete a product model",
"security": [
{
"apiKeyAuth": []
}
],
"x-domain": {
"appVersion": "api-v1",
"boundedContext": "product-marketplace",
"service": "api-v1",
"aggregate": "ProductModel",
"useCase": "Open question",
"domainEvents": [
"Open question"
],
"domainErrors": [
"ApplicationError"
]
},
"responses": {
"501": {
"description": "Mounted legacy stub; not implemented"
}
}
}
},
"/api/v1/product-marketplace/product-models/delete": {
"post": {
"operationId": "deleteSelectedProductModels",
"tags": [
"Product Marketplace / Product Models"
],
"summary": "Delete selected product models",
"security": [
{
"apiKeyAuth": []
}
],
"x-domain": {
"appVersion": "api-v1",
"boundedContext": "product-marketplace",
"service": "api-v1",
"aggregate": "ProductModel",
"useCase": "DeleteProductModelsUseCase",
"domainEvents": [
"Open question"
],
"domainErrors": [
"ApplicationError"
]
},
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"userId": {
"type": "string"
},
"actionSource": {
"type": "string"
},
"triggerDomainId": {
"type": "string"
},
"triggerRecordId": {
"type": "string"
},
"selection": {
"type": "object",
"properties": {
"mode": {
"type": "string",
"enum": [
"flagged",
"explicit"
]
},
"productIds": {
"type": "array",
"items": {
"type": "string",
"minLength": 1
}
},
"productModelIds": {
"type": "array",
"items": {
"type": "string",
"minLength": 1
}
},
"includeVariants": {
"type": "boolean"
}
}
}
},
"additionalProperties": {}
}
}
}
},
"responses": {
"200": {
"description": "Product model action completed",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {},
"additionalProperties": {}
}
}
}
},
"400": {
"description": "Validation or workflow error",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"error": {
"type": "string"
},
"message": {
"type": "string"
}
},
"additionalProperties": {}
}
}
}
}
}
}
},
"/api/v1/product-marketplace/product-models/copy": {
"post": {
"operationId": "copySelectedProductModels",
"tags": [
"Product Marketplace / Product Models"
],
"summary": "Copy selected product models",
"security": [
{
"apiKeyAuth": []
}
],
"x-domain": {
"appVersion": "api-v1",
"boundedContext": "product-marketplace",
"service": "api-v1",
"aggregate": "ProductModel",
"useCase": "CopyProductModelsUseCase",
"domainEvents": [
"Open question"
],
"domainErrors": [
"ApplicationError"
]
},
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"userId": {
"type": "string"
},
"actionSource": {
"type": "string"
},
"triggerDomainId": {
"type": "string"
},
"triggerRecordId": {
"type": "string"
},
"selection": {
"type": "object",
"properties": {
"mode": {
"type": "string",
"enum": [
"flagged",
"explicit"
]
},
"productIds": {
"type": "array",
"items": {
"type": "string",
"minLength": 1
}
},
"productModelIds": {
"type": "array",
"items": {
"type": "string",
"minLength": 1
}
},
"includeVariants": {
"type": "boolean"
}
}
}
},
"additionalProperties": {}
}
}
}
},
"responses": {
"200": {
"description": "Product model action completed",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {},
"additionalProperties": {}
}
}
}
},
"400": {
"description": "Validation or workflow error",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"error": {
"type": "string"
},
"message": {
"type": "string"
}
},
"additionalProperties": {}
}
}
}
}
}
}
},
"/api/v1/product-marketplace/product-models/refresh": {
"post": {
"operationId": "refreshProductModels",
"tags": [
"Product Marketplace / Product Models"
],
"summary": "Queue refresh scrape jobs for selected product models",
"security": [
{
"apiKeyAuth": []
}
],
"x-domain": {
"appVersion": "api-v1",
"boundedContext": "product-marketplace",
"service": "api-v1",
"aggregate": "ProductModel",
"useCase": "RefreshProductModelUseCase",
"domainEvents": [
"Open question"
],
"domainErrors": [
"ApplicationError"
]
},
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"userId": {
"type": "string"
},
"actionSource": {
"type": "string"
},
"triggerDomainId": {
"type": "string"
},
"triggerRecordId": {
"type": "string"
},
"selection": {
"type": "object",
"properties": {
"mode": {
"type": "string",
"enum": [
"flagged",
"explicit"
]
},
"productIds": {
"type": "array",
"items": {
"type": "string",
"minLength": 1
}
},
"productModelIds": {
"type": "array",
"items": {
"type": "string",
"minLength": 1
}
},
"includeVariants": {
"type": "boolean"
}
}
}
},
"additionalProperties": {}
}
}
}
},
"responses": {
"200": {
"description": "Product model action completed",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {},
"additionalProperties": {}
}
}
}
},
"400": {
"description": "Validation or workflow error",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"error": {
"type": "string"
},
"message": {
"type": "string"
}
},
"additionalProperties": {}
}
}
}
}
}
}
},
"/api/v1/product-marketplace/product-models/reload": {
"post": {
"operationId": "reloadProductModels",
"tags": [
"Product Marketplace / Product Models"
],
"summary": "Queue reload scrape jobs for selected product models",
"security": [
{
"apiKeyAuth": []
}
],
"x-domain": {
"appVersion": "api-v1",
"boundedContext": "product-marketplace",
"service": "api-v1",
"aggregate": "ProductModel",
"useCase": "ReloadProductModelUseCase",
"domainEvents": [
"Open question"
],
"domainErrors": [
"ApplicationError"
]
},
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"userId": {
"type": "string"
},
"actionSource": {
"type": "string"
},
"triggerDomainId": {
"type": "string"
},
"triggerRecordId": {
"type": "string"
},
"selection": {
"type": "object",
"properties": {
"mode": {
"type": "string",
"enum": [
"flagged",
"explicit"
]
},
"productIds": {
"type": "array",
"items": {
"type": "string",
"minLength": 1
}
},
"productModelIds": {
"type": "array",
"items": {
"type": "string",
"minLength": 1
}
},
"includeVariants": {
"type": "boolean"
}
}
}
},
"additionalProperties": {}
}
}
}
},
"responses": {
"200": {
"description": "Product model action completed",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {},
"additionalProperties": {}
}
}
}
},
"400": {
"description": "Validation or workflow error",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"error": {
"type": "string"
},
"message": {
"type": "string"
}
},
"additionalProperties": {}
}
}
}
}
}
}
},
"/api/v1/product-marketplace/product-models/migrate-dimensions": {
"post": {
"operationId": "migrateProductModelDimensions",
"tags": [
"Product Marketplace / Product Models"
],
"summary": "Migrate product model dimensions",
"security": [
{
"apiKeyAuth": []
}
],
"x-domain": {
"appVersion": "api-v1",
"boundedContext": "product-marketplace",
"service": "api-v1",
"aggregate": "ProductModel",
"useCase": "MigrateProductDimensionsUseCase",
"domainEvents": [
"Open question"
],
"domainErrors": [
"ApplicationError"
]
},
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"testMode": {
"type": "boolean"
},
"productIds": {
"type": "array",
"items": {
"type": "string",
"minLength": 1
}
},
"userId": {
"type": "string"
},
"requestedByUserId": {
"type": "string"
}
},
"additionalProperties": {}
}
}
}
},
"responses": {
"200": {
"description": "Product model action completed",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {},
"additionalProperties": {}
}
}
}
},
"400": {
"description": "Validation or workflow error",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"error": {
"type": "string"
},
"message": {
"type": "string"
}
},
"additionalProperties": {}
}
}
}
}
}
}
},
"/api/v1/product-marketplace/product-models/from-url": {
"post": {
"operationId": "createProductModelFromUrlNested",
"tags": [
"Product Marketplace / Product Models"
],
"summary": "Create a product model from a source URL",
"security": [
{
"apiKeyAuth": []
}
],
"x-domain": {
"appVersion": "api-v1",
"boundedContext": "product-marketplace",
"service": "api-v1",
"aggregate": "ProductModel",
"useCase": "CreateProductModelFromUrlUseCase",
"domainEvents": [
"Open question"
],
"domainErrors": [
"ApplicationError"
],
"command": "CreateProductFromUrlCommand"
},
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"userId": {
"type": "string"
},
"requestedByUserId": {
"type": "string"
}
},
"additionalProperties": {}
}
}
}
},
"responses": {
"200": {
"description": "Product creation workflow queued",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {},
"additionalProperties": {}
}
}
}
},
"400": {
"description": "Validation or workflow error",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"error": {
"type": "string"
},
"message": {
"type": "string"
}
},
"additionalProperties": {}
}
}
}
}
}
}
},
"/api/v1/product-marketplace/product-models/{id}/onboard": {
"post": {
"operationId": "onboardProductModel",
"tags": [
"Product Marketplace / Product Models"
],
"summary": "Run onboarding workflow for a product model",
"security": [
{
"apiKeyAuth": []
}
],
"x-domain": {
"appVersion": "api-v1",
"boundedContext": "product-marketplace",
"service": "api-v1",
"aggregate": "ProductModel",
"useCase": "OnboardProductModelUseCase",
"domainEvents": [
"Open question"
],
"domainErrors": [
"ApplicationError"
]
},
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"userId": {
"type": "string"
},
"requestedByUserId": {
"type": "string"
}
},
"additionalProperties": {}
}
}
}
},
"responses": {
"200": {
"description": "Product model onboarding completed",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {},
"additionalProperties": {}
}
}
}
},
"400": {
"description": "Validation or workflow error",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"error": {
"type": "string"
},
"message": {
"type": "string"
}
},
"additionalProperties": {}
}
}
}
}
}
}
},
"/api/v1/product-marketplace/product-models/{id}/publish-images": {
"post": {
"operationId": "publishProductModelImages",
"tags": [
"Product Marketplace / Product Models"
],
"summary": "Publish product model images",
"security": [
{
"apiKeyAuth": []
}
],
"x-domain": {
"appVersion": "api-v1",
"boundedContext": "product-marketplace",
"service": "api-v1",
"aggregate": "ProductModel",
"useCase": "PublishProductModelImagesUseCase",
"domainEvents": [
"Open question"
],
"domainErrors": [
"ApplicationError"
]
},
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"userId": {
"type": "string"
},
"requestedByUserId": {
"type": "string"
}
},
"additionalProperties": {}
}
}
}
},
"responses": {
"200": {
"description": "Product model image publishing completed",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {},
"additionalProperties": {}
}
}
}
},
"400": {
"description": "Validation or workflow error",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"error": {
"type": "string"
},
"message": {
"type": "string"
}
},
"additionalProperties": {}
}
}
}
}
}
}
},
"/api/v1/product-marketplace/products": {
"get": {
"operationId": "listProductsAlias",
"tags": [
"Product Marketplace / Product Models"
],
"summary": "(stub) List product models",
"security": [
{
"apiKeyAuth": []
}
],
"x-domain": {
"appVersion": "api-v1",
"boundedContext": "product-marketplace",
"service": "api-v1",
"aggregate": "ProductModel",
"useCase": "Open question",
"domainEvents": [
"Open question"
],
"domainErrors": [
"ApplicationError"
]
},
"responses": {
"501": {
"description": "Mounted legacy stub; not implemented"
}
}
},
"post": {
"operationId": "createProductAlias",
"tags": [
"Product Marketplace / Product Models"
],
"summary": "(stub) Create a product model",
"security": [
{
"apiKeyAuth": []
}
],
"x-domain": {
"appVersion": "api-v1",
"boundedContext": "product-marketplace",
"service": "api-v1",
"aggregate": "ProductModel",
"useCase": "Open question",
"domainEvents": [
"Open question"
],
"domainErrors": [
"ApplicationError"
]
},
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"userId": {
"type": "string"
},
"requestedByUserId": {
"type": "string"
}
},
"additionalProperties": {}
}
}
}
},
"responses": {
"501": {
"description": "Mounted legacy stub; not implemented"
}
}
}
},
"/api/v1/product-marketplace/products/{id}": {
"get": {
"operationId": "getProductAliasById",
"tags": [
"Product Marketplace / Product Models"
],
"summary": "(stub) Get a product model by ID",
"security": [
{
"apiKeyAuth": []
}
],
"x-domain": {
"appVersion": "api-v1",
"boundedContext": "product-marketplace",
"service": "api-v1",
"aggregate": "ProductModel",
"useCase": "ReadProductModelByIdUseCase",
"domainEvents": [
"Open question"
],
"domainErrors": [
"ApplicationError"
]
},
"responses": {
"501": {
"description": "Mounted legacy stub; not implemented"
}
}
},
"put": {
"operationId": "updateProductAlias",
"tags": [
"Product Marketplace / Product Models"
],
"summary": "(stub) Update a product model",
"security": [
{
"apiKeyAuth": []
}
],
"x-domain": {
"appVersion": "api-v1",
"boundedContext": "product-marketplace",
"service": "api-v1",
"aggregate": "ProductModel",
"useCase": "Open question",
"domainEvents": [
"Open question"
],
"domainErrors": [
"ApplicationError"
]
},
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"userId": {
"type": "string"
},
"requestedByUserId": {
"type": "string"
}
},
"additionalProperties": {}
}
}
}
},
"responses": {
"501": {
"description": "Mounted legacy stub; not implemented"
}
}
},
"delete": {
"operationId": "deleteProductAlias",
"tags": [
"Product Marketplace / Product Models"
],
"summary": "(stub) Delete a product model",
"security": [
{
"apiKeyAuth": []
}
],
"x-domain": {
"appVersion": "api-v1",
"boundedContext": "product-marketplace",
"service": "api-v1",
"aggregate": "ProductModel",
"useCase": "Open question",
"domainEvents": [
"Open question"
],
"domainErrors": [
"ApplicationError"
]
},
"responses": {
"501": {
"description": "Mounted legacy stub; not implemented"
}
}
}
},
"/api/v1/product-marketplace/products/delete": {
"post": {
"operationId": "deleteSelectedProductModelsAlias",
"tags": [
"Product Marketplace / Product Models"
],
"summary": "Delete selected product models",
"security": [
{
"apiKeyAuth": []
}
],
"x-domain": {
"appVersion": "api-v1",
"boundedContext": "product-marketplace",
"service": "api-v1",
"aggregate": "ProductModel",
"useCase": "DeleteProductModelsUseCase",
"domainEvents": [
"Open question"
],
"domainErrors": [
"ApplicationError"
]
},
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"userId": {
"type": "string"
},
"actionSource": {
"type": "string"
},
"triggerDomainId": {
"type": "string"
},
"triggerRecordId": {
"type": "string"
},
"selection": {
"type": "object",
"properties": {
"mode": {
"type": "string",
"enum": [
"flagged",
"explicit"
]
},
"productIds": {
"type": "array",
"items": {
"type": "string",
"minLength": 1
}
},
"productModelIds": {
"type": "array",
"items": {
"type": "string",
"minLength": 1
}
},
"includeVariants": {
"type": "boolean"
}
}
}
},
"additionalProperties": {}
}
}
}
},
"responses": {
"200": {
"description": "Product model action completed",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {},
"additionalProperties": {}
}
}
}
},
"400": {
"description": "Validation or workflow error",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"error": {
"type": "string"
},
"message": {
"type": "string"
}
},
"additionalProperties": {}
}
}
}
}
}
}
},
"/api/v1/product-marketplace/products/copy": {
"post": {
"operationId": "copySelectedProductModelsAlias",
"tags": [
"Product Marketplace / Product Models"
],
"summary": "Copy selected product models",
"security": [
{
"apiKeyAuth": []
}
],
"x-domain": {
"appVersion": "api-v1",
"boundedContext": "product-marketplace",
"service": "api-v1",
"aggregate": "ProductModel",
"useCase": "CopyProductModelsUseCase",
"domainEvents": [
"Open question"
],
"domainErrors": [
"ApplicationError"
]
},
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"userId": {
"type": "string"
},
"actionSource": {
"type": "string"
},
"triggerDomainId": {
"type": "string"
},
"triggerRecordId": {
"type": "string"
},
"selection": {
"type": "object",
"properties": {
"mode": {
"type": "string",
"enum": [
"flagged",
"explicit"
]
},
"productIds": {
"type": "array",
"items": {
"type": "string",
"minLength": 1
}
},
"productModelIds": {
"type": "array",
"items": {
"type": "string",
"minLength": 1
}
},
"includeVariants": {
"type": "boolean"
}
}
}
},
"additionalProperties": {}
}
}
}
},
"responses": {
"200": {
"description": "Product model action completed",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {},
"additionalProperties": {}
}
}
}
},
"400": {
"description": "Validation or workflow error",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"error": {
"type": "string"
},
"message": {
"type": "string"
}
},
"additionalProperties": {}
}
}
}
}
}
}
},
"/api/v1/product-marketplace/products/refresh": {
"post": {
"operationId": "refreshProductModelsAlias",
"tags": [
"Product Marketplace / Product Models"
],
"summary": "Queue refresh scrape jobs for selected product models",
"security": [
{
"apiKeyAuth": []
}
],
"x-domain": {
"appVersion": "api-v1",
"boundedContext": "product-marketplace",
"service": "api-v1",
"aggregate": "ProductModel",
"useCase": "RefreshProductModelUseCase",
"domainEvents": [
"Open question"
],
"domainErrors": [
"ApplicationError"
]
},
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"userId": {
"type": "string"
},
"actionSource": {
"type": "string"
},
"triggerDomainId": {
"type": "string"
},
"triggerRecordId": {
"type": "string"
},
"selection": {
"type": "object",
"properties": {
"mode": {
"type": "string",
"enum": [
"flagged",
"explicit"
]
},
"productIds": {
"type": "array",
"items": {
"type": "string",
"minLength": 1
}
},
"productModelIds": {
"type": "array",
"items": {
"type": "string",
"minLength": 1
}
},
"includeVariants": {
"type": "boolean"
}
}
}
},
"additionalProperties": {}
}
}
}
},
"responses": {
"200": {
"description": "Product model action completed",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {},
"additionalProperties": {}
}
}
}
},
"400": {
"description": "Validation or workflow error",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"error": {
"type": "string"
},
"message": {
"type": "string"
}
},
"additionalProperties": {}
}
}
}
}
}
}
},
"/api/v1/product-marketplace/products/reload": {
"post": {
"operationId": "reloadProductModelsAlias",
"tags": [
"Product Marketplace / Product Models"
],
"summary": "Queue reload scrape jobs for selected product models",
"security": [
{
"apiKeyAuth": []
}
],
"x-domain": {
"appVersion": "api-v1",
"boundedContext": "product-marketplace",
"service": "api-v1",
"aggregate": "ProductModel",
"useCase": "ReloadProductModelUseCase",
"domainEvents": [
"Open question"
],
"domainErrors": [
"ApplicationError"
]
},
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"userId": {
"type": "string"
},
"actionSource": {
"type": "string"
},
"triggerDomainId": {
"type": "string"
},
"triggerRecordId": {
"type": "string"
},
"selection": {
"type": "object",
"properties": {
"mode": {
"type": "string",
"enum": [
"flagged",
"explicit"
]
},
"productIds": {
"type": "array",
"items": {
"type": "string",
"minLength": 1
}
},
"productModelIds": {
"type": "array",
"items": {
"type": "string",
"minLength": 1
}
},
"includeVariants": {
"type": "boolean"
}
}
}
},
"additionalProperties": {}
}
}
}
},
"responses": {
"200": {
"description": "Product model action completed",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {},
"additionalProperties": {}
}
}
}
},
"400": {
"description": "Validation or workflow error",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"error": {
"type": "string"
},
"message": {
"type": "string"
}
},
"additionalProperties": {}
}
}
}
}
}
}
},
"/api/v1/product-marketplace/products/migrate-dimensions": {
"post": {
"operationId": "migrateProductModelDimensionsAlias",
"tags": [
"Product Marketplace / Product Models"
],
"summary": "Migrate product model dimensions",
"security": [
{
"apiKeyAuth": []
}
],
"x-domain": {
"appVersion": "api-v1",
"boundedContext": "product-marketplace",
"service": "api-v1",
"aggregate": "ProductModel",
"useCase": "MigrateProductDimensionsUseCase",
"domainEvents": [
"Open question"
],
"domainErrors": [
"ApplicationError"
]
},
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"testMode": {
"type": "boolean"
},
"productIds": {
"type": "array",
"items": {
"type": "string",
"minLength": 1
}
},
"userId": {
"type": "string"
},
"requestedByUserId": {
"type": "string"
}
},
"additionalProperties": {}
}
}
}
},
"responses": {
"200": {
"description": "Product model action completed",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {},
"additionalProperties": {}
}
}
}
},
"400": {
"description": "Validation or workflow error",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"error": {
"type": "string"
},
"message": {
"type": "string"
}
},
"additionalProperties": {}
}
}
}
}
}
}
},
"/api/v1/product-marketplace/products/from-url": {
"post": {
"operationId": "createProductFromUrlAlias",
"tags": [
"Product Marketplace / Product Models"
],
"summary": "Create a product model from a source URL",
"security": [
{
"apiKeyAuth": []
}
],
"x-domain": {
"appVersion": "api-v1",
"boundedContext": "product-marketplace",
"service": "api-v1",
"aggregate": "ProductModel",
"useCase": "CreateProductModelFromUrlUseCase",
"domainEvents": [
"Open question"
],
"domainErrors": [
"ApplicationError"
],
"command": "CreateProductFromUrlCommand"
},
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"userId": {
"type": "string"
},
"requestedByUserId": {
"type": "string"
}
},
"additionalProperties": {}
}
}
}
},
"responses": {
"200": {
"description": "Product creation workflow queued",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {},
"additionalProperties": {}
}
}
}
},
"400": {
"description": "Validation or workflow error",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"error": {
"type": "string"
},
"message": {
"type": "string"
}
},
"additionalProperties": {}
}
}
}
}
}
}
},
"/api/v1/product-marketplace/products/{id}/onboard": {
"post": {
"operationId": "onboardProductAlias",
"tags": [
"Product Marketplace / Product Models"
],
"summary": "Run onboarding workflow for a product model",
"security": [
{
"apiKeyAuth": []
}
],
"x-domain": {
"appVersion": "api-v1",
"boundedContext": "product-marketplace",
"service": "api-v1",
"aggregate": "ProductModel",
"useCase": "OnboardProductModelUseCase",
"domainEvents": [
"Open question"
],
"domainErrors": [
"ApplicationError"
]
},
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"userId": {
"type": "string"
},
"requestedByUserId": {
"type": "string"
}
},
"additionalProperties": {}
}
}
}
},
"responses": {
"200": {
"description": "Product model onboarding completed",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {},
"additionalProperties": {}
}
}
}
},
"400": {
"description": "Validation or workflow error",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"error": {
"type": "string"
},
"message": {
"type": "string"
}
},
"additionalProperties": {}
}
}
}
}
}
}
},
"/api/v1/product-marketplace/products/{id}/publish-images": {
"post": {
"operationId": "publishProductAliasImages",
"tags": [
"Product Marketplace / Product Models"
],
"summary": "Publish product model images",
"security": [
{
"apiKeyAuth": []
}
],
"x-domain": {
"appVersion": "api-v1",
"boundedContext": "product-marketplace",
"service": "api-v1",
"aggregate": "ProductModel",
"useCase": "PublishProductModelImagesUseCase",
"domainEvents": [
"Open question"
],
"domainErrors": [
"ApplicationError"
]
},
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"userId": {
"type": "string"
},
"requestedByUserId": {
"type": "string"
}
},
"additionalProperties": {}
}
}
}
},
"responses": {
"200": {
"description": "Product model image publishing completed",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {},
"additionalProperties": {}
}
}
}
},
"400": {
"description": "Validation or workflow error",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"error": {
"type": "string"
},
"message": {
"type": "string"
}
},
"additionalProperties": {}
}
}
}
}
}
}
},
"/api/v1/product-marketplace/product-scrape": {
"post": {
"operationId": "runProductScrape",
"tags": [
"Product Marketplace / Product Scraping"
],
"summary": "Run product scrape requests for products or product families",
"security": [
{
"apiKeyAuth": []
}
],
"x-domain": {
"appVersion": "api-v1",
"boundedContext": "product-marketplace",
"service": "api-v1",
"aggregate": "ProductModel",
"useCase": "ProductScrapeRequestUseCases",
"command": "RunProductScrapeCommand",
"domainEvents": [
"Open question"
],
"domainErrors": [
"ApplicationError"
]
},
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"triggerType": {
"type": "string",
"enum": [
"product",
"product_model"
]
},
"requestedByUserId": {
"type": "string"
},
"productModel": {
"type": "object",
"properties": {
"productModelId": {
"type": "string",
"minLength": 1
},
"name": {
"type": "string"
},
"url": {
"type": "string"
},
"normalizedUrl": {
"type": "string"
},
"uiScrapeAction": {
"type": "string"
},
"companyId": {
"type": "string"
},
"objectCategoryId": {
"type": "string"
}
},
"required": [
"productModelId"
],
"additionalProperties": {}
},
"products": {
"type": "array",
"items": {
"type": "object",
"properties": {
"productVariantId": {
"type": "string"
},
"productRecordId": {
"type": "string"
},
"productName": {
"type": "string"
},
"url": {
"type": "string"
},
"normalizedUrl": {
"type": "string"
},
"uiScrapeAction": {
"type": "string"
},
"companyId": {
"type": "string"
},
"objectCategoryId": {
"type": "string"
},
"productModelId": {
"type": "string"
},
"attemptCount": {
"type": "integer",
"minimum": 0,
"maximum": 9007199254740991
}
},
"additionalProperties": {}
}
}
},
"additionalProperties": {}
}
}
}
},
"responses": {
"200": {
"description": "Product scrape request accepted or queued",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {},
"additionalProperties": {}
}
}
}
},
"400": {
"description": "Validation or workflow error"
}
}
}
},
"/api/v1/product-marketplace/scrapers/{scraperId}/actions/run": {
"post": {
"operationId": "runBulkProductScraper",
"tags": [
"Product Marketplace / Bulk Scrapers"
],
"summary": "Start a bulk product scraper run (async)",
"security": [
{
"apiKeyAuth": []
}
],
"x-domain": {
"appVersion": "api-v1",
"boundedContext": "product-marketplace",
"service": "api-v1",
"aggregate": "BulkScraperRun",
"useCase": "StartBulkProductScrapeUseCase",
"command": "StartBulkProductScrapeCommand",
"domainEvents": [
"Open question"
],
"domainErrors": [
"BulkScraperRequestFailedError"
]
},
"parameters": [
{
"name": "scraperId",
"in": "path",
"required": true,
"schema": {
"type": "string"
}
}
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"uiScraperRecordId": {
"type": "string"
},
"requestedByUserId": {
"type": "string"
},
"vendorId": {
"type": "string"
},
"objectCategoryId": {
"type": "string",
"minLength": 1
},
"productUrls": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
]
},
"collectionUrls": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
]
},
"maxProducts": {
"anyOf": [
{
"type": "integer",
"exclusiveMinimum": 0,
"maximum": 9007199254740991
},
{
"type": "null"
}
]
},
"persistenceMode": {
"type": "string",
"enum": [
"merge",
"no_purge",
"purge"
]
},
"args": {
"type": "object",
"propertyNames": {
"type": "string"
},
"additionalProperties": {}
}
},
"required": [
"objectCategoryId"
],
"additionalProperties": {}
}
}
}
},
"responses": {
"202": {
"description": "Bulk scrape accepted for background execution",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {},
"additionalProperties": {}
}
}
}
},
"400": {
"description": "Validation error"
}
}
}
},
"/api/v1/product-marketplace/product-variants/{id}/publish-images": {
"post": {
"operationId": "publishProductVariantImages",
"tags": [
"Product Marketplace / Product Variants",
"Product Marketplace / Images"
],
"summary": "Publish product variant images",
"security": [
{
"apiKeyAuth": []
}
],
"x-domain": {
"appVersion": "api-v1",
"boundedContext": "product-marketplace",
"service": "api-v1",
"aggregate": "ProductVariant",
"useCase": "PublishProductVariantImagesUseCase",
"domainEvents": [
"Open question"
],
"domainErrors": [
"ApplicationError"
]
},
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"userId": {
"type": "string"
},
"requestedByUserId": {
"type": "string"
}
},
"additionalProperties": {}
}
}
}
},
"responses": {
"200": {
"description": "Product variant image publishing completed",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {},
"additionalProperties": {}
}
}
}
},
"400": {
"description": "Validation or workflow error"
}
}
}
},
"/api/v1/product-marketplace/images/publish": {
"post": {
"operationId": "bulkPublishProductImages",
"tags": [
"Product Marketplace / Images"
],
"summary": "Bulk publish product images by image IDs",
"security": [
{
"apiKeyAuth": []
}
],
"x-domain": {
"appVersion": "api-v1",
"boundedContext": "product-marketplace",
"service": "api-v1",
"aggregate": "ProductImage",
"useCase": "BulkPublishProductImagesUseCase",
"command": "Open question",
"domainEvents": [
"Open question"
],
"domainErrors": [
"ApplicationError"
]
},
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"imageIds": {
"type": "array",
"items": {
"type": "string",
"minLength": 1
}
},
"userId": {
"type": "string"
},
"requestedByUserId": {
"type": "string"
}
},
"additionalProperties": {}
}
}
}
},
"responses": {
"200": {
"description": "Bulk image publishing completed",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {},
"additionalProperties": {}
}
}
}
},
"400": {
"description": "Validation or workflow error"
}
}
}
},
"/api/v1/product-marketplace/parameter-values/{id}": {
"get": {
"operationId": "getParameterValueById",
"tags": [
"Product Marketplace / Catalog Read"
],
"summary": "Read a parameter value by ID",
"security": [
{
"apiKeyAuth": []
}
],
"x-domain": {
"appVersion": "api-v1",
"boundedContext": "product-marketplace",
"service": "api-v1",
"domainEvents": [
"Open question"
],
"domainErrors": [
"ApplicationError"
],
"aggregate": "ParameterValue",
"useCase": "ReadParameterValueByIdUseCase",
"query": "ReadCatalogResourceCommand"
},
"responses": {
"200": {
"description": "Catalog resource returned",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {},
"additionalProperties": {}
}
}
}
},
"400": {
"description": "Validation error"
},
"404": {
"description": "Catalog resource not found",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"error": {
"type": "string"
}
},
"required": [
"error"
],
"additionalProperties": {}
}
}
}
}
}
}
},
"/api/v1/product-marketplace/parameter-value-groups/{id}": {
"get": {
"operationId": "getParameterValueGroupById",
"tags": [
"Product Marketplace / Catalog Read"
],
"summary": "Read a parameter value group by ID",
"security": [
{
"apiKeyAuth": []
}
],
"x-domain": {
"appVersion": "api-v1",
"boundedContext": "product-marketplace",
"service": "api-v1",
"domainEvents": [
"Open question"
],
"domainErrors": [
"ApplicationError"
],
"aggregate": "ParameterValueGroup",
"useCase": "ReadParameterValueGroupByIdUseCase",
"query": "ReadCatalogResourceCommand"
},
"responses": {
"200": {
"description": "Catalog resource returned",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {},
"additionalProperties": {}
}
}
}
},
"400": {
"description": "Validation error"
},
"404": {
"description": "Catalog resource not found",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"error": {
"type": "string"
}
},
"required": [
"error"
],
"additionalProperties": {}
}
}
}
}
}
}
},
"/api/v1/product-marketplace/price-groups/{id}": {
"get": {
"operationId": "getPriceGroupById",
"tags": [
"Product Marketplace / Catalog Read"
],
"summary": "Read a price group by ID",
"security": [
{
"apiKeyAuth": []
}
],
"x-domain": {
"appVersion": "api-v1",
"boundedContext": "product-marketplace",
"service": "api-v1",
"domainEvents": [
"Open question"
],
"domainErrors": [
"ApplicationError"
],
"aggregate": "PriceGroup",
"useCase": "ReadPriceGroupByIdUseCase",
"query": "ReadCatalogResourceCommand"
},
"responses": {
"200": {
"description": "Catalog resource returned",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {},
"additionalProperties": {}
}
}
}
},
"400": {
"description": "Validation error"
},
"404": {
"description": "Catalog resource not found",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"error": {
"type": "string"
}
},
"required": [
"error"
],
"additionalProperties": {}
}
}
}
}
}
}
},
"/api/v1/product-marketplace/inventory/{id}": {
"get": {
"operationId": "getInventoryById",
"tags": [
"Product Marketplace / Catalog Read"
],
"summary": "Read inventory by ID",
"security": [
{
"apiKeyAuth": []
}
],
"x-domain": {
"appVersion": "api-v1",
"boundedContext": "product-marketplace",
"service": "api-v1",
"domainEvents": [
"Open question"
],
"domainErrors": [
"ApplicationError"
],
"aggregate": "Inventory",
"useCase": "ReadInventoryByIdUseCase",
"query": "ReadCatalogResourceCommand"
},
"responses": {
"200": {
"description": "Catalog resource returned",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {},
"additionalProperties": {}
}
}
}
},
"400": {
"description": "Validation error"
},
"404": {
"description": "Catalog resource not found",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"error": {
"type": "string"
}
},
"required": [
"error"
],
"additionalProperties": {}
}
}
}
}
}
}
},
"/api/v1/product-marketplace/cad-assets/{id}": {
"get": {
"operationId": "getCadAssetById",
"tags": [
"Product Marketplace / Catalog Read"
],
"summary": "Read a CAD asset by ID",
"security": [
{
"apiKeyAuth": []
}
],
"x-domain": {
"appVersion": "api-v1",
"boundedContext": "product-marketplace",
"service": "api-v1",
"domainEvents": [
"Open question"
],
"domainErrors": [
"ApplicationError"
],
"aggregate": "CadAsset",
"useCase": "ReadCadAssetByIdUseCase",
"query": "ReadCatalogResourceCommand"
},
"responses": {
"200": {
"description": "Catalog resource returned",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {},
"additionalProperties": {}
}
}
}
},
"400": {
"description": "Validation error"
},
"404": {
"description": "Catalog resource not found",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"error": {
"type": "string"
}
},
"required": [
"error"
],
"additionalProperties": {}
}
}
}
}
}
}
},
"/api/v1/product-marketplace/materials/{id}": {
"get": {
"operationId": "getMaterialById",
"tags": [
"Product Marketplace / Catalog Read"
],
"summary": "Read a material by ID",
"security": [
{
"apiKeyAuth": []
}
],
"x-domain": {
"appVersion": "api-v1",
"boundedContext": "product-marketplace",
"service": "api-v1",
"domainEvents": [
"Open question"
],
"domainErrors": [
"ApplicationError"
],
"aggregate": "Material",
"useCase": "ReadMaterialByIdUseCase",
"query": "ReadCatalogResourceCommand"
},
"responses": {
"200": {
"description": "Catalog resource returned",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {},
"additionalProperties": {}
}
}
}
},
"400": {
"description": "Validation error"
},
"404": {
"description": "Catalog resource not found",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"error": {
"type": "string"
}
},
"required": [
"error"
],
"additionalProperties": {}
}
}
}
}
}
}
},
"/api/v1/product-marketplace/product-docs/{id}": {
"get": {
"operationId": "getProductDocById",
"tags": [
"Product Marketplace / Catalog Read"
],
"summary": "Read a product document by ID",
"security": [
{
"apiKeyAuth": []
}
],
"x-domain": {
"appVersion": "api-v1",
"boundedContext": "product-marketplace",
"service": "api-v1",
"domainEvents": [
"Open question"
],
"domainErrors": [
"ApplicationError"
],
"aggregate": "ProductDoc",
"useCase": "ReadProductDocByIdUseCase",
"query": "ReadCatalogResourceCommand"
},
"responses": {
"200": {
"description": "Catalog resource returned",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {},
"additionalProperties": {}
}
}
}
},
"400": {
"description": "Validation error"
},
"404": {
"description": "Catalog resource not found",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"error": {
"type": "string"
}
},
"required": [
"error"
],
"additionalProperties": {}
}
}
}
}
}
}
},
"/api/v1/product-marketplace/cad-assets/generic-specifier": {
"get": {
"operationId": "getGenericCadSpecifier",
"tags": [
"Product Marketplace / Catalog Read"
],
"summary": "(stub) Generate a generic CAD specifier",
"security": [
{
"apiKeyAuth": []
}
],
"x-domain": {
"appVersion": "api-v1",
"boundedContext": "product-marketplace",
"service": "api-v1",
"domainEvents": [
"Open question"
],
"domainErrors": [
"ApplicationError"
],
"aggregate": "CadAsset",
"useCase": "Open question",
"query": "Open question"
},
"responses": {
"400": {
"description": "Exactly one productModelId or objectCategoryId is required"
},
"501": {
"description": "Generic CAD specifier generation is not yet implemented in api-v1",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"error": {
"type": "string"
}
},
"required": [
"error"
],
"additionalProperties": {}
}
}
}
}
}
}
},
"/api/v1/construction/site-images/actions/bulk-upload": {
"post": {
"operationId": "queueBulkUploadSiteImages",
"tags": [
"Construction Management / Site Images"
],
"summary": "Queue a bulk site-image upload",
"description": "Accepts a bulk create of site visit photos and returns 202 immediately. Poll GET /api/v1/construction/operations/{operationId}. Progress is also written to the requesting ui_site_images_bulk_upload row and script_log.",
"security": [
{
"apiKeyAuth": []
}
],
"x-domain": {
"appVersion": "api-v1",
"boundedContext": "construction-management",
"service": "api-v1",
"aggregate": "SiteImage",
"useCase": "QueueBulkUploadSiteImagesUseCase",
"command": "QueueBulkUploadSiteImagesCommand",
"domainEvents": [
"SiteImageRecorded"
],
"domainErrors": [
"SiteImageProjectRequiredError",
"SiteImageReportDateRequiredError",
"SiteImageReportDateInvalidError",
"SiteImageAttachmentRequiredError",
"SiteImageAttachmentUrlInvalidError"
]
},
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"projectId": {
"type": "string",
"minLength": 1
},
"reportDate": {
"type": "string",
"minLength": 1
},
"images": {
"minItems": 1,
"type": "array",
"items": {
"type": "object",
"properties": {
"url": {
"type": "string",
"minLength": 1
},
"filename": {
"anyOf": [
{
"type": "string",
"minLength": 1
},
{
"type": "null"
}
]
}
},
"required": [
"url"
]
}
},
"requestedByUserId": {
"type": "string",
"minLength": 1
},
"requestRecordId": {
"type": "string",
"minLength": 1
}
},
"required": [
"projectId",
"reportDate",
"images"
],
"additionalProperties": false
}
}
}
},
"responses": {
"202": {
"description": "Bulk upload accepted",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"ok": {
"type": "boolean",
"const": true
},
"queued": {
"type": "boolean",
"const": true
},
"operationId": {
"type": "string"
},
"statusUrl": {
"type": "string"
},
"message": {
"type": "string"
}
},
"required": [
"ok",
"queued",
"operationId",
"statusUrl",
"message"
],
"additionalProperties": false
}
}
}
},
"400": {
"description": "Validation error or missing Idempotency-Key"
},
"403": {
"description": "Caller is not allowed"
},
"404": {
"description": "Construction project not found"
}
}
}
},
"/api/v1/construction/operations/{operationId}": {
"get": {
"operationId": "getSiteImageBulkUploadOperation",
"tags": [
"Construction Management / Site Images"
],
"summary": "Get bulk site-image upload operation status",
"description": "Polls the status of a previously accepted bulk site-image upload. Status is stored on ui_site_images_bulk_upload.operation_id.",
"security": [
{
"apiKeyAuth": []
}
],
"x-domain": {
"appVersion": "api-v1",
"boundedContext": "construction-management",
"service": "api-v1",
"aggregate": "SiteImage",
"useCase": "GetSiteImageBulkUploadOperationUseCase",
"query": "GetSiteImageBulkUploadOperationQuery",
"domainEvents": [],
"domainErrors": [
"SiteImageBulkUploadOperationNotFoundError"
]
},
"parameters": [
{
"in": "path",
"name": "operationId",
"required": true,
"schema": {
"type": "string"
}
}
],
"responses": {
"200": {
"description": "Operation status",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"operationId": {
"type": "string"
},
"status": {
"type": "string",
"enum": [
"queued",
"running",
"success",
"error"
]
},
"message": {
"type": "string"
}
},
"required": [
"operationId",
"status",
"message"
],
"additionalProperties": false
}
}
}
},
"403": {
"description": "Caller is not allowed"
},
"404": {
"description": "Operation not found"
}
}
}
}
},
"components": {
"schemas": {
"__schema0": {
"type": "object",
"properties": {
"id": {
"type": "string"
},
"key": {
"type": "string"
},
"name": {
"type": "string"
},
"parentId": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
]
},
"hasChildren": {
"type": "boolean"
},
"depth": {
"type": "integer",
"minimum": 0,
"maximum": 9007199254740991
},
"aliases": {
"type": "array",
"items": {
"type": "string"
}
},
"status": {
"type": "string",
"enum": [
"draft",
"approved",
"archived"
]
},
"revitCategory": {
"anyOf": [
{
"type": "object",
"properties": {
"builtinName": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
]
},
"builtinCategoryInt": {
"anyOf": [
{
"type": "integer",
"minimum": -9007199254740991,
"maximum": 9007199254740991
},
{
"type": "null"
}
]
},
"status": {
"type": "string",
"enum": [
"current",
"stale",
"needs_review",
"unresolved"
]
},
"source": {
"type": "string",
"enum": [
"authored",
"inherited"
]
},
"reviewReason": {
"type": "string"
}
},
"required": [
"builtinName",
"builtinCategoryInt",
"status"
],
"additionalProperties": false,
"description": "Singular 1:1 Revit category binding. When status !== current, builtinName and builtinCategoryInt are null."
},
{
"type": "null"
}
]
},
"children": {
"type": "array",
"items": {
"$ref": "#/components/schemas/__schema0"
}
}
},
"required": [
"id",
"name",
"parentId",
"hasChildren",
"status",
"revitCategory"
],
"additionalProperties": false
}
},
"securitySchemes": {
"apiKeyAuth": {
"type": "apiKey",
"in": "header",
"name": "X-API-Key",
"description": "api-v1 API key for protected routes."
}
}
}
}
---
id: api-v2
name: api-v2 (target)
version: 1.0.0
owners:
- buildplan
summary: |
Target backend application. Source of the formal HTTP API contract via
generated OpenAPI. EventCatalog renders the spec on this page.
badges:
- content: Target
backgroundColor: blue
textColor: blue
- content: Deployable
backgroundColor: blue
textColor: blue
- content: "Status: Implemented in api-v2"
backgroundColor: green
textColor: green
specifications:
- type: openapi
path: openapi.json
name: Buildplan API v2
---
## Overview
This service represents the target backend. The OpenAPI tab on this page renders the spec generated from Zod schemas in `apps/api-v2`.
## Source of Truth
- API contract: `apps/api-v2/openapi/openapi.json` (generated)
- Runtime: `apps/api-v2` (Express, Zod, Prisma)
- Architecture conventions: `eventcatalog/CONVENTIONS.md`
## Documentation Policy
Generated OpenAPI is the source of truth for HTTP paths, methods, parameters, request bodies, response bodies, errors, authentication, tags, and operation IDs.
EventCatalog provides the surrounding context: domain ownership, service boundaries, migration status, commands, queries, events, and flows.
## Raw Schema:openapi.json
{
"openapi": "3.1.0",
"info": {
"title": "Buildplan API v2",
"version": "1.0.0",
"description": "Formal HTTP API contract for the target Buildplan backend. Generated from api-v2 Zod route and DTO schemas; architecture context lives in EventCatalog."
},
"servers": [
{
"url": "/",
"description": "Current host"
},
{
"url": "https://api.mybuildplan.ai",
"description": "Production"
}
],
"paths": {
"/api/v2/crm/email-verifications/run": {
"post": {
"operationId": "runEmailVerifications",
"tags": [
"CRM / Email Verifications"
],
"summary": "Run an email-verification batch against a recipient scope",
"security": [
{
"bearerAuth": []
}
],
"x-domain": {
"appVersion": "api-v2",
"boundedContext": "crm",
"service": "crm-api",
"aggregate": "Open question",
"useCase": "RunEmailVerificationsUseCase",
"command": "RunEmailVerificationsCommand",
"domainEvents": [
"Open question"
],
"domainErrors": [
"EmailVerificationValidationError"
],
"replaces": {
"appVersion": "api-v1",
"notes": "Legacy email verification flow exists in api-v1; api-v2 is the target API contract surface."
}
},
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"scope": {
"type": "object",
"properties": {
"campaignId": {
"type": "string"
},
"campaignIds": {
"type": "array",
"items": {
"type": "string"
}
},
"contactListId": {
"type": "string"
},
"contactListIds": {
"type": "array",
"items": {
"type": "string"
}
},
"recipientIds": {
"type": "array",
"items": {
"type": "string"
}
},
"recipients": {
"type": "array",
"items": {
"type": "object",
"properties": {
"recipientType": {
"type": "string",
"enum": [
"contact",
"company"
]
},
"recipientId": {
"type": "string"
}
},
"required": [
"recipientType",
"recipientId"
],
"additionalProperties": false
}
},
"contactIds": {
"type": "array",
"items": {
"type": "string"
}
},
"organizationIds": {
"type": "array",
"items": {
"type": "string"
}
},
"companyIds": {
"type": "array",
"items": {
"type": "string"
}
},
"emailIds": {
"type": "array",
"items": {
"type": "string"
}
}
},
"additionalProperties": false
},
"provider": {
"type": "string"
},
"mode": {
"type": "string",
"enum": [
"single",
"batch"
]
},
"forceRecheck": {
"type": "boolean"
},
"staleAfterDays": {
"type": "number"
},
"options": {
"type": "object",
"properties": {
"runLocalChecks": {
"type": "boolean"
},
"runProviderChecks": {
"type": "boolean"
},
"skipSyntaxInvalid": {
"type": "boolean"
},
"skipRecentlyChecked": {
"type": "boolean"
}
},
"additionalProperties": false
}
},
"required": [
"scope"
],
"additionalProperties": false
}
}
}
},
"responses": {
"200": {
"description": "Email verification run completed",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"provider": {
"type": "string",
"const": "bouncer"
},
"mode": {
"type": "string",
"const": "batch"
},
"resolved": {
"type": "object",
"properties": {
"campaignId": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
]
},
"campaignIds": {
"type": "array",
"items": {
"type": "string"
}
},
"contactListIds": {
"type": "array",
"items": {
"type": "string"
}
},
"recipientCount": {
"type": "number"
},
"emailCount": {
"type": "number"
},
"uniqueEmailCount": {
"type": "number"
}
},
"required": [
"campaignIds",
"contactListIds",
"recipientCount",
"emailCount",
"uniqueEmailCount"
],
"additionalProperties": false
},
"results": {
"type": "object",
"properties": {
"checked": {
"type": "number"
},
"skippedRecentlyChecked": {
"type": "number"
},
"skippedSyntaxInvalid": {
"type": "number"
},
"skippedProviderDisabled": {
"type": "number"
},
"valid": {
"type": "number"
},
"risky": {
"type": "number"
},
"invalid": {
"type": "number"
},
"unknown": {
"type": "number"
},
"needsReview": {
"type": "number"
},
"providerMissingResults": {
"type": "number"
}
},
"required": [
"checked",
"skippedRecentlyChecked",
"skippedSyntaxInvalid",
"skippedProviderDisabled",
"valid",
"risky",
"invalid",
"unknown",
"needsReview",
"providerMissingResults"
],
"additionalProperties": false
}
},
"required": [
"provider",
"mode",
"resolved",
"results"
],
"additionalProperties": false
}
}
}
},
"400": {
"description": "Validation error",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"error": {
"type": "object",
"properties": {
"code": {
"type": "string"
},
"message": {
"type": "string"
},
"details": {}
},
"required": [
"code",
"message"
],
"additionalProperties": false
}
},
"required": [
"error"
],
"additionalProperties": false
}
}
}
},
"409": {
"description": "Idempotency key conflict",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"error": {
"type": "object",
"properties": {
"code": {
"type": "string"
},
"message": {
"type": "string"
},
"details": {}
},
"required": [
"code",
"message"
],
"additionalProperties": false
}
},
"required": [
"error"
],
"additionalProperties": false
}
}
}
},
"502": {
"description": "Email verification provider error",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"error": {
"type": "object",
"properties": {
"code": {
"type": "string"
},
"message": {
"type": "string"
},
"details": {}
},
"required": [
"code",
"message"
],
"additionalProperties": false
}
},
"required": [
"error"
],
"additionalProperties": false
}
}
}
}
}
}
}
},
"components": {
"securitySchemes": {
"bearerAuth": {
"type": "http",
"scheme": "bearer",
"bearerFormat": "JWT",
"description": "Supabase JWT bearer token."
}
}
}
}
---
id: BrightData
name: BrightData
version: 1.0.0
owners:
- buildplan
summary: External browser, proxy, and Web Unlocker provider used by shared-scraping.
externalSystem: true
receives:
- id: BrightDataPageFetchRequested
version: 1.0.0
sends:
- id: BrightDataPageFetched
version: 1.0.0
badges:
- content: External system
backgroundColor: gray
textColor: gray
---
## Role
BrightData provides browser/proxy infrastructure for deterministic and hybrid product scraping.
## Buildplan Integration Points
Only shared-scraping should call BrightData in this catalog view. The visualiser should show api-v1 -> shared-scraping -> BrightData, never api-v1 -> BrightData directly.
## Operational Notes
Credentials and proxy zones are environment-specific and are not documented in catalog content.
---
id: front-end
name: front-end
version: 1.0.0
owners:
- buildplan
summary: |
Next.js 14 application UI. Deployable on Vercel (`apps/front-end`).
badges:
- content: Deployable
backgroundColor: blue
textColor: blue
- content: "Status: Implemented in the front-end"
backgroundColor: yellow
textColor: yellow
---
## Overview
The product UI lives in `apps/front-end` (Next.js App Router, Tailwind, shadcn). Hosting is **Vercel**. This is a deployable, not a bounded-context module.
It calls api-v1 and api-v2 over HTTP. It does not own domain aggregates.
## Open question
Which catalog System should own this deployable alongside api-v1/api-v2 is not decided here. It is listed as a service so it is not missing from the runtime inventory.
---
id: Medusa
name: Medusa
version: 1.0.0
owners:
- buildplan
summary: External commerce engine receiving selected product catalog data and forwarding commerce webhooks.
externalSystem: true
receives:
- id: PublishCatalogToMedusa
version: 1.0.0
sends:
- id: MedusaStripeWebhookReceived
version: 1.0.0
badges:
- content: External system
backgroundColor: gray
textColor: gray
---
## Role
Medusa owns commerce infrastructure adjacent to, but separate from, the product-marketplace catalog domain.
## Buildplan Integration Points
api-v1 can publish product catalog data to Medusa, and Medusa can forward Stripe commerce webhooks back into api-v1.
## Operational Notes
Commerce engine internals should not become the canonical product-domain model.
---
id: party-directory
name: Party Directory
version: 1.0.0
owners:
- buildplan
summary: Canonical party, person, organization, and contact identity service shared by business modules.
receives:
- id: ResolveManufacturerOrganization
version: 1.0.0
- id: UpsertOrganizationFromScrape
version: 1.0.0
- id: LookupOrganizationByDomain
version: 1.0.0
sends:
- id: OrganizationCreated
version: 1.0.0
- id: OrganizationUpdated
version: 1.0.0
- id: PersistPartyToAirtable
version: 1.0.0
badges:
- content: Bounded-context module — not a deployable
backgroundColor: purple
textColor: purple
- content: "Status: Implemented in api-v1"
backgroundColor: yellow
textColor: yellow
attachments:
- title: TypeDoc reference
url: https://docs.mybuildplan.ai/typedoc/api-v1/
type: reference
icon: CodeIcon
---
## Bounded Context Boundaries
Party Directory owns canonical party identity: organizations, people, emails, phone numbers, websites, and relationship data. Product Marketplace references organizations by id when a manufacturer or vendor identity is resolved from scraped catalog data.
## Round-1 Surface
Round 1 documents only the product-marketplace-facing surface: resolving manufacturer organizations, upserting organizations discovered from scrapes, domain lookup, and organization created/updated events. Fuller cross-module party-directory documentation is deferred to a future round.
---
id: Sendgrid
name: Sendgrid
version: 1.0.0
owners:
- buildplan
summary: External email delivery provider for transactional api-v1 notifications.
externalSystem: true
receives:
- id: SendTransactionalEmail
version: 1.0.0
badges:
- content: External system
backgroundColor: gray
textColor: gray
---
## Role
Sendgrid delivers transactional emails such as upload links, notifications, and unsubscribe-related messages.
## Buildplan Integration Points
api-v1 sends transactional email commands. Provider webhooks are deferred to a future round.
## Operational Notes
API keys and sender identities are environment variables and are not stored in EventCatalog.
---
id: shared-scraping
name: Shared Scraping Core
version: 1.0.0
owners:
- buildplan
summary: Cross-domain scraping orchestration that owns ScrapingPlan, ScraperCapability, ScrapeRunScope, and provider-facing scraping ports.
receives:
- id: ExecuteScrapingPlan
version: 1.0.0
- id: FetchPage
version: 1.0.0
- id: RouteByVendor
version: 1.0.0
- id: BrightDataPageFetched
version: 1.0.0
- id: AgentQLExtractionCompleted
version: 1.0.0
sends:
- id: ScrapeRunCompleted
version: 1.0.0
- id: ScrapeRunFailed
version: 1.0.0
- id: BrightDataPageFetchRequested
version: 1.0.0
- id: AgentQLExtractionRequested
version: 1.0.0
badges:
- content: Bounded-context module — not a deployable
backgroundColor: purple
textColor: purple
- content: "Status: Implemented in api-v1"
backgroundColor: yellow
textColor: yellow
attachments:
- title: TypeDoc reference
url: https://docs.mybuildplan.ai/typedoc/api-v1/
type: reference
icon: CodeIcon
---
## Why It Is Its Own Service
Shared Scraping is a bounded context inside api-v1 rather than a vendor-specific scraper. It defines the orchestration contracts and outbound ports that product-marketplace uses today and service-marketplace can reuse in future rounds.
## Outbound Dependencies
Shared Scraping talks to BrightData for browser/proxy fetches and AgentQL for LLM-assisted extraction. Product Marketplace should depend on shared-scraping contracts, not provider SDKs directly.
## Not In Scope
Vendor-specific deterministic scraper adapters remain inside the consuming module's outbound scraping adapters.
---
id: workers
name: workers
version: 1.0.0
owners:
- buildplan
summary: |
Background migration, sync, and reconciliation jobs (`@buildplan/workers`).
badges:
- content: Deployable
backgroundColor: blue
textColor: blue
- content: "Status: Implemented in workers"
backgroundColor: yellow
textColor: yellow
- content: "Hosting: Unknown / open question"
backgroundColor: gray
textColor: gray
---
## Overview
`apps/workers` (`@buildplan/workers`) is an npm workspace with scripts `migrate`, `sync`, and `reconcile`. Production and staging CI treat `apps/workers/` as a backend change path.
## Hosting
**Unknown / open question.** There is no documented Render or Vercel service for this package in the deployment overview. Do not invent a host. Treat it as a deployable job package whose runtime home is not catalogued yet.
---
id: bim-ontology
name: BIM Ontology
version: 1.0.0
owners:
- buildplan
summary: |
The canonical classification of building objects and the canonical parameter
dictionary. Owns ObjectClass, ParameterKey, ClassParameter, ParameterGroup,
ParameterValue, and the Revit parameter projection. Consumed by
product-marketplace, which assigns classes to products and uses the
associated parameters.
services:
- id: api-v1
version: 1.0.0
- id: Airtable
version: 1.0.0
sends:
- id: ObjectClassParentGraphChanged
version: 1.0.0
- id: ParameterKeyCreated
version: 1.0.0
- id: ParameterKeyUpdated
version: 1.0.0
- id: ParameterKeyActivated
version: 1.0.0
- id: ParameterKeyApproved
version: 1.0.0
- id: ParameterKeyArchived
version: 1.0.0
- id: ParameterKeyDeleted
version: 1.0.0
- id: ParameterGroupChanged
version: 1.0.0
- id: ClassParameterAssigned
version: 1.0.0
- id: ClassParameterAssignmentUpdated
version: 1.0.0
- id: ClassParameterAllowedValuesReplaced
version: 1.0.0
- id: ClassParameterUnassigned
version: 1.0.0
- id: RevitParameterProjectionCreated
version: 1.0.0
- id: RevitParameterProjectionUpdated
version: 1.0.0
- id: RevitParameterProjectionApproved
version: 1.0.0
- id: RevitParameterProjectionDemoted
version: 1.0.0
- id: RevitParameterProjectionReused
version: 1.0.0
- id: RevitParameterProjectionArchived
version: 1.0.0
- id: RevitParameterProjectionResynced
version: 1.0.0
entities:
- id: ObjectClass
version: 1.0.0
- id: ParameterKey
version: 1.0.0
- id: ClassParameter
version: 1.0.0
- id: ParameterGroup
version: 1.0.0
- id: ParameterValue
version: 1.0.0
- id: ParameterValueGroup
version: 1.0.0
- id: RevitParameterProjection
version: 1.0.0
flows:
- id: object-class-graph-mutations
version: 1.0.0
- id: parameter-key-to-revit-projection
version: 1.0.0
- id: revit-projection-archive-and-reuse
version: 1.0.0
- id: object-class-demotion-cascade
version: 1.1.0
badges:
- content: "Status: Implemented in api-v1"
backgroundColor: yellow
textColor: yellow
- content: Airtable-backed
backgroundColor: gray
textColor: gray
---
## Overview
BIM Ontology answers two questions that everything downstream depends on:
1. **What kind of thing is this?** A single-parent tree of `ObjectClass` nodes (ADR-0046), cross-referenced to Uniformat, Masterformat, OmniClass, ObjectClass codes, and Revit categories.
2. **What parameter is this, canonically?** One [ParameterKey](/docs/entities/ParameterKey) per concept, defined once, with value semantics (`ParameterSpec`), presentation (`ParameterControl`), authoring grouping ([ParameterGroup](/docs/entities/ParameterGroup)), and canonical values ([ParameterValue](/docs/entities/ParameterValue), grouped into reusable templates by [ParameterValueGroup](/docs/entities/ParameterValueGroup)) as separate concepts that reference it.
A [ClassParameter](/docs/entities/ClassParameter) assignment connects the two, and a [RevitParameterProjection](/docs/entities/RevitParameterProjection) expresses an approved parameter inside Revit.
The module is `apps/api-v1/src/modules/bim-ontology` — a hexagonal module with Airtable persistence and an Airtable steward UI driven by `AT-*` scripts. Its base is `appoSN6xLYm5M7SkN`.
## Implementation Status
Status: `Implemented in api-v1`
## Ownership boundary with Product Marketplace
**`ParameterKey` is one concept, owned here and consumed by product-marketplace.** BIM Ontology defines parameter keys and their relationships to object classes and Revit. Product Marketplace assigns a class to a product and uses the associated parameters. Scraping either maps a scraped attribute onto an approved key or creates a new key in `active`, so product data is visible on the storefront while still flagged as needing steward mapping.
That is the whole reason the `active` lifecycle state exists — see [ubiquitous language](/docs/domains/bim-ontology/language).
Product-marketplace pages cross-reference the entity pages here rather than redefining them. The junctions that genuinely belong to product-marketplace — `ModelParameter`, `ModelParameterAllowedValue`, `VariantParameter` — stay in that domain.
## Aggregates and lifecycles
| Aggregate | Lifecycle | Note |
| --- | --- | --- |
| `ObjectClass` | `draft → approved → archived` | Approve requires Uniformat, Masterformat, ObjectClass code, and Revit category, plus an approved ancestor chain (ADR-0050) |
| `ParameterKey` | `draft → active → approved → archived` | The only aggregate with `active` (ADR-0052) |
| `RevitParameterProjection` | `draft → approved → archived` | System-created in `draft`, steward-completed, steward-approved |
| `ClassParameter` | none | An assignment, not a lifecycle. `AssignmentOrigin` is provenance |
| `ParameterGroup`, `ParameterValue`, `ParameterSpec`, `ParameterControl`, `ParameterValueGroup` | none | Reference and catalog concepts |
## Flows
- [Object Class Graph Mutations](/docs/flows/object-class-graph-mutations) — mutate the tree and fan out to the four registered handlers
- [Parameter Key to Revit Projection](/docs/flows/parameter-key-to-revit-projection) — authoring through to an approved projection
- [Revit Projection Archive and Reuse](/docs/flows/revit-projection-archive-and-reuse) — archive rather than delete, and reuse the GUID
- [Object Class Demotion Cascade](/docs/flows/object-class-demotion-cascade) — losing a classification demotes the class and blocks projection approval
## Primary Code References
- Module: `apps/api-v1/src/modules/bim-ontology`
- Composition and event registrations: `apps/api-v1/src/modules/bim-ontology/composition/bim-ontology.composition.ts`
- REST: `apps/api-v1/src/modules/bim-ontology/adapters/inbound/rest/app/` — eight registration areas, 55 operations
- OpenAPI: [api-v1 spec](https://docs.mybuildplan.ai/openapi/api-v1.json), operations tagged `BIM Ontology / …`
## Documentation Links
- [Ubiquitous language](/docs/domains/bim-ontology/language)
- [Platform ubiquitous language](/docs/domains/platform/language)
- ADRs: [`docs/adr/README.md`](https://github.com/buildplan-construction/buildplan-monorepo/blob/develop/docs/adr/README.md)
- Module docs: [`docs/api-v1/bim-ontology/_OVERVIEW.md`](https://github.com/buildplan-construction/buildplan-monorepo/blob/develop/docs/api-v1/bim-ontology/_OVERVIEW.md)
- Mutation ownership: [`docs/api-v1/bim-ontology/MUTATION_ROUTING.md`](https://github.com/buildplan-construction/buildplan-monorepo/blob/develop/docs/api-v1/bim-ontology/MUTATION_ROUTING.md)
## Open Questions
- **api-v2 target.** The module lives in api-v1 only. No migration plan exists yet, so no page here carries `Being migrated from api-v1 to api-v2`.
- **Steward authorization.** Every route requires an API key, but there is no per-operation role model; approve and archive are not distinguished from reads by authorization.
---
id: platform
name: Platform
version: 1.0.0
owners:
- buildplan
summary: |
Shared kernel — cross-cutting vocabulary and the catalog entry point.
Not a runtime bounded context and not an api-v2 module folder.
badges:
- content: Shared kernel
backgroundColor: blue
textColor: blue
- content: "Status: Unknown / open question"
backgroundColor: gray
textColor: gray
---
# Start here
Buildplan architecture documentation is **work in progress**. **product-marketplace** and **bim-ontology** are the started domain slices; other domains and platform terms are added incrementally.
This domain is the **shared kernel**: terms more than one bounded context has to agree on. It owns no deployable and no aggregates.
## Ubiquitous language (read order)
1. **[Platform ubiquitous language](/docs/domains/platform/language)** — POM, BFF, `project-coordination`, configuration DSL, committed vs proposed state, and the BIM terms other contexts consume.
2. **[BIM Ontology ubiquitous language](/docs/domains/bim-ontology/language)** — object classes, parameter keys, assignment and inheritance, Revit projection (bounded context only).
3. **[Product Marketplace ubiquitous language](/docs/domains/product-marketplace/language)** — catalog, scraping, object classes (bounded context only).
## Domains
- [BIM Ontology](/docs/domains/bim-ontology) — canonical classification tree and parameter dictionary; owns `ParameterKey`
- [Product Marketplace](/docs/domains/product-marketplace) — catalog, scraping, product configuration; consumes `ParameterKey`
- [Platform](/docs/domains/platform) — this page; shared-kernel language only
## Services and APIs
- [api-v1](/docs/services/api-v1)
- [api-v2](/docs/services/api-v2)
- [Backend platform](/docs/systems/backend-platform)
## Architecture decisions
ADRs live in the repo, not as catalog custom-docs pages.
There are **two ADR series**, and both number from `ADR-0001`. Qualify every citation: `platform ADR-0008` or `built api-v1 ADR-0008`.
| Series | Where | Scope |
| --- | --- | --- |
| **Platform — canonical** | [`docs/adr/07_Architectural_Decision_Records.md`](https://github.com/buildplan-construction/buildplan-monorepo/blob/develop/docs/adr/07_Architectural_Decision_Records.md) | New decisions go here |
| **Per-file** | [`docs/adr/`](https://github.com/buildplan-construction/buildplan-monorepo/blob/develop/docs/adr/README.md) `ADR-NNNN-*.md` | Documentation stack and built api-v1 domain |
Index and citation rules: [`docs/adr/README.md`](https://github.com/buildplan-construction/buildplan-monorepo/blob/develop/docs/adr/README.md). Repo conventions: `eventcatalog/CONVENTIONS.md`.
## Agents (Cursor)
- **MCP is not activated.** Do not call the `eventcatalog` MCP server. It requires Scale and a configured `.mcp.json`.
- **Use now:** `npm run docs:catalog:dev` (no licence key), catalog MDX, and `llms.txt` after build.
- Cursor rules: `documentation-stack-overview.mdc`, `architecture-documentation-entry.mdc`.
---
id: product-marketplace
name: Product Marketplace
version: 1.0.0
owners:
- buildplan
summary: |
Product catalog, vendor product data, object-category graph, product scraping,
image publishing, and BIM-oriented product metadata.
services:
- id: api-v1
version: 1.0.0
- id: party-directory
version: 1.0.0
- id: shared-scraping
version: 1.0.0
systems:
- id: product-ingestion
version: 1.0.0
sends:
- id: ProductModelCreated
version: 1.0.0
- id: ProductModelImagesPublished
version: 1.0.0
- id: OrganizationCreated
version: 1.0.0
receives:
- id: ProductFromUrlRequested
version: 1.0.0
- id: ScrapeRunCompleted
version: 1.0.0
- id: ScrapeRunFailed
version: 1.0.0
entities:
- id: ProductModel
version: 1.0.0
- id: ProductVariant
version: 1.0.0
- id: ModelParameter
version: 1.0.0
- id: ModelParameterAllowedValue
version: 1.0.0
- id: VariantParameter
version: 1.0.0
- id: PriceGroup
version: 1.0.0
- id: VendorProductCollection
version: 1.0.0
- id: CadAsset
version: 1.0.0
- id: Inventory
version: 1.0.0
- id: Material
version: 1.0.0
- id: ProductDescription
version: 1.0.0
- id: ProductDoc
version: 1.0.0
- id: ProductImage
version: 1.0.0
flows:
- id: create-product-from-url
version: 1.0.0
- id: refresh-reload-product-scrape
version: 1.0.0
- id: parameter-graph-persistence
version: 1.0.0
badges:
- content: "Status: Implemented in api-v1"
backgroundColor: yellow
textColor: yellow
- content: Airtable-backed
backgroundColor: gray
textColor: gray
---
## Overview
Product Marketplace owns the active product catalog workflows in `api-v1`: product models and variants, object categories, parameter values, price groups, vendors, image publishing, and the product scraping platform.
The current implementation lives in `apps/api-v1/src/modules/product-marketplace`. It follows the api-v1 modular monolith and ports/adapters conventions with Airtable as the persistence adapter.
## Implementation Status
Status: `Implemented in api-v1`
The future `api-v2` product-marketplace module is the long-term Prisma/Postgres target, but this domain page documents the active api-v1 implementation first.
## Primary Code References
- Module root: `apps/api-v1/src/modules/product-marketplace`
- Composition root: `apps/api-v1/src/modules/product-marketplace/composition/product-marketplace.composition.ts`
- REST adapters: `apps/api-v1/src/modules/product-marketplace/adapters/inbound/rest`
- Generated OpenAPI: `apps/api-v1/openapi/openapi.json`
## Documentation Links
- api-v1 service: `/docs/services/api-v1`
- Party Directory shared service: `/docs/services/party-directory`
- Shared Scraping service: `/docs/services/shared-scraping`
- Product ingestion system: `/docs/systems/product-ingestion`
- Backend platform: `/docs/systems/backend-platform`
- Ubiquitous language: [`/docs/domains/product-marketplace/language`](/docs/domains/product-marketplace/language)
- ADRs: [`docs/adr/README.md`](https://github.com/buildplan-construction/buildplan-monorepo/blob/develop/docs/adr/README.md)
- Migration overview source: `eventcatalog/domains/product-marketplace/migration.mdx`
## Flows
- [Create Product From URL](/docs/flows/create-product-from-url)
- [Refresh and Reload Product Scrape](/docs/flows/refresh-reload-product-scrape)
- [Parameter Graph Persistence](/docs/flows/parameter-graph-persistence)
Graph mutations are no longer a product-marketplace flow: see [Object Class Graph Mutations](/docs/flows/object-class-graph-mutations) in [BIM Ontology](/docs/domains/bim-ontology).
## Parameter ownership
**The canonical parameter dictionary is owned by [BIM Ontology](/docs/domains/bim-ontology), not by this domain.** `ParameterKey`, `ParameterValue`, `ParameterValueGroup`, `ParameterGroup`, `ParameterSpec`, and `ParameterControl` are defined once there. Product Marketplace assigns an object class to a product and uses the parameters that come with that class; it does not define its own parameter concepts.
The live code is `apps/api-v1/src/modules/bim-ontology/core/domain/`. The former product-marketplace parameter-key, parameter-value, parameter-value-group, and category-parameter directories no longer exist. Where this domain still needs to read a canonical value it does so by explicit cross-module import — `CatalogReadUseCases.ts` imports `ParameterValue` and `ParameterValueGroup` from `@modules/bim-ontology` and serves them over the internal catalog-read routes.
| Retired product-marketplace page | Where the concept lives now |
| --- | --- |
| `ParameterKey` | [ParameterKey](/docs/entities/ParameterKey) — same concept, one record per handle, lifecycle `draft → active → approved → archived` |
| `CategoryParameter` | [ClassParameter](/docs/entities/ClassParameter) — the assignment of one parameter key to one [ObjectClass](/docs/entities/ObjectClass) |
| `CategoryParameterAllowedValue` | The allowed-value children of [ClassParameter](/docs/entities/ClassParameter), documented on that page |
| `ParameterValue` | [ParameterValue](/docs/entities/ParameterValue) — one canonical value a key can take, with its `QuantityValue` parts |
| `ParameterValueGroup` | [ParameterValueGroup](/docs/entities/ParameterValueGroup) — a reusable ordered set of values applied as a template |
BIM Ontology also owns two concepts this domain never had a page for: [ParameterGroup](/docs/entities/ParameterGroup), the authoring grouping, and [RevitParameterProjection](/docs/entities/RevitParameterProjection), the Revit-facing expression of an approved key.
`ObjectCategory` is likewise the old name for [ObjectClass](/docs/entities/ObjectClass); scraping documentation in this domain that still says "object category" is describing that concept. The class tree and its mutation endpoints belong to bim-ontology — see [Object Class Graph Mutations](/docs/flows/object-class-graph-mutations).
Scraping either maps a scraped attribute onto an approved key or creates a new key in `active`, so product data is visible on the storefront while still flagged as needing steward mapping — that is the whole reason the `active` state exists. See [Why `active` exists](/docs/domains/bim-ontology/language).
The junctions that genuinely belong to this domain stay here:
- L2: [ModelParameter](/docs/entities/ModelParameter) (+ allowed-value children; MTO source of truth)
- L3: [VariantParameter](/docs/entities/VariantParameter) (renamed from legacy `parameter_assignments`)
Scrape persistence details: [Parameter Graph Persistence](/docs/flows/parameter-graph-persistence).
## Open Questions
- Which product-marketplace concepts move first into api-v2?
- Which legacy REST stubs should remain visible in OpenAPI versus be removed?
- Which Airtable tables should become the first Azimutt layout when persistence-model documentation starts?
---
id: buildplan
name: Buildplan Engineering
summary: Default owner for Buildplan architecture, API, and domain documentation.
---
## Overview
Buildplan Engineering owns the current system catalog until more specific domain owners are added.
---
id: CadAsset
name: CAD Asset
version: 1.0.0
owners:
- buildplan
summary: BIM/CAD file attached to a product model or generic object category.
identifier: cadAssetId
properties:
- name: cadAssetId
type: string
required: true
description: Stable CAD asset identity.
- name: name
type: string
required: true
description: Human-readable asset name.
- name: format
type: RFA | DWG | STEP | IFC | OBJ | FBX | GLB | GLTF
required: true
description: CAD or BIM file format.
- name: url
type: string
required: true
description: Storage or source URL for the asset.
- name: productModelId
type: string | null
required: false
references: ProductModel
referencesIdentifier: productModelId
relationType: many-to-one
description: Product model context when the asset is product-specific.
- name: objectCategoryId
type: string | null
required: false
description: Generic taxonomy context when the asset is category-level.
badges:
- content: "Status: Implemented in api-v1"
backgroundColor: yellow
textColor: yellow
attachments:
- title: TypeDoc reference
url: https://docs.mybuildplan.ai/typedoc/api-v1/
type: reference
icon: CodeIcon
- title: Source
url: https://github.com/buildplan-construction/buildplan-monorepo/blob/develop/apps/api-v1/src/modules/product-marketplace/core/domain/cad-asset/CadAsset.ts
type: source
icon: FileTextIcon
---
## Purpose
CAD assets connect catalog data to downstream design, takeoff, and Revit workflows. They describe the file format and ownership context without embedding storage-provider details in the domain.
## Invariants
- A CAD asset must have a stable identity and non-empty URL.
- A CAD asset belongs to exactly one product-model context or one generic object-category context.
- RFA assets require a Revit family template.
## Related Ports
- `ForReadingCatalogResources`
- `ForManagingProductModels`
## OpenAPI Operations
- [getCadAssetById](/docs/services/api-v1/openapi/operations/getCadAssetById)
- [getGenericSpecifierCadAssetById](/docs/services/api-v1/openapi/operations/getGenericSpecifierCadAssetById)
---
id: ClassParameter
name: Class Parameter
version: 1.0.0
owners:
- buildplan
summary: The assignment of one ParameterKey to one ObjectClass, with the local rules governing it. Always called an assignment — binding is reserved for Revit placement.
aggregateRoot: true
identifier: classParameterId
properties:
- name: classParameterId
type: string
required: true
description: Stable assignment identity (`domain_id`, never a `rec…` id — ADR-0048).
- name: objectClassId
type: string
required: true
references: ObjectClass
referencesIdentifier: objectClassId
relationType: many-to-one
description: The object class this assignment applies to. Named `objectCategoryId` in the aggregate's constructor and Airtable mapper; the event payload and HTTP contract say `objectClassId`.
- name: parameterKeyId
type: string
required: true
references: ParameterKey
referencesIdentifier: parameterKeyId
relationType: many-to-one
description: The canonical parameter key being assigned.
- name: isRequired
type: boolean
required: true
description: Whether products of this class must carry a value for the parameter. Defaults to false.
- name: assignmentOrigin
type: direct | rendered | legacy_unknown
required: true
description: Provenance, not a lifecycle. Server-owned and hidden from stewards. `create` defaults to `direct`; `rehydrate` defaults to `legacy_unknown`.
- name: inheritanceMode
type: unique | apply_to_descendants | null
required: false
description: On `direct` rows only — whether the assignment applies to this class alone or also to its descendants.
- name: childBehavior
type: inherit | suppress | override | null
required: false
description: On `rendered` rows only — how a class-local overlay responds to an inherited assignment. Defaults to `inherit`.
- name: allowedValues
type: ClassParameterAllowedValue[]
required: true
description: Optional bounded value set for this (class, key) pair. Empty means unconstrained. Editable on `direct` rows only.
badges:
- content: "Status: Implemented in api-v1"
backgroundColor: yellow
textColor: yellow
- content: Aggregate Root
backgroundColor: blue
textColor: blue
attachments:
- title: TypeDoc reference
url: https://docs.mybuildplan.ai/typedoc/api-v1/
type: reference
icon: CodeIcon
- title: Source
url: https://github.com/buildplan-construction/buildplan-monorepo/blob/develop/apps/api-v1/src/modules/bim-ontology/core/domain/class-parameter/ClassParameter.ts
type: source
icon: FileTextIcon
- title: Inheritance mode and child behavior
url: https://github.com/buildplan-construction/buildplan-monorepo/blob/develop/apps/api-v1/src/modules/bim-ontology/core/domain/class-parameter/Inheritance.ts
type: source
icon: FileTextIcon
- title: Effective parameter set resolver
url: https://github.com/buildplan-construction/buildplan-monorepo/blob/develop/apps/api-v1/src/modules/bim-ontology/core/domain/class-parameter/EffectiveParameterSetResolver.ts
type: source
icon: FileTextIcon
---
## Purpose
A class parameter is the **assignment** of one [ParameterKey](/docs/entities/ParameterKey) to one [ObjectClass](/docs/entities/ObjectClass), carrying the rules that apply locally: whether the parameter is required, how far down the tree it reaches, and which values are allowed at that class.
The word is **assignment**, never *binding*. Binding is reserved for Revit placement — project binding, family application, binding kind — and using it here loses that distinction. The aggregate still has method names from before the rename (`bind`, `reviseBinding`), and the resolver still calls a selected assignment a "binding" internally; the vocabulary of the contract and of this catalog is assignment.
## No lifecycle
A class parameter has no lifecycle. It exists or it does not.
`AssignmentOrigin` looks like a status and is not one — it is **provenance**:
| Origin | Meaning |
| --- | --- |
| `direct` | Authored on this class. The only origin that accepts deletion and allowed-value edits |
| `rendered` | A materialized overlay produced from an ancestor's assignment |
| `legacy_unknown` | Provenance was never recorded. The default when rehydrating a row that has no origin |
`AssignmentOrigin` is server-owned and hidden from stewards: it is set by the server on create, on rehydrate, and by the promote paths (`promoteToDirect`, `promoteToDirectAndReviseBinding`), and it is not something a steward chooses in the Airtable UI.
## Invariants
- One assignment pairs exactly one object class with exactly one parameter key.
- **Origin fields are mutually exclusive** (`assertOriginFields`, `INVALID_CLASS_PARAMETER_ORIGIN_FIELDS`): a `direct` row must not carry a child behavior, and a `rendered` row must not carry an inheritance mode. This is a domain invariant checked on create, on rehydrate, and on revise — not a contract-layer nicety. Only `legacy_unknown` rows may carry both.
- Allowed values must be unique by `parameterValueId` (`DUPLICATE_CLASS_PARAMETER_ALLOWED_VALUE`).
- At most one allowed value may be the default (`MULTIPLE_DEFAULT_CLASS_PARAMETER_ALLOWED_VALUES`). Allowed-value state is validated before the no-change early return, so an idempotent replay still surfaces a violation instead of silently passing.
- Deletion and allowed-value editing apply to `direct` rows only.
### InheritanceMode — `direct` rows only
`unique` applies the assignment to this class alone; `apply_to_descendants` also reaches every descendant. An assignment always includes itself.
The retired wire vocabulary `self_only` / `self_plus_descendants` / `descendants_only` is **gone from the HTTP contract** — the request schemas reject those values. Persistence and Assign scripts resolve `inheritance_modes.handle` only (`unique` / `apply_to_descendants`). Display `name` is steward-facing. `Inheritance.ts` still maps leftover `self_*` tokens if one is read. `descendants_only` has no canonical equivalent and is rejected outright.
### ChildBehavior — `rendered` rows only
`inherit` (the default) takes the ancestor's assignment as-is, `suppress` removes it for this class only, and `override` replaces it with this class's local values, again for this class only.
## Effective parameter set
The answer to "what parameters does this class actually have" is a **read model resolved on request**, not a stored field. `getEffectiveParameterSet` resolves one class and `resolveEffectiveParameterSets` resolves a batch, and each decision comes back with a resolution kind and a human-readable explanation:
| Kind | Meaning |
| --- | --- |
| `direct` | Assigned directly on this class |
| `inherited` | Reached from an ancestor, with the path in the explanation |
| `override` | A local direct assignment supersedes the inherited one |
| `suppressed` | A local overlay removes the inherited assignment; the suppressing row's id is reported |
`rendered` rows are a **cache for Airtable's benefit** — they let a steward see inherited parameters as rows in a grid. The resolution above is authoritative. When the two disagree, the resolver is right and the rendered rows need resyncing, which is what the rendered-template sync handler does after a parent-graph change or a new assignment.
## Domain Events
Four names on one payload shape (`classParameterId`, `objectClassId`, `parameterKeyId`, and `allowedValueCount` on the replace event):
| Event | Emitted by |
| --- | --- |
| `ClassParameterAssigned` | `bind` — a new direct assignment |
| `ClassParameterAssignmentUpdated` | `reviseBinding`, `promoteToDirect`, `promoteToDirectAndReviseBinding` — only when something actually changed |
| `ClassParameterAllowedValuesReplaced` | `replaceAllowedValues`, when the set differs |
| `ClassParameterUnassigned` | `markUnassigned` |
`ClassParameterAssigned` is also consumed inside the module: it triggers the rendered parameter template sync.
## OpenAPI Operations
- [listClassParameters](/docs/services/api-v1/openapi/operations/listClassParameters)
- [assignClassParameters](/docs/services/api-v1/openapi/operations/assignClassParameters)
- [upsertClassParameter](/docs/services/api-v1/openapi/operations/upsertClassParameter)
- [unassignClassParameter](/docs/services/api-v1/openapi/operations/unassignClassParameter)
- [replaceClassParameterAllowedValues](/docs/services/api-v1/openapi/operations/replaceClassParameterAllowedValues)
- [validateClassParameterAllowedValues](/docs/services/api-v1/openapi/operations/validateClassParameterAllowedValues)
- [getEffectiveParameterSet](/docs/services/api-v1/openapi/operations/getEffectiveParameterSet)
- [resolveEffectiveParameterSets](/docs/services/api-v1/openapi/operations/resolveEffectiveParameterSets)
## ADRs
The platform series lives in [`docs/adr/07_Architectural_Decision_Records.md`](https://github.com/buildplan-construction/buildplan-monorepo/blob/develop/docs/adr/07_Architectural_Decision_Records.md). The per-file series is separate and the numbers collide — see [`docs/adr/README.md`](https://github.com/buildplan-construction/buildplan-monorepo/blob/develop/docs/adr/README.md).
| ADR | Decision |
| --- | --- |
| Platform ADR-0046 | The object-class hierarchy is a single-parent tree, so inheritance follows one path to the root |
| Platform ADR-0048 | `domain_id` is the public identifier; never a `rec…` id in a payload |
| [Per-file ADR-0006](https://github.com/buildplan-construction/buildplan-monorepo/blob/develop/docs/adr/ADR-0006-parameter-schema-canonical-keys-and-junctions.md) | Canonical parameter keys and values with per-level junctions |
---
id: Inventory
name: Inventory
version: 1.0.0
owners:
- buildplan
summary: Catalog stock snapshot for a product variant.
identifier: inventoryId
properties:
- name: inventoryId
type: string
required: true
description: Stable inventory snapshot identity.
- name: productVariantId
type: string
required: true
references: ProductVariant
referencesIdentifier: productVariantId
relationType: many-to-one
description: Variant whose stock this snapshot represents.
- name: stockQuantity
type: number
required: true
description: Quantity currently available before reservations.
- name: reservedQuantity
type: number
required: true
description: Quantity reserved from stock.
- name: unitId
type: string | null
required: false
description: Unit for interpreting quantities.
- name: onBackorder
type: boolean
required: true
description: Whether the variant is backordered or effectively out of available stock.
badges:
- content: "Status: Implemented in api-v1"
backgroundColor: yellow
textColor: yellow
attachments:
- title: TypeDoc reference
url: https://docs.mybuildplan.ai/typedoc/api-v1/
type: reference
icon: CodeIcon
- title: Source
url: https://github.com/buildplan-construction/buildplan-monorepo/blob/develop/apps/api-v1/src/modules/product-marketplace/core/domain/inventory/Inventory.ts
type: source
icon: FileTextIcon
---
## Purpose
Inventory gives product workflows a read model for stock quantity, availability, unit, and related supply information. It is a snapshot, not the owner of procurement or warehouse operations.
## Invariants
- Inventory is linked to a product variant.
- Stock and reserved quantities cannot be negative.
- Reserved quantity cannot exceed stock quantity.
- Backorder state is true when explicitly set or when no available stock remains.
## Related Ports
- `ForReadingCatalogResources`
## OpenAPI Operations
- [getInventoryById](/docs/services/api-v1/openapi/operations/getInventoryById)
---
id: Material
name: Material
version: 1.0.0
owners:
- buildplan
summary: Renderable material data and texture assets associated with catalog products.
identifier: materialId
properties:
- name: materialId
type: string
required: true
description: Stable material identity.
- name: name
type: string
required: true
description: Material display name.
- name: assets
type: MaterialAsset[]
required: true
description: Texture assets such as diffuse, normal, bump, metalness, specular, PSD, or reference maps.
badges:
- content: "Status: Implemented in api-v1"
backgroundColor: yellow
textColor: yellow
attachments:
- title: TypeDoc reference
url: https://docs.mybuildplan.ai/typedoc/api-v1/
type: reference
icon: CodeIcon
- title: Source
url: https://github.com/buildplan-construction/buildplan-monorepo/blob/develop/apps/api-v1/src/modules/product-marketplace/core/domain/material/Material.ts
type: source
icon: FileTextIcon
---
## Purpose
Materials let the catalog connect product selections to rendering, BIM, and specification workflows. The domain model tracks material identity and texture roles while storage and publishing details remain adapter concerns.
## Invariants
- A material must have a non-empty name.
- Texture assets are typed by role.
- A material can have at most one diffuse texture.
- Each material asset requires a non-empty URL.
## Related Ports
- `ForReadingCatalogResources`
## OpenAPI Operations
- [getMaterialById](/docs/services/api-v1/openapi/operations/getMaterialById)
---
id: ModelParameter
name: Model Parameter
version: 1.0.0
owners:
- buildplan
summary: L2 junction binding a canonical ParameterKey to a ProductModel; MTO allowable values and ranges live here.
identifier: modelParameterId
properties:
- name: modelParameterId
type: string
required: true
description: Stable junction identity.
- name: productModelId
type: string
required: true
references: ProductModel
referencesIdentifier: productModelId
relationType: many-to-one
description: Product model this parameter applies to.
- name: parameterKeyId
type: string
required: true
references: ParameterKey
referencesIdentifier: parameterKeyId
relationType: many-to-one
description: Canonical parameter key bound at model level.
- name: isRequired
type: boolean
required: true
description: Whether the parameter is required for this model.
- name: allowedValueSource
type: defined_on_model | derived_from_variants | null
required: false
description: Whether allowable values are authored on the model or derived from existing variants.
- name: defaultValueId
type: string | null
required: false
references: ParameterValue
referencesIdentifier: parameterValueId
relationType: many-to-one
description: Optional default parameter value for this model.
- name: sortOrder
type: number | null
required: false
description: Display ordering among a model's parameters.
- name: scopes
type: ModelParameterScope[]
required: true
description: "Model-level scope, from `ModelParameterScope` — `variant_option`, `spec`, `pricing`, `bim`, `manufacturing`. Multi-select and the source of truth. Empty when unset; the deprecated singular `scope` is folded into a one-element array on rehydrate."
- name: allowedValues
type: ModelParameterAllowedValue[]
required: true
references: ModelParameterAllowedValue
relationType: one-to-many
description: MTO allowable values and ranges for this (model, key) pair.
badges:
- content: "Status: Implemented in api-v1"
backgroundColor: yellow
textColor: yellow
attachments:
- title: TypeDoc reference
url: https://docs.mybuildplan.ai/typedoc/api-v1/
type: reference
icon: CodeIcon
- title: Source
url: https://github.com/buildplan-construction/buildplan-monorepo/blob/develop/apps/api-v1/src/modules/product-marketplace/core/domain/model-parameter/ModelParameter.ts
type: source
icon: FileTextIcon
---
## Purpose
Model parameters are the level-2 junction and the **MTO source of truth** for allowable values and ranges (ADR-0007, ADR-0008). Made-to-order products store their option universe here; stock/predefined-variant products derive displayed options from `variant_parameters` instead of duplicating values on the model.
Ranges use linked `quantity_values` with min/max roles rather than range columns on the junction.
This junction belongs to product-marketplace. The key and values it references do not — [ParameterKey](/docs/entities/ParameterKey) and [ParameterValue](/docs/entities/ParameterValue) are owned by [BIM Ontology](/docs/domains/bim-ontology).
## `ModelParameterScope` is not `ParameterUsage`
`scopes` uses `ModelParameterScope` — `variant_option`, `spec`, `pricing`, `bim`, `manufacturing` — which is a **different vocabulary** from the bim-ontology `ParameterUsage` set (`variant_option`, `product_spec`, `bim_parameter`, `mfg_takeoff`, `qto_takeoff`). Only `variant_option` is spelled the same in both. The two overlap conceptually and do not correspond term for term; do not translate one into the other.
`scopes` (plural) is the multi-select source of truth. The singular `scope` input is `@deprecated`; `rehydrate` prefers `scopes` when non-empty, falls back to wrapping `scope` in a one-element array, and otherwise leaves the set empty.
## Invariants
- Each junction pairs exactly one product model with one canonical parameter key.
- Allowed values reference canonical `ParameterValue` records.
- Stock scrape persistence does **not** write `model_parameters` (deferred MTO migration in PARAM-S7).
The aggregate itself validates nothing today: `ModelParameter.rehydrate` is the only entry point and it applies defaults rather than checks. The invariants above are enforced by the application layer and by the Airtable schema, not by the domain object.
## Related Ports
- `ForManagingProductModels`
- `ForCreatingScrapedParameterGraph`
## OpenAPI Operations
No dedicated REST endpoint today; populated during MTO migration (PARAM-S7) and model authoring workflows.
## ADRs
- [ADR-0006](https://github.com/buildplan-construction/buildplan-monorepo/blob/develop/docs/adr/ADR-0006-parameter-schema-canonical-keys-and-junctions.md)
- [ADR-0007](https://github.com/buildplan-construction/buildplan-monorepo/blob/develop/docs/adr/ADR-0007-mto-model-level-allowable-values.md)
- [ADR-0008](https://github.com/buildplan-construction/buildplan-monorepo/blob/develop/docs/adr/ADR-0008-product-configuration-modes-and-option-source-of-truth.md)
---
id: ModelParameterAllowedValue
name: Model Parameter Allowed Value
version: 1.0.0
owners:
- buildplan
summary: Child row defining an allowable ParameterValue (or range via quantity_values) for a ModelParameter junction.
identifier: modelParameterAllowedValueId
properties:
- name: modelParameterAllowedValueId
type: string
required: true
description: Stable child-row identity.
- name: parameterValueId
type: string
required: true
references: ParameterValue
referencesIdentifier: parameterValueId
relationType: many-to-one
description: Canonical value permitted at this model level.
- name: isDefault
type: boolean
required: true
description: Whether this value is the default for the model binding.
- name: sortOrder
type: number | null
required: false
description: Display ordering among allowable values.
- name: availabilityRule
type: string | null
required: false
description: Optional rule expression for conditional availability (complex rules deferred to config-logic code).
badges:
- content: "Status: Implemented in api-v1"
backgroundColor: yellow
textColor: yellow
attachments:
- title: TypeDoc reference
url: https://docs.mybuildplan.ai/typedoc/api-v1/
type: reference
icon: CodeIcon
- title: Source
url: https://github.com/buildplan-construction/buildplan-monorepo/blob/develop/apps/api-v1/src/modules/product-marketplace/core/domain/model-parameter/ModelParameter.ts
type: source
icon: FileTextIcon
---
## Purpose
Model-level allowed values express MTO option sets and discrete ranges. Implemented as a value object (`ModelParameterAllowedValue`) colocated with `ModelParameter`; persisted as `model_parameter_allowed_values` in product-marketplace.
## Invariants
- Must reference a canonical parameter value.
- Belongs to exactly one model-parameter junction.
- Range semantics use linked `quantity_values` min/max roles on the referenced value, not columns on this row.
## Related Ports
- `ForManagingProductModels`
## ADRs
- [ADR-0007](https://github.com/buildplan-construction/buildplan-monorepo/blob/develop/docs/adr/ADR-0007-mto-model-level-allowable-values.md)
---
id: ObjectClass
name: Object Class
version: 1.0.0
owners:
- buildplan
summary: The canonical kind of building object BuildPlan can classify, specify, and eventually build. Aggregate root of a single-parent tree, cross-referenced to Uniformat, Masterformat, ObjectClass codes, and one Revit category.
aggregateRoot: true
identifier: objectClassId
properties:
- name: objectClassId
type: string
required: true
description: Stable object class identity (`domain_id`, never a `rec…` id — ADR-0048).
- name: name
type: string
required: true
description: Class name. Required and non-empty.
- name: parentId
type: string | null
required: false
references: ObjectClass
referencesIdentifier: objectClassId
relationType: many-to-one
description: The single parent (ADR-0046). Null for a tree root.
- name: childIds
type: string[]
required: true
references: ObjectClass
referencesIdentifier: objectClassId
relationType: one-to-many
description: Direct children. A class may be neither its own parent nor its own child.
- name: code
type: ObjectClassCode | null
required: false
description: Linked ObjectClass code reference (`id`, `code`, optional `name`).
- name: path
type: string | null
required: false
description: Derived ancestry path. Written by the hierarchy handler after a parent-graph change, never authored.
- name: depth
type: number | null
required: false
description: Derived depth from the root. Handler-written.
- name: isLeaf
type: boolean | null
required: false
description: Derived childless flag. Handler-written.
- name: lifecycleStatus
type: draft | approved | archived
required: true
description: ADR-0050 lifecycle (`OBJECT_CLASS_STATUSES`). Rehydrate defaults to `approved` for legacy rows with no persisted value and normalizes legacy Airtable `active` to `approved`.
- name: authoredRevitCategoryId
type: string | null
required: false
description: The authored `revit_categories` link. At most one; rehydrating more than one throws.
- name: revitCategoryStatus
type: current | stale | needs_review | unresolved | null
required: false
description: Health of the Revit category link. Not a lifecycle.
- name: revitCategorySource
type: authored | inherited | null
required: false
description: Whether the resolved category was authored on this class or filled from an ancestor.
- name: revitCategoryReviewReason
type: string | null
required: false
description: Why the link is `stale` or `needs_review`. Required when marking `needs_review`.
- name: classParameters
type: ClassParameter[]
required: false
references: ClassParameter
relationType: one-to-many
description: Parameter assignments made against this class. The effective set is resolved on request, not stored here.
badges:
- content: "Status: Implemented in api-v1"
backgroundColor: yellow
textColor: yellow
- content: Aggregate Root
backgroundColor: blue
textColor: blue
attachments:
- title: TypeDoc reference
url: https://docs.mybuildplan.ai/typedoc/api-v1/
type: reference
icon: CodeIcon
- title: Source
url: https://github.com/buildplan-construction/buildplan-monorepo/blob/develop/apps/api-v1/src/modules/bim-ontology/core/domain/object-class/ObjectClass.ts
type: source
icon: FileTextIcon
- title: Lifecycle status
url: https://github.com/buildplan-construction/buildplan-monorepo/blob/develop/apps/api-v1/src/modules/bim-ontology/core/domain/object-class/ObjectClassStatus.ts
type: source
icon: FileTextIcon
- title: Classification fill (ADR-0050)
url: https://github.com/buildplan-construction/buildplan-monorepo/blob/develop/apps/api-v1/src/modules/bim-ontology/core/domain/object-class/ClassificationInheritance.ts
type: source
icon: FileTextIcon
- title: Revit category link projection
url: https://github.com/buildplan-construction/buildplan-monorepo/blob/develop/apps/api-v1/src/modules/bim-ontology/core/domain/object-class/ObjectClassRevitCategory.ts
type: source
icon: FileTextIcon
---
## Purpose
An object class answers **what kind of thing is this?** It is not a product, a product model, or a Revit category — it is the classification node those things hang off. Classes carry the external classification cross-references (Uniformat, Masterformat, OmniClass, ObjectClass code), at most one Revit category link, and the parameter assignments that make a class specifiable.
The class graph is the spine of the module: [ClassParameter](/docs/entities/ClassParameter) assignments attach parameters to it, Revit category scope is resolved from it, and product-marketplace assigns a class to a product rather than defining its own taxonomy.
## Lifecycle
`draft → approved → archived`, from `OBJECT_CLASS_STATUSES`.
| Transition | Rule |
| --- | --- |
| `draft → approved` | Steward approve. Gated — see below |
| `draft → archived` | Allowed |
| `approved → archived` | Allowed |
| `approved → draft` | Demotion only, via `demoteToDraft()`. Clearing a required unique classification with no remaining ancestor value demotes the class |
| `archived → draft` | `reinstate()` only. Archived has no forward transition |
`approved` is the only usable tier (`isUsableObjectClassStatus`), and the only visible one when `includeInactive` is false. A new root is authored in `draft` (`createDraftRoot`).
**Approve gate.** Approving requires all four of Uniformat, Masterformat, ObjectClass code, and Revit category (`REQUIRED_OBJECT_CLASS_APPROVE_KINDS`) to be present — resolved along the ancestry, so an inherited value satisfies the gate exactly as a directly authored one does. It additionally requires **every ancestor to already be `approved`** (`assertApprovedAncestorChain`, ADR-0050); the error names the offending ancestor and tells the steward to approve it first. `ApproveObjectClassUseCase` runs the ancestor check before the classification check and is idempotent on an already-approved class.
## Invariants
- Exactly one parent, or none (ADR-0046). A class may not be its own parent or its own child, and the graph is acyclic.
- `name` is required and non-empty.
- At most one authored Revit category link. More than one on rehydrate is a domain error, not a silent pick.
- A class may be `approved` only if every ancestor on its single path to the root is `approved`.
- `needs_review` requires a `reviewReason`.
- When the Revit category status is not `current`, the projected bindable ids are null — a `stale` or `needs_review` link never hands out a category to bind against.
- `depth`, `path`, `is_leaf`, and `root_object_class` are derived. They are recomputed by the `ObjectClassParentGraphChanged` hierarchy handler over the seeded subtree, never authored and never a source of truth.
### `root_object_class`
`root_object_class` is the parentless ancestor at the top of a class's lineage, derived and persisted on every row. It is **explicitly not an input to Revit category resolution** — that was a DAG-era rule retired with ADR-0046. Revit applicability comes from the effective assignment set, not from which tree a class happens to sit in.
## Classification fill (ADR-0050)
Stewards author `*_direct` values only. Everything else is inferred:
- **Make-unique is inferred, never authored.** On the edited node, a direct value that differs from the value at the nearest make-unique ancestor becomes the new make-unique source; clearing it unsets make-unique and the node inherits again.
- **Fill runs top-down.** Walk up to the nearest make-unique ancestor, then fill that value down until the next one.
- **Effective mirrors materialized `*_direct`.** Because the fill is materialized, no read-time ancestry walk happens. `*_inherited` is always cleared under this rule.
- **Kinds are independent.** Uniformat, Masterformat, ObjectClass code, and Revit category each fill separately. OmniClass is a flat, non-propagating link and is not part of the fill or the approve gate.
- More than one direct link for a kind is recorded as a `MULTIPLE_DIRECT_CLASSIFICATIONS` violation rather than resolved by guessing.
## Revit category binding status
The link carries a health status — `current`, `stale`, `needs_review`, `unresolved` — and a provenance source of `authored` or `inherited`.
**This is not a lifecycle.** It says nothing about whether the class is approved and it never gates approve or archive on its own; it describes whether the class's category link is currently trustworthy. Two words that look similar are also distinct: this status describes an object class *classifying as* a Revit category, whereas Revit category **scope** describes what a Revit project binding covers.
## Domain Events
- `ObjectClassParentGraphChanged` — published after parent tree edges change (move, merge, copy, delete, add-child, create-root), carrying `affectedObjectClassIds` and `changedAt`. Four handlers are registered against it: Revit category reconciliation, hierarchy field recomputation, classification fill with demote-on-lost-classification, and rendered parameter template sync. Ids in the payload are de-duplicated.
No other object-class domain event exists — lifecycle transitions are persisted directly by the lifecycle use cases.
## OpenAPI Operations
- [listObjectClasses](/docs/services/api-v1/openapi/operations/listObjectClasses)
- [createObjectClass](/docs/services/api-v1/openapi/operations/createObjectClass)
- [approveObjectClass](/docs/services/api-v1/openapi/operations/approveObjectClass)
- [archiveObjectClass](/docs/services/api-v1/openapi/operations/archiveObjectClass)
- [moveObjectClasses](/docs/services/api-v1/openapi/operations/moveObjectClasses)
- [mergeObjectClasses](/docs/services/api-v1/openapi/operations/mergeObjectClasses)
- [copyObjectClasses](/docs/services/api-v1/openapi/operations/copyObjectClasses)
- [addObjectClassChild](/docs/services/api-v1/openapi/operations/addObjectClassChild)
- [deleteObjectClasses](/docs/services/api-v1/openapi/operations/deleteObjectClasses)
- [setObjectClassRevitCategory](/docs/services/api-v1/openapi/operations/setObjectClassRevitCategory)
- [syncObjectClassHierarchy](/docs/services/api-v1/openapi/operations/syncObjectClassHierarchy)
- [syncObjectClassClassificationInheritance](/docs/services/api-v1/openapi/operations/syncObjectClassClassificationInheritance)
- [syncObjectClassRenderedParamTemplates](/docs/services/api-v1/openapi/operations/syncObjectClassRenderedParamTemplates)
- [syncAllObjectClasses](/docs/services/api-v1/openapi/operations/syncAllObjectClasses)
## ADRs
The platform series lives in [`docs/adr/07_Architectural_Decision_Records.md`](https://github.com/buildplan-construction/buildplan-monorepo/blob/develop/docs/adr/07_Architectural_Decision_Records.md). The per-file series is separate and the numbers collide — see [`docs/adr/README.md`](https://github.com/buildplan-construction/buildplan-monorepo/blob/develop/docs/adr/README.md).
| ADR | Decision |
| --- | --- |
| Platform ADR-0046 | The object-class hierarchy is a single-parent tree; no primary-parent device |
| Platform ADR-0048 | `domain_id` is the public identifier; never a `rec…` id in a payload |
| Platform ADR-0050 | Object-class lifecycle plus the four-kind classification approve gate; inherit-by-default fill with make-unique |
| Platform ADR-0052 | `active` is displayable-but-unvetted and belongs to ParameterKey alone — which is why this class has no `active` tier |
---
id: ParameterGroup
name: Parameter Group
version: 1.0.0
owners:
- buildplan
summary: The authoring grouping that organizes related ParameterKeys for humans. No lifecycle. The source a Revit UI group projection derives from — not itself a Revit artifact.
aggregateRoot: true
identifier: parameterGroupId
properties:
- name: parameterGroupId
type: string
required: true
description: Stable group identity (`domain_id`, never a `rec…` id — ADR-0048).
- name: name
type: string
required: true
description: Canonical group label, held as a `ParameterGroupName` value object. Trimmed on the way in and compared case-insensitively (`en-US`) for uniqueness.
badges:
- content: "Status: Implemented in api-v1"
backgroundColor: yellow
textColor: yellow
- content: Aggregate Root
backgroundColor: blue
textColor: blue
attachments:
- title: TypeDoc reference
url: https://docs.mybuildplan.ai/typedoc/api-v1/
type: reference
icon: CodeIcon
- title: Source
url: https://github.com/buildplan-construction/buildplan-monorepo/blob/develop/apps/api-v1/src/modules/bim-ontology/core/domain/parameter-group/ParameterGroup.ts
type: source
icon: FileTextIcon
- title: Domain events
url: https://github.com/buildplan-construction/buildplan-monorepo/blob/develop/apps/api-v1/src/modules/bim-ontology/core/domain/parameter-group/ParameterGroup.events.ts
type: source
icon: FileTextIcon
- title: Authoring use cases
url: https://github.com/buildplan-construction/buildplan-monorepo/blob/develop/apps/api-v1/src/modules/bim-ontology/core/application/use-cases/ParameterGroupAuthoringUseCases.ts
type: source
icon: FileTextIcon
- title: Group projectors
url: https://github.com/buildplan-construction/buildplan-monorepo/blob/develop/apps/api-v1/src/modules/bim-ontology/core/domain/revit/RevitProjectionDerivationPolicies.ts
type: source
icon: FileTextIcon
---
## Purpose
A parameter group is the **authoring grouping that organizes related [ParameterKeys](/docs/entities/ParameterKey) for humans** — *Dimensions*, *Materials and Finishes*, *Identity Data*. It exists so a steward reading the dictionary in Airtable, or an author reading a property palette, sees related parameters together.
It carries no semantics. Value semantics live on `ParameterSpec`, presentation on `ParameterControl`, and applicability on [ClassParameter](/docs/entities/ClassParameter). A group is a label and an ordering hint, nothing more.
Airtable table `parameter_groups`. A key points at a group through `ParameterKey.parameterGroupId`, which is optional at every lifecycle stage — a key never needs a group to be created, activated, approved, or archived.
## No lifecycle
A parameter group has no lifecycle. It exists or it does not, and its only mutation is a rename.
## Invariants
- `name` is required and non-empty after trimming (`PARAMETER_GROUP_NAME_REQUIRED`), enforced on create, on rename, and on rehydrate. Rehydrating a row with a blank name **throws** rather than defaulting.
- Group names are **unique case-insensitively**. `CreateParameterGroupUseCase` and `UpdateParameterGroupUseCase` both look the candidate name up before writing and return `DuplicateParameterGroupNameError` on a collision with any other group.
- A rename to the same name is a **no-op**: `rename` returns success without recording an event, and the use case skips the Airtable write entirely when no event was recorded. That is deliberate — an `AT-C1` "when record updated" automation would otherwise loop.
`sortOrder` and `revitUiGroupTypeId` exist as Airtable columns on `parameter_groups` and are read by the Revit derivation projectors, but they are **not aggregate state**: `ParameterGroup` holds only id and name, the Airtable mapper writes only those two fields, and the update contract accepts only `name`.
## Relationship to Revit grouping
A parameter group is the **source a Revit group projection derives from; it is not itself a Revit artifact**. It has no ForgeTypeId of its own and Revit never sees a BuildPlan group id.
`RevitUiGroupProjector` turns a group into a `ProjectedRevitUiGroup` — a Revit property-palette group carrying a Forge `groupTypeId`. The group's `revitUiGroupTypeId` is accepted only when it matches the Forge group pattern (`autodesk.parameter.group:…` or `autodesk.revit.group:…` with a semantic version suffix); anything else, including a missing group, **falls back to `autodesk.parameter.group:data-1.0.0`** with `usedFallback: true`. The fallback is reported, not hidden: the derivation-drift report raises `revit_ui_group_missing_or_invalid` for every projection that used it, and `missing_group` for a key with no group at all.
**Do not conflate this with `ProjectedSharedParameterFileGroup`.** That is the shared-parameter-file `*GROUP` row, and package generation derives it from the **root of the binding ObjectClass's single-parent lineage** (`SharedParameterFileObjectClassGroupProjector`, ADR-0046) — an entirely different input. The two groupings answer different questions: UI palette placement versus SPF file structure.
One caveat worth stating plainly, because the names invite the mistake: a second projector, `SharedParameterFileGroupProjector`, *does* shape a `ParameterGroup` into the same `ProjectedSharedParameterFileGroup` interface, and the derivation-metadata read path still uses it. That path is a **legacy derivation read**, not SPF package generation. Package `*GROUP` assignment is ObjectClass lineage; only the drift/derivation read projects `parameter_groups` into that shape.
## Domain Events
Exactly one, from `ParameterGroup.events.ts`:
| Event | Emitted by | Payload |
| --- | --- | --- |
| `ParameterGroupChanged` | `create` (`change: 'created'`) and `rename` (`change: 'renamed'`) | `parameterGroupId`, `change`, `name`, `previousName`, `changedAt` |
`previousName` is `null` on `created` and carries the prior label on `renamed`.
`ParameterGroupChanged` **is** consumed inside the module. `CanonicalProjectionMetadataChangedHandler` is registered for it and calls `ResyncDerivedProjectionMetadataUseCase.resyncForParameterGroup`, which walks every projection derived from the group and refreshes its non-overridden metadata. Renaming a group therefore resyncs Revit projection metadata. The handler is idempotent on the event's own identity — group id plus `changedAt`, `change`, `name`, and `previousName` — never on projection state.
A resync that changes shared-parameter-file output on an **approved** projection demotes it to `draft`. See [RevitParameterProjection](/docs/entities/RevitParameterProjection).
## OpenAPI Operations
- [createParameterGroup](/docs/services/api-v1/openapi/operations/createParameterGroup)
- [updateParameterGroup](/docs/services/api-v1/openapi/operations/updateParameterGroup)
- [listParameterGroups](/docs/services/api-v1/openapi/operations/listParameterGroups)
- [assignParameterKeyToGroup](/docs/services/api-v1/openapi/operations/assignParameterKeyToGroup)
`assignParameterKeyToGroup` is a `PUT` on the key, not the group — the reference lives on `ParameterKey` and a null body clears it.
The Airtable steward path is documented in [`MUTATION_ROUTING.md`](https://github.com/buildplan-construction/buildplan-monorepo/blob/develop/docs/api-v1/bim-ontology/MUTATION_ROUTING.md): create is `AT-B1` (`ui_create_parameter_group`), and a grid rename is the `parameter-group-projections` `AT-C1` derivation, which watches `parameter_groups.name` and calls the group resync endpoint.
## ADRs
The platform series lives in [`docs/adr/07_Architectural_Decision_Records.md`](https://github.com/buildplan-construction/buildplan-monorepo/blob/develop/docs/adr/07_Architectural_Decision_Records.md). The per-file series is separate and the numbers collide — see [`docs/adr/README.md`](https://github.com/buildplan-construction/buildplan-monorepo/blob/develop/docs/adr/README.md).
| ADR | Decision |
| --- | --- |
| Platform ADR-0046 | The object-class hierarchy is a single-parent tree — the lineage the SPF `*GROUP` projection walks |
| Platform ADR-0047 | `AT-A*` / `AT-B1` written fields and `AT-C1` watch lists stay disjoint, which is why the no-op rename skips its write |
| Platform ADR-0048 | `domain_id` is the public identifier; never a `rec…` id in a payload |
| [Per-file ADR-0006](https://github.com/buildplan-construction/buildplan-monorepo/blob/develop/docs/adr/ADR-0006-parameter-schema-canonical-keys-and-junctions.md) | Canonical parameter keys and values with per-level junctions |
---
id: ParameterKey
name: Parameter Key
version: 1.0.0
owners:
- buildplan
summary: The canonical identity and meaning of one parameter, independent of where it is used or how it is rendered. Owned by bim-ontology, consumed by product-marketplace. One record per handle, never one per product model.
aggregateRoot: true
identifier: parameterKeyId
properties:
- name: parameterKeyId
type: string
required: true
description: Stable parameter key identity (`domain_id`, never a `rec…` id — ADR-0048).
- name: name
type: string
required: true
description: Human-readable canonical label (for example `Width`). Required and non-empty.
- name: handle
type: string
required: true
description: Normalized slug used for dictionary lookup and stable identity. Required and non-empty; no mutator changes it and the update contract does not accept it.
- name: description
type: string | null
required: false
description: Optional prose meaning. Blank strings normalize to null.
- name: usage
type: ParameterUsageSet | null
required: false
description: Which product contexts the key participates in — `variant_option`, `product_spec`, `bim_parameter`, `mfg_takeoff`, `qto_takeoff`. When present the set must be non-empty.
- name: parameterSpecId
type: string | null
required: false
description: Value semantics reference (`ParameterSpec`). Required to approve; locked once approved or archived.
- name: parameterControlId
type: string | null
required: false
description: Presentation reference (`ParameterControl`). Required to approve; locked once approved or archived.
- name: parameterGroupId
type: string | null
required: false
description: Authoring group reference (`ParameterGroup`). Optional at every lifecycle stage.
- name: parameterValueGroupIds
type: string[]
required: true
description: Reusable value-group bindings applied as templates.
- name: revitParameterIds
type: string[]
required: true
description: Revit parameter projections expressing this key. Written by the projection side, not authored here.
- name: lifecycleStatus
type: draft | active | approved | archived
required: true
description: "`PARAMETER_STATUSES`. Required on rehydrate — a persisted key with no status is a domain error, not a default."
- name: isSystem
type: boolean
required: true
description: Registry-backed system / product-model parameter (not ObjectClass-scoped). Setting it requires `approved` plus `usage:bim_parameter`.
badges:
- content: "Status: Implemented in api-v1"
backgroundColor: yellow
textColor: yellow
- content: Aggregate Root
backgroundColor: blue
textColor: blue
attachments:
- title: TypeDoc reference
url: https://docs.mybuildplan.ai/typedoc/api-v1/
type: reference
icon: CodeIcon
- title: Source
url: https://github.com/buildplan-construction/buildplan-monorepo/blob/develop/apps/api-v1/src/modules/bim-ontology/core/domain/parameter-key/ParameterKey.ts
type: source
icon: FileTextIcon
- title: Lifecycle status
url: https://github.com/buildplan-construction/buildplan-monorepo/blob/develop/apps/api-v1/src/modules/bim-ontology/core/domain/parameter-key/ParameterStatus.ts
type: source
icon: FileTextIcon
- title: Domain events
url: https://github.com/buildplan-construction/buildplan-monorepo/blob/develop/apps/api-v1/src/modules/bim-ontology/core/domain/parameter-key/ParameterKey.events.ts
type: source
icon: FileTextIcon
- title: Usage set
url: https://github.com/buildplan-construction/buildplan-monorepo/blob/develop/apps/api-v1/src/modules/bim-ontology/core/domain/parameter-key/ParameterUsage.ts
type: source
icon: FileTextIcon
---
## Purpose
A parameter key is **what this parameter is**, once. *Width* is one key, not one key per product model, per class, or per vendor. Everything else about a parameter is a separate concept that references the key: `ParameterSpec` for value semantics, `ParameterControl` for presentation, [ParameterGroup](/docs/entities/ParameterGroup) for authoring grouping, [ParameterValue](/docs/entities/ParameterValue) for canonical values and [ParameterValueGroup](/docs/entities/ParameterValueGroup) for reusable sets of them, [ClassParameter](/docs/entities/ClassParameter) for the assignment to an object class, and [RevitParameterProjection](/docs/entities/RevitParameterProjection) for the Revit-facing expression.
Keys are reused through dictionary lookup by name, handle, or `domain_id` — never minted per scrape target.
## Ownership boundary with Product Marketplace
`ParameterKey` is **one concept, owned here**. BIM Ontology defines parameter keys and their relationships to object classes and Revit. Product Marketplace assigns a class to a product and uses the parameters that come with it; its own pages cross-reference this one instead of redefining the key. The junctions that genuinely belong to product-marketplace — `ModelParameter`, `ModelParameterAllowedValue`, `VariantParameter` — stay in that domain and reference this entity.
## Lifecycle
`draft → active → approved → archived`, from `PARAMETER_STATUSES`.
| Transition | Rule |
| --- | --- |
| `draft → active` | Spec and control need not be complete, but if either reference is set it must resolve to the same record, and a resolved spec/control pair must be compatible |
| `draft → approved` | Same stewardship gate as `active → approved`. Activate is not required |
| `active → approved` | Requires a parameter spec and a parameter control. BIM usage is not required |
| `active → archived` | Allowed |
| `approved → archived` | Allowed; idempotent when already archived |
| `archived → draft` | `reinstate()` only. Archived has no forward transition |
`draft → archived` is **not** legal. Both `active` and `approved` count as usable (`isUsableParameterStatus`); only `approved` counts as vetted (`isApprovedParameterStatus`). Activate remains the scraper path into displayable-but-unvetted; a steward who already meets the approve gates may skip it.
Hard delete is deliberately not a lifecycle transition — `markDeleted` records `ParameterKeyDeleted` for audit and is allowed from any status, with the actual removal left to the use case.
### Why `active` exists
This is a business contract, not an extra approval step. `active` means **displayable-but-unvetted**.
When a scraper reads a product page it either maps the scraped attribute onto an existing approved key, or it creates a new key in `active`. The `active` key is live immediately — a scraped `product_spec` reaches the e-commerce page without waiting for a steward — while still being visibly flagged as needing canonical mapping. A steward later promotes it to `approved` or folds it into an existing key. Without `active`, either scraped product data would be invisible until a human reviewed it, or unvetted keys would be indistinguishable from canonical ones.
`ParameterKey` is the only aggregate in the module with this state (ADR-0052). Nothing renders an `ObjectClass` or a `RevitParameterProjection` to a customer ahead of review, so neither needs the tier.
The ordering is load-bearing downstream: **automatic Revit projection is triggered by `ParameterKeyApproved`** — not by creation and not by activation. An `active` key is deliberately not yet in Revit.
## Usages
`variant_option`, `product_spec`, `bim_parameter`, `mfg_takeoff`, `qto_takeoff` (`PARAMETER_USAGES`). A usage set, when present, must be non-empty and may only contain supported values; `addUsages` unions onto the existing set idempotently.
`bim_parameter` membership is the load-bearing one for Revit. It gates automatic projection ensure and archive, and it is required to `markSystem`. It is **not** an approve precondition — a `product_spec`-only key can be canonical. It is not the same thing as Revit document altitude, which is always spelled `revit_*`.
## Invariants
- `name` and `handle` are both required and non-empty.
- A persisted key must carry a `lifecycleStatus`; rehydrating without one raises `PARAMETER_KEY_LIFECYCLE_STATUS_REQUIRED`.
- Rehydrating a key that claims `approved` re-checks the approve requirements and fails with `ParameterKeyLifecycleInvariantViolatedError` if spec or control is missing. An `approved` key that does not satisfy its own gate cannot be loaded.
- Approve requires a parameter spec and a parameter control — the missing ones are named in `ParameterKeyApprovePreconditionError`. `usage:bim_parameter` is not on that list.
- Spec and control references are **locked** while `approved` or `archived` (`ParameterKeyReferencesLockedError`). Rename and regroup remain possible; changing what the value means does not.
- A supplied spec or control must resolve to the record the key references, and the control must be compatible with the spec's data type.
- `isSystem` requires `approved` plus `usage:bim_parameter`, enforced both by `markSystem` and by metadata updates.
- Usage sets may not be empty and may not contain unsupported values.
## Domain Events
Exactly six, from `ParameterKey.events.ts`:
| Event | Emitted by |
| --- | --- |
| `ParameterKeyCreated` | `create` |
| `ParameterKeyUpdated` | `updateCanonicalMetadata`, `addUsages`, `markSystem` — only when something actually changed |
| `ParameterKeyActivated` | `activate` |
| `ParameterKeyApproved` | `approve` |
| `ParameterKeyArchived` | `archive` |
| `ParameterKeyDeleted` | `markDeleted` (audit record; hard delete) |
`ParameterKeyApproved` is what triggers the Revit projection ensure. `ParameterKeyUpdated` also reaches the projection side — for the ensure path and for archive-on-retirement — and `ParameterKeyArchived` archives the projection rather than deleting it, so the shared parameter GUID survives.
## OpenAPI Operations
- [listParameterKeys](/docs/services/api-v1/openapi/operations/listParameterKeys)
- [createParameterKey](/docs/services/api-v1/openapi/operations/createParameterKey)
- [getParameterKey](/docs/services/api-v1/openapi/operations/getParameterKey)
- [updateParameterKey](/docs/services/api-v1/openapi/operations/updateParameterKey)
- [activateParameterKey](/docs/services/api-v1/openapi/operations/activateParameterKey)
- [approveParameterKey](/docs/services/api-v1/openapi/operations/approveParameterKey)
- [archiveParameterKey](/docs/services/api-v1/openapi/operations/archiveParameterKey)
- [deleteParameterKey](/docs/services/api-v1/openapi/operations/deleteParameterKey)
- [assignParameterKeyToGroup](/docs/services/api-v1/openapi/operations/assignParameterKeyToGroup)
## ADRs
The platform series lives in [`docs/adr/07_Architectural_Decision_Records.md`](https://github.com/buildplan-construction/buildplan-monorepo/blob/develop/docs/adr/07_Architectural_Decision_Records.md). The per-file series is separate and the numbers collide — see [`docs/adr/README.md`](https://github.com/buildplan-construction/buildplan-monorepo/blob/develop/docs/adr/README.md).
| ADR | Decision |
| --- | --- |
| Platform ADR-0048 | `domain_id` is the public identifier; never a `rec…` id in a payload |
| Platform ADR-0052 | `active` is displayable-but-unvetted and belongs to ParameterKey alone |
| [Per-file ADR-0006](https://github.com/buildplan-construction/buildplan-monorepo/blob/develop/docs/adr/ADR-0006-parameter-schema-canonical-keys-and-junctions.md) | Canonical parameter keys and values with per-level junctions |
| [Per-file ADR-0009](https://github.com/buildplan-construction/buildplan-monorepo/blob/develop/docs/adr/ADR-0009-parameter-tables-base-placement-and-cross-base-sync.md) | Parameter table base placement and cross-base sync |
---
id: ParameterValue
name: Parameter Value
version: 1.0.0
owners:
- buildplan
summary: One canonical value a ParameterKey can take, optionally carrying measured quantities. Owned by bim-ontology; product-marketplace reads it. No lifecycle.
aggregateRoot: true
identifier: parameterValueId
properties:
- name: parameterValueId
type: string
required: true
description: Stable value identity (`domain_id`, never a `rec…` id — ADR-0048).
- name: parameterKeyId
type: string
required: true
references: ParameterKey
referencesIdentifier: parameterKeyId
relationType: many-to-one
description: The one canonical key this value belongs to. Set on create and on every replace; no mutator moves a value between keys.
- name: name
type: string
required: true
description: Display label. Required and non-empty; trimmed on the way in.
- name: code
type: string | null
required: false
description: Optional normalized code-style identifier. Absent normalizes to null.
- name: quantityValues
type: QuantityValue[]
required: true
description: Measured readings for this value. Empty for a purely nominal value such as a finish name.
badges:
- content: "Status: Implemented in api-v1"
backgroundColor: yellow
textColor: yellow
- content: Aggregate Root
backgroundColor: blue
textColor: blue
attachments:
- title: TypeDoc reference
url: https://docs.mybuildplan.ai/typedoc/api-v1/
type: reference
icon: CodeIcon
- title: Source
url: https://github.com/buildplan-construction/buildplan-monorepo/blob/develop/apps/api-v1/src/modules/bim-ontology/core/domain/parameter-value/ParameterValue.ts
type: source
icon: FileTextIcon
- title: Authoring use cases
url: https://github.com/buildplan-construction/buildplan-monorepo/blob/develop/apps/api-v1/src/modules/bim-ontology/core/application/use-cases/ParameterValueAuthoringUseCases.ts
type: source
icon: FileTextIcon
- title: Quantity role contract
url: https://github.com/buildplan-construction/buildplan-monorepo/blob/develop/apps/api-v1/src/modules/bim-ontology/adapters/inbound/rest/app/parameter-authoring/parameterAuthoring.schemas.ts
type: source
icon: FileTextIcon
- title: Airtable field maps
url: https://github.com/buildplan-construction/buildplan-monorepo/blob/develop/apps/api-v1/src/modules/shared/infrastructure/airtable/bimOntologyAirtableFieldMaps.ts
type: source
icon: FileTextIcon
---
## Purpose
A parameter value is **one canonical value a [ParameterKey](/docs/entities/ParameterKey) can take** — `Brushed Nickel` for *Finish*, `36 in` for *Width*. It is defined once and reused everywhere: [ClassParameter](/docs/entities/ClassParameter) allowed-value sets, `ModelParameter` allowed values, and `VariantParameter` selections all point at the same row rather than restating the value.
When the value is measurable it carries `QuantityValue` parts, each a number plus a unit plus a role. A nominal value carries none.
Airtable tables `parameter_values` and `quantity_values`, both in the BIM Ontology base.
## Ownership boundary with Product Marketplace
`ParameterValue` is **owned here**. The aggregate lives in `bim-ontology/core/domain/parameter-value/`; there is no product-marketplace parameter-value domain directory. Product Marketplace consumes it through an explicit cross-module import — `CatalogReadUseCases.ts` imports `ParameterValue` and `ParameterValueGroup` from `@modules/bim-ontology` and serves them over its internal catalog-read routes. The junctions that reference values (`ModelParameter`, `ModelParameterAllowedValue`, `VariantParameter`) stay in that domain.
## No lifecycle
A parameter value has no lifecycle and emits **no domain events**. It has no `pullDomainEvents`, and nothing in the module subscribes to a value change. Update is a **full replace**: `UpdateParameterValueUseCase` reconstructs the aggregate from the request under the existing id, so the request body carries the whole value including its quantity set. Omitting `quantityValues` clears them.
## Quantity values and roles
`QuantityValue` is a value object of `value`, `unitId`, and `role`. The role set is exactly seven, and the names are **two-word tokens with a space**, not separate `min` / `max` roles:
| Role | Meaning |
| --- | --- |
| `primary` | The single headline reading |
| `primary min` / `primary max` | The primary range endpoints |
| `alternate` | A second reading in another unit system or convention |
| `alternate min` / `alternate max` | The alternate range endpoints |
| `modifier` | A qualifying reading that is neither headline nor alternate |
A quantity value is rejected unless its `value` is finite and its `unitId` is non-empty.
`unitId` points at a **Unit**, Airtable `units`. A unit belongs to neither the value nor the key: quantity values reference it, and `ParameterSpec` constrains which units are legitimate for the key through its own `units` link and `unitSystem`. Unit is a reference catalog with a read-only `GET …/units` surface and no entity page of its own — see [ubiquitous language](/docs/domains/bim-ontology/language).
## Invariants
Enforced by `ParameterValue.rehydrate`, which `create` delegates to, so create and replace are validated identically:
- A value belongs to **exactly one** parameter key.
- `name` is required and non-empty after trimming.
- **At most one `primary` quantity value.** Two primaries is a domain error, not a last-write-wins.
- **Ranges require both endpoints.** `primary min` and `primary max` must both be present or both absent; the same rule applies independently to `alternate min` / `alternate max`. A half-open range is refused.
- Every quantity `value` must be finite and every `unitId` non-empty.
Violations surface as `ParameterValueDomainError`. The application layer adds two reference checks that the aggregate cannot see: the referenced key must exist (`ParameterKeyNotFoundError`) and, on replace, the value itself must exist (`ParameterValueNotFoundError`).
Note what is **not** enforced: nothing checks the quantity unit against the referenced key's `ParameterSpec` unit list, and nothing requires a value to be in an allowed-value set before it is referenced. Both are application-level concerns today.
## Domain Events
None. `ParameterValue` records no events and has no event file.
## OpenAPI Operations
Authoring, in bim-ontology:
- [createParameterValue](/docs/services/api-v1/openapi/operations/createParameterValue)
- [updateParameterValue](/docs/services/api-v1/openapi/operations/updateParameterValue)
`createParameterValue` is a `POST` and requires an `Idempotency-Key`; `updateParameterValue` is a `PUT` and replaces the authored key, name, code, and quantity set.
Read, over the product-marketplace internal catalog-read surface:
- [getParameterValueById](/docs/services/api-v1/openapi/operations/getParameterValueById)
## ADRs
The platform series lives in [`docs/adr/07_Architectural_Decision_Records.md`](https://github.com/buildplan-construction/buildplan-monorepo/blob/develop/docs/adr/07_Architectural_Decision_Records.md). The per-file series is separate and the numbers collide — see [`docs/adr/README.md`](https://github.com/buildplan-construction/buildplan-monorepo/blob/develop/docs/adr/README.md).
| ADR | Decision |
| --- | --- |
| Platform ADR-0048 | `domain_id` is the public identifier; never a `rec…` id in a payload |
| [Per-file ADR-0006](https://github.com/buildplan-construction/buildplan-monorepo/blob/develop/docs/adr/ADR-0006-parameter-schema-canonical-keys-and-junctions.md) | Canonical parameter keys and values with per-level junctions, rather than value arrays on key records |
| [Per-file ADR-0007](https://github.com/buildplan-construction/buildplan-monorepo/blob/develop/docs/adr/ADR-0007-mto-model-level-allowable-values.md) | MTO allowable values and ranges live on the model-level junction and reference canonical values |
| [Per-file ADR-0009](https://github.com/buildplan-construction/buildplan-monorepo/blob/develop/docs/adr/ADR-0009-parameter-tables-base-placement-and-cross-base-sync.md) | Parameter table base placement and cross-base sync |
---
id: ParameterValueGroup
name: Parameter Value Group
version: 1.0.0
owners:
- buildplan
summary: A reusable ordered set of ParameterValues applied as a template. Owned by bim-ontology; product-marketplace reads it. No lifecycle, no events, no authoring endpoint yet.
aggregateRoot: true
identifier: parameterValueGroupId
properties:
- name: parameterValueGroupId
type: string
required: true
description: Stable group identity (`domain_id`, never a `rec…` id — ADR-0048).
- name: name
type: string
required: true
description: Group label. Required and non-empty; trimmed on the way in.
- name: parameterValueIds
type: string[]
required: true
references: ParameterValue
referencesIdentifier: parameterValueId
relationType: many-to-many
description: The group's members, in authored order. Ordering is carried by the Airtable link and by `parameter_value_group_items.displayOrder`.
- name: templates
type: ParameterValueTemplate[]
required: true
description: "`(parameterKeyId, parameterValueId)` pairs binding a member value to the key it templates for. Every pair's value must be a group member."
badges:
- content: "Status: Implemented in api-v1"
backgroundColor: yellow
textColor: yellow
- content: Aggregate Root
backgroundColor: blue
textColor: blue
attachments:
- title: TypeDoc reference
url: https://docs.mybuildplan.ai/typedoc/api-v1/
type: reference
icon: CodeIcon
- title: Source
url: https://github.com/buildplan-construction/buildplan-monorepo/blob/develop/apps/api-v1/src/modules/bim-ontology/core/domain/parameter-value-group/ParameterValueGroup.ts
type: source
icon: FileTextIcon
- title: Airtable read adapter
url: https://github.com/buildplan-construction/buildplan-monorepo/blob/develop/apps/api-v1/src/modules/bim-ontology/adapters/outbound/persistence/airtable/AirtableParameterValueGroupQueryAdapter.ts
type: source
icon: FileTextIcon
- title: Airtable field maps
url: https://github.com/buildplan-construction/buildplan-monorepo/blob/develop/apps/api-v1/src/modules/shared/infrastructure/airtable/bimOntologyAirtableFieldMaps.ts
type: source
icon: FileTextIcon
---
## Purpose
A parameter value group is a **reusable ordered set of [ParameterValues](/docs/entities/ParameterValue) applied as a template**. Rather than restating the same twelve finish options on every product model, a steward authors the set once and applies the group.
The `templates` collection is what makes it a template rather than a plain list: each entry pairs a member value with the [ParameterKey](/docs/entities/ParameterKey) it stands in for, so applying the group answers "which value for which key" instead of just "which values".
`ParameterKey.parameterValueGroupIds` holds the bindings from the key side.
Airtable tables `parameter_value_groups` and `parameter_value_group_items`.
## Ownership boundary with Product Marketplace
Owned here, alongside `ParameterValue`. The aggregate lives in `bim-ontology/core/domain/parameter-value-group/`; there is no product-marketplace equivalent. Product Marketplace's `CatalogReadUseCases.ts` imports `ParameterValueGroup` from `@modules/bim-ontology` and exposes a read route over it.
## No lifecycle
No lifecycle, no mutators, and **no domain events**. `create` and `rehydrate` are the only entry points and both run the same validation; every field is readonly on the constructed instance.
## Invariants
- `name` is required and non-empty after trimming (`ParameterValueGroupDomainError`).
- **Every template's value must belong to its group.** `templates` is checked against the `parameterValueIds` membership set; a template naming a non-member is refused.
Two gaps are worth naming because they are load-bearing if authoring is ever added. Nothing enforces uniqueness of `parameterValueIds`, so a duplicate member is accepted. And the Airtable read adapter always rehydrates with `templates: []` — the `parameter_value_group_items` rows are not mapped into the aggregate today, so the template invariant is vacuously satisfied on every read from Airtable. `parameter_value_group_items` does carry `displayOrder`, `valueUnits`, `description`, and `model` columns that the aggregate has no representation for.
## Domain Events
None.
## OpenAPI Operations
There is **no authoring endpoint**. The only surface is the read, over the product-marketplace internal catalog-read routes:
- [getParameterValueGroupById](/docs/services/api-v1/openapi/operations/getParameterValueGroupById)
Groups are authored directly in Airtable today, and [`MUTATION_ROUTING.md`](https://github.com/buildplan-construction/buildplan-monorepo/blob/develop/docs/api-v1/bim-ontology/MUTATION_ROUTING.md) does not list a routed mutation for either table.
## ADRs
The platform series lives in [`docs/adr/07_Architectural_Decision_Records.md`](https://github.com/buildplan-construction/buildplan-monorepo/blob/develop/docs/adr/07_Architectural_Decision_Records.md). The per-file series is separate and the numbers collide — see [`docs/adr/README.md`](https://github.com/buildplan-construction/buildplan-monorepo/blob/develop/docs/adr/README.md).
| ADR | Decision |
| --- | --- |
| Platform ADR-0048 | `domain_id` is the public identifier; never a `rec…` id in a payload |
| [Per-file ADR-0006](https://github.com/buildplan-construction/buildplan-monorepo/blob/develop/docs/adr/ADR-0006-parameter-schema-canonical-keys-and-junctions.md) | Canonical parameter keys and values with per-level junctions |
| [Per-file ADR-0008](https://github.com/buildplan-construction/buildplan-monorepo/blob/develop/docs/adr/ADR-0008-product-configuration-modes-and-option-source-of-truth.md) | Where a configurable product's option universe comes from |
| [Per-file ADR-0009](https://github.com/buildplan-construction/buildplan-monorepo/blob/develop/docs/adr/ADR-0009-parameter-tables-base-placement-and-cross-base-sync.md) | Parameter table base placement and cross-base sync |
---
id: PriceGroup
name: Price Group
version: 1.0.0
owners:
- buildplan
summary: Pricing aggregate that gates price and cost by parameter values, value groups, and tiered quantities.
aggregateRoot: true
identifier: priceGroupId
properties:
- name: priceGroupId
type: string
required: true
description: Stable price group identity.
- name: name
type: string
required: true
description: Business label for the price rule.
- name: calculationFormula
type: CalculationFormula
required: true
description: Calculation method and parameter keys used by the rule.
- name: unitPrice
type: UnitMoney
required: true
description: Base sell price per unit.
- name: unitCost
type: UnitMoney | null
required: false
description: Base cost per unit.
- name: parameterValueGroupIds
type: string[]
required: true
references: ParameterValueGroup
referencesIdentifier: parameterValueGroupId
relationType: many-to-many
description: Value groups that activate the price rule.
- name: parameterValueIds
type: string[]
required: true
references: ParameterValue
referencesIdentifier: parameterValueId
relationType: many-to-many
description: Individual values that activate the price rule.
badges:
- content: "Status: Implemented in api-v1"
backgroundColor: yellow
textColor: yellow
- content: Aggregate Root
backgroundColor: blue
textColor: blue
attachments:
- title: TypeDoc reference
url: https://docs.mybuildplan.ai/typedoc/api-v1/
type: reference
icon: CodeIcon
- title: Source
url: https://github.com/buildplan-construction/buildplan-monorepo/blob/develop/apps/api-v1/src/modules/product-marketplace/core/domain/price-group/PriceGroup.ts
type: source
icon: FileTextIcon
---
## Purpose
Price groups model how product options, value groups, units, minimum chargeable quantities, and pricing tiers affect a product variant quote.
## Invariants
- A price group must have a name.
- It must gate on at least one parameter value or parameter value group.
- Unit cost cannot exceed unit price.
- Tier minimum quantities must be strictly increasing.
## Related Ports
- `ForReadingCatalogResources`
- `ForManagingProductModels`
## OpenAPI Operations
- [getPriceGroupById](/docs/services/api-v1/openapi/operations/getPriceGroupById)
---
id: ProductDescription
name: Product Description
version: 1.0.0
owners:
- buildplan
summary: Marketing, technical, and SEO copy associated with catalog products.
identifier: productDescriptionId
properties:
- name: productDescriptionId
type: string
required: true
description: Stable description identity.
- name: productVariantId
type: string
required: true
references: ProductVariant
referencesIdentifier: productVariantId
relationType: many-to-one
description: Variant this description belongs to.
- name: body
type: string
required: true
description: Human-readable product copy.
- name: seoMetadata
type: SeoMetadata | null
required: false
description: Optional title, description, and keywords for catalog/search presentation.
badges:
- content: "Status: Implemented in api-v1"
backgroundColor: yellow
textColor: yellow
attachments:
- title: TypeDoc reference
url: https://docs.mybuildplan.ai/typedoc/api-v1/
type: reference
icon: CodeIcon
- title: Source
url: https://github.com/buildplan-construction/buildplan-monorepo/blob/develop/apps/api-v1/src/modules/product-marketplace/core/domain/product-description/ProductDescription.ts
type: source
icon: FileTextIcon
---
## Purpose
Descriptions preserve human-readable product copy extracted from vendor pages or maintained in Airtable. They support catalog display, search, and downstream product-selection workflows.
## Invariants
- Description identity is stable and read through catalog ports.
- Body text is required.
- SEO metadata is descriptive catalog data, not route or UI state.
## Related Ports
- `ForReadingCatalogResources`
- `ForRunningProductScrapeRequests`
## OpenAPI Operations
- (pending) No dedicated round-1 REST operation reads descriptions directly.
---
id: ProductDoc
name: Product Document
version: 1.0.0
owners:
- buildplan
summary: Product document linked to a product model or variant.
identifier: productDocId
properties:
- name: productDocId
type: string
required: true
description: Stable product document identity.
- name: name
type: string
required: true
description: Document display name.
- name: url
type: string
required: true
description: Document URL.
- name: docType
type: spec_sheet | install_guide | warranty | datasheet | brochure | drawing
required: true
description: Document category.
- name: productModelId
type: string | null
required: false
references: ProductModel
referencesIdentifier: productModelId
relationType: many-to-one
description: Product model context when model-level.
- name: productVariantId
type: string | null
required: false
references: ProductVariant
referencesIdentifier: productVariantId
relationType: many-to-one
description: Product variant context when variant-level.
badges:
- content: "Status: Implemented in api-v1"
backgroundColor: yellow
textColor: yellow
attachments:
- title: TypeDoc reference
url: https://docs.mybuildplan.ai/typedoc/api-v1/
type: reference
icon: CodeIcon
- title: Source
url: https://github.com/buildplan-construction/buildplan-monorepo/blob/develop/apps/api-v1/src/modules/product-marketplace/core/domain/product-doc/ProductDoc.ts
type: source
icon: FileTextIcon
---
## Purpose
Product documents include technical sheets, install guides, warranty information, environmental declarations, and other files needed by design, procurement, and construction users.
## Invariants
- A product document must have a non-empty name and URL.
- It belongs to exactly one product model or product variant.
- Document language uses ISO 639-1.
- File storage, download URLs, and vendor fetch behavior belong to adapters.
## Related Ports
- `ForReadingCatalogResources`
- `ForRunningProductScrapeRequests`
## OpenAPI Operations
- [getProductDocById](/docs/services/api-v1/openapi/operations/getProductDocById)
---
id: ProductImage
name: Product Image
version: 1.0.0
owners:
- buildplan
summary: Catalog image metadata and publishing state for a product model or variant.
identifier: productImageId
properties:
- name: productImageId
type: string
required: true
description: Stable product image identity.
- name: url
type: string
required: true
description: Source or published image URL.
- name: type
type: hero | detail | generic | technical_diagram | lifestyle
required: true
description: Image role.
- name: productModelId
type: string | null
required: false
references: ProductModel
referencesIdentifier: productModelId
relationType: many-to-one
description: Product model context when model-level.
- name: productVariantId
type: string | null
required: false
references: ProductVariant
referencesIdentifier: productVariantId
relationType: many-to-one
description: Product variant context when variant-level.
- name: isForEcommerce
type: boolean
required: true
description: Whether the image is intended for commerce/catalog presentation.
- name: isForAiRendering
type: boolean
required: true
description: Whether the image can feed AI rendering workflows.
badges:
- content: "Status: Implemented in api-v1"
backgroundColor: yellow
textColor: yellow
attachments:
- title: TypeDoc reference
url: https://docs.mybuildplan.ai/typedoc/api-v1/
type: reference
icon: CodeIcon
- title: Source
url: https://github.com/buildplan-construction/buildplan-monorepo/blob/develop/apps/api-v1/src/modules/product-marketplace/core/domain/product-image/ProductImage.ts
type: source
icon: FileTextIcon
---
## Purpose
Product images connect scraped or manually curated image records to persistent media URLs and Airtable attachment fields. Image publishing is an application workflow; the domain model describes the image record and attachment set.
## Invariants
- A product image requires a non-empty URL.
- It belongs to exactly one product model or product variant.
- Published URLs and attachment payloads preserve enough metadata for clients and downstream design workflows.
## Related Ports
- `ForPublishingProductImages`
- `ForReadingCatalogResources`
## OpenAPI Operations
- [publishProductImages](/docs/services/api-v1/openapi/operations/publishProductImages)
- [publishProductVariantImages](/docs/services/api-v1/openapi/operations/publishProductVariantImages)
---
id: ProductModel
name: Product Model
version: 1.0.0
owners:
- buildplan
summary: Master catalog aggregate for product model identity, configuration mode, parameter bindings, variant strategy, and refresh capability.
aggregateRoot: true
identifier: productModelId
properties:
- name: productModelId
type: string
required: true
description: Stable product model identity.
- name: name
type: string
required: true
description: Canonical catalog name.
- name: sourceUrl
type: string | null
required: false
description: Vendor source URL used by scrape workflows.
- name: objectCategoryId
type: string | null
required: false
description: Optional product taxonomy node reference.
- name: vendorId
type: string | null
required: false
description: Optional manufacturer or vendor organization projection.
- name: configurationMode
type: simple | predefined_variants | made_to_order | hybrid
required: true
description: How this model exposes options (ADR-0008).
- name: isMadeToOrder
type: boolean
required: true
description: Whether the model supports made-to-order configuration.
- name: modelParameters
type: ModelParameter[]
required: true
references: ModelParameter
relationType: one-to-many
description: Level-2 parameter bindings; MTO allowable values live here.
- name: productVariantIds
type: string[]
required: true
references: ProductVariant
referencesIdentifier: productVariantId
relationType: one-to-many
description: Variants materialized from this model.
badges:
- content: "Status: Implemented in api-v1"
backgroundColor: yellow
textColor: yellow
- content: Aggregate Root
backgroundColor: blue
textColor: blue
attachments:
- title: TypeDoc reference
url: https://docs.mybuildplan.ai/typedoc/api-v1/
type: reference
icon: CodeIcon
- title: Source
url: https://github.com/buildplan-construction/buildplan-monorepo/blob/develop/apps/api-v1/src/modules/product-marketplace/core/domain/product-model/ProductModel.ts
type: source
icon: FileTextIcon
---
## Purpose
Product models own catalog-level identity, naming, vendor/category references, configuration mode, model-level parameter bindings, refresh capabilities, and variant strategy. They coordinate how scraped product data becomes canonical product-marketplace catalog data.
Stock models derive option universes from variant parameters; MTO models store allowable values on `model_parameters` (ADR-0007, ADR-0008).
## Invariants
- A product model must have a non-empty name.
- A model cannot bind the same canonical parameter key twice at the model level.
- `made_to_order` / `hybrid` models use `model_parameters` as the MTO source of truth.
- `made_to_order` models cannot pre-persist cartesian variants without hybrid strategy.
- Refresh and reload workflows must respect the model's allowed refresh field set.
## Related Ports
- `ForCreatingProductsFromUrls`
- `ForManagingProductModels`
- `ForRunningProductScrapeRequests`
- `ForCreatingScrapedParameterGraph`
## OpenAPI Operations
- [createProductModelFromUrl](/docs/services/api-v1/openapi/operations/createProductModelFromUrl)
- [createProductFromUrlAlias](/docs/services/api-v1/openapi/operations/createProductFromUrlAlias)
- [refreshProductModels](/docs/services/api-v1/openapi/operations/refreshProductModels)
- [reloadProductModels](/docs/services/api-v1/openapi/operations/reloadProductModels)
## ADRs
- [ADR-0008](https://github.com/buildplan-construction/buildplan-monorepo/blob/develop/docs/adr/ADR-0008-product-configuration-modes-and-option-source-of-truth.md)
---
id: ProductVariant
name: Product Variant
version: 1.0.0
owners:
- buildplan
summary: Catalog aggregate for a concrete product variant, including SKU data, pricing snapshots, stock, and variant parameters.
aggregateRoot: true
identifier: productVariantId
properties:
- name: productVariantId
type: string
required: true
description: Stable product variant identity.
- name: productModelId
type: string
required: true
references: ProductModel
referencesIdentifier: productModelId
relationType: many-to-one
description: Parent product model.
- name: name
type: string
required: true
description: Variant display name.
- name: sku
type: string | null
required: false
description: Vendor or catalog SKU.
- name: priceForDisplay
type: Money | null
required: false
description: Sale price when available, otherwise retail price.
- name: materializationOrigin
type: CATALOG | ON_DEMAND
required: true
description: Whether this variant is pre-persisted or materialized on demand.
- name: variantParameters
type: VariantParameter[]
required: true
references: VariantParameter
relationType: one-to-many
description: Level-3 parameter selections for this variant (variant_parameters table).
badges:
- content: "Status: Implemented in api-v1"
backgroundColor: yellow
textColor: yellow
- content: Aggregate Root
backgroundColor: blue
textColor: blue
attachments:
- title: TypeDoc reference
url: https://docs.mybuildplan.ai/typedoc/api-v1/
type: reference
icon: CodeIcon
- title: Source
url: https://github.com/buildplan-construction/buildplan-monorepo/blob/develop/apps/api-v1/src/modules/product-marketplace/core/domain/product-variant/ProductVariant.ts
type: source
icon: FileTextIcon
---
## Purpose
Variants carry SKU/model data, pricing snapshots, lead times, stock, materialization origin, and variant parameters. They are the product-marketplace representation used by design and downstream purchasing workflows. Legacy `parameterAssignments` is renamed to `VariantParameter` (PARAM-S3).
## Invariants
- A product variant must have a non-empty name.
- Each variant parameter must contain at least one value for its key.
- `ON_DEMAND` variants require a materialization timestamp and configuration snapshot.
- `ON_DEMAND` variants cannot carry stock levels.
- `CATALOG` variants cannot carry materialization snapshots.
## Related Ports
- `ForManagingProductVariants`
- `ForPublishingProductImages`
- `ForReadingCatalogResources`
- `ForLinkingVariantParameters`
## OpenAPI Operations
- [publishProductVariantImages](/docs/services/api-v1/openapi/operations/publishProductVariantImages)
- [getInventoryById](/docs/services/api-v1/openapi/operations/getInventoryById)
- [publishProductImages](/docs/services/api-v1/openapi/operations/publishProductImages)
## ADRs
- [ADR-0006](https://github.com/buildplan-construction/buildplan-monorepo/blob/develop/docs/adr/ADR-0006-parameter-schema-canonical-keys-and-junctions.md)
- [ADR-0008](https://github.com/buildplan-construction/buildplan-monorepo/blob/develop/docs/adr/ADR-0008-product-configuration-modes-and-option-source-of-truth.md)
---
id: RevitParameterProjection
name: Revit Parameter Projection
version: 1.0.0
owners:
- buildplan
summary: The Revit-facing expression of exactly one ParameterKey — one definition identity plus optional placements. Lifecycle draft → approved → archived. System-created in draft, steward-completed, steward-approved. Archived, never deleted.
aggregateRoot: true
identifier: projectionId
properties:
- name: projectionId
type: string
required: true
description: Stable projection identity (`domain_id`, never a `rec…` id — ADR-0048). The `:key` path parameter on every REST route is this id.
- name: parameterKeyId
type: string
required: true
references: ParameterKey
referencesIdentifier: parameterKeyId
relationType: one-to-one
description: The one canonical key this projection expresses. Required and non-empty (`BO-RP-INV-02`). Derived and refused by the steward PATCH.
- name: definition
type: RevitParameterDefinition
required: true
description: The Revit identity — one of `SharedParameterDefinition`, `FamilyLocalParameterDefinition`, `ProjectLocalParameterReference`, `BuiltInParameterReference`, `LegacyUnknownParameterReference`. A shared definition owns the GUID.
- name: revitName
type: RevitParameterName | null
required: false
description: Stored Revit name. A non-authoritative cache of the derivation — the effective name is recomputed on every read.
- name: nameSource
type: derived | override | null
required: false
description: Provenance of `revitName`. `derived` follows the canonical ParameterKey name; `override` pins it.
- name: familyApplication
type: FamilyParameterApplication | null
required: false
description: Family-document placement, carrying exactly one binding kind. Null means not enabled for families.
- name: projectBinding
type: ProjectParameterBinding | null
required: false
description: Project-document placement, carrying exactly one binding kind. Requires a shared definition (`BO-RP-INV-07`).
- name: bindingKindPreference
type: instance | type | null
required: false
description: Aggregate-level default binding kind. Left null at auto-ensure; required to approve a non-built-in projection.
- name: revitPrimaryUsage
type: family | project | null
required: false
description: Which document kind is primary. Left null at auto-ensure; required to approve.
- name: isSystem
type: boolean
required: true
description: Registry-backed platform parameter. When true, approve additionally requires `revitPrimaryUsage` of `project` (`BO-RP-INV-17`).
- name: specTypeId
type: RevitSpecTypeId | null
required: false
description: Autodesk Forge spec type id resolved from the key's `ParameterSpec`. Derived; refused by the steward PATCH.
- name: storageType
type: RevitStorageType | null
required: false
description: Revit storage type resolved from the same spec. Derived.
- name: visibilityFlags
type: RevitVisibilityFlags | null
required: false
description: "`isVisible`, `isUserModifiable`, `hidesWhenNoValue`. Steward-editable; a change on an approved projection demotes it."
- name: discipline
type: Common | Electrical | Energy | HVAC | Infrastructure | Piping | Structural | null
required: false
description: Revit discipline. Defaults on create to `command.discipline ?? spec?.discipline ?? 'Common'`. Required to approve.
- name: tooltipDescription
type: string | null
required: false
description: Revit tooltip text. Blank strings normalize to null.
- name: status
type: draft | approved | archived
required: true
description: "`REVIT_PARAMETER_PROJECTION_STATUSES`. Rehydrating an unrecognized status is a domain error, not a default."
- name: revitVersionRange
type: RevitVersionRange | null
required: false
description: Optional Revit version applicability window. Not settable through the create or update contract today.
badges:
- content: "Status: Implemented in api-v1"
backgroundColor: yellow
textColor: yellow
- content: Aggregate Root
backgroundColor: blue
textColor: blue
attachments:
- title: TypeDoc reference
url: https://docs.mybuildplan.ai/typedoc/api-v1/
type: reference
icon: CodeIcon
- title: Source
url: https://github.com/buildplan-construction/buildplan-monorepo/blob/develop/apps/api-v1/src/modules/bim-ontology/core/domain/revit/RevitParameterProjection.ts
type: source
icon: FileTextIcon
- title: Lifecycle status
url: https://github.com/buildplan-construction/buildplan-monorepo/blob/develop/apps/api-v1/src/modules/bim-ontology/core/domain/revit/RevitParameterProjectionStatus.ts
type: source
icon: FileTextIcon
- title: Approve gate policy
url: https://github.com/buildplan-construction/buildplan-monorepo/blob/develop/apps/api-v1/src/modules/bim-ontology/core/domain/revit/RevitProjectionApprovePolicy.ts
type: source
icon: FileTextIcon
- title: Definition forms
url: https://github.com/buildplan-construction/buildplan-monorepo/blob/develop/apps/api-v1/src/modules/bim-ontology/core/domain/revit/RevitDefinitionForm.ts
type: source
icon: FileTextIcon
- title: Category scope resolver
url: https://github.com/buildplan-construction/buildplan-monorepo/blob/develop/apps/api-v1/src/modules/bim-ontology/core/domain/revit/RevitCategoryScopeResolver.ts
type: source
icon: FileTextIcon
- title: Authoring and ensure use cases
url: https://github.com/buildplan-construction/buildplan-monorepo/blob/develop/apps/api-v1/src/modules/bim-ontology/core/application/use-cases/RevitProjectionAuthoringUseCases.ts
type: source
icon: FileTextIcon
- title: Approve use case
url: https://github.com/buildplan-construction/buildplan-monorepo/blob/develop/apps/api-v1/src/modules/bim-ontology/core/application/use-cases/ApproveRevitParameterProjectionUseCase.ts
type: source
icon: FileTextIcon
- title: Derivation and resync use cases
url: https://github.com/buildplan-construction/buildplan-monorepo/blob/develop/apps/api-v1/src/modules/bim-ontology/core/application/use-cases/RevitProjectionDerivationUseCases.ts
type: source
icon: FileTextIcon
- title: Demote-on-SPF-change rule
url: https://github.com/buildplan-construction/buildplan-monorepo/blob/develop/apps/api-v1/src/modules/bim-ontology/core/application/use-cases/demoteApprovedIfSpfOutputChanged.ts
type: source
icon: FileTextIcon
---
## Purpose
A Revit parameter projection is the **Revit-facing expression of exactly one [ParameterKey](/docs/entities/ParameterKey)**: one definition identity, plus zero, one, or two placements. The key says what the parameter *means*; the projection says what Revit *sees*.
It is the only place Revit vocabulary is allowed to live. The key knows nothing about GUIDs, Forge spec types, binding kinds, or disciplines.
Airtable table `revit_parameters`.
## Lifecycle
`draft → approved → archived`, from `REVIT_PARAMETER_PROJECTION_STATUSES`.
| Transition | Rule |
| --- | --- |
| create → `draft` | The aggregate's `create` accepts an `initialStatus` of `draft` or `approved`, but `initialStatus` is not exposed on the HTTP contract and both ensure paths pass `draft`. In practice a projection is always born in `draft` |
| `draft → approved` | `approve()`, only after `RevitProjectionApprovePolicy` returns no unmet requirements. Idempotent when already approved |
| `approved → draft` | `demoteToDraft()`. Not a rollback a steward asks for — the system does it when shared-parameter-file output would change |
| any → `archived` | `archive()`. Idempotent when already archived |
| `archived → draft` | `reactivateFromArchive()` only. Archived has no forward transition and cannot be edited or approved directly |
There was no `active` tier and there is no longer one named that: the state now called `approved` **was named `active`**, renamed by platform ADR-0052. Legacy Airtable rows still holding `active` rehydrate as `approved` — `rehydrateRevitParameterProjectionStatus` normalizes the token before validating it. Nothing renders a projection to a customer ahead of review, so the displayable-but-unvetted tier that [ParameterKey](/docs/entities/ParameterKey) needs has no meaning here.
An archived projection is frozen: `updateAuthoredFacts`, `approve`, and `demoteToDraft` all refuse it with `REVIT_PROJECTION_ARCHIVED` (`BO-RP-INV-06`).
## System-created, steward-completed
The projection is **created by the system and finished by a human**, and the split is deliberate.
Auto-ensure creates the projection in `draft` carrying only what the ParameterKey can yield: the resolved spec type and storage type, a derived name, default visibility flags, and a discipline. `bindingKindPreference` and `revitPrimaryUsage` are left **null**. The domain must not require Revit-specific facts at draft creation, because at that moment nobody has decided them — so `validateRuntimeShape` accepts a null binding preference and a null primary usage, and the comment in the source says so explicitly: `BO-RP-INV-17` / `BO-RP-INV-18` are approve-time only, and draft may persist incomplete primary-usage facts so dirty rows can rehydrate.
The steward then PATCHes the Revit-specific facts and approves. The approve gate returns **every** unmet requirement at once so that pass is a single round trip.
### The ensure trigger is `ParameterKeyApproved`
The event that mints a projection is **`ParameterKeyApproved`** — not `ParameterKeyCreated` and not `ParameterKeyActivated`. Those two events exist on the key and have **no handler registered anywhere in the module**. An `active` key is deliberately not yet in Revit.
A secondary, guarded ensure runs on `ParameterKeyUpdated` through `ParameterKeyUpdatedProjectionEnsureHandler`, whose only purpose is repairing a projection that should exist and does not. It reloads the key and returns without acting unless the key is already `approved` **and** carries `usage:bim_parameter`.
Inside `ensureRevitProjectionForParameterKey` the ordering is: assert the key links to at most one projection, load the key, reuse an archived projection if one exists, short-circuit on catalog-only keys, then require approval.
- **Catalog-only keys short-circuit.** A key whose usages are `product_spec` or `variant_option` and which lacks `bim_parameter` returns `null`. No projection, no error.
- **`PARAMETER_KEY_NOT_APPROVED`** is returned both on create and on archived-reuse when the key is not `approved`. The check runs *before* the reuse write, so an unapproved key cannot resurrect an archived projection.
- Registry commons resolved by `CommonParameterProjectionPolicy` prefer a `built_in` definition; every other steward key defaults to `shared`.
### Spec-type errors are hard, not silent
A key that cannot yield a Revit spec fails loudly rather than producing a half-formed draft:
| Code | Raised when |
| --- | --- |
| `PARAMETER_SPEC_ID_MISSING` | A `bim_parameter` key has no `ParameterSpec` at all |
| `SPEC_TYPE_ID_MISSING` | No Revit spec type is mapped to that spec, or the mapped spec has no `forgeTypeId` |
| `STORAGE_TYPE_MISSING` | The mapped spec has a `forgeTypeId` but no storage type |
`validateAuthoredStructure` raises the same two latter codes at the aggregate boundary, so neither can be bypassed by a direct create.
## Approve gate
`RevitProjectionApprovePolicy.unmetRequirements` is the hard gate and the single source for the `missingForApproval` field on the detail read — the list is never copied.
| Requirement | Note |
| --- | --- |
| `parameterKey.approved` | The linked key must be `approved` |
| `usage:bim_parameter` | The key must carry that usage |
| `discipline`, `revitSpecType` | Must resolve |
| `enabledApplication` | At least one of family application or project binding, unless the definition is `built_in` (`BO-RP-INV-11`) |
| `bindingKindPreference` | Required unless `built_in` |
| `revitPrimaryUsage` | Always required |
| `BO-RP-INV-17` | A system projection requires `revitPrimaryUsage` of `project` |
| `BO-RP-INV-18` | `revitPrimaryUsage` must name an actually enabled usage |
| `sharedGuid` | A `shared` definition requires its GUID |
| `objectClass.approved` | **Every ObjectClass the projection applies to must itself be `approved`** |
| `status.not_archived` | An archived projection must be reused into draft first |
The ObjectClass gate is the load-bearing one. Applicability is resolved through class assignment — the effective parameter set, direct plus inherited minus suppressed — so it is the classes that actually carry the key, not an authored list. Each unmet class is reported by key and id.
Because [ObjectClass](/docs/entities/ObjectClass) approval already requires all four classifications (ADR-0050), **an approved projection necessarily has a complete category set**: there is no way to reach an approved projection through an approved class that is missing its Revit category. Uniformat and Masterformat sit in that same gate deliberately even though Revit does not read them today — Revit will consume them later, and admitting a class without them would mean re-gating then.
## Revit category scope is derived only
Category scope is the set of Revit categories a project binding covers. It is **derived and only derived**, resolved from the effective class assignments and the classes' own Revit category links.
The steward override is **gone**. `explicitCategoryRefs` and the `'explicit'` scope provenance were removed; the name survives in exactly one place, the `DERIVED_STEWARD_PATCH_FIELDS` reject-list, where sending it earns an HTTP 400 naming the field. Scope kinds are now `derived | unscoped` — `derived` when a project binding resolves a scope, `unscoped` when there is no project binding or no resolved scope.
Categories are controlled through class assignments and the classes' own categories, never by overriding the derived output. `resolveRevitParameterCategoryScope` computes the current scope with no writes at all; persisting the derived set is a separate use case.
The word **scope** in this module means this and nothing else — not parameter applicability, not graph traversal extent, and not a class's own category classification, which is a *link*. See [ubiquitous language](/docs/domains/bim-ontology/language).
## Archive, never delete
Retirement archives. It does not delete. The one exception is cascade: when the ParameterKey itself is **hard-deleted**, `DeleteParameterKeyUseCase` cascade-deletes the linked projection and reports the deleted `cascadedRevitProjectionId`.
`ArchiveRevitProjectionOnParameterKeyRetirementHandler` is registered for both `ParameterKeyUpdated` and `ParameterKeyArchived`. On retirement the link to the key is **preserved** and the shared-parameter GUID is **never recycled** — `updateAuthoredFacts` refuses to remove or replace a shared GUID with `REVIT_GUID_IMMUTABLE` (`BO-RP-INV-01`), and archiving keeps the row.
That immutability is the whole reason for the archive-then-reuse shape. Re-adding `bim_parameter` to a key **searches for an existing linked projection including archived ones** and reactivates it into `draft`; it only creates when none exists. A steward who retires and later reinstates a parameter gets the same GUID back, so any Revit model already carrying it still resolves.
## Canonical-name resync demotes
The effective Revit name is normally **derived** from the canonical ParameterKey name, so renaming the key drifts the projection. `resyncDerivedName` refreshes the stored cache, and `demoteApprovedIfSpfOutputChanged` decides what that costs.
A resync of an **approved** projection **demotes it to `draft`** when the shared-parameter-file output would change — the comparison is on `revitName` and `nameSource`, alongside binding kind preference, primary usage, the two application binding kinds, and visibility flags. Approved means vetted for Revit emission, so any actual change to what would be emitted sends the row back through the gate.
**Override names no-op and do not demote.** `resyncDerivedName` returns the same instance untouched when `nameSource === 'override'` or when the derived name already equals the stored one, so nothing is saved, no event fires, and the status is unchanged. The same is true of a no-op PATCH.
Drift that is not repaired silently is reported instead. `listRevitProjectionDerivationDrift` categorizes `projection_missing`, `name_stale`, `name_override`, `missing_group`, and `revit_ui_group_missing_or_invalid`. A canonical rename is also preflighted for Revit-name collisions before any Airtable write, failing with `REVIT_PARAMETER_NAME_COLLISION` rather than minting a duplicate.
## Definition versus Reference
`RevitDefinitionForm` is `shared`, `family_local`, `project_local`, `built_in`, `legacy_unknown`.
The distinction the words carry is binding: a **Definition** is an identity BuildPlan authors and owns; a **Reference** is an external identity it reads but never authors. `SharedParameterDefinition` and `FamilyLocalParameterDefinition` are authored. `ProjectLocalParameterReference`, `BuiltInParameterReference`, and `LegacyUnknownParameterReference` are not BuildPlan's to mint.
| Form | Creatable | Notes |
| --- | --- | --- |
| `shared` | Yes | Owns the GUID. The only form that may carry a project binding (`BO-RP-INV-07`) |
| `family_local` | Yes | Family application only; a project binding is refused (`BO-RP-INV-10`) |
| `built_in` | Yes | Requires `builtInParameterName`; may carry **neither** application (`BO-RP-INV-05`) |
| `project_local` | No | Read-only imported reference (`BO-RP-INV-16`, `READ_ONLY_DEFINITION_FORM`) |
| `legacy_unknown` | No | Read-only; provenance was never recorded |
Changing form after the fact is refused with `DEFINITION_FORM_CHANGE_REQUIRES_ARCHIVE`, because the Revit parameter identity would change. Archive and create a new one.
## Invariants
- Exactly one ParameterKey per projection, non-empty (`BO-RP-INV-02`), and at most one projection per key — `MULTIPLE_PARAMETER_PROJECTIONS` when the key links to more than one, `REVIT_PROJECTION_STEWARDSHIP_REQUIRED` when it links to an unresolved row.
- A shared GUID is **immutable** (`BO-RP-INV-01`) and globally unique (`SHARED_GUID_CONFLICT`); `builtInParameterName` is likewise unique (`BUILT_IN_PARAMETER_NAME_CONFLICT`).
- An authored projection requires a name, a name source, and visibility flags, plus a Forge spec type and a storage type.
- Beyond `draft`, a non-built-in projection requires at least one enabled application (`NO_ENABLED_REVIT_APPLICATION`).
- Rehydrating an unrecognized status fails; there is no default.
- Every instance is frozen after construction — mutators return a new aggregate rather than mutating in place.
## Domain Events
Exactly seven, all on one payload shape (`projectionId`, `parameterKeyId`, `occurredAt`):
| Event | Emitted by |
| --- | --- |
| `RevitParameterProjectionCreated` | `create`, when a clock is supplied |
| `RevitParameterProjectionUpdated` | `updateAuthoredFacts` |
| `RevitParameterProjectionApproved` | `approve` |
| `RevitParameterProjectionDemoted` | `demoteToDraft` |
| `RevitParameterProjectionReused` | `reactivateFromArchive` |
| `RevitParameterProjectionArchived` | `archive` |
| `RevitParameterProjectionResynced` | `resyncDerivedName`, only when the name actually changed |
**None of the seven has a subscriber inside the module.** They are dispatched and recorded; no handler is registered for any of them. Every consequence documented above — ensure, archive-on-retirement, resync-on-canonical-change — is driven by a `ParameterKey*` or `ParameterGroupChanged` event flowing *into* the projection side, never by a projection event flowing out. Treat these seven as emitted-but-unsubscribed until a consumer is registered.
## OpenAPI Operations
The user-facing REST surface is deliberately narrow: a PATCH of Revit-specific fields, an approve action, and reads.
- [listRevitParameterProjections](/docs/services/api-v1/openapi/operations/listRevitParameterProjections)
- [getRevitParameterProjection](/docs/services/api-v1/openapi/operations/getRevitParameterProjection)
- [updateRevitParameterProjection](/docs/services/api-v1/openapi/operations/updateRevitParameterProjection)
- [approveRevitParameterProjection](/docs/services/api-v1/openapi/operations/approveRevitParameterProjection)
- [lookupRevitParameterProjections](/docs/services/api-v1/openapi/operations/lookupRevitParameterProjections)
- [resolveRevitParameterCategoryScope](/docs/services/api-v1/openapi/operations/resolveRevitParameterCategoryScope)
- [listRevitProjectionDerivationDrift](/docs/services/api-v1/openapi/operations/listRevitProjectionDerivationDrift)
The PATCH accepts binding kind preference, Revit primary usage, the two applications and their binding kinds, and the visibility flags. Everything derived or key-owned is refused with an HTTP 400 naming the field.
Create, admin archive, and the three resync endpoints are **maintenance and drift-repair paths**, marked `x-internal: true` and tagged `system-maintenance`:
- [createRevitParameterProjection](/docs/services/api-v1/openapi/operations/createRevitParameterProjection)
- [archiveRevitParameterProjection](/docs/services/api-v1/openapi/operations/archiveRevitParameterProjection)
- [resyncDerivedRevitProjectionMetadata](/docs/services/api-v1/openapi/operations/resyncDerivedRevitProjectionMetadata)
- [resyncRevitProjectionsForParameterKey](/docs/services/api-v1/openapi/operations/resyncRevitProjectionsForParameterKey)
- [resyncRevitProjectionsForParameterGroup](/docs/services/api-v1/openapi/operations/resyncRevitProjectionsForParameterGroup)
**No Airtable script touches a Revit row.** Every `AT-C1` derivation in this base watches `object_classes`, `class_parameters`, `class_parameter_allowed_values`, `parameter_keys`, or `parameter_groups` — never `revit_parameters` — and the routing matrix marks the `revit_parameters` grid fields `lock` or `monitor`. Stewardship reaches the projection only through the API. See [`MUTATION_ROUTING.md`](https://github.com/buildplan-construction/buildplan-monorepo/blob/develop/docs/api-v1/bim-ontology/MUTATION_ROUTING.md).
## ADRs
The platform series lives in [`docs/adr/07_Architectural_Decision_Records.md`](https://github.com/buildplan-construction/buildplan-monorepo/blob/develop/docs/adr/07_Architectural_Decision_Records.md). The per-file series is separate and the numbers collide — see [`docs/adr/README.md`](https://github.com/buildplan-construction/buildplan-monorepo/blob/develop/docs/adr/README.md).
| ADR | Decision |
| --- | --- |
| Platform ADR-0046 | The object-class hierarchy is a single-parent tree, so applicability follows one path to the root |
| Platform ADR-0048 | `domain_id` is the public identifier; never a `rec…` id in a payload |
| Platform ADR-0050 | The four-kind classification approve gate on ObjectClass, which is why an approved projection has a complete category set |
| Platform ADR-0052 | `active` belongs to ParameterKey alone; this aggregate's `active` was renamed `approved` |
| [Per-file ADR-0006](https://github.com/buildplan-construction/buildplan-monorepo/blob/develop/docs/adr/ADR-0006-parameter-schema-canonical-keys-and-junctions.md) | Canonical parameter keys and values with per-level junctions |
---
id: VariantParameter
name: Variant Parameter
version: 1.0.0
owners:
- buildplan
summary: L3 junction linking a ProductVariant to selected ParameterValue records for one ParameterKey (renamed from parameter_assignments).
identifier: variantParameterId
properties:
- name: parameterKeyId
type: string
required: true
references: ParameterKey
referencesIdentifier: parameterKeyId
relationType: many-to-one
description: Canonical key this variant selection applies to.
- name: parameterValueIds
type: string[]
required: true
references: ParameterValue
referencesIdentifier: parameterValueId
relationType: many-to-many
description: One or more values selected for this key on the variant.
badges:
- content: "Status: Implemented in api-v1"
backgroundColor: yellow
textColor: yellow
attachments:
- title: TypeDoc reference
url: https://docs.mybuildplan.ai/typedoc/api-v1/
type: reference
icon: CodeIcon
- title: Source
url: https://github.com/buildplan-construction/buildplan-monorepo/blob/develop/apps/api-v1/src/modules/product-marketplace/core/domain/product-variant/ProductVariant.ts
type: source
icon: FileTextIcon
---
## Purpose
Variant parameters are the level-3 junction (O4 rename of legacy `parameter_assignments` / `variant_parameters` Airtable table). They record which canonical values a concrete product variant carries for each key. Stock option universes derive from the set of variant parameters across all variants of a model (ADR-0008).
This junction genuinely belongs to product-marketplace. The keys and values it points at do not — [ParameterKey](/docs/entities/ParameterKey) and [ParameterValue](/docs/entities/ParameterValue) are owned by [BIM Ontology](/docs/domains/bim-ontology).
`VariantParameter` is a value object **colocated with the `ProductVariant` aggregate** in `product-variant/ProductVariant.ts` rather than in a directory of its own, which is why the source link points there. In the domain model it carries no identity of its own — only the key reference and the selected values; the identity belongs to the Airtable junction row.
The scraped parameter graph adapter is the **sole writer** of PM `variant_parameters` records during bulk scrape persistence.
## Invariants
- Must contain at least one parameter value per key binding.
- References canonical keys and values by synced PM mirror record IDs after taxonomy sync.
- Legacy `ParameterAssignment` type alias is deprecated; use `VariantParameter`.
## Related Ports
- `ForCreatingScrapedParameterGraph`
- `ForLinkingVariantParameters`
- `ForStoringPendingParameterLinks`
- `ForManagingProductVariants`
## OpenAPI Operations
No dedicated REST endpoint; written during scrape persistence. See [Parameter Graph Persistence](/docs/flows/parameter-graph-persistence).
## ADRs
- [ADR-0006](https://github.com/buildplan-construction/buildplan-monorepo/blob/develop/docs/adr/ADR-0006-parameter-schema-canonical-keys-and-junctions.md)
- [ADR-0008](https://github.com/buildplan-construction/buildplan-monorepo/blob/develop/docs/adr/ADR-0008-product-configuration-modes-and-option-source-of-truth.md)
---
id: VendorProductCollection
name: Vendor Product Collection
version: 1.0.0
owners:
- buildplan
summary: Catalog aggregate for manufacturer or vendor product lines, distinct from design-management user collections.
aggregateRoot: true
identifier: vendorProductCollectionId
properties:
- name: vendorProductCollectionId
type: string
required: true
description: Stable vendor product collection identity.
- name: name
type: string
required: true
description: Manufacturer or vendor product line name.
- name: slug
type: string | null
required: false
description: Optional URL or display slug.
- name: vendorId
type: string | null
required: false
description: Owning vendor or manufacturer projection.
- name: members
type: VendorProductCollectionMember[]
required: true
references: ProductVariant
referencesIdentifier: productVariantId
relationType: many-to-many
description: Ordered product variants in the vendor collection.
badges:
- content: "Status: Implemented in api-v1"
backgroundColor: yellow
textColor: yellow
- content: Aggregate Root
backgroundColor: blue
textColor: blue
attachments:
- title: TypeDoc reference
url: https://docs.mybuildplan.ai/typedoc/api-v1/
type: reference
icon: CodeIcon
- title: Source
url: https://github.com/buildplan-construction/buildplan-monorepo/blob/develop/apps/api-v1/src/modules/product-marketplace/core/domain/vendor-product-collection/VendorProductCollection.ts
type: source
icon: FileTextIcon
---
## Purpose
Vendor product collections group manufacturer or vendor product lines inside the product-marketplace catalog. They are distinct from user-curated design-management collections and are owned by product-marketplace catalog behavior.
## Invariants
- A vendor product collection must have a non-empty name.
- The same product variant cannot appear twice in the same collection.
- Members are stored in display order.
## Related Ports
- `ForManagingProductModels`
- `ForReadingCatalogResources`
## OpenAPI Operations
- (pending) No dedicated round-1 REST operation documents vendor product collection mutations directly.
---
id: create-product-from-url
name: Create Product From URL
version: 1.0.0
owners:
- buildplan
summary: Queue and execute product creation from a vendor source URL in api-v1.
badges:
- content: "Status: Implemented in api-v1"
backgroundColor: yellow
textColor: yellow
steps:
- id: submit_request
title: Submit source URL
summary: Retool or script caller sends a create-from-URL request.
actor:
name: Operator (Retool)
next_step:
id: api_main_accepts
label: Validate request
- id: api_main_accepts
title: api-v1 accepts workflow
service:
id: api-v1
version: 1.0.0
next_step:
id: product_from_url_requested
label: Emit accepted event
- id: product_from_url_requested
title: ProductFromUrlRequested
message:
id: ProductFromUrlRequested
version: 1.0.0
next_step:
id: route_scrape
label: Route scraper
- id: route_scrape
title: Route through shared scraping
service:
id: shared-scraping
version: 1.0.0
next_step:
id: fetch_page
label: Fetch page
- id: fetch_page
title: BrightData fetches source page
service:
id: BrightData
version: 1.0.0
next_step:
id: extract_data
label: Extract product data
- id: extract_data
title: AgentQL extracts product data
service:
id: AgentQL
version: 1.0.0
next_step:
id: scrape_completed
label: Return scrape result
- id: scrape_completed
title: ScrapeRunCompleted
message:
id: ScrapeRunCompleted
version: 1.0.0
next_step:
id: queue_workflow
label: Persist catalog
- id: queue_workflow
title: CreateProductModelFromUrlUseCase
service:
id: api-v1
version: 1.0.0
next_step:
id: persist_parameter_graph
label: Persist parameter graph
- id: persist_parameter_graph
title: Parameter graph persistence
summary: Upsert canonical keys/values and write variant_parameters via AirtableScrapedParameterGraphAdapter.
service:
id: api-v1
version: 1.0.0
next_step:
id: resolve_org
label: Resolve manufacturer
- id: resolve_org
title: Party Directory resolves manufacturer
service:
id: party-directory
version: 1.0.0
next_step:
id: org_created
label: Organization identity
- id: org_created
title: OrganizationCreated
message:
id: OrganizationCreated
version: 1.0.0
next_step:
id: product_model_created
label: Product model materialized
- id: product_model_created
title: ProductModelCreated
message:
id: ProductModelCreated
version: 1.0.0
---
## Overview
This flow documents the active api-v1 create-from-URL path for Product Marketplace.
## OpenAPI
- `createProductModelFromUrl`
- `createProductModelFromUrlNested`
- `createProductFromUrlAlias`
## Event Path
- `ProductFromUrlRequested`
- `ScrapeRunCompleted`
- `OrganizationCreated`
- `ProductModelCreated`
## Code References
- `apps/api-v1/src/modules/product-marketplace/core/application/use-cases/ProductCreationUseCases.ts`
- `apps/api-v1/src/modules/product-marketplace/core/application/use-cases/ProductFromUrlWorkflowUseCase.ts`
- `apps/api-v1/src/modules/product-marketplace/adapters/outbound/scraping/ProductFromUrlScraperAdapter.ts`
## Parameter persistence
See [Parameter Graph Persistence](/docs/flows/parameter-graph-persistence) for canonical key/value upsert and variant_parameters linking during bulk creation.
## Notes
The request schemas are intentionally permissive today because this is an active api-v1 workflow used by script and Retool callers.
---
id: object-class-demotion-cascade
name: Object Class Demotion Cascade
version: 1.1.0
owners:
- buildplan
summary: An ObjectClass that loses a required classification is demoted approved → draft. That records ObjectClassDemoted, and covering approved Revit projections are demoted to draft for re-review.
badges:
- content: "Status: Implemented in api-v1"
backgroundColor: yellow
textColor: yellow
steps:
- id: lose_classification
title: A class loses a required classification
summary: "Either a steward clears a `*_direct` value on `object_classes`, or a re-parent changes the nearest make-unique ancestor and re-materializes effective classification down the moved subtree. The four required kinds are Uniformat, Masterformat, ObjectClass code, and Revit category."
actor:
name: Steward (Airtable / Retool)
next_steps:
- id: reconcile_endpoint
label: Cleared *_direct — AT-C1 reconcile
- id: parent_graph_changed
label: Re-parent — graph event
- id: reconcile_endpoint
title: syncObjectClassClassificationInheritance
summary: "AT-C1 `request_reconcile.js` POSTs `/object-classes/{key}/sync-classification-inheritance` with `recomputeWholeTree: false`. Always seeded, so demote always runs on this path."
service:
id: api-v1
version: 1.0.0
next_step:
id: demote_use_case
label: Refill, then demote
- id: parent_graph_changed
title: ObjectClassParentGraphChanged
message:
id: ObjectClassParentGraphChanged
version: 1.0.0
next_step:
id: classification_handler
label: Third of four registered handlers
- id: classification_handler
title: ObjectClassParentGraphChangedClassificationHandler
summary: "Loops per affected id — not batched. Per id it calls `syncClassificationInheritance`, skipping not-found seeds, then runs the demote use case once for each of the four required kinds."
service:
id: api-v1
version: 1.0.0
next_step:
id: demote_use_case
label: Demote per required kind
- id: demote_use_case
title: DemoteObjectClassesThatLostClassificationUseCase
summary: "BFS chooses which approved ids lost the kind. Each matching ObjectClass aggregate is loaded from the graph snapshot, `demoteToDraft(now)` records ObjectClassDemoted, statuses persist, then events drain and dispatch."
service:
id: api-v1
version: 1.0.0
next_step:
id: class_demoted_event
label: ObjectClassDemoted
- id: class_demoted_event
title: ObjectClassDemoted
message:
id: ObjectClassDemoted
version: 1.0.0
next_step:
id: projection_handler
label: Thin handler commands covering projections
- id: projection_handler
title: DemoteRevitProjectionOnObjectClassDemotionHandler
summary: "Idempotent on handler name + objectClassId + occurredAt. Translates the payload into a command and calls DemoteRevitProjectionsCoveringObjectClassUseCase. Does not mutate projections itself."
service:
id: api-v1
version: 1.0.0
next_step:
id: projection_demote
label: Approved covering projections → draft
- id: projection_demote
title: Covering approved projections demote to draft
summary: "Applicability is RevitCategoryScopeResolver.listApplicableObjectClasses. Only approved projections demote (emits RevitParameterProjectionDemoted). Draft and archived are skipped. Archive remains the ParameterKey retirement path."
service:
id: api-v1
version: 1.0.0
next_step:
id: approve_blocked
label: Re-review required
- id: approve_blocked
title: RevitProjectionApprovePolicy reports objectClass.approved until the class is re-approved
summary: "GET still surfaces objectClass.approved on missingForApproval if a handler miss leaves an approved projection covering a draft class. The dispatcher swallows handler errors, so that read remains the second signal."
service:
id: api-v1
version: 1.0.0
---
## Overview
[ObjectClass](/docs/entities/ObjectClass) approval is a promise: the class is complete enough to publish. Under ADR-0050 that means all four classifications resolve — Uniformat, Masterformat, ObjectClass code, and Revit category — **and** every ancestor is already `approved`.
If that promise is broken, the class returns to `draft`. That records [ObjectClassDemoted](/docs/events/ObjectClassDemoted). A thin handler then demotes covering **approved** Revit projections to `draft` so they cannot keep shipping into Revit output until the class is fixed and both are re-approved. Demote is reversible re-review; archive is for ParameterKey retirement.
## OpenAPI
| Operation | Route | Role in this flow |
| --- | --- | --- |
| [syncObjectClassClassificationInheritance](/docs/services/api-v1/openapi/operations/syncObjectClassClassificationInheritance) | `POST /object-classes/{key}/sync-classification-inheritance` | Seeded refill plus demote. Declares `ObjectClassDemoted` |
| [syncAllObjectClasses](/docs/services/api-v1/openapi/operations/syncAllObjectClasses) | `POST /object-classes/sync-all` | Whole-tree maintenance. Calls the same facade with `recomputeWholeTree: true` and **no** key, so demote is skipped |
| [moveObjectClasses](/docs/services/api-v1/openapi/operations/moveObjectClasses) | `POST /object-classes/actions/move` | Re-parent. Dispatches `ObjectClassParentGraphChanged`; `ObjectClassDemoted` is downstream |
| [approveObjectClass](/docs/services/api-v1/openapi/operations/approveObjectClass) | `POST /object-classes/{key}/actions/approve` | The forward transition a demoted class must re-earn |
| [approveRevitParameterProjection](/docs/services/api-v1/openapi/operations/approveRevitParameterProjection) | `POST /revit/parameter-projections/{key}/actions/approve` | 409 while any applicable class is not approved |
| [getRevitParameterProjection](/docs/services/api-v1/openapi/operations/getRevitParameterProjection) | `GET /revit/parameter-projections/{key}` | Surfaces `objectClass.approved` on `missingForApproval` even if a projection is still approved |
## Event path
`ObjectClassParentGraphChanged` carries de-duplicated `affectedObjectClassIds` plus `changedAt`. Four handlers are registered against it; the third fills classification then demotes. See [Object Class Graph Mutations](/docs/flows/object-class-graph-mutations) for the full fan-out.
`ObjectClassParentGraphChangedClassificationHandler` does, per affected id:
1. `syncClassificationInheritance({ categoryId, recomputeWholeTree: false })`. A seed whose row is gone is **skipped**, matched on `/not found/i`. Any other failure throws.
2. For each of the four `REQUIRED_OBJECT_CLASS_APPROVE_KINDS`, `demoteWhenClassificationLost({ seedKey: categoryId, kind })`.
The loop is per id and per kind, and it is not batched.
`ObjectClassWorkflowUseCases.syncClassificationInheritance` reaches the same demote loop directly, gated on a seed key. `syncAllObjectClasses` is seedless and **skips demote**. AT-C1 always supplies `domain_id`, so the steward-facing path always demotes.
## What demotion records
`DemoteObjectClassesThatLostClassificationUseCase` still uses snapshot BFS to **choose** ids. It then loads each `ObjectClass` from `snapshot.categories` (already rehydrated aggregates — no `rehydrate` in the use case) and calls `demoteToDraft(now, { kind })`. Persist via `setLifecycleStatuses`, then `pullDomainEvents()` only if the write succeeded, then `eventDispatcher.dispatch`. Several demotions drain several `ObjectClassDemoted` events and dispatch together.
`demoteToDraft` is a no-op (no event) when already `draft` and `InvalidObjectClassStatusTransitionError` from `archived`.
## Cascade into Revit
`DemoteRevitProjectionOnObjectClassDemotionHandler` translates the payload into `DemoteRevitProjectionsCoveringObjectClassUseCase`. That use case lists projections, resolves applicable object classes with `RevitCategoryScopeResolver.listApplicableObjectClasses`, and calls `RevitParameterProjection.demoteToDraft(now)` on **approved** rows that cover the demoted class.
Hex §7: the dispatcher **swallows** handler errors, so this reaction is not the only way to notice a stuck approved projection. GET already surfaces `objectClass.approved` on `missingForApproval`.
SPF steward PATCH demote (`demoteApprovedIfSpfOutputChanged`) is a separate path and is unchanged. ObjectClass **archive** cascade is out of scope.
## Airtable entry points
- **AT-C1** — `request_reconcile.js` with `DERIVATION = 'classification-inheritance'`
- **AT-A1** — `object_class_actions.js` on `ui_object_class_actions`
- **AT-A2** — `object_class_approve.js` and `object_class_archive.js`
## Code references
- `apps/api-v1/src/modules/bim-ontology/core/domain/object-class/ObjectClass.ts` — `demoteToDraft` records `ObjectClassDemoted`
- `apps/api-v1/src/modules/bim-ontology/core/application/use-cases/ObjectClassLifecycleUseCases.ts` — drain after persist
- `apps/api-v1/src/modules/bim-ontology/core/application/use-cases/DemoteRevitProjectionsCoveringObjectClassUseCase.ts`
- `apps/api-v1/src/modules/bim-ontology/adapters/inbound/events/DemoteRevitProjectionOnObjectClassDemotionHandler.ts`
- `apps/api-v1/src/modules/bim-ontology/composition/bim-ontology.composition.ts`
## ADRs
| ADR | Bearing on this flow |
| --- | --- |
| Platform ADR-0046 | Single-parent tree, so ancestry is one path |
| Platform ADR-0047 | Seeded recompute on the event path |
| Platform ADR-0049 | Handler failures stamped onto `object_classes` rows |
| Platform ADR-0050 | Four-kind approve gate plus approved-ancestor chain |
| Platform ADR-0052 | `approved` naming across ObjectClass and the projection |
| Platform ADR-0056 | Class demotion records `ObjectClassDemoted` and covering approved projections return to draft |
---
id: object-class-graph-mutations
name: Object Class Graph Mutations
version: 1.0.0
owners:
- buildplan
summary: Restructure the single-parent ObjectClass tree in api-v1 and fan out to the four handlers that recompute everything derived from tree position.
badges:
- content: "Status: Implemented in api-v1"
backgroundColor: yellow
textColor: yellow
steps:
- id: steward_action
title: Steward requests a graph change
summary: An Airtable AT-A1 action row (Add Child, Move, Copy, Merge, Delete) or a direct API caller submits the mutation. Scripts call the API; they never write domain state themselves (ADR-0047).
actor:
name: Steward (Airtable / Retool)
next_step:
id: graph_endpoint
label: POST an object-class endpoint
- id: graph_endpoint
title: ObjectClassesController
summary: "Validates the body and resolves the caller. Identifiers are domain_id or handle, never a rec… id (ADR-0048). create and add-child additionally require an Idempotency-Key header."
service:
id: api-v1
version: 1.0.0
next_step:
id: workflow_facade
label: Route to the workflow facade
- id: workflow_facade
title: ObjectClassWorkflowUseCases
summary: "createRoot and addChild build their write-set inline; delete, move, merge, and copy go through runGraphMutation, which loads a snapshot and delegates to the domain service."
service:
id: api-v1
version: 1.0.0
next_step:
id: graph_policy
label: Compute the write-set
- id: graph_policy
title: ObjectClassGraphService
summary: Pure domain logic. Returns a GraphMutationResult carrying parent edges, created nodes, deleted ids, classification make-unique writes, and affectedIds — or typed errors, in which case nothing is persisted.
next_step:
id: persist_graph
label: Persist the write-set
- id: persist_graph
title: AirtableObjectClassGraphMutationAdapter
summary: Writes parent links, created rows, and deletions to the object_classes table in the bim-ontology Airtable base.
service:
id: Airtable
version: 1.0.0
next_step:
id: clone_direct_params
label: Clone direct assignments (merge and copy only)
- id: clone_direct_params
title: persistDirectClassParametersForGraphMutation
summary: Merge and copy carry authored ClassParameter rows onto the surviving or cloned nodes. A failure here best-effort deletes the nodes this mutation created before rethrowing; parent-link edits are not reversed.
service:
id: api-v1
version: 1.0.0
next_step:
id: parent_graph_changed
label: Dispatch the domain event
- id: parent_graph_changed
title: ObjectClassParentGraphChanged
message:
id: ObjectClassParentGraphChanged
version: 1.0.0
next_steps:
- id: revit_category_handler
label: Reconcile Revit categories
- id: hierarchy_handler
label: Recompute hierarchy fields
- id: classification_handler
label: Refill classification inheritance
- id: rendered_templates_handler
label: Re-materialize inherited parameters
- id: revit_category_handler
title: ObjectClassParentGraphChangedRevitCategoryHandler
summary: ReconcileObjectClassRevitCategoriesUseCase with apply and includeDescendants, then PersistDerivedRevitCategorySetsUseCase to refresh derived projection category sets.
service:
id: api-v1
version: 1.0.0
- id: hierarchy_handler
title: ObjectClassParentGraphChangedHierarchyHandler
summary: One batched syncHierarchy call for every affected id. Recomputes depth, path, is_leaf, and root_object_class over the union of the seeded subtrees.
service:
id: api-v1
version: 1.0.0
- id: classification_handler
title: ObjectClassParentGraphChangedClassificationHandler
summary: Per affected id, refills Uniformat, Masterformat, ObjectClass code, and Revit category from the nearest make-unique ancestor. Not-found seeds are skipped rather than failing the handler.
service:
id: api-v1
version: 1.0.0
next_step:
id: demote_lost_classification
label: Demote classes that lost a required kind
- id: demote_lost_classification
title: DemoteObjectClassesThatLostClassificationUseCase
summary: Runs once per affected id per required kind. An approved class whose required classification no longer resolves anywhere on its ancestry is demoted to draft.
service:
id: api-v1
version: 1.0.0
- id: rendered_templates_handler
title: RenderedParamTemplatesSyncHandler
summary: SyncObjectClassRenderedParamTemplatesUseCase with recomputeSubtree per surviving seed, re-materializing inherited ClassParameter rows. Fails only when every seed fails.
service:
id: api-v1
version: 1.0.0
---
## Overview
This flow covers restructuring the single-parent [ObjectClass](/docs/entities/ObjectClass) tree **and** the derived-state recomputation that follows, because they are one causal chain rather than two stories. A graph write on its own leaves hierarchy fields, classification inheritance, Revit categories, and rendered parameter templates stale; the mutation is only complete once [ObjectClassParentGraphChanged](/docs/events/ObjectClassParentGraphChanged) has fanned out to its four handlers.
It replaces the retired `object-category-graph-mutations` flow. That flow described `ObjectCategoryWorkflowUseCases`, `CategoryGraphService`, `AirtableObjectCategoryGraphAdapter`, and an `ObjectCategoryHierarchyChanged` event — none of which exist as concrete implementations in `apps/api-v1/src` today. The graph itself moved to [BIM Ontology](/docs/domains/bim-ontology) as `ObjectClass`.
## Operations that dispatch the event
Only `createRoot`, `addChild`, and `runGraphMutation` reach `dispatchParentGraphChanged`, and `runGraphMutation` backs delete, move, merge, and copy. So exactly six operations restructure the tree. Routes below are relative to the `/api/v1/bim-ontology/object-classes` mount.
| Operation | Route | Notes |
| --- | --- | --- |
| [createObjectClass](/docs/services/api-v1/openapi/operations/createObjectClass) | `POST /object-classes` | Creates a `draft` tree root. Requires `Idempotency-Key`. Rejects any parent key with a message pointing at add-child. Returns 201 with a `Location` header |
| [addObjectClassChild](/docs/services/api-v1/openapi/operations/addObjectClassChild) | `POST /object-classes/actions/add-child` | Creates a `draft` child. Requires `Idempotency-Key` |
| [deleteObjectClasses](/docs/services/api-v1/openapi/operations/deleteObjectClasses) | `POST /object-classes/actions/delete` | The only bulk action with a cap — `sourceObjectClassIds` is `max(500)` |
| [moveObjectClasses](/docs/services/api-v1/openapi/operations/moveObjectClasses) | `POST /object-classes/actions/move` | Re-parents subtrees. Unbounded id list |
| [mergeObjectClasses](/docs/services/api-v1/openapi/operations/mergeObjectClasses) | `POST /object-classes/actions/merge` | `keepData` of `source` or `target` is required; omitting it is a 400. Unbounded id list |
| [copyObjectClasses](/docs/services/api-v1/openapi/operations/copyObjectClasses) | `POST /object-classes/actions/copy` | Clones subtrees under a target. Unbounded id list |
**Partial success is a real outcome.** When the graph write persists but a handler throws, the workflow returns a `warning` result and the controller answers **207**, not 200 — the tree changed, but some derived state is stale. This applies to all six operations.
## Operations that do NOT dispatch the event
This distinction matters, because these look like they would touch the graph and do not:
| Operation | Why no event |
| --- | --- |
| [approveObjectClass](/docs/services/api-v1/openapi/operations/approveObjectClass), [archiveObjectClass](/docs/services/api-v1/openapi/operations/archiveObjectClass) | Lifecycle transitions, not parent edges. `ObjectClassLifecycleUseCases.ts` contains no dispatch at all — the lifecycle use cases persist directly |
| [setObjectClassRevitCategory](/docs/services/api-v1/openapi/operations/setObjectClassRevitCategory) | Writes an authored classification link on one class. Requires `Idempotency-Key`, but changes no parent edge |
| [listObjectClasses](/docs/services/api-v1/openapi/operations/listObjectClasses) | Read-only. `ETag` / `If-None-Match` on `ontologyVersion` |
| [syncAllObjectClasses](/docs/services/api-v1/openapi/operations/syncAllObjectClasses), [syncObjectClassHierarchy](/docs/services/api-v1/openapi/operations/syncObjectClassHierarchy), [syncObjectClassRenderedParamTemplates](/docs/services/api-v1/openapi/operations/syncObjectClassRenderedParamTemplates), [syncObjectClassClassificationInheritance](/docs/services/api-v1/openapi/operations/syncObjectClassClassificationInheritance) | **Recompute** endpoints. They call the same ports and use cases the handlers call, invoked directly rather than through the event, so publishing an event would be circular. Whole-tree recompute stays here rather than on the event path (ADR-0047) |
Two nuances in the sync endpoints:
- `syncObjectClassClassificationInheritance` does slightly **more** than the classification handler: after the refill and demote pass it also runs `PersistDerivedRevitCategorySetsUseCase`. Demote only runs when a seed key is supplied, so a seedless whole-tree recompute skips it.
- `syncAllObjectClasses` chains hierarchy (whole tree), rendered templates, and classification (whole tree) — it does **not** run the Revit reconcile use case that the graph event's first handler runs.
## Event Path
- `ObjectClassParentGraphChanged` — payload is `affectedObjectClassIds` (de-duplicated `domain_id` seeds) plus `changedAt`
Four handlers are registered against it in `bim-ontology.composition.ts`, in this order:
| Handler | Work | Scoping |
| --- | --- | --- |
| `ObjectClassParentGraphChangedRevitCategoryHandler` | `ReconcileObjectClassRevitCategoriesUseCase`, then `PersistDerivedRevitCategorySetsUseCase` | One call with all ids, `includeDescendants: true` |
| `ObjectClassParentGraphChangedHierarchyHandler` | `syncHierarchy` | Batched — all affected ids in a single call. The adapter reads the table once and unions each seed's descendant ids into one write scope, so untouched rows are never rewritten |
| `ObjectClassParentGraphChangedClassificationHandler` | `syncClassificationInheritance`, then `DemoteObjectClassesThatLostClassificationUseCase` per required kind | Loops per id |
| `RenderedParamTemplatesSyncHandler` | `SyncObjectClassRenderedParamTemplatesUseCase` with `recomputeSubtree` | Loops per id; a move with `includeChildren: false` yields disjoint subtrees, so it must not stop after the first success |
The demote use case is an **optional** constructor parameter on the classification handler, but composition always passes it, so demotion is unconditional in production.
Every handler is idempotent on the sorted id list plus `changedAt`, skips seeds whose row is gone (deleted seeds stay in the payload on purpose), and records failures through `ForRecordingBimOntologyHandlerFailure` before rethrowing.
## Why a graph mutation must trigger classification recomputation
Under ADR-0050 the classification fill is **materialized**, not resolved at read time:
- Stewards author `*_direct` values only. Make-unique is never authored — it is **inferred** on the edited node when its direct value differs from the value at the nearest make-unique ancestor. Clearing the value unsets make-unique and the node inherits again.
- The fill walks **up** to the nearest make-unique ancestor and then **down** until it reaches the next one.
- The effective field mirrors the materialized `*_direct`, so no read-time ancestry walk happens and `*_inherited` is always cleared.
- The four kinds — Uniformat, Masterformat, ObjectClass code, Revit category — fill independently. More than one direct link for a kind is recorded as a `MULTIPLE_DIRECT_CLASSIFICATIONS` violation rather than resolved by guessing.
Re-parenting a node changes which ancestor is its nearest make-unique source, which changes the materialized value for that node and every descendant down to the next make-unique boundary. That is precisely why the graph event has to drive classification and Revit-category recomputation: nothing would notice otherwise, and an approved class could silently keep an effective classification it no longer inherits.
## Graph invariants and limits
- **Single-parent tree, no primary-parent device** (ADR-0046). A missing parent field is a true root. A parent link pointing at a record with no `domain_id` is integrity damage, not a root — both `loadSnapshot` and `listParentGraph` throw `ObjectClassParentIdentityMissingError` and fail closed.
- **Public identifiers only** (ADR-0048). `sourceObjectClassIds`, `targetObjectClassId`, and `parentObjectClassIds` are `domain_id` values; a handle is also accepted, and Airtable `rec…` ids are accepted during the transition but logged as deprecated.
- **Bulk caps are asymmetric.** Delete caps `sourceObjectClassIds` at 500; move, merge, and copy are unbounded today.
- **Live shape as of 2026-08-16:** 776 `object_classes` rows, 60 true roots, 0 dangling parent links.
## Airtable entry point
- **AT-A1** — `object_class_actions.js` drives the `ui_object_class_actions` table. It handles Add Child, Move, Copy, Merge, and Delete, plus bulk Approve and Archive (which are lifecycle calls and dispatch no graph event). Setting a Revit category is no longer an AT-A1 action; stewards edit `revit_categories_direct` on the class instead.
- **AT-A2** — `object_class_approve.js` and `object_class_archive.js` sit on the `object_classes` record detail, one automation per action, for single-class lifecycle changes.
Both patterns POST to api-v1 and never write domain state directly (ADR-0047).
## Code References
- `apps/api-v1/src/modules/bim-ontology/core/application/use-cases/ObjectClassWorkflowUseCases.ts`
- `apps/api-v1/src/modules/bim-ontology/core/domain/object-class/ObjectClassGraphService.ts`
- `apps/api-v1/src/modules/bim-ontology/core/domain/object-class/ClassificationInheritance.ts`
- `apps/api-v1/src/modules/bim-ontology/core/domain/object-class/ObjectClass.events.ts`
- `apps/api-v1/src/modules/bim-ontology/core/application/use-cases/ObjectClassLifecycleUseCases.ts`
- `apps/api-v1/src/modules/bim-ontology/adapters/inbound/rest/app/object-classes/objectClasses.routes.ts`
- `apps/api-v1/src/modules/bim-ontology/adapters/inbound/rest/app/object-classes/objectClasses.controller.ts`
- `apps/api-v1/src/modules/bim-ontology/adapters/inbound/rest/app/object-classes/objectClasses.schemas.ts`
- `apps/api-v1/src/modules/bim-ontology/adapters/inbound/events/ObjectClassParentGraphChangedRevitCategoryHandler.ts`
- `apps/api-v1/src/modules/bim-ontology/adapters/inbound/events/ObjectClassParentGraphChangedHierarchyHandler.ts`
- `apps/api-v1/src/modules/bim-ontology/adapters/inbound/events/ObjectClassParentGraphChangedClassificationHandler.ts`
- `apps/api-v1/src/modules/bim-ontology/adapters/inbound/events/RenderedParamTemplatesSyncHandler.ts`
- `apps/api-v1/src/modules/bim-ontology/adapters/outbound/persistence/airtable/AirtableObjectClassGraphMutationAdapter.ts`
- `apps/api-v1/src/modules/bim-ontology/adapters/outbound/persistence/airtable/AirtableObjectClassGraphReadAdapter.ts`
- `apps/api-v1/src/modules/bim-ontology/adapters/outbound/persistence/airtable/AirtableObjectClassHierarchyAdapter.ts`
- `apps/api-v1/src/modules/bim-ontology/composition/bim-ontology.composition.ts` — the four `ObjectClassParentGraphChanged` registrations
- `apps/airtable-front-end/current/bim-ontology/object_class_actions.js`
## ADRs
| ADR | Bearing on this flow |
| --- | --- |
| Platform ADR-0046 | The hierarchy is a single-parent tree; no primary-parent device |
| Platform ADR-0047 | Handlers recompute seeded subtrees; whole-tree recompute stays on explicit sync endpoints, and Airtable scripts call the API rather than writing domain state |
| Platform ADR-0048 | `domain_id` at the public boundary, never a `rec…` id |
| Platform ADR-0050 | Inherit-by-default classification fill with inferred make-unique, and the lifecycle that demotion feeds |
---
id: parameter-graph-persistence
name: Parameter Graph Persistence
version: 1.0.0
owners:
- buildplan
summary: Persist scraped parameter assignments through the canonical dictionary, taxonomy upsert, and variant_parameters junction writer.
badges:
- content: "Status: Implemented in api-v1"
backgroundColor: yellow
textColor: yellow
steps:
- id: scrape_assignments
title: Scrape emits parameter assignments
summary: Deterministic or AgentQL scraper returns normalized key/value assignments for a product model or variant.
service:
id: shared-scraping
version: 1.0.0
next_step:
id: load_dictionary
label: Load canonical dictionary
- id: load_dictionary
title: CanonicalParameterDictionaryIndex
summary: Load synced taxonomy keys/values from product-taxonomy into an in-memory dictionary index.
service:
id: api-v1
version: 1.0.0
next_step:
id: resolve_mapping
label: Resolve scraper mapping
- id: resolve_mapping
title: Scraper parameter mapping (stub)
summary: Optional vendor-specific mapping resolver; falls through to dictionary lookup when no mapping table row exists.
service:
id: api-v1
version: 1.0.0
next_step:
id: upsert_taxonomy
label: Upsert taxonomy graph
- id: upsert_taxonomy
title: Upsert keys and values in product-taxonomy
summary: Reuse existing canonical records by name/handle/domain_id; mint stable domain_id for genuinely new keys; merge scopes on existing keys.
service:
id: api-v1
version: 1.0.0
next_step:
id: sync_barrier
label: Wait for PM mirror sync
- id: sync_barrier
title: Taxonomy to PM sync barrier
summary: Operator runs Sync now or waits for interval; PM mirror must expose domain_id before dependent junction writes.
actor:
name: Operator (Airtable sync)
next_step:
id: write_variant_parameters
label: Write variant_parameters
- id: write_variant_parameters
title: AirtableScrapedParameterGraphAdapter
summary: Sole writer of variant_parameters during bulk scrape; batch-loads PM mirror by domain_id; does not write model_parameters for stock.
service:
id: api-v1
version: 1.0.0
next_step:
id: pending_or_done
label: Link or defer
- id: pending_or_done
title: Link or queue pending
summary: When PM mirror records exist, ensure variant_parameters links via ForLinkingVariantParameters; otherwise queue PendingParameterLink entries.
service:
id: api-v1
version: 1.0.0
next_step:
id: reconcile
label: Reconcile pending links
- id: reconcile
title: ReconcilePendingParameterLinksUseCase
summary: CLI or poller retries pending links after sync; merges value sets on existing junction rows.
service:
id: api-v1
version: 1.0.0
---
## Overview
This flow documents how api-v1 scrape persistence aligns with the canonical parameter schema (PARAM-S5/S6). It runs inside bulk product creation and refresh/reload scrape paths — not as a standalone HTTP endpoint.
Parent workflows:
- [Create Product From URL](/docs/flows/create-product-from-url) — parameter graph runs during the "Persist catalog" step
- [Refresh and Reload Product Scrape](/docs/flows/refresh-reload-product-scrape) — parameter graph runs during re-materialization
## Entities
- [ParameterKey](/docs/entities/ParameterKey) — owned by bim-ontology; scraping either maps onto an approved key or creates one in `active`
- [ParameterValue](/docs/entities/ParameterValue) — also owned by bim-ontology; product-marketplace reads it through the internal catalog-read routes
- [VariantParameter](/docs/entities/VariantParameter) — owned by product-marketplace
## Ports
- `ForCreatingScrapedParameterGraph` — orchestrates taxonomy upsert + junction writes
- `ForReadingCanonicalParameterDictionary` — loads dictionary snapshot
- `ForStoringPendingParameterLinks` — ephemeral pending-link store
- `ForLinkingVariantParameters` — PM mirror lookups and variant_parameters ensure-link
## Code References
- `apps/api-v1/src/modules/product-marketplace/adapters/outbound/persistence/airtable/AirtableScrapedParameterGraphAdapter.ts`
- `apps/api-v1/src/modules/product-marketplace/adapters/outbound/persistence/airtable/CanonicalParameterDictionaryIndex.ts`
- `apps/api-v1/src/modules/product-marketplace/adapters/outbound/persistence/airtable/AirtableVariantParameterLinkingAdapter.ts`
- `apps/api-v1/src/modules/product-marketplace/adapters/outbound/scraping/persistence/airtable/bulkProductCreation.service.ts`
- `apps/api-v1/src/modules/product-marketplace/core/application/use-cases/ReconcilePendingParameterLinksUseCase.ts`
- `apps/api-v1/src/modules/product-marketplace/composition/scrapedParameterGraph.composition.ts`
## Notes
- **Single junction writer:** bulk product creation must not duplicate variant_parameters writes; the graph adapter owns junction creation.
- **Pending-link store:** local file at `apps/api-v1/.cache/pending-parameter-links.json` (ephemeral; durable backing deferred).
- **Stable domain_id:** TS mint uses slugified handle; Python Phase-B backfill used legacy key-type inputs — reuse is via dictionary name/handle lookup.
- **Deferred:** scraper mapping Airtable tables, live dry-run scrape validation, MTO `model_parameters` writes (PARAM-S7).
## ADRs
- [ADR-0006](https://github.com/buildplan-construction/buildplan-monorepo/blob/develop/docs/adr/ADR-0006-parameter-schema-canonical-keys-and-junctions.md)
- [ADR-0009](https://github.com/buildplan-construction/buildplan-monorepo/blob/develop/docs/adr/ADR-0009-parameter-tables-base-placement-and-cross-base-sync.md)
---
id: parameter-key-to-revit-projection
name: Parameter Key to Revit Projection
version: 1.0.0
owners:
- buildplan
summary: Author a canonical ParameterKey, approve it, and let ParameterKeyApproved mint the draft Revit projection a steward then completes and approves.
badges:
- content: "Status: Implemented in api-v1"
backgroundColor: yellow
textColor: yellow
steps:
- id: author_key
title: Steward authors a ParameterKey
summary: A row on the Airtable `ui_create_parameter_key` table (AT-B1 `create_parameter_key.js`) or a direct API caller submits the canonical concept. The script POSTs; it never writes domain state (ADR-0047).
actor:
name: Steward (Airtable / Retool)
next_step:
id: create_key
label: POST /parameter-keys
- id: create_key
title: CreateParameterKeyUseCase
summary: "Creates the key in `draft`. Handle is slugified from `handle ?? name`; a duplicate handle is refused. Requires an Idempotency-Key header. Raises ParameterKeyCreated — which has no handler."
service:
id: api-v1
version: 1.0.0
next_step:
id: activate_key
label: Activate for displayable-but-unvetted use
- id: activate_key
title: ActivateParameterKeyUseCase
summary: "`draft → active`. Optional for a steward who already meets the approve gates. Spec and control completeness are not required here — `active` exists so a scraped attribute can be shown on the storefront while still flagged for steward mapping. No projection is created."
service:
id: api-v1
version: 1.0.0
next_step:
id: approve_key
label: Approve when vetted
- id: approve_key
title: ApproveParameterKeyUseCase
summary: "`draft|active → approved`, the stewardship gate. `collectApproveMissingRequirements` must return empty — a parameter spec and a parameter control — and the resolved spec and control must match the key and be compatible. `usage:bim_parameter` is not required here; it only decides whether the following ensure mints a projection."
service:
id: api-v1
version: 1.0.0
next_step:
id: parameter_key_approved
label: Dispatch the domain event
- id: parameter_key_approved
title: ParameterKeyApproved
message:
id: ParameterKeyApproved
version: 1.0.0
next_step:
id: ensure_handler
label: The one registered ensure trigger
- id: ensure_handler
title: ParameterKeyApprovedProjectionHandler
summary: Idempotent on `parameterKeyId + approvedAt`. Records failures through `recordHandlerFailure` and rethrows, so the key row carries the reason.
service:
id: api-v1
version: 1.0.0
next_step:
id: ensure_use_case
label: Ensure a projection exists
- id: key_updated
title: ParameterKeyUpdated
message:
id: ParameterKeyUpdated
version: 1.0.0
next_step:
id: repair_handler
label: Guarded repair path
- id: repair_handler
title: ParameterKeyUpdatedProjectionEnsureHandler
summary: Reloads the key and returns without acting unless it is already `approved` and carries `usage:bim_parameter`. Its only job is repairing a projection that should exist and does not.
service:
id: api-v1
version: 1.0.0
next_step:
id: ensure_use_case
label: Same ensure use case
- id: ensure_use_case
title: EnsureRevitProjectionForParameterKeyUseCase
summary: "Asserts at most one projection link, loads the key, reuses an archived projection if one exists, short-circuits catalog-only keys to `ok(null)`, then requires approval. Registry commons resolve to `built_in`; every other key to `shared`."
service:
id: api-v1
version: 1.0.0
next_step:
id: projection_created
label: Create the draft projection
- id: projection_created
title: RevitParameterProjectionCreated
message:
id: RevitParameterProjectionCreated
version: 1.0.0
next_step:
id: steward_patch
label: Steward completes the Revit facts
- id: steward_patch
title: UpdateRevitParameterProjectionUseCase
summary: "PATCH /revit/parameter-projections/{key} accepts exactly five steward-writable facts. Every one of them feeds shared-parameter-file output, so any effective PATCH on an approved projection demotes it back to `draft`."
service:
id: api-v1
version: 1.0.0
next_step:
id: approve_projection
label: Request approval
- id: approve_projection
title: ApproveRevitParameterProjectionUseCase
summary: "Runs RevitProjectionApprovePolicy and returns every unmet requirement at once as a 409. The load-bearing one is `objectClass.approved` — every ObjectClass the projection applies to, resolved through class assignment, must itself be `approved`."
service:
id: api-v1
version: 1.0.0
next_step:
id: projection_approved
label: Dispatch the domain event
- id: projection_approved
title: RevitParameterProjectionApproved
message:
id: RevitParameterProjectionApproved
version: 1.0.0
next_step:
id: package_output
label: Eligible for Revit emission
- id: package_output
title: Package, shared parameter file, common project parameters
summary: "`resolveRevitParameterPackage` hard-fails a projection that is not `approved`; `generateRevitSharedParameterFile` runs the same resolution; `listCommonProjectParameters` filters to `approved` before anything else. Approved is the entry ticket to Revit output."
service:
id: api-v1
version: 1.0.0
---
## Overview
This is the parameter half of BIM Ontology, end to end: a steward defines a canonical concept, vets it, and the system responds by minting the Revit-facing row that only a human can finish. Two aggregates and two lifecycles are involved — [ParameterKey](/docs/entities/ParameterKey) (`draft → active → approved → archived`) and [RevitParameterProjection](/docs/entities/RevitParameterProjection) (`draft → approved → archived`) — and the join between them is exactly one event.
The split of labour is the point. **The system creates; the human completes.** Auto-ensure can only know what the key knows, so it writes the key-derivable facts and leaves the Revit-specific decisions null. Nothing about GUIDs, binding kinds, or primary usage is knowable at the moment a key is approved, so the domain does not pretend otherwise — `validateRuntimeShape` accepts a `draft` projection with a null binding preference and a null primary usage, and the approve gate is where completeness is demanded.
### The `active` tier and why it exists
`ParameterStatus` transitions are strictly linear: `draft → active`, `active → approved | archived`, `approved → archived`. A `draft` key cannot jump to `approved`, and it cannot be archived without being activated first.
`active` means **displayable but unvetted**. It exists for the scraping path: a scraped product attribute either maps onto an approved key or creates a new key in `active`, so the value is visible on the storefront while still flagged as needing steward mapping. An `active` key is deliberately **not** in Revit — see [ubiquitous language](/docs/domains/bim-ontology/language).
## OpenAPI
Routes are relative to the `/api/v1/bim-ontology` mount.
| Operation | Route | Role in this flow |
| --- | --- | --- |
| [createParameterKey](/docs/services/api-v1/openapi/operations/createParameterKey) | `POST /parameter-keys` | Creates the key in `draft`. Requires `Idempotency-Key` |
| [activateParameterKey](/docs/services/api-v1/openapi/operations/activateParameterKey) | `POST /parameter-keys/{id}/actions/activate` | `draft → active` |
| [approveParameterKey](/docs/services/api-v1/openapi/operations/approveParameterKey) | `POST /parameter-keys/{id}/actions/approve` | `active → approved`. **The trigger for everything downstream** |
| [updateParameterKey](/docs/services/api-v1/openapi/operations/updateParameterKey) | `POST /parameter-keys/{id}/actions/update` | Raises `ParameterKeyUpdated`, which drives the guarded repair ensure |
| [getRevitParameterProjection](/docs/services/api-v1/openapi/operations/getRevitParameterProjection) | `GET /revit/parameter-projections/{key}` | Detail read, including `missingForApproval` from the same policy the approve gate uses |
| [updateRevitParameterProjection](/docs/services/api-v1/openapi/operations/updateRevitParameterProjection) | `PATCH /revit/parameter-projections/{key}` | The steward completion step |
| [approveRevitParameterProjection](/docs/services/api-v1/openapi/operations/approveRevitParameterProjection) | `POST /revit/parameter-projections/{key}/actions/approve` | `draft → approved`, gated |
| [resolveRevitParameterPackage](/docs/services/api-v1/openapi/operations/resolveRevitParameterPackage) | `POST /revit/parameter-packages/resolve` | Manifest — approved projections only |
| [generateRevitSharedParameterFile](/docs/services/api-v1/openapi/operations/generateRevitSharedParameterFile) | `POST /revit/parameter-packages/shared-parameter-file` | SPF text from the same resolution |
| [listCommonProjectParameters](/docs/services/api-v1/openapi/operations/listCommonProjectParameters) | `GET /revit/common-project-parameters` | Installable system commons — approved projections only |
`:key` on every projection route is the projection `domain_id` (ADR-0048), not an ObjectClass handle.
### The approve gate on the key
`collectApproveMissingRequirements` returns all unmet items at once, and they are exactly two:
| Requirement | Missing-reference token |
| --- | --- |
| A `ParameterSpec` reference | `parameterSpecId` |
| A `ParameterControl` reference | `parameterControlId` |
A non-empty list is a `ParameterKeyApprovePreconditionError` (`PARAMETER_KEY_APPROVE_PRECONDITION_FAILED`). Two further checks follow: the resolved spec and control must be the ones the key references (`ParameterKeyResolutionMismatchError`) and they must be compatible with each other (`IncompatibleParameterSpecControlError`). The same list is also enforced at rehydration — a stored row claiming `approved` without them throws `ParameterKeyLifecycleInvariantViolatedError`.
`usage:bim_parameter` is **not** an approve token. It remains the Revit gate: `EnsureRevitProjectionForParameterKeyUseCase` skips catalog-only keys, `RevitProjectionApprovePolicy` requires it on the linked key, and dropping it archives the projection.
### The steward PATCH accepts five fields and nothing else
`updateRevitProjectionBodySchema` is `.strict()` and admits only `bindingKindPreference`, `revitPrimaryUsage`, `familyApplication`, `projectBinding`, and `visibilityFlags`. Anything derived or key-owned is refused with an HTTP 400 naming the field, from `DERIVED_STEWARD_PATCH_FIELDS` — which includes `discipline`, `revitSpecTypeId`, `revitName`, `revitNameOverride`, `sharedGuid`, `status`, and the removed `explicitCategoryRefs`.
All five writable fields are in `changesSharedParameterFileOutput`'s comparison set. The consequence is stronger than "a PATCH that changes SPF output demotes": **every PATCH that actually changes anything demotes an approved projection to `draft`**, because there is no steward-writable field that does not affect Revit emission. A no-op PATCH changes nothing, saves nothing, and does not demote.
## Event Path
`ParameterKeyApproved` is the **only** event that mints a projection.
| Event | Registered handlers | Guard |
| --- | --- | --- |
| `ParameterKeyApproved` | `ParameterKeyApprovedProjectionHandler` | None beyond idempotency — ensure itself decides |
| `ParameterKeyUpdated` | `ParameterKeyUpdatedProjectionEnsureHandler` | Key is `approved` **and** carries `usage:bim_parameter` |
| `ParameterKeyUpdated` | `ArchiveRevitProjectionOnParameterKeyRetirementHandler` | Key is **not** `approved` **or** has lost `usage:bim_parameter` |
| `ParameterKeyUpdated` | `CanonicalProjectionMetadataChangedHandler` | None — resyncs the derived Revit name |
| `ParameterKeyArchived` | `ArchiveRevitProjectionOnParameterKeyRetirementHandler` | Always archives |
| `ParameterKeyCreated`, `ParameterKeyActivated`, `ParameterKeyDeleted` | **none** | — |
The two `ParameterKeyUpdated` ensure/archive guards are exact complements, so any one update either repairs a projection or retires one, never both.
`ParameterKeyCreated` and `ParameterKeyActivated` have **no registration anywhere in the module**. Creating a key does not touch Revit; activating one does not either. The seven `RevitParameterProjection*` events are likewise emitted with no behavioural subscriber — only the wildcard `DomainEventAuditLogger` sees them.
### What ensure actually writes
`ensureRevitProjectionForParameterKey` runs in this order, and the order is load-bearing:
1. `assertSingleProjectionLink` — `MULTIPLE_PARAMETER_PROJECTIONS` when the key links to more than one row, `REVIT_PROJECTION_STEWARDSHIP_REQUIRED` when it links to an unresolved one.
2. Load the key — `PARAMETER_KEY_NOT_FOUND` when it is gone.
3. If a projection already exists: reuse it when it is `archived` **and** the key still carries `bim_parameter` (see [Revit Projection Archive and Reuse](/docs/flows/revit-projection-archive-and-reuse)); otherwise return it untouched.
4. **Catalog-only short-circuit** — a key without `bim_parameter` returns `ok(null)`. No projection, no error.
5. `requireApprovedParameterKey` — `PARAMETER_KEY_NOT_APPROVED`.
6. `CommonParameterProjectionPolicy.resolve(handle)` picks `built_in` for a registry common, `shared` for everything else.
The created projection carries only what the key can yield:
| Field | Value at ensure |
| --- | --- |
| `status` | `draft` — both ensure paths pass `initialStatus: 'draft'` |
| `specTypeId`, `storageType` | Resolved from the key's `ParameterSpec` |
| `revitName`, `nameSource` | Derived from the canonical name (`derived`); the `built_in` path uses the enum name and `override` |
| `discipline` | `command.discipline ?? spec?.discipline ?? 'Common'`. Neither ensure path passes a discipline, so in practice the spec's discipline or `Common` |
| `visibilityFlags` | `shared`: visible, user-modifiable, not hidden. `built_in`: visible, **not** user-modifiable |
| `bindingKindPreference` | `null` on both paths |
| `revitPrimaryUsage` | `null` on the shared path. A registry **`built_in`** entry passes `project`, because `CommonParameterRegistry` requires `revitPrimaryUsage: 'project'` for every entry |
### Ensure fails loudly
A key that cannot yield a Revit spec produces an error, not a half-formed draft:
| Code | Fires when |
| --- | --- |
| `PARAMETER_KEY_NOT_APPROVED` | The key is not `approved`, on the create path and on archived reuse |
| `PARAMETER_SPEC_ID_MISSING` | The key has `bim_parameter` but no `parameterSpecId`. **Shared path only** — the `built_in` path resolves a Text spec from the catalog and never reads the key's spec |
| `SPEC_TYPE_ID_MISSING` | No `RevitSpecType` is mapped to the key's spec, or the best mapped candidate has no `forgeTypeId`. Also raised on the `built_in` path when no complete Text spec exists |
| `STORAGE_TYPE_MISSING` | A mapped spec has a `forgeTypeId` but no `storageType` |
When more than one spec type is mapped, `ensureShared` prefers the first candidate with **both** a `forgeTypeId` and a `storageType`; only if none qualifies does it report which half is missing. `validateSpec` raises the same two latter codes inside create and update, so a direct call cannot bypass them.
Handler failures are recorded through `ForRecordingBimOntologyHandlerFailure` and then **rethrown**. `AirtableBimOntologyHandlerFailureAdapter` creates a `script_log` row and stamps `last_script_status: 'error'` plus `last_script_message` on the `parameter_keys` row (ADR-0049), so a steward sees the reason on the record they were working on. The archive-on-retirement handler is the exception: it has no failure recorder and only logs before throwing.
## The ObjectClass precondition
`RevitProjectionApprovePolicy.unmetRequirements` is the hard gate and the single source of the `missingForApproval` field on the detail read — the list is computed by the same unit, never copied.
Applicability is **resolved, not authored**: `RevitCategoryScopeResolver.listApplicableObjectClasses` derives the classes from the effective parameter set — direct plus inherited minus suppressed — so the gate covers the classes that actually carry the key. Each unmet class is reported by key and id.
Because [ObjectClass](/docs/entities/ObjectClass) approval already requires all four classifications (ADR-0050), an approved projection necessarily has a complete Revit category set. The reverse direction is the subject of [Object Class Demotion Cascade](/docs/flows/object-class-demotion-cascade).
## Only approved projections reach Revit output
| Output | Filter |
| --- | --- |
| Package manifest (`resolveRevitParameterPackage`) | A projection that is not `approved` is a **hard error** — `PARAMETER_PROJECTION_INACTIVE`. With `includeArchived: true`, an `archived` projection is admitted; `draft` never is |
| Shared parameter file (`generateRevitSharedParameterFile`) | Same resolution, then only `SharedParameterDefinition` entries with an SPF datatype survive; others are reported as exclusions rather than dropped silently |
| Common project parameters (`listCommonProjectParameters`) | Filters `status === 'approved'` first, then requires `ParameterKey.isSystem`, an approved BIM key, and a shared definition |
| Projection list (`listRevitParameterProjections`) | **Not** gated on approved — it is a steward list with a `status` filter, and it soft-excludes incomplete rows instead of failing |
## Airtable entry points
- **AT-B1** — `create_parameter_key.js` on `ui_create_parameter_key`, one row per requested key, POSTing `/parameter-keys`. It can optionally assign the new key onto object classes in the same run.
- **AT-A1** — `parameter_key_actions.js` on `ui_parameter_key_actions`, flag-based over `parameter_keys`, handling Activate, Approve, Archive, and Delete. Group assignment is a grid edit of `parameter_keys.parameter_group` plus AT-C1 `parameter-key-projections` resync — not an action.
**No Airtable script creates, updates, or archives a `revit_parameters` row.** Grepping `apps/airtable-front-end/current/bim-ontology/` for `revit` returns only: comments explaining that scripts never write Revit rows, ObjectClass Revit-*category* handling (a classification link, not a projection), and two AT-C1 recompute calls in `request_reconcile.js` — `by-parameter-key/{key}/resync` and `by-parameter-group/{key}/resync`. Those are derived-metadata recomputes, not steward writes, and `parameter_key_actions.js` Delete relies on the API's cascade. Stewardship of a projection reaches it only through the REST surface.
## Code References
- `apps/api-v1/src/modules/bim-ontology/core/domain/parameter-key/ParameterKey.ts` — `approve`, `collectApproveMissingRequirements`
- `apps/api-v1/src/modules/bim-ontology/core/domain/parameter-key/ParameterStatus.ts` — the linear lifecycle
- `apps/api-v1/src/modules/bim-ontology/core/application/use-cases/ParameterKeyAuthoringUseCases.ts`
- `apps/api-v1/src/modules/bim-ontology/core/application/use-cases/RevitProjectionAuthoringUseCases.ts` — create, update, archive, ensure
- `apps/api-v1/src/modules/bim-ontology/core/application/use-cases/ApproveRevitParameterProjectionUseCase.ts`
- `apps/api-v1/src/modules/bim-ontology/core/application/use-cases/demoteApprovedIfSpfOutputChanged.ts`
- `apps/api-v1/src/modules/bim-ontology/core/application/use-cases/RevitProjectionReadUseCases.ts` — `missingForApproval`
- `apps/api-v1/src/modules/bim-ontology/core/application/use-cases/ResolveRevitParameterPackageUseCase.ts`
- `apps/api-v1/src/modules/bim-ontology/core/application/use-cases/ListCommonProjectParametersUseCase.ts`
- `apps/api-v1/src/modules/bim-ontology/core/domain/revit/RevitProjectionApprovePolicy.ts`
- `apps/api-v1/src/modules/bim-ontology/core/domain/revit/CommonParameterRegistry.ts` — `CommonParameterProjectionPolicy`
- `apps/api-v1/src/modules/bim-ontology/adapters/inbound/events/ParameterKeyApprovedProjectionHandler.ts`
- `apps/api-v1/src/modules/bim-ontology/adapters/inbound/events/ParameterKeyUpdatedProjectionEnsureHandler.ts`
- `apps/api-v1/src/modules/bim-ontology/adapters/inbound/rest/app/parameter-authoring/parameterAuthoring.openapi.ts`
- `apps/api-v1/src/modules/bim-ontology/adapters/inbound/rest/app/revit-parameter-projections/revitParameterProjections.routes.ts`
- `apps/api-v1/src/modules/bim-ontology/adapters/inbound/rest/app/revit-parameter-projections/revitParameterProjections.schemas.ts` — `DERIVED_STEWARD_PATCH_FIELDS`
- `apps/api-v1/src/modules/bim-ontology/adapters/outbound/persistence/airtable/AirtableBimOntologyHandlerFailureAdapter.ts`
- `apps/api-v1/src/modules/bim-ontology/composition/bim-ontology.composition.ts` — the `ParameterKey*` registrations
- `apps/airtable-front-end/current/bim-ontology/create_parameter_key.js`, `parameter_key_actions.js`
## ADRs
| ADR | Bearing on this flow |
| --- | --- |
| Platform ADR-0047 | Airtable scripts call the API and never write domain state; handlers recompute seeded scope |
| Platform ADR-0048 | `domain_id` at the public boundary — the `:key` path parameter is never a `rec…` id |
| Platform ADR-0049 | Handler failures are stamped back onto the subject row as `last_script_status` / `last_script_message` |
| Platform ADR-0050 | The four-kind ObjectClass approve gate, which is why an approved projection has a complete category set |
| Platform ADR-0052 | `active` belongs to ParameterKey alone; the projection's old `active` was renamed `approved` |
---
id: refresh-reload-product-scrape
name: Refresh and Reload Product Scrape
version: 1.0.0
owners:
- buildplan
summary: Queue and run refresh/reload scrape jobs for selected Product Marketplace catalog records.
badges:
- content: "Status: Implemented in api-v1"
backgroundColor: yellow
textColor: yellow
steps:
- id: select_products
title: Resolve product selection
summary: Flagged or explicit product selections are resolved to concrete catalog IDs.
actor:
name: Cron or Retool operator
next_step:
id: queue_jobs
label: Queue selected products
- id: queue_jobs
title: RefreshProductModelUseCase / ReloadProductModelUseCase
service:
id: api-v1
version: 1.0.0
next_step:
id: route_scraping
label: Route scraping
- id: route_scraping
title: Route through shared scraping
service:
id: shared-scraping
version: 1.0.0
next_step:
id: fetch_page
label: Fetch page
- id: fetch_page
title: BrightData or AgentQL provider fetch/extraction
service:
id: BrightData
version: 1.0.0
next_step:
id: scrape_result
label: Return scrape result
- id: scrape_result
title: Scrape run result
message:
id: ScrapeRunCompleted
version: 1.0.0
next_step:
id: run_pipeline
label: Re-materialize catalog state
- id: run_pipeline
title: RunProductRefreshScrapeJobUseCase / RunProductReloadScrapeJobUseCase
service:
id: api-v1
version: 1.0.0
next_step:
id: persist_parameter_graph
label: Persist parameter graph
- id: persist_parameter_graph
title: Parameter graph persistence
summary: Reconcile scraped assignments to canonical keys/values and variant_parameters.
service:
id: api-v1
version: 1.0.0
next_step:
id: sync_status
label: Persist and sync status
- id: sync_status
title: ProductScrapeCompleted or ProductScrapeFailed
message:
id: ProductScrapeCompleted
version: 1.0.0
---
## Overview
This flow documents the active api-v1 refresh and reload workflows for Product Marketplace. Refresh updates selected allowed fields while reload replaces a broader set of scrape-owned records.
## OpenAPI
- `refreshProductModels`
- `reloadProductModels`
- `runProductScrape`
- `refreshProductModelsAlias`
- `reloadProductModelsAlias`
## Event Path
- `ScrapeRunCompleted` or `ScrapeRunFailed`
- `ProductScrapeCompleted` or `ProductScrapeFailed`
## Code References
- `apps/api-v1/src/modules/product-marketplace/core/application/use-cases/ProductCatalogWorkflowUseCases.ts`
- `apps/api-v1/src/modules/product-marketplace/core/application/use-cases/ProductScrapeRequestUseCases.ts`
- `apps/api-v1/src/modules/product-marketplace/core/application/use-cases/ProductScrapeRequestJobUseCases.ts`
- `apps/api-v1/src/modules/product-marketplace/adapters/outbound/scraping/ProductActionScrapePipelineAdapter.ts`
## Parameter persistence
See [Parameter Graph Persistence](/docs/flows/parameter-graph-persistence) when refresh/reload re-materializes parameter assignments.
---
id: revit-projection-archive-and-reuse
name: Revit Projection Archive and Reuse
version: 1.0.0
owners:
- buildplan
summary: Retiring a ParameterKey archives its Revit projection instead of deleting it, so the shared-parameter GUID survives and re-adding the parameter reuses the same identity. One path breaks the rule, and it is documented here.
badges:
- content: "Status: Implemented in api-v1"
backgroundColor: yellow
textColor: yellow
steps:
- id: retire_key
title: Steward retires a ParameterKey
summary: Either an Archive action (AT-A1 `parameter_key_actions.js`), or an update that drops `usage:bim_parameter` or moves the key off `approved`.
actor:
name: Steward (Airtable / Retool)
next_steps:
- id: key_archived
label: Archive the key
- id: key_updated_retire
label: Update that retires BIM usage
- id: key_archived
title: ParameterKeyArchived
message:
id: ParameterKeyArchived
version: 1.0.0
next_step:
id: archive_handler
label: Always archives
- id: key_updated_retire
title: ParameterKeyUpdated
message:
id: ParameterKeyUpdated
version: 1.0.0
next_step:
id: archive_handler
label: Archives only when shouldArchive says so
- id: archive_handler
title: ArchiveRevitProjectionOnParameterKeyRetirementHandler
summary: "`shouldArchive` returns true unconditionally for ParameterKeyArchived. For ParameterKeyUpdated it reloads the key and returns true when the key is no longer `approved` or has lost `usage:bim_parameter`. Idempotent on parameterKeyId + event name + occurredAt."
service:
id: api-v1
version: 1.0.0
next_step:
id: archive_use_case
label: Archive the linked projection
- id: archive_use_case
title: ArchiveRevitParameterProjectionUseCase
summary: "Loads the projection, returns it unchanged when already archived, otherwise calls `archive()` and saves. The row stays, the ParameterKey link stays, and the shared GUID stays."
service:
id: api-v1
version: 1.0.0
next_step:
id: projection_archived
label: Dispatch the domain event
- id: projection_archived
title: RevitParameterProjectionArchived
message:
id: RevitParameterProjectionArchived
version: 1.0.0
next_step:
id: reinstate
label: Later, the steward reinstates the parameter
- id: reinstate
title: Steward re-approves or re-adds bim_parameter
summary: Re-approving the key raises ParameterKeyApproved; re-adding `usage:bim_parameter` to an already-approved key raises ParameterKeyUpdated. Either reaches the same ensure use case.
actor:
name: Steward (Airtable / Retool)
next_step:
id: ensure_search
label: Ensure runs again
- id: ensure_search
title: EnsureRevitProjectionForParameterKeyUseCase
summary: "Search-then-reuse. `findByParameterKeyId` follows the link on `parameter_keys` with no status filter, so archived rows are found. Reuse requires the key to be `approved` and to carry `bim_parameter`; it only creates when nothing is linked."
service:
id: api-v1
version: 1.0.0
next_step:
id: projection_reused
label: reactivateFromArchive to draft
- id: projection_reused
title: RevitParameterProjectionReused
message:
id: RevitParameterProjectionReused
version: 1.0.0
next_step:
id: recomplete
label: Back through the steward gate
- id: recomplete
title: Draft again, same GUID
summary: "The reused projection returns as `draft` with its identity, shared GUID, spec type, and authored facts intact. It must pass RevitProjectionApprovePolicy again before it re-enters Revit output."
service:
id: api-v1
version: 1.0.0
- id: delete_key
title: DeleteParameterKeyUseCase
summary: "The one exception. A hard DELETE of the key cascade-deletes the linked projection through `projections.deleteById`, discarding the GUID. Blocked with a 409 while ClassParameter assignments reference the key."
service:
id: api-v1
version: 1.0.0
next_step:
id: key_deleted
label: Dispatch the domain event
- id: key_deleted
title: ParameterKeyDeleted
message:
id: ParameterKeyDeleted
version: 1.0.0
---
## Overview
[RevitParameterProjection](/docs/entities/RevitParameterProjection) retirement **archives**. It does not delete, and the reason is a single immutable value: the shared-parameter GUID.
A shared parameter's GUID *is* its identity inside every Revit model that has ever received it. A `.txt` shared parameter file that ships a new GUID for the same concept produces a second, unrelated parameter in the model — the old one keeps its values and the new one starts empty. So BuildPlan treats the GUID as permanent: `updateAuthoredFacts` refuses to remove or replace it (`REVIT_GUID_IMMUTABLE`, `BO-RP-INV-01`), and retirement keeps the row that holds it.
That immutability is what makes archive strictly better than delete. Delete would strand every model that already carries the parameter. Archive plus search-then-reuse means a steward can retire a parameter, change their mind a year later, and the reinstated projection hands Revit back the identity it already knows.
## OpenAPI
Retirement and reuse are **event-driven**; there is no steward "archive" or "reuse" endpoint in the normal workflow. The projection archive endpoint exists only as maintenance.
| Operation | Route | Role |
| --- | --- | --- |
| [archiveParameterKey](/docs/services/api-v1/openapi/operations/archiveParameterKey) | `POST /parameter-keys/{id}/actions/archive` | Raises `ParameterKeyArchived`, which always archives the projection |
| [updateParameterKey](/docs/services/api-v1/openapi/operations/updateParameterKey) | `POST /parameter-keys/{id}/actions/update` | Dropping `usage:bim_parameter` retires the projection through the same handler |
| [approveParameterKey](/docs/services/api-v1/openapi/operations/approveParameterKey) | `POST /parameter-keys/{id}/actions/approve` | The reinstatement trigger — ensure finds the archived row and reuses it |
| [deleteParameterKey](/docs/services/api-v1/openapi/operations/deleteParameterKey) | `DELETE /parameter-keys/{id}` | **The one exception.** Hard-deletes the key and cascade-deletes the projection |
| [archiveRevitParameterProjection](/docs/services/api-v1/openapi/operations/archiveRevitParameterProjection) | `POST /revit/parameter-projections/{id}/archive` | Maintenance only — `x-internal`, tagged system-maintenance |
| [createRevitParameterProjection](/docs/services/api-v1/openapi/operations/createRevitParameterProjection) | `POST /revit/parameter-projections` | Maintenance only. Also reuses an archived row when one is linked — see the caveat below |
## Event Path
`ArchiveRevitProjectionOnParameterKeyRetirementHandler` is registered on **both** `ParameterKeyUpdated` and `ParameterKeyArchived` in `bim-ontology.composition.ts`. Same handler instance, two registrations.
Its `shouldArchive` is:
| Event | Decision |
| --- | --- |
| `ParameterKeyArchived` | `true`, always — no key reload |
| `ParameterKeyUpdated` | Reload the key. `false` if the key is not found; otherwise `true` when `lifecycleStatus !== 'approved'` **or** `usage:bim_parameter` is absent |
Two properties follow. First, because the key lifecycle is linear (`approved → archived` only), a real-world `ParameterKeyUpdated` that trips the status half of the guard is a key still in `draft` or `active` — the guard is mostly there for the usage drop. Second, the guard is the exact complement of `ParameterKeyUpdatedProjectionEnsureHandler`'s, so a single update either ensures or archives, never both. See [Parameter Key to Revit Projection](/docs/flows/parameter-key-to-revit-projection).
The handler then loads the linked projection and stops early when there is none or it is already `archived`. Unlike the ensure handlers, it has **no failure recorder**: on a failed archive it logs `Archive Revit projection after ParameterKey retirement failed` with the projection id and error code, then rethrows. Nothing is stamped onto the `parameter_keys` row.
### The seven projection events have no in-module subscriber
`RevitParameterProjectionCreated`, `Updated`, `Approved`, `Demoted`, `Reused`, `Archived`, and `Resynced` are all dispatched, and **no behavioural handler is registered for any of them** anywhere in the module. The only thing that receives them is the wildcard `DomainEventAuditLogger`, registered on `'*'` when composition builds its own dispatcher — it logs and does nothing else.
Every consequence in this flow is driven by a `ParameterKey*` event flowing *into* the projection side, never by a projection event flowing out. Treat the seven as emitted-but-unsubscribed until a consumer appears.
## Search-then-reuse
`EnsureRevitProjectionForParameterKeyUseCase` never blindly creates. It searches first, and the search **includes archived rows**: `findByParameterKeyId` resolves the `revit_parameters` link on the `parameter_keys` record and applies no status filter at all.
When the found row is `archived` and the key still carries `bim_parameter`:
1. `requireApprovedParameterKey` runs **before** any write. An unapproved key gets `PARAMETER_KEY_NOT_APPROVED` and the archived row stays archived — an unapproved key cannot resurrect a projection.
2. `reuseArchivedProjection` calls `reactivateFromArchive(now)`, which returns a new aggregate with `status: 'draft'` and records `RevitParameterProjectionReused`. Everything else — id, `parameterKeyId`, definition and GUID, spec type, storage type, visibility flags, discipline, applications — is carried over verbatim.
3. The row is saved, derived category sets are re-persisted, and the event is dispatched.
`reactivateFromArchive` is a no-op returning the same instance when the projection is not archived, so the path is safe to re-enter.
When the found row is **not** archived, ensure returns it untouched. When nothing is linked, ensure creates — the create path is the only one that mints a GUID.
`archived` has no forward transition. `updateAuthoredFacts`, `approve`, and `demoteToDraft` all refuse an archived projection with `REVIT_PROJECTION_ARCHIVED` (`BO-RP-INV-06`, HTTP 409). Reuse into `draft` is the only way out, and the reused projection must pass `RevitProjectionApprovePolicy` again before it re-enters any Revit output.
### One caveat on the maintenance create path
`CreateRevitParameterProjectionUseCase` also reuses an archived row when the key already links to one — but on that path the reuse happens after an identity comparison and **without** a key-approval check. Ensure applies `requireApprovedParameterKey` before reuse; the raw `POST /revit/parameter-projections` maintenance endpoint does not. Reaching it requires calling the internal create endpoint directly with a matching definition form.
## The one place GUID preservation does not hold
`DeleteParameterKeyUseCase` **hard-deletes** the linked projection:
```ts
const projection = await this.deps.projections.findByParameterKeyId(
command.id as string,
);
const cascadedRevitProjectionId = projection?.id ?? null;
if (projection) {
await this.deps.projections.deleteById(projection.id);
}
```
`apps/api-v1/src/modules/bim-ontology/core/application/use-cases/ParameterKeyAuthoringUseCases.ts` lines 324–330. The row is removed, the GUID goes with it, and it is never reissued. The response reports the removed id as `cascadedRevitProjectionId`. The port documents the intent explicitly: `deleteById` says callers "must not filter by projection status — delete is allowed in any status (D10: only when the ParameterKey itself is deleted)."
**This is the single place in the module where the GUID-preservation rule does not apply.** Say so plainly rather than implying archive is universal.
Two things limit the damage:
- **Assignments block it.** `listByParameterKeyId` runs first; any surviving ClassParameter assignment produces `ParameterKeyHasClassParameterAssignmentsError` — an HTTP 409 whose details list the blocking assignment `domain_id`s so a steward unassigns first. Assignments are deliberately **not** cascade-deleted, because that would destroy steward work silently.
- **Reachability.** In practice delete is therefore reachable mainly for unassigned keys — a mistyped or abandoned key, which is exactly the case where discarding a GUID costs nothing. A key that ever reached real classes has assignments to clear first, and clearing them is a visible act.
An already-deleted key returns `{ alreadyDeleted: true, cascadedRevitProjectionId: null }` and a 204, so the operation is idempotent.
The Airtable side matches: `parameter_key_actions.js` documents Delete as "cascade-deletes the linked Revit projection on the API. This script never reads or writes a Revit row."
## Why not soft-delete the ParameterKey too?
Archive already exists on the key (`approved → archived`) and is the ordinary retirement. `DELETE` is the escape hatch for a key that should never have existed, and the design accepts that using the escape hatch forfeits the GUID. If a GUID needs to survive, archive the key; do not delete it.
## Code References
- `apps/api-v1/src/modules/bim-ontology/adapters/inbound/events/ArchiveRevitProjectionOnParameterKeyRetirementHandler.ts`
- `apps/api-v1/src/modules/bim-ontology/core/application/use-cases/RevitProjectionAuthoringUseCases.ts` — `ArchiveRevitParameterProjectionUseCase`, `EnsureRevitProjectionForParameterKeyUseCase`, `reuseArchivedProjection`, `requireApprovedParameterKey`
- `apps/api-v1/src/modules/bim-ontology/core/application/use-cases/ParameterKeyAuthoringUseCases.ts` — `DeleteParameterKeyUseCase`, lines 295–344
- `apps/api-v1/src/modules/bim-ontology/core/application/ports/outbound/ForStoringRevitParameterProjections.ts` — the `deleteById` contract
- `apps/api-v1/src/modules/bim-ontology/core/domain/revit/RevitParameterProjection.ts` — `archive`, `reactivateFromArchive`, `REVIT_GUID_IMMUTABLE`
- `apps/api-v1/src/modules/bim-ontology/core/domain/revit/RevitParameterProjectionStatus.ts`
- `apps/api-v1/src/modules/bim-ontology/core/domain/revit/RevitSharedParameterGuid.ts`, `SharedParameterDefinition.ts` — the value that must never be recycled
- `apps/api-v1/src/modules/bim-ontology/adapters/outbound/persistence/airtable/AirtableRevitParameterQueryAdapter.ts` — `findByParameterKeyId`, which does not filter on status
- `apps/api-v1/src/modules/bim-ontology/adapters/outbound/persistence/airtable/AirtableRevitParameterRepository.ts` — `countPhysicalLinksForParameterKey`, `deleteById`
- `apps/api-v1/src/modules/bim-ontology/composition/bim-ontology.composition.ts` — the two `archiveOnRetirement` registrations
- `apps/airtable-front-end/current/bim-ontology/parameter_key_actions.js`
## ADRs
| ADR | Bearing on this flow |
| --- | --- |
| Platform ADR-0047 | Airtable scripts call the API; retirement and reuse are handler work, not script work |
| Platform ADR-0048 | `domain_id` at the public boundary; the cascade result reports a `domain_id` |
| Platform ADR-0052 | The projection's `active` was renamed `approved`; legacy `active` rows rehydrate as `approved`, so archived-then-reused rows normalize cleanly |
---
id: platform-ubiquitous-language
name: Platform Ubiquitous Language
summary: Cross-cutting vocabulary shared across modules — not specific to any one bounded context.
dictionary:
- id: ProjectObjectModel
name: Project Object Model (POM)
summary: Like a DOM for the project — the current rendered state of the project graph. Not a bounded context or api-v2 module. Persisted in Postgres (api-v2); may be serialized to JSON for APIs and visualization. The app materializes POM views from committed state plus in-flight workflow.
- id: PomGraph
name: POM graph
summary: JSON or API projection of project nodes and relationships used for rendering and integration; not a separate module folder.
- id: CommittedState
name: Committed state
summary: Official project data owned by bounded contexts (e.g. design-management design/spec graph, construction-management cost/execution). Mutations after approval go through module ports, often invoked by project-coordination after commit gates.
- id: ProposedState
name: Proposed state
summary: In-flight change proposals, approvals, and workflow status owned by project-coordination until commit.
- id: ChangeProposal
name: Change Proposal
summary: Aggregate in project-coordination representing a proposed change awaiting review/approval/commit.
- id: ConfigurationDsl
name: Configuration DSL (JSX)
summary: Domain-specific JSX language that encapsulates complex project configuration logic; evaluated in app/runtime context, not a backend module name.
- id: ProjectCoordination
name: project-coordination
summary: api-v2 module — cross-role workflow orchestration (approvals, proposals, tasks). Process manager / saga; does not own product catalog or committed POM slices.
- id: Bff
name: BFF (app-level API)
summary: Role-specific API composition under apps/api-v2/src/apps/** (e.g. client-portal-api). Not a bounded context; composes module inbound ports and api-v1 HTTP during cutover.
- id: ExperienceLayer
name: apps/** experience layer
summary: apps/api-v2/src/apps/{bff-name}/ — routes, controllers, presenters, composition; no core/domain folder.
- id: ObjectClass
name: Object Class
summary: The canonical classification node other contexts attach to — "what kind of thing is this?". A single-parent tree (ADR-0046). Owned by bim-ontology; product-marketplace assigns classes to products rather than defining them. Full definition on the bim-ontology language page.
- id: ParameterKey
name: Parameter Key
summary: The canonical identity and meaning of one parameter — one key per concept, defined once, independent of where it is used. Owned by bim-ontology, consumed by product-marketplace (ADR-0006). Full definition on the bim-ontology language page.
- id: ClassParameter
name: Class Parameter
summary: The assignment of a parameter key to an object class, with the local rules governing it. Always called an assignment, never a binding — "binding" is reserved for Revit placement.
- id: RevitParameterProjection
name: Revit Parameter Projection
summary: The Revit-facing expression of an approved parameter key — one definition identity plus optional placements. A projection of a key, not a second definition of it.
- id: RootObjectClass
name: root_object_class
summary: The parentless ancestor at the top of a class's lineage, derived and persisted on each row. Explicitly not an input to Revit category resolution — that was a DAG-era rule retired with ADR-0046.
- id: ActiveParameterKeyState
name: active (ParameterKey state)
summary: Displayable-but-unvetted. ParameterKey is the only aggregate with this state (ADR-0052). A scraper maps onto an approved key or creates a new key in active so scraped data is live while flagged for steward mapping.
- id: AtA1
name: AT-A1
summary: Airtable steward-UI mutation pattern (ADR-0047) — flag-based action table. Calls the API; never writes domain state directly.
- id: AtA2
name: AT-A2
summary: Airtable steward-UI mutation pattern (ADR-0047) — record-detail action button. Calls the API; never writes domain state directly.
- id: AtB1
name: AT-B1
summary: Airtable steward-UI mutation pattern (ADR-0047) — create-request table. Calls the API; never writes domain state directly.
- id: AtC1
name: AT-C1
summary: Airtable steward-UI mutation pattern (ADR-0047) — watched-field recompute. Calls the API; never writes domain state directly. AT-A* / AT-B1 written fields and AT-C1 watch lists must stay disjoint.
badges:
- content: Shared kernel
backgroundColor: blue
textColor: blue
---
This dictionary carries only terms that **more than one bounded context has to agree on**. Vocabulary that belongs to a single context lives on that context's own language page:
- [BIM Ontology ubiquitous language](/docs/domains/bim-ontology/language) — the full classification and parameter vocabulary
- [Product Marketplace ubiquitous language](/docs/domains/product-marketplace/language) — catalog, scraping, product configuration
The BIM terms above are the subset other contexts must understand in order to consume the ontology. They are deliberately abbreviated; the owning definitions are on the bim-ontology page.
## `active` is a parameter-key state only
`active` means **displayable-but-unvetted**, and it exists to serve a scraping contract rather than to add an approval step. A scraper either maps a scraped attribute onto an existing approved `ParameterKey`, or creates a new key in `active`. The `active` key is live immediately — scraped product data reaches the storefront without waiting for a steward — while staying visibly flagged as needing canonical mapping.
`ParameterKey` is the **only** aggregate with this state. `ObjectClass` and `RevitParameterProjection` go straight `draft → approved → archived` (ADR-0052), because nothing renders either to a customer ahead of review.
## `AT-*` — Airtable steward-UI mutation patterns
The four sanctioned ways the Airtable steward UI triggers a domain mutation (ADR-0047). **Every one of them calls the API; none writes domain state directly.** Worth knowing outside bim-ontology because Airtable is a temporary MVP adapter across the platform (ADR-0022), so any Airtable-backed module inherits these patterns.
`AT-A*` / `AT-B1` written fields and `AT-C1` watch lists must stay disjoint, and the backend guards duplicate calls.
## Module-owned slices of POM
| Concern | Owning module |
| --- | --- |
| Design selections, BIM assignments, design graph | `design-management` |
| Estimates, cost, procurement, execution | `construction-management` |
| Products in project context | `product-marketplace` (catalog) + design-management (selections) |
| Approvals and proposals | `project-coordination` (workflow only) |
`bim-ontology` is deliberately **not** a row in this table. It owns project-independent reference data — the classification tree and the parameter dictionary — that POM slices reference by identity. It holds no project state, so it owns no slice of the POM.
There is **no** `project-model` module. Do not create `apps/api-v2/src/modules/project-model/`.
## Related catalog pages
- [Platform domain](/docs/domains/platform)
- [BIM Ontology domain](/docs/domains/bim-ontology) and its [ubiquitous language](/docs/domains/bim-ontology/language)
- [Product Marketplace domain](/docs/domains/product-marketplace) and its [ubiquitous language](/docs/domains/product-marketplace/language)
- ADRs: [`docs/adr/README.md`](https://github.com/buildplan-construction/buildplan-monorepo/blob/develop/docs/adr/README.md)
---
id: bim-ontology-ubiquitous-language
name: BIM Ontology Ubiquitous Language
summary: Shared vocabulary for the api-v1 bim-ontology bounded context — classification graph, parameter dictionary, assignment and inheritance, and Revit projection.
dictionary:
- id: ObjectClass
name: Object Class
summary: A kind of building object BuildPlan can classify, specify, and eventually build. Aggregate root; Airtable object_classes. Answers "what kind of thing is this?" — not a product, a model, or a Revit category.
- id: ProductParentGraph
name: Product parent graph
summary: The single-parent ancestry between object classes (ADR-0046). At most one parent per class, acyclic. This is the edge set only; parameter inheritance is a separate concept that reads it.
- id: RootObjectClass
name: root_object_class
summary: The parentless ancestor at the top of a class's lineage. Derived and persisted on each row. Explicitly NOT an input to Revit category resolution — that was a DAG-era rule retired with ADR-0046.
- id: ExternalClassificationMapping
name: External classification mapping
summary: Uniformat, Masterformat, OmniClass, and ObjectClass codes cross-referenced from a class. The first three plus Revit category use inherit-by-default fill with make-unique (ADR-0050); OmniClass is a flat non-propagating link.
- id: MakeUnique
name: Make unique
summary: ADR-0050 fill rule. Stewards author *_direct only; make-unique is inferred on edit. The value fills down from the nearest make-unique ancestor until the next one. Effective mirrors materialized *_direct, so no read-time walk happens.
- id: RevitCategoryClassificationLink
name: Revit category classification link
summary: The single Revit category an object class classifies as (0..1), with health status current | stale | needs_review | unresolved and provenance authored | inherited. Distinct from Revit category scope — this classifies a class, that describes a parameter binding's coverage.
- id: ParameterKey
name: Parameter Key
summary: The canonical identity and meaning of one parameter, independent of where it is used or how it is rendered. Aggregate root; Airtable parameter_keys; REST /parameter-keys. Owned by bim-ontology, consumed by product-marketplace (ADR-0006).
- id: ParameterLifecycle
name: Parameter lifecycle
summary: draft → active → approved → archived, invariant-checked. ParameterKey is the only aggregate with active. See the "Why active exists" section below — it is the scraping contract, not an extra step.
- id: ParameterUsage
name: Parameter Usage
summary: Which product contexts a key participates in — variant_option, product_spec, bim_parameter, mfg_takeoff, qto_takeoff. Non-empty set. bim_parameter membership is load-bearing - it gates automatic Revit projection. Not the same as Revit document altitude.
- id: ParameterSpec
name: Parameter Spec
summary: The value semantics of a key — what kind of value, whether measurable, which unit system. BuildPlan's own semantics, mapped onto an Autodesk RevitSpecType. Airtable parameter_specs.
- id: ParameterControl
name: Parameter Control
summary: How a parameter is presented for authoring or selection. Constrains the interaction where ParameterSpec constrains the value. Airtable parameter_controls.
- id: ParameterGroup
name: Parameter Group
summary: The authoring grouping that organizes related keys for humans. The source both Revit group projections derive from; not itself a Revit artifact.
- id: ParameterValue
name: Parameter Value
summary: One canonical value a key can take, optionally carrying measured quantities. Quantity roles distinguish primary, alternate, min, max, and modifier readings.
- id: ParameterValueGroup
name: Parameter Value Group
summary: A reusable ordered set of parameter values applied as a template.
- id: Unit
name: Unit
summary: A canonical unit of measure, referenced from quantity values and constrained by ParameterSpec. Belongs to neither.
- id: ClassParameter
name: Class Parameter
summary: The assignment of one parameter key to one object class, with the local rules governing it. Aggregate root. Called assignment, never binding — binding is reserved for Revit placement.
- id: AssignmentOrigin
name: Assignment Origin
summary: How an assignment came to exist — direct, rendered, or legacy_unknown. Only direct rows accept deletion and allowed-value edits. Server-owned; hidden from stewards.
- id: InheritanceMode
name: Inheritance Mode
summary: On direct rows only — unique (this node only) or apply_to_descendants. The retired wire vocabulary self_only / self_plus_descendants / descendants_only is gone from the HTTP contract.
- id: ChildBehavior
name: Child Behavior
summary: On rendered rows only — inherit (default), suppress (this class only), or override (this class only, local values).
- id: EffectiveParameterSet
name: Effective parameter set
summary: The resolved answer to "what parameters does this class actually have", computed on request with an explanation. Resolution kinds are direct, inherited, override, suppressed. Rendered rows are a cache for Airtable's benefit; the resolution is authoritative.
- id: RevitParameterProjection
name: Revit Parameter Projection
summary: The Revit-facing expression of exactly one parameter key — one definition identity plus optional placements. Aggregate root; lifecycle draft → approved → archived. System-created, steward-completed.
- id: RevitDefinitionForm
name: Revit definition form
summary: shared, family_local, project_local, built_in, legacy_unknown. Definition means an identity BuildPlan authors and owns; Reference means an external identity it reads but never authors.
- id: RevitSharedParameterGuid
name: Revit shared parameter GUID
summary: Minted once for a shared definition and never regenerated. That immutability is what makes shared definitions portable, and why retirement archives a projection rather than deleting it.
- id: RevitCategoryScope
name: Revit category scope
summary: The set of Revit categories a project binding covers, resolved from effective class assignments. Derived only — the steward override was removed. Scope in this module means this and nothing else.
- id: RevitBindingKind
name: Revit binding kind
summary: instance or type — Revit's own vocabulary and the one sanctioned bare use of the word "type". RevitBindingKindPreference holds the aggregate-level default.
- id: RevitDocumentAltitude
name: Document altitude (revit_usages)
summary: Which Revit document kinds a projection is enabled for — family and/or project — derived at write time from the two placements, plus an authored primary preference. Same word as ParameterUsage, different altitude.
- id: EffectiveRevitName
name: Effective Revit name
summary: Normally derived from the canonical name, optionally pinned by override. Because it is derived, editing the canonical name drifts the projection — which is what the derivation-drift report and resync exist to reconcile.
- id: CommonParameters
name: Common parameters
summary: A stewarded allowlist of platform-wide parameters in the bp.common.* handle space that project with a fixed shape. Sheet, view, and annotation categories are denylisted from common project binding.
- id: ATPatterns
name: AT-A1 / AT-A2 / AT-B1 / AT-C1
summary: The Airtable steward-UI mutation patterns. AT-A1 is a flag-based action table, AT-A2 a record-detail action button, AT-B1 a create-request table, AT-C1 a watched-field recompute (ADR-0047). Every one calls the API; none writes domain state directly.
badges:
- content: "Status: Implemented in api-v1"
backgroundColor: yellow
textColor: yellow
---
## Usage
This dictionary is the catalog-side summary. The full concept reference — with the **Answers** / **Not** / **Appears as** structure, the overloaded-word rules, and the concept-to-structure alignment table — lives in [`docs/api-v1/bim-ontology/GLOSSARY.md`](https://github.com/buildplan-construction/buildplan-monorepo/blob/develop/docs/api-v1/bim-ontology/GLOSSARY.md).
Entity-like concepts have their own pages under this domain. This page carries the vocabulary, policies, and status names that are not first-class EventCatalog entities.
## Why `active` exists
`ParameterKey` is the only aggregate in this module with an `active` state, and the reason is a business contract rather than an extra approval step.
`active` means **displayable-but-unvetted**. When a scraper reads a product page it either maps the attribute onto an existing approved key, or it creates a new key in `active`. The `active` key is live immediately — a scraped `product_spec` reaches the e-commerce page without waiting for a steward — while still being visibly flagged as needing canonical mapping. A steward later promotes it to `approved` or folds it into an existing key.
Nothing renders an `ObjectClass` or a `RevitParameterProjection` to a customer ahead of review, so neither has an `active` tier. Both go straight from `draft` to `approved` (ADR-0052).
The order matters downstream: automatic Revit projection is triggered by `ParameterKeyApproved`, not by creation or activation. An `active` key is deliberately not yet in Revit.
## Four words that carry more than one meaning
These rules are binding.
| Word | Permitted meaning | Never |
| --- | --- | --- |
| **usage** | Two altitudes only — `ParameterUsage` (product contexts) and Revit document altitude (family / project). Always qualify the Revit one as `revit_*` | A third meaning, or an unqualified `usage` field on a Revit object |
| **scope** | Revit category scope, and nothing else | Parameter applicability, graph traversal extent (use `extent`), or class classification |
| **binding** | Revit placement only — project binding, family application, binding kind | Class-to-parameter attachment (use **assignment**) or class-to-category attachment (use **link**) |
| **type** | Qualified external identifiers (`RevitSpecTypeId`, `RevitStorageType`, ForgeTypeId) and Revit's `RevitBindingKind` value `type` | Any BuildPlan-owned concept or a `*_types` table. Sole exception: `ParameterDataType` |
Two further distinctions are deliberate: **Definition** versus **Reference** (authored versus external identity), and **derived** versus **authored** fields — a derived field is never a source of truth.
## Class over category
This context says **object class**, not object category. The rename is complete in the code: `apps/api-v1/src/modules/product-marketplace/core/domain/object-category/` no longer exists, and the graph now lives in `bim-ontology/core/domain/object-class/`. Product-marketplace scraping documentation that still says "object category" is describing this concept and should be read as such.
## ADRs
The platform series lives in [`docs/adr/07_Architectural_Decision_Records.md`](https://github.com/buildplan-construction/buildplan-monorepo/blob/develop/docs/adr/07_Architectural_Decision_Records.md); the per-file series is separate and the numbers collide. See [`docs/adr/README.md`](https://github.com/buildplan-construction/buildplan-monorepo/blob/develop/docs/adr/README.md).
| ADR | Decision |
| --- | --- |
| Platform ADR-0046 | The object-class hierarchy is a single-parent tree; no primary-parent device |
| Platform ADR-0047 | `AT-A*` / `AT-B1` written fields and `AT-C1` watch lists stay disjoint |
| Platform ADR-0048 | `domain_id` is the public identifier; never a `rec…` id in a payload |
| Platform ADR-0049 | Script feedback is denormalized onto the subject record |
| Platform ADR-0050 | Object-class lifecycle plus the four-kind classification approve gate; inherit-by-default fill with make-unique |
| Platform ADR-0052 | `active` is displayable-but-unvetted and belongs to ParameterKey alone |
| Per-file ADR-0006 | Canonical parameter keys and values with per-level junctions |
| Per-file ADR-0009 | Parameter table base placement and cross-base sync |
---
id: product-marketplace-ubiquitous-language
name: Product Marketplace Ubiquitous Language
summary: Shared vocabulary for the active api-v1 Product Marketplace domain.
dictionary:
- id: ObjectCategory
name: Object Category
summary: Product taxonomy node backed by the api-v1 object_categories table.
- id: ObjectCategoryCode
name: Object Category Code
summary: Code reference linked from an object category record.
- id: CategoryGraphService
name: Category Graph Service
summary: Pure domain policy for object-category tree changes.
- id: ParameterKey
name: Parameter Key
summary: Canonical parameter concept (one record per handle). Owned by bim-ontology and consumed here — see the ParameterKey entity page under that domain. Product-marketplace assigns a class to a product and uses the associated parameters.
- id: ParameterScope
name: Parameter Scope
summary: Retired name for ParameterUsage (variant_option, product_spec, bim_parameter, mfg_takeoff, qto_takeoff). In bim-ontology "scope" means Revit category scope and nothing else; use usage for this concept.
- id: CategoryParameter
name: Category Parameter
summary: Retired name for bim-ontology's ClassParameter — the assignment of one parameter key to one object class, with allowed-value children. Product-marketplace does not own this junction.
- id: ModelParameter
name: Model Parameter
summary: L2 junction on ProductModel; MTO allowable values and ranges are stored here (ADR-0007).
- id: VariantParameter
name: Variant Parameter
summary: L3 junction on ProductVariant (renamed from parameter_assignments / variant_parameters table).
- id: ConfigurationMode
name: Configuration Mode
summary: Product model strategy — simple, predefined_variants, made_to_order, or hybrid (ADR-0008).
- id: CanonicalParameterDictionaryIndex
name: Canonical Parameter Dictionary Index
summary: In-memory index of synced taxonomy keys/values used during scrape persistence to reuse canonical records by name, handle, or domain_id.
- id: ScrapedParameterGraph
name: Scraped Parameter Graph
summary: Outbound persistence pipeline that upserts taxonomy keys/values and writes variant_parameters from scrape assignments.
- id: PendingParameterLink
name: Pending Parameter Link
summary: Deferred variant_parameters link queued when PM mirror records are not yet synced; reconciled by ReconcilePendingParameterLinksUseCase.
- id: ParameterTemplateInheritanceService
name: Parameter Template Inheritance Service
summary: Policy that computes inherited effective parameter templates across category ancestors.
- id: Vendor
name: Vendor
summary: Product-marketplace organization projection backed by vendor records.
- id: Seller
name: Seller
summary: Product-marketplace person projection backed by seller records.
- id: ProductCollection
name: Product Collection
summary: Deprecated product-marketplace name that now means VendorProductCollection; user-curated collections belong to design-management.
- id: RefreshFieldCatalog
name: Refresh Field Catalog
summary: Product-specific catalog of stable fields a refresh scrape is allowed to update.
- id: ProductScrapeRunBehavior
name: Product Scrape Run Behavior
summary: Policy that combines scrape run scope with catalog persistence behavior.
- id: CreateFromUrl
name: Create from URL
summary: User-driven workflow that validates a source URL and starts scrape/persist processing for a product.
- id: RefreshScrape
name: Refresh Scrape
summary: Update allowed fields for an existing product model while preserving records outside the refresh field set.
- id: ReloadScrape
name: Reload Scrape
summary: Re-scrape and replace a broader set of scrape-owned records for an existing model or family.
- id: ImagePublishing
name: Image Publishing
summary: Workflow that publishes Airtable-linked product images to persistent media URLs and writes attachment state back.
---
## Usage
Entity-like catalog concepts live as entity pages under this domain. This dictionary captures supporting vocabulary, policies, statuses, and workflow names that are important to product-marketplace but are not first-class EventCatalog entities.
## Parameter schema ADRs
- [ADR-0006](https://github.com/buildplan-construction/buildplan-monorepo/blob/develop/docs/adr/ADR-0006-parameter-schema-canonical-keys-and-junctions.md) — canonical keys/values + per-level junctions
- [ADR-0007](https://github.com/buildplan-construction/buildplan-monorepo/blob/develop/docs/adr/ADR-0007-mto-model-level-allowable-values.md) — MTO model-level allowable values
- [ADR-0008](https://github.com/buildplan-construction/buildplan-monorepo/blob/develop/docs/adr/ADR-0008-product-configuration-modes-and-option-source-of-truth.md) — configuration modes
- [ADR-0009](https://github.com/buildplan-construction/buildplan-monorepo/blob/develop/docs/adr/ADR-0009-parameter-tables-base-placement-and-cross-base-sync.md) — base placement and sync