> ## Documentation Index
> Fetch the complete documentation index at: https://docs.structural.app/llms.txt
> Use this file to discover all available pages before exploring further.

# Propagate a change

> Writes a change through the canonical layer and fans out to all connected platforms.
Supports two input modes: canonical (when you have a canonical ID) and platform
(when you only have a platform-native ID).

Rate limited to 300 requests per minute per customer.


Writes a change through the canonical layer and fans it out to all connected platforms.
This is the stateful write path - data is persisted. For a stateless preview, use
[`POST /transform`](/api-reference/transform) instead.

## Two input modes

The request body accepts two modes, controlled by the `format` field:

### Canonical mode

Set `format: "canonical"` and provide `canonical_id`. Use this when you already have the
canonical ID from a previous [`GET /objects`](/api-reference/objects) call:

```json theme={null}
{
  "format": "canonical",
  "canonical_id": "pay_xK9mN2vL8gM3y6P",
  "entity_type": "payment",
  "data": {
    "amount": { "amount": 175000000, "currency": "USD" },
    "notes": "Updated payment amount"
  }
}
```

### Platform mode

Set `format: "platform"` and provide `platform` + `platform_id`. Use this when you only
have the platform-native ID (e.g., from a Procore webhook or scrape):

```json theme={null}
{
  "format": "platform",
  "platform": "procore",
  "platform_id": "1823456",
  "entity_type": "payment",
  "data": {
    "amount": "1750000.00",
    "notes": "Updated payment amount"
  }
}
```

Structural resolves the platform ID to the canonical entity and patches from there.

**Rule of thumb:** if you read via `/objects`, use canonical mode. If you got the ID from
a platform webhook or external system, use platform mode.

## Response

The response includes the updated canonical object, which fields changed, and the
propagated output per platform. The `updated_fields` array tells you exactly which
canonical fields were modified.

<Warning>
  **Writes are not transactional across platforms.** The write order is: (1) canonical
  update, (2) source platform mirror, (3) fan-out to each other connected platform
  sequentially. If step 3 fails for one platform, earlier writes are already persisted.

  Only platforms that succeeded appear in the `propagated` map. Missing platforms were not
  propagated - retry the `/propagate` call. Do not assume atomicity.
</Warning>


## OpenAPI

````yaml POST /propagate
openapi: 3.1.0
info:
  title: Structural API
  version: '1.0'
  description: |
    Bidirectional data translation between construction management platforms.
    One integration gives you normalized access to every connected platform.
  contact:
    email: support@structural.app
    url: https://structural.app
servers:
  - url: https://api.structural.app/api/v1
    description: Production
security: []
paths:
  /propagate:
    post:
      tags:
        - Propagation
      summary: Propagate a change
      description: >
        Writes a change through the canonical layer and fans out to all
        connected platforms.

        Supports two input modes: canonical (when you have a canonical ID) and
        platform

        (when you only have a platform-native ID).


        Rate limited to 300 requests per minute per customer.
      operationId: propagateChange
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/PropagateRequest'
      responses:
        '200':
          description: Change propagated
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    $ref: '#/components/schemas/PropagationResult'
        '400':
          $ref: '#/components/responses/ValidationError'
        '401':
          $ref: '#/components/responses/AuthenticationError'
        '404':
          $ref: '#/components/responses/NotFoundError'
        '429':
          $ref: '#/components/responses/RateLimitError'
      security:
        - bearerAuth: []
components:
  schemas:
    PropagateRequest:
      type: object
      required:
        - entity_type
        - data
      properties:
        entity_type:
          type: string
          enum:
            - payment
            - project
            - customer
            - invoice
            - contract
        format:
          type: string
          enum:
            - canonical
            - platform
          default: canonical
          description: >
            Input mode. `canonical` — you have a canonical ID from a previous
            `/objects` call.

            `platform` — you only have the platform-native ID.
        data:
          type: object
          description: The fields to update
          additionalProperties: true
        canonical_id:
          type: string
          description: Required when `format` is `canonical`
        platform:
          type: string
          enum:
            - procore
            - dynamics
          description: Required when `format` is `platform`
        platform_id:
          type: string
          minLength: 1
          description: Required when `format` is `platform`
    PropagationResult:
      type: object
      properties:
        canonical:
          type: object
          properties:
            id:
              type: string
              description: Canonical entity ID
              example: pay_xK9mN2vL8gM3y6P
            entity_type:
              type: string
              example: payment
            updated_fields:
              type: array
              items:
                type: string
              description: Canonical fields that actually changed
              example:
                - amount
                - notes
            data:
              type: object
              description: The full canonical object after the update
              additionalProperties: true
        propagated:
          type: object
          description: >
            Map of platform name to the propagated output. Only platforms that
            succeeded

            are included. Missing platforms were not propagated — retry the
            call.
          additionalProperties:
            type: object
            properties:
              platform_id:
                type: string
              data:
                type: object
                additionalProperties: true
              canonical_view:
                type: object
                additionalProperties: true
              writeStatus:
                type: string
                enum:
                  - pending
                  - synced
                  - conflict
                  - failed
                description: |
                  Per-platform outbound write status. Present only when the
                  connection is in 'live' mode and a write was attempted.
              writeError:
                type: string
                description: >
                  Human-readable error message when writeStatus is 'failed' or
                  'conflict'.
              conflictData:
                type: object
                description: >
                  The remote entity's current state when writeStatus is
                  'conflict'

                  (from the GET after a 412 response).
                additionalProperties: true
        references:
          type: object
          description: >-
            Cross-entity reference resolution results (present when references
            exist)
          properties:
            resolved:
              type: array
              items:
                type: object
                properties:
                  field:
                    type: string
                  canonical_id:
                    type: string
                  platform_id:
                    type: string
                  injected:
                    type: boolean
                  reason:
                    type: string
            unresolved:
              type: array
              items:
                type: object
                properties:
                  field:
                    type: string
                  canonical_id:
                    type: string
                  reason:
                    type: string
    ErrorResponse:
      type: object
      properties:
        error:
          type: object
          required:
            - type
            - message
            - request_id
          properties:
            type:
              type: string
              description: Machine-readable error code
              example: VALIDATION_ERROR
            message:
              type: string
              description: Human-readable error description
              example: platform and entity_type query params required
            request_id:
              type: string
              description: Unique request identifier — quote this in support requests
              example: req_a1b2c3d4e5f6
  responses:
    ValidationError:
      description: Request validation failed
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ErrorResponse'
          example:
            error:
              type: VALIDATION_ERROR
              message: Invalid input
              request_id: req_a1b2c3d4e5f6
    AuthenticationError:
      description: Missing or invalid API key
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ErrorResponse'
          example:
            error:
              type: AUTHENTICATION_ERROR
              message: Invalid or missing API key
              request_id: req_a1b2c3d4e5f6
    NotFoundError:
      description: Resource not found
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ErrorResponse'
          example:
            error:
              type: NOT_FOUND
              message: Entity mapping not found
              request_id: req_a1b2c3d4e5f6
    RateLimitError:
      description: Too many requests
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ErrorResponse'
          example:
            error:
              type: RATE_LIMIT_ERROR
              message: Too many requests
              request_id: req_a1b2c3d4e5f6
  securitySchemes:
    bearerAuth:
      type: http
      scheme: bearer
      description: >
        API key passed as a Bearer token. Keys are prefixed: `sk_demo_*`
        (sandbox),

        `sk_live_*` (production), `sk_test_*` (staging).

````