openapi: 3.1.0
x-customer-voice: true
info:
  title: Placecast Select
  version: 0.4.0
  description: |
    Purchase, poll, and retrieve a reach-and-frequency analysis for an
    event scenario — Placecast Select. Creating and managing the event
    scenario itself — the places, active window, and audience it
    describes — is a separate endpoint family at `/v2/scenarios/events/*`
    (see the Scenarios reference).

    Purchasing an analysis is asynchronous:
    `POST /v2/placecast/measurements/purchase/select` returns `202` with
    `status: "queued"`; poll `GET /v2/placecast/measurements/{asset_id}`
    for state. Expect a report roughly one to two days after purchase, not
    same-day — purchases are batched once daily before analysis starts.
    Once state is `completed`, the report is available at
    `GET /v2/placecast/select/{asset_id}`, final and permanently
    retrievable. A failed run auto-refunds the purchase charge, less a
    1-credit charge for the API call itself; the event scenario moves to a
    `failed` state that stays fully editable and can be purchased again in
    place.

    Authenticate with an org-scoped API key (`X-API-Key: mw_…`). Purchasing
    an analysis costs 30,000 credits. Polling is free; report retrieval
    costs 1 credit.
  contact:
    name: Motionworks AI
    url: https://mworks.com
    email: api@mworks.com
servers:
  - url: https://api2.mworks.com/v2
    description: Production
security:
  - apiKey: []
components:
  securitySchemes:
    apiKey:
      type: apiKey
      name: X-API-Key
      in: header
  schemas:
    PlacecastSelectStatus:
      type: string
      enum:
        - queued
        - running
        - completed
        - failed
      description: |
        Where the analysis run is: `queued` (accepted, not started),
        `running`, `completed` (the event asset's report is retrievable)
        or `failed` (`failure_reason` is populated and the purchase charge
        is refunded, less a 1-credit charge for the API call itself).
    PlacecastSelectPurchaseRequest:
      type: object
      required:
        - asset_id
        - measurement_request_id
      properties:
        asset_id:
          type: string
          description: The event scenario asset to purchase an analysis for, owned by your organization.
        measurement_request_id:
          type: string
          format: uuid
          description: |
            Idempotency key you mint (UUID). A retry with the same key and
            an identical body replays the original result at no additional
            charge; the same key with a different body is rejected with
            `409`. Carried in the request body, not an `Idempotency-Key`
            header.
      description: POST /measurements/purchase request body — asset_id rides in the body, not the path.
    PlacecastSelectResult:
      type: object
      required:
        - measurement_id
        - asset_id
        - status
        - queued_at
      description: The `202` initial-acceptance body for a purchase.
      properties:
        measurement_id:
          type: string
          nullable: true
          description: Null only in the brief poll-before-queue race window.
        asset_id:
          type: string
        status:
          $ref: '#/components/schemas/PlacecastSelectStatus'
        queued_at:
          type: string
          format: date-time
        completed_at:
          type: string
          format: date-time
        refund_credits:
          type: integer
          description: Populated on `failed` — the purchase charge, refunded less a 1-credit charge for the API call itself (29,999 of the 30,000 credits).
        failure_reason:
          type: string
    PlacecastMeasurementPollResult:
      type: object
      required:
        - asset_id
        - state
        - updated_at
      description: |
        The current status of an event's measurement run, plus refund
        details if it failed.
      properties:
        asset_id:
          type: string
        state:
          type: string
          enum:
            - draft
            - queued
            - in_flight
            - completed
            - failed
        refund_credits:
          type: integer
          description: 'Populated only on `state: failed`, when a refund ledger row exists.'
        failure_reason:
          type: string
          description: 'Populated only on `state: failed`.'
        updated_at:
          type: string
          format: date-time
    MetricBasis:
      type: string
      enum:
        - measured
        - modeled
        - below_floor
      description: Per-cell provenance for an estimate.
    DataQualityStatus:
      type: string
      enum:
        - ok
        - structural_zero
        - below_floor
        - suppressed
        - outside_measured_surface
        - null_metric
      description: |
        Distinguishes a real measured zero (`structural_zero`) from a
        suppressed, below-floor, or otherwise-unavailable value. No zeros
        are ever silently substituted.
    MetricCellEstimate:
      type: object
      required:
        - status
        - value
        - ci
        - basis
        - data_quality_status
      properties:
        status:
          type: string
          const: estimated
        value:
          type: number
        ci:
          type: array
          items:
            type: number
          minItems: 2
          maxItems: 2
          description: |
            90% confidence interval as `[lower, upper]`. A directly
            measured quantity collapses this to `[value, value]`. Typed as
            a same-typed 2-element array rather than a strict
            `prefixItems` tuple (the repo's Spectral ruleset flags
            tuple-only arrays).
        basis:
          $ref: '#/components/schemas/MetricBasis'
        data_quality_status:
          $ref: '#/components/schemas/DataQualityStatus'
    MetricCellAbstained:
      type: object
      required:
        - status
        - abstained
        - reason
      properties:
        status:
          type: string
          const: abstained
        abstained:
          type: boolean
          const: true
        reason:
          type: string
          minLength: 1
      description: |
        A first-class abstention: the underlying observation was too thin
        to estimate honestly. Render as "not available", never as zero.
    MetricCell:
      oneOf:
        - $ref: '#/components/schemas/MetricCellEstimate'
        - $ref: '#/components/schemas/MetricCellAbstained'
      description: Every estimated quantity in a report is one of these two shapes — never a bare number.
    ImpressionPoint:
      type: object
      required:
        - date
        - measurement_state
        - time_basis
        - ots
        - lts
        - circulation
      properties:
        date:
          type: string
          format: date
        measurement_state:
          type: string
          enum:
            - projected
            - preliminary
            - final
        time_basis:
          type: string
        ots:
          $ref: '#/components/schemas/MetricCell'
        lts:
          $ref: '#/components/schemas/MetricCell'
        circulation:
          $ref: '#/components/schemas/MetricCell'
    ReachThreshold:
      type: object
      required:
        - effective_freq
        - reach
      properties:
        effective_freq:
          type: integer
          minimum: 1
        reach:
          $ref: '#/components/schemas/MetricCell'
    ReachBasis:
      type: object
      required:
        - thresholds
        - unique_exposure
        - total_exposures
        - average_frequency
      properties:
        thresholds:
          type: array
          items:
            $ref: '#/components/schemas/ReachThreshold'
        unique_exposure:
          $ref: '#/components/schemas/MetricCell'
        total_exposures:
          $ref: '#/components/schemas/MetricCell'
        average_frequency:
          $ref: '#/components/schemas/MetricCell'
    MarketRef:
      type: object
      required:
        - kind
      properties:
        kind:
          type: string
          enum:
            - market
            - market_group
            - national
          description: |
            A market slice counts residents of that geography; the
            always-on National slice counts everyone reached nationwide. A
            market slice is always at most the National slice.
        geography_id:
          type: string
        name:
          type: string
    SegmentRef:
      type: object
      required:
        - segment_id
      properties:
        segment_id:
          type: integer
        role:
          type: string
    MetricSlice:
      type: object
      required:
        - market
        - segment
        - impressions
        - reach
      properties:
        market:
          $ref: '#/components/schemas/MarketRef'
        segment:
          $ref: '#/components/schemas/SegmentRef'
        impressions:
          type: object
          required:
            - daily
            - cumulative
          properties:
            daily:
              type: array
              items:
                $ref: '#/components/schemas/ImpressionPoint'
            cumulative:
              type: array
              items:
                $ref: '#/components/schemas/ImpressionPoint'
        reach:
          type: object
          required:
            - ots
            - lts
          properties:
            ots:
              $ref: '#/components/schemas/ReachBasis'
            lts:
              $ref: '#/components/schemas/ReachBasis'
      description: |
        All metrics for one market × segment combination. The market axis
        always includes the requested market group (primary read), one
        slice per constituent market when the group has more than one
        member, and the always-on National slice.
    MarketDetail:
      type: object
      required:
        - geography_id
        - name
        - level
        - population
      properties:
        geography_id:
          type: string
        name:
          type: string
        level:
          type: string
          enum:
            - dma
            - cbsa
            - county
        population:
          type: object
          required:
            - base
          properties:
            base:
              type: integer
            by_segment:
              type: array
              items:
                type: object
                properties:
                  segment_id:
                    type: integer
                  population:
                    type: integer
    EventPlaceDetail:
      type: object
      required:
        - place_id
      properties:
        place_id:
          type: string
        assignment_status:
          type: string
          x-motionworks-status: roadmap
      description: |
        No `customer_place_id` / `windows` here — the real contract's
        `place_ids` is a flat array of strings with no per-place
        customization, and periods apply globally across every place.
    EventPeriod:
      type: object
      required:
        - period_name
        - start_time
        - end_time
      properties:
        period_name:
          type: string
        start_time:
          type: string
          format: date-time
        end_time:
          type: string
          format: date-time
    EventCohort:
      type: object
      required:
        - geography_ids
        - motionworks_segment_id
      properties:
        geography_ids:
          type: array
          items:
            type: string
        motionworks_segment_id:
          type: string
    EventCustomer:
      type: object
      required:
        - customer_id
        - customer_scenario_id
      properties:
        customer_id:
          type: string
        customer_name:
          type: string
        customer_scenario_id:
          type: string
        customer_scenario_name:
          type: string
        customer_scenario_info:
          type: string
    PlacecastSelectReport:
      type: object
      required:
        - asset_id
        - periods
        - cohort
        - customer
        - markets
        - place_details
        - metric_slices
      properties:
        asset_id:
          type: string
        name:
          type: string
        description:
          type: string
        periods:
          type: array
          items:
            $ref: '#/components/schemas/EventPeriod'
        cohort:
          $ref: '#/components/schemas/EventCohort'
        customer:
          $ref: '#/components/schemas/EventCustomer'
        markets:
          type: array
          items:
            $ref: '#/components/schemas/MarketDetail'
        place_details:
          type: array
          items:
            $ref: '#/components/schemas/EventPlaceDetail'
        metric_slices:
          type: array
          items:
            $ref: '#/components/schemas/MetricSlice'
        measurement_state_boundaries:
          type: object
          properties:
            final_through:
              type: string
              format: date
              nullable: true
            preliminary_through:
              type: string
              format: date
              nullable: true
            projected_from:
              type: string
              format: date
              nullable: true
        debug:
          type: object
          additionalProperties: true
          description: Intermediate-measurement context. Shape may change; do not build against it except `debug.messages`.
      description: Retrievable once the event asset is `completed` (final, permanent). There is no interim report before then.
    Provenance:
      type: object
      properties:
        source:
          type: string
        source_doc:
          type: string
          format: uri
        methodology_version:
          type: string
        data_vintage:
          type: string
          format: date
        data_freshness:
          type: string
          enum:
            - hourly
            - daily
            - weekly
            - monthly
            - annually
            - on-demand
            - static
        data_latency_days:
          type: integer
        data_maturity:
          type: string
          enum:
            - production
            - research-preview
            - synthetic-only
            - roadmap
    Meta:
      type: object
      properties:
        request_id:
          type: string
        credits_used:
          type: integer
        credits_remaining:
          type: integer
        product:
          type: string
        version:
          type: string
        provenance:
          $ref: '#/components/schemas/Provenance'
    Error:
      type: object
      properties:
        error:
          type: object
          properties:
            code:
              type: string
            message:
              type: string
            status:
              type: integer
            request_id:
              type: string
            product:
              type: string
            docs_url:
              type: string
paths:
  /placecast/measurements/purchase/select:
    post:
      operationId: purchasePlacecastSelect
      summary: Purchase a Placecast Select analysis
      description: |
        Purchases and runs a reach-and-frequency Placecast Select analysis
        for an event scenario asset you own. `asset_id` rides in the body,
        not the path. Costs 30,000 credits, charged on acceptance. `202` +
        `status: "queued"`. Automatically refunded, less a 1-credit charge
        for the API call itself, if the run fails. Poll
        `GET /v2/placecast/measurements/{asset_id}` for completion.
      tags:
        - Measurements
      x-credit-cost: 30000
      x-motionworks-status: production
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/PlacecastSelectPurchaseRequest'
      responses:
        '202':
          description: Accepted and queued.
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    $ref: '#/components/schemas/PlacecastSelectResult'
                  meta:
                    $ref: '#/components/schemas/Meta'
        '400':
          description: Invalid request, or the asset is not in a purchasable state (`draft` or `failed`).
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '404':
          description: Unknown asset_id, or an asset that does not belong to your organization.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '409':
          description: '`measurement_request_id` reused with a different body.'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
  /placecast/measurements/{asset_id}:
    get:
      operationId: pollPlacecastMeasurement
      summary: Poll an event asset's measurement state
      description: |
        Returns the event asset's current lifecycle state — a dedicated,
        free poll route on this surface (unlike Viewcast Select, which
        reuses an existing endpoint; Placecast has no equivalent
        pre-existing per-asset poll route). 0 credits.
      tags:
        - Measurements
      x-credit-cost: 0
      x-motionworks-status: production
      parameters:
        - name: asset_id
          in: path
          required: true
          schema:
            type: string
      responses:
        '200':
          description: Current state.
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    $ref: '#/components/schemas/PlacecastMeasurementPollResult'
                  meta:
                    $ref: '#/components/schemas/Meta'
        '404':
          description: Unknown asset_id, or an asset that does not belong to your organization.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
  /placecast/select/{asset_id}:
    get:
      operationId: getPlacecastSelectReport
      summary: Retrieve a completed Placecast Select report
      description: |
        Returns the report for an event scenario asset once it is
        `completed` (final, permanently retrievable): resolved markets,
        per-place delivery detail, and confidence-banded `metric_slices`.
        There is no interim report before the run completes. 1 credit
        per call.
      tags:
        - Reports
      x-credit-cost: 1
      x-motionworks-status: production
      parameters:
        - name: asset_id
          in: path
          required: true
          schema:
            type: string
      responses:
        '200':
          description: The report.
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    $ref: '#/components/schemas/PlacecastSelectReport'
                  meta:
                    $ref: '#/components/schemas/Meta'
        '404':
          description: Unknown asset_id, an asset that does not belong to your organization, or an asset in `draft`, `queued`, or `failed` state (no report to serve yet).
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
x-tagGroups:
  - name: Placecast Select
    tags:
      - Measurements
      - Reports
tags:
  - name: Measurements
    description: Purchase a Placecast Select reach-and-frequency analysis and poll it for completion.
  - name: Reports
    description: Retrieve a completed Placecast Select analysis report.
