openapi: 3.1.0
info:
  title: Motionworks API - Popcast Change
  version: 0.1.0
  description: |
    Popcast Change will measure how fast the physical fabric of a US Census block group is changing: building permits, businesses opening and closing, and real-estate activity, rolled up over a window you choose and summarized as a single change-velocity score.

    **Coming soon.** This capability is on the Motionworks roadmap and is not yet available to call. The request and response shapes are published so you can plan an integration; they may change before launch.
  x-internal-notes: |
    Change layer of The Conscience Stack (TF-100). Planned sources: Atlanta/NYC/LA/Chicago/SF Socrata permit portals (real), state Secretary-of-State business registries (per-state stub), real-estate aggregate (placeholder data).
  contact:
    name: Motionworks AI
    url: https://mworks.com
    email: api@mworks.com

servers:
  - url: https://api2.mworks.com

tags:
  - name: change
    description: |
      Permits, business openings and closings, and real-estate activity for a block group, with a change-velocity score. Coming soon.

    x-displayName: Change

paths:
  /v2/popcast/change/{fips}:
    get:
      operationId: getChangeSignal
      summary: Read the current change signal
      description: |
        Will return permit, business-change and real-estate activity for one block group over a rolling window of 30, 90, 180 or 365 days, with a change-velocity score. Planned cost: 1 credit per call. Coming soon — not yet available to call.
      tags: [change]
      x-motionworks-status: roadmap
      x-motionworks-roadmap-issue: "TF-100"
      x-motionworks-data-maturity: synthetic-only
      x-credit-cost: 1
      parameters:
        - $ref: '#/components/parameters/FipsParam'
        - name: window
          in: query
          schema: { type: integer, enum: [30, 90, 180, 365], default: 90 }
          description: "Rolling window in days (30, 90, 180 or 365)."
      responses:
        '200':
          description: Change signal
          content:
            application/json:
              schema: { $ref: '#/components/schemas/ChangeSignalResponse' }
        '404': { $ref: '#/components/responses/NotFound' }
        '400': { $ref: '#/components/responses/ValidationError' }

  /v2/popcast/change/{fips}/history:
    get:
      operationId: getChangeHistory
      summary: Read change history
      description: |
        Will return monthly change snapshots for one block group between `from` and `to`, with cursor pagination. Planned cost: 3 credits per call. Coming soon — not yet available to call.
      tags: [change]
      x-motionworks-status: roadmap
      x-motionworks-roadmap-issue: "TF-100"
      x-motionworks-data-maturity: synthetic-only
      x-credit-cost: 3
      parameters:
        - $ref: '#/components/parameters/FipsParam'
        - { name: from, in: query, schema: { type: string, format: date }, description: "Earliest month to include (`YYYY-MM-DD`)." }
        - { name: to, in: query, schema: { type: string, format: date }, description: "Latest month to include (`YYYY-MM-DD`)." }
        - { name: cursor, in: query, schema: { type: string }, description: "Opaque cursor from the previous page." }
        - { name: limit, in: query, schema: { type: integer, maximum: 24 }, description: "Results per page." }
      responses:
        '200':
          description: Change history
          content:
            application/json:
              schema: { $ref: '#/components/schemas/ChangeHistoryResponse' }
  /v2/popcast/change/batch:
    post:
      operationId: batchChangeSignals
      summary: Read change signals for many block groups
      description: |
        Will return the current change signal for up to 100 block groups in one call. Planned cost: 100 credits per call. Coming soon — not yet available to call.
      tags: [change]
      x-motionworks-status: roadmap
      x-motionworks-roadmap-issue: "TF-100"
      x-motionworks-data-maturity: synthetic-only
      x-credit-cost: 100
      requestBody:
        required: true
        content:
          application/json:
            schema: { $ref: '#/components/schemas/ChangeBatchRequest' }
      responses:
        '200':
          description: Batch results
          content:
            application/json:
              schema: { $ref: '#/components/schemas/ChangeBatchResponse' }

components:
  parameters:
    FipsParam:
      name: fips
      in: path
      required: true
      schema: { type: string, pattern: '^\d{12}$' }
      description: 12-digit US Census block group FIPS code.

  schemas:
    PermitEvent:
      type: object
      properties:
        id: { type: string, description: "Unique id of the record." }
        permit_type: { type: string, enum: [new-construction, addition, renovation, demolition, signage, electrical, plumbing, mechanical, other], description: "Kind of permit." }
        description: { type: string, description: "Permit description as filed." }
        issue_date: { type: string, description: "When the permit was issued (`YYYY-MM-DD`)." }
        estimated_value_usd: { type: number, description: "Estimated construction value, USD." }
        latitude: { type: number, description: "Latitude (WGS84)." }
        longitude: { type: number, description: "Longitude (WGS84)." }
        address: { type: string, description: "Street address of the permitted work." }
        source: { type: string, description: "Which source supplied this record." }
      description: One building permit.
    BusinessEvent:
      type: object
      properties:
        id: { type: string, description: "Unique id of the record." }
        change_type: { type: string, enum: [new-open, closed, renovation, ownership-transfer, rebranding], description: "Kind of change." }
        business_name: { type: string, description: "Name of the business." }
        effective_date: { type: string, description: "When the change took effect (`YYYY-MM-DD`)." }
        source: { type: string, description: "Which source supplied this record." }
      description: "One business opening, closing or change."
    RealEstateSnapshot:
      type: object
      properties:
        active_listings: { type: integer, description: "Listings currently active." }
        median_list_price_usd: { type: number, nullable: true, description: "Median list price, USD, or `null`." }
        median_days_on_market: { type: number, nullable: true, description: "Median days on market, or `null`." }
        price_trend_30d_pct: { type: number, nullable: true, description: "Change in median list price over the last 30 days, as a percentage, or `null`." }
        new_listings_window: { type: integer, description: "Listings added during the window." }
        sold_window: { type: integer, description: "Sales closed during the window." }
        source: { type: string, description: "Which source supplied this record." }
      description: Real-estate activity over the window.
    ChangePayload:
      type: object
      properties:
        permits:
          type: object
          properties:
            total: { type: integer, description: "Total number of items in the window." }
            total_value_usd: { type: number, description: "Total estimated value of the permits, USD." }
            by_type: { type: object, additionalProperties: { type: integer }, description: "Count of items of each type, keyed by type." }
            recent:
              type: array
              items: { $ref: '#/components/schemas/PermitEvent' }
              description: The most recent items in the window.
          description: Building-permit activity in the window.
        business_changes:
          type: object
          properties:
            total: { type: integer, description: "Total number of items in the window." }
            by_type: { type: object, additionalProperties: { type: integer }, description: "Count of items of each type, keyed by type." }
            recent:
              type: array
              items: { $ref: '#/components/schemas/BusinessEvent' }
              description: The most recent items in the window.
          description: "Business openings, closings and changes in the window."
        real_estate: { $ref: '#/components/schemas/RealEstateSnapshot' }
        change_velocity: { type: number, minimum: 0, maximum: 100, description: "How fast the block group is changing, 0–100." }
      description: The change signal contents.
    ChangeSignal:
      type: object
      required: [fips, signal_layer, period_end, window_days, refreshed_at, payload]
      properties:
        fips: { type: string, description: "12-digit US Census block group FIPS code." }
        signal_layer: { type: string, enum: [change], description: "Which signal layer this record belongs to." }
        period_end: { type: string, description: "Last day of the period this record covers (`YYYY-MM-DD`)." }
        window_days: { type: integer, enum: [30, 90, 180, 365], description: "Length of the rolling window, in days." }
        refreshed_at: { type: string, format: date-time, description: "When the signal was last refreshed (ISO 8601)." }
        data_quality: { type: string, enum: [high, medium, low], description: "Confidence in the signal: `high`, `medium` or `low`." }
        source_breakdown: { type: object, additionalProperties: { type: integer }, description: "How many records each contributing source supplied, keyed by source." }
        payload: { $ref: '#/components/schemas/ChangePayload' }
      description: The current change signal for one block group.
    Meta:
      type: object
      properties:
        request_id: { type: string, description: "Unique id for this request. Quote it when contacting support." }
        credits_used: { type: integer, description: "Credits charged for this call." }
        credits_remaining: { type: integer, description: "Credits remaining on your account after this call." }
        product: { type: string, description: "The Motionworks product that served the call." }
        version: { type: string, description: "API version that served the call." }
        provenance:
          type: object
          properties:
            source: { type: string, description: "The Motionworks dataset or model the result came from." }
            data_maturity: { type: string, description: "Maturity of the data behind the result: `production` (supported and stable), `research-preview` (available, subject to change), or a pre-release value (`roadmap`, or generated placeholder data not yet backed by production measurement)." }
          description: Where the data came from and how mature it is.
      description: Request metadata.
    ChangeSignalResponse:
      type: object
      properties:
        data: { $ref: '#/components/schemas/ChangeSignal' }
        meta: { $ref: '#/components/schemas/Meta' }
      description: One change signal with request metadata.
    ChangeHistorySnapshot:
      type: object
      properties:
        fips: { type: string, description: "12-digit US Census block group FIPS code." }
        period_start: { type: string, description: "First day of the period this record covers (`YYYY-MM-DD`)." }
        period_end: { type: string, description: "Last day of the period this record covers (`YYYY-MM-DD`)." }
        window_days: { type: integer, description: "Length of the rolling window, in days." }
        permit_count: { type: integer, description: "Permits issued in the period." }
        business_event_count: { type: integer, description: "Business openings, closings and changes in the period." }
        change_velocity: { type: number, description: "Change-velocity score for the period, 0–100." }
        data_quality: { type: string, description: "Confidence in the signal: `high`, `medium` or `low`." }
      description: Change activity for one month.
    ChangeHistoryResponse:
      type: object
      properties:
        data:
          type: array
          items: { $ref: '#/components/schemas/ChangeHistorySnapshot' }
          description: The list of results.
        pagination:
          type: object
          properties:
            cursor: { type: string, nullable: true, description: "Opaque cursor for the next page. Pass it back as `cursor` to continue; absent on the last page." }
            has_more: { type: boolean, description: "Whether another page follows." }
            total: { type: integer, description: "Total number of items in the window." }
          description: Paging information for the list.
        meta: { $ref: '#/components/schemas/Meta' }
      description: A page of monthly snapshots with request metadata.
    ChangeBatchRequest:
      type: object
      required: [fips_codes]
      properties:
        fips_codes:
          type: array
          maxItems: 100
          items: { type: string, pattern: '^\d{12}$' }
          description: Block group FIPS codes to look up (12 digits each).
        window: { type: integer, enum: [30, 90, 180, 365], description: "Rolling window in days (30, 90, 180 or 365)." }
      description: Block groups to look up and the window to use.
    ChangeBatchResponse:
      type: object
      properties:
        data:
          type: array
          items: { $ref: '#/components/schemas/ChangeSignal' }
          description: The list of results.
        meta: { $ref: '#/components/schemas/Meta' }
      description: "One change signal per requested block group, with request metadata."
    ApiError:
      type: object
      properties:
        error:
          type: object
          properties:
            code: { type: string, description: "Stable machine-readable error code." }
            message: { type: string, description: "Human-readable explanation of the error." }
            status: { type: integer, description: "HTTP status code, repeated in the body." }
            request_id: { type: string, description: "Unique id for this request. Quote it when contacting support." }
          description: "The error: a stable `code`, a human-readable `message`, and the HTTP `status`."
      description: The standard error envelope.
  responses:
    NotFound:
      description: Not found
      content:
        application/json:
          schema: { $ref: '#/components/schemas/ApiError' }
    ValidationError:
      description: Validation error
      content:
        application/json:
          schema: { $ref: '#/components/schemas/ApiError' }
x-tagGroups:
  - name: Popcast Change
    tags:
      - change
x-customer-voice: true
