openapi: '3.1.0'
info:
  title: Motionworks API - Platform (Markets, Segments, Auth, Billing)
  version: 2.0.0
  description: >
    Shared platform endpoints for markets, audience segments, authentication,
    billing, health checks, and search.
  contact:
    name: Motionworks AI
    url: https://mworks.com
    email: api@mworks.com

servers:
  - url: https://api.mworks.com/v2
    description: Production

components:
  securitySchemes:
    apiKey:
      type: apiKey
      name: X-API-Key
      in: header
    bearerAuth:
      type: http
      scheme: bearer
      bearerFormat: JWT

  schemas:
    Market:
      type: object
      properties:
        market_id:
          type: string
        name:
          type: string
        dma_code:
          type: string
        population:
          type: integer
        households:
          type: integer
        spots_count:
          type: integer
        latitude:
          type: number
          format: double
        longitude:
          type: number
          format: double
        zoom:
          type: integer

    Segment:
      type: object
      properties:
        segment_id:
          type: string
        name:
          type: string
        description:
          type: string
        category:
          type: string
          enum: [Demographic, Behavioral, Lifestyle, Purchase Intent, Custom]
        universe_size:
          type: integer

    CreditBalance:
      type: object
      properties:
        org_id:
          type: string
        tier:
          type: string
          enum: [sandbox, growth, enterprise]
        credits_used:
          type: integer
        credits_remaining:
          type: integer
        credits_total:
          type: integer
        reset_at:
          type: string
          format: date-time

    HealthStatus:
      type: object
      properties:
        status:
          type: string
          enum: [healthy, degraded, down]
        version:
          type: string
        timestamp:
          type: string
          format: date-time

    DataFreshness:
      type: object
      properties:
        latest_measurement_period:
          type: string
          format: date
        profiles_updated_count:
          type: integer
        next_expected_refresh:
          type: string
          format: date
        data_sources:
          type: array
          items:
            type: object
            properties:
              name:
                type: string
              last_updated:
                type: string
                format: date-time
              status:
                type: string

    SignupRequest:
      type: object
      required: [email, company]
      properties:
        email:
          type: string
          format: email
        company:
          type: string
        use_case:
          type: string

    SignupResponse:
      type: object
      properties:
        org_id:
          type: string
        api_key:
          type: string
        tier:
          type: string
        credits_per_month:
          type: integer
        rate_limit:
          type: string
        dashboard_url:
          type: string

    # ─── Pricing Manifest (ADR-19a) ─────────────────────────────────────

    PricingOperation:
      type: object
      required: [id, display_name, credits_per_call, usd_per_call, qualifier, endpoints]
      properties:
        id:
          type: string
        display_name:
          type: string
        credits_per_call:
          type: number
          minimum: 0
        usd_per_call:
          type: number
          minimum: 0
        qualifier:
          type: string
          nullable: true
        endpoints:
          type: array
          items:
            type: string
        status:
          type: string
          enum: [production, planned, roadmap]
        planned_endpoint:
          type: string
        note:
          type: string

    PricingSku:
      type: object
      required: [id, billing_cadence]
      properties:
        id:
          type: string
        display_name:
          type: string
        billing_cadence:
          type: string
          enum: [monthly, annual, one-time]
        price_usd:
          type: number
          nullable: true
        included_calls:
          type: integer
          nullable: true
        included_calls_per_year:
          type: integer
          nullable: true
        expected_calls:
          type: integer
          nullable: true
        expected_calls_per_year:
          type: integer
          nullable: true
        included_operation_id:
          type: string
        status:
          type: string
          enum: [production, planned, roadmap]
        roadmap_issue:
          type: string
        included_locations:
          type: integer
          nullable: true
        rate_per_location_yr:
          type: number
        annual_minimum_usd:
          type: number
        scope:
          type: string
        place_universe:
          type: string
        price_usd_per_event:
          type: number
        included_select_events_per_year:
          type: integer
        overage_select_event_usd:
          type: number
        flexible_composition:
          type: boolean
        note:
          type: string

    PricingPlatformSku:
      type: object
      required: [id, billing_cadence]
      properties:
        id:
          type: string
        display_name:
          type: string
        billing_cadence:
          type: string
          enum: [monthly, annual, one-time]
        price_usd:
          type: number
          nullable: true
        weekly_impression_tier:
          type: string
        included_calls_per_year:
          type: integer
          nullable: true
        max_events:
          type: integer
          nullable: true
        price_usd_per_event:
          type: number
        pricing_basis:
          type: string
        cap:
          type: string
        included_campaigns_per_year:
          type: integer
        status:
          type: string
          enum: [production, planned, roadmap]
        note:
          type: string

    PricingAddon:
      type: object
      required: [id, billing_cadence, unit]
      properties:
        id:
          type: string
        display_name:
          type: string
        billing_cadence:
          type: string
          enum: [monthly, annual, one-time]
        price_usd_per_unit:
          type: number
          nullable: true
        price_usd_per_unit_pct_of_base:
          type: number
        unit:
          type: string
        max_units:
          type: integer
        kind:
          type: string
          enum:
            - registration_and_measurement
            - standing_definition
            - standing_definition_with_usage_budget
        planned_endpoint:
          type: string
        implied_credits_per_unit:
          type: number
        attaches_to:
          type: array
          items:
            type: string
        note:
          type: string

    PricingCrossCuttingOperation:
      type: object
      required: [id, credits_per_call, endpoints]
      properties:
        id:
          type: string
        display_name:
          type: string
        credits_per_call:
          type: number
          minimum: 0
        usd_per_call:
          type: number
          minimum: 0
        qualifier:
          type: string
          nullable: true
        endpoints:
          type: array
          items:
            type: string
        note:
          type: string

paths:
  /markets:
    get:
      operationId: listMarkets
      summary: List all DMA markets
      x-credit-cost: 1
      security:
        - apiKey: []
      parameters:
        - name: include_metrics
          in: query
          schema:
            type: boolean
            default: false
      responses:
        '200':
          description: Array of markets

  /markets/{id}:
    get:
      operationId: getMarket
      summary: Get market detail
      x-credit-cost: 1
      security:
        - apiKey: []
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: string
      responses:
        '200':
          description: Market detail
        '404':
          description: Market not found

  /segments:
    get:
      operationId: listSegments
      summary: List audience segments
      x-credit-cost: 1
      security:
        - apiKey: []
      responses:
        '200':
          description: Array of segments

  /segments/{id}:
    get:
      operationId: getSegment
      summary: Get segment detail
      x-credit-cost: 1
      security:
        - apiKey: []
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: string
      responses:
        '200':
          description: Segment detail
        '404':
          description: Segment not found

  /health:
    get:
      operationId: getHealth
      summary: API health check
      x-credit-cost: 0
      security: []
      responses:
        '200':
          description: Health status

  /health/data:
    get:
      operationId: getDataFreshness
      summary: Data freshness status
      x-credit-cost: 0
      security: []
      responses:
        '200':
          description: Data freshness info

  /signup:
    post:
      operationId: createAccount
      summary: Create sandbox account
      x-credit-cost: 0
      security: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/SignupRequest'
      responses:
        '201':
          description: Account created
        '409':
          description: Email already registered

  /billing/credits:
    get:
      operationId: getCreditBalance
      summary: Check credit balance
      x-credit-cost: 0
      security:
        - apiKey: []
      responses:
        '200':
          description: Credit balance
    post:
      operationId: createCreditsCheckoutSession
      summary: Create a Stripe Checkout session for credit pack, committed monthly plan, or pay-as-you-go
      description: |
        Creates a Stripe Checkout Session for one of:
          - one-time credit pack purchase (`price_pack_10k`/`100k`/`1M` → `mode=payment`,
            `success_url` `type=pack`),
          - recurring monthly committed plan (`price_sub_10k`/`100k` → `mode=subscription`,
            `success_url` `type=sub`),
          - pay-as-you-go metered subscription (`credits_metered` → `mode=subscription`,
            `success_url` `type=payg`). Stripe rejects `line_items[*][quantity]` on metered
            prices — quantity is derived from reported meter events — so quantity is omitted
            for `credits_metered`. PAYG carries no wallet top-up; credits flow via the
            Stripe Billing Meter API from the router after `deduct_credits` succeeds.

        Resolves the Stripe lookup key to a price ID at request time, attaches
        the caller's org context (via Supabase JWT → `org_members` lookup), and
        returns the hosted checkout URL inside a standard `{ data, meta }`
        envelope. Pack purchases additionally set `payment_intent_data.metadata`
        so a future `payment_intent.succeeded` webhook can top up the org's
        credit wallet (NOT set for `price_sub_*` or `credits_metered`).
        Auth REQUIRED. No credits charged.
      tags: [billing]
      x-credit-cost: 0
      x-motionworks-status: production
      security:
        - bearerAuth: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [priceKey]
              properties:
                priceKey:
                  type: string
                  enum:
                    - price_pack_10k
                    - price_pack_100k
                    - price_pack_1M
                    - price_sub_10k
                    - price_sub_100k
                    - credits_metered
                  description: Stripe lookup key for the desired credit pack, committed monthly subscription, or pay-as-you-go metered subscription
      responses:
        '200':
          description: Checkout session created
          content:
            application/json:
              schema:
                type: object
                required: [data, meta]
                properties:
                  data:
                    type: object
                    properties:
                      url:
                        type: string
                        format: uri
                        description: Stripe Checkout hosted page URL
                  meta:
                    type: object
                    properties:
                      request_id:
                        type: string
                      credits_used:
                        type: integer
                      credits_remaining:
                        type: integer
                      product:
                        type: string
                      version:
                        type: string
                      provenance:
                        type: object
        '400':
          description: Invalid priceKey
        '401':
          description: Missing or invalid JWT
        '503':
          description: Stripe or Supabase not configured

  /billing/credit-schedule:
    get:
      operationId: getCreditWeightSchedule
      summary: Get the versioned credit weight schedule manifest
      description: |
        Returns the complete per-endpoint credit cost table as a versioned JSON
        manifest (ADR-19). No authentication required — this is public pricing
        information. Cached aggressively (1h CDN, 1d edge). Clients should poll
        infrequently — the schedule changes only on quarterly recalibrations.
        Consumed at build time by www-mworks-com to render the
        `/docs/api/credit-weight-schedule` page and (eventually) the pricing
        calculator.
      tags: [billing]
      x-credit-cost: 0
      x-motionworks-status: production
      security: []
      responses:
        '200':
          description: Credit weight schedule manifest
          headers:
            Cache-Control:
              schema:
                type: string
              example: public, max-age=3600, s-maxage=86400
          content:
            application/json:
              schema:
                type: object
                required: [data, meta]
                properties:
                  data:
                    type: object
                    required: [version, ratePerCredit, updatedAt, endpoints]
                    properties:
                      version:
                        type: string
                        example: "1.0.0"
                      ratePerCredit:
                        type: number
                        example: 0.05
                      updatedAt:
                        type: string
                        format: date
                        example: "2026-05-05"
                      endpoints:
                        type: object
                        additionalProperties:
                          type: integer
                          minimum: 0
                        example:
                          "GET /v2/placecast/profiles/:id": 2
                          "GET /v2/placecast/profiles": 10
                          "POST /v2/placecast/select": 25
                  meta:
                    type: object
                    properties:
                      request_id:
                        type: string
                      credits_used:
                        type: integer
                      credits_remaining:
                        type: integer
                      product:
                        type: string
                      version:
                        type: string
                      provenance:
                        type: object

  /billing/pricing:
    get:
      operationId: getPricingManifest
      summary: Get the versioned pricing manifest
      description: |
        Returns the canonical Motionworks Pricing Manifest as a versioned
        JSON document (ADR-19a). The manifest is the single source of truth
        for product-level pricing: SKUs, operations, addons, surfaces, and
        overage policies. The legacy `GET /v2/billing/credit-schedule`
        endpoint (ADR-19) is now a derived projection of this manifest;
        both URLs stay live.

        Two surfaces:
          - `products` — per-call PAYG (Popcast, Pathcast roadmap, Placecast
            Profiles / Select / Premium, Custom add-ons). Overage continues
            at the per-credit rate.
          - `platform` — subscription-tier with a hard `contact_sales` cutoff
            at quota (Viewcast Profiles, Campaign Measurement).

        No authentication required — this is public pricing information.
        Cached aggressively (1h CDN, 1d edge). The optional `?v=<version>`
        query parameter is a CDN-cache-bust handle only — most CDNs
        include the query string in the cache key, so a different `?v=`
        value misses the edge cache. The handler does NOT version-pin:
        it always returns the current manifest. Clients that need the
        version should read `data.version` from the response body.
        Consumed at build time by www-mworks-com (pricing calculator +
        pricing pages, Track 2).
      tags: [billing]
      x-credit-cost: 0
      x-motionworks-status: production
      security: []
      parameters:
        - in: query
          name: v
          required: false
          schema:
            type: string
          description: |
            Optional CDN-cache-bust handle (e.g. `1.0.0`). The handler
            does not branch on this value; it's only useful because most
            CDNs include the query string in their cache key, so passing
            a different `?v=` forces an edge-cache miss. The served
            manifest version is always the current one and is carried in
            `data.version` in the response body.
      responses:
        '200':
          description: Pricing manifest
          headers:
            Cache-Control:
              schema:
                type: string
              example: public, max-age=3600, s-maxage=86400
          content:
            application/json:
              schema:
                type: object
                required: [data, meta]
                properties:
                  data:
                    type: object
                    required:
                      - $schema
                      - version
                      - updated_at
                      - currency
                      - credit
                      - overage_policy
                      - products
                      - platform
                      - reference_operations
                      - free_operations
                      - runtime_credit_schedule
                    properties:
                      $schema:
                        type: string
                        format: uri
                        example: "https://docs.mworks.com/schemas/pricing/v1.json"
                      version:
                        type: string
                        example: "1.0.0"
                      updated_at:
                        type: string
                        format: date
                        example: "2026-05-08"
                      currency:
                        type: string
                        enum: [USD]
                      credit:
                        type: object
                        required:
                          - rate_per_credit_usd
                          - stripe_lookup_key_payg
                          - free_monthly_grant
                          - free_grant_accumulates
                        properties:
                          rate_per_credit_usd:
                            type: number
                            example: 0.05
                          stripe_lookup_key_payg:
                            type: string
                            example: credits_metered
                          free_monthly_grant:
                            type: integer
                            example: 2000
                          free_grant_accumulates:
                            type: boolean
                      overage_policy:
                        type: object
                        required: [products, platform]
                        properties:
                          products:
                            type: string
                            enum: [payg, contact_sales]
                          platform:
                            type: string
                            enum: [payg, contact_sales]
                      products:
                        type: object
                        description: |
                          Map of API-metered products keyed by id (e.g.
                          `popcast`, `placecast_profiles`). Each entry
                          carries `display_name`, `kicker`, `operations`,
                          `skus`, and optional `addons`.
                        additionalProperties:
                          type: object
                          required: [display_name, kicker, operations, skus]
                          properties:
                            display_name:
                              type: string
                            kicker:
                              type: string
                            status:
                              type: string
                              enum: [production, planned, roadmap]
                            operations:
                              type: array
                              items:
                                $ref: '#/components/schemas/PricingOperation'
                            skus:
                              type: array
                              items:
                                $ref: '#/components/schemas/PricingSku'
                            addons:
                              type: array
                              items:
                                $ref: '#/components/schemas/PricingAddon'
                      platform:
                        type: object
                        description: |
                          Map of subscription-tier products keyed by id
                          (e.g. `viewcast_profiles`, `campaign_measurement`).
                          Hard cutoff at quota — overage = `contact_sales`.
                        additionalProperties:
                          type: object
                          required: [display_name, kicker, operations]
                          properties:
                            display_name:
                              type: string
                            kicker:
                              type: string
                            status:
                              type: string
                              enum: [production, planned, roadmap]
                            operations:
                              type: array
                              items:
                                $ref: '#/components/schemas/PricingOperation'
                            skus:
                              type: array
                              items:
                                $ref: '#/components/schemas/PricingPlatformSku'
                            skus_subscription:
                              type: array
                              items:
                                $ref: '#/components/schemas/PricingPlatformSku'
                            skus_per_event:
                              type: array
                              items:
                                $ref: '#/components/schemas/PricingPlatformSku'
                            legacy_endpoints:
                              type: object
                              required: [note]
                              properties:
                                note:
                                  type: string
                      reference_operations:
                        type: array
                        description: |
                          Cross-cutting reference-tier operations (low-cost
                          lookups not owned by a single product).
                        items:
                          $ref: '#/components/schemas/PricingCrossCuttingOperation'
                      free_operations:
                        type: array
                        description: |
                          Cross-cutting 0-credit operations (health, signup,
                          billing reads, redemption flow).
                        items:
                          $ref: '#/components/schemas/PricingCrossCuttingOperation'
                      runtime_credit_schedule:
                        type: object
                        required: [version, updated_at, endpoints]
                        description: |
                          Per-endpoint credit costs (legacy projection target).
                          The `GET /v2/billing/credit-schedule` endpoint emits
                          this same data in the legacy camelCase shape via
                          `getCreditScheduleFromManifest`. See ADR-19a.
                        properties:
                          version:
                            type: string
                          updated_at:
                            type: string
                            format: date
                          endpoints:
                            type: object
                            additionalProperties:
                              type: integer
                              minimum: 0
                  meta:
                    type: object
                    properties:
                      request_id:
                        type: string
                      credits_used:
                        type: integer
                      credits_remaining:
                        type: integer
                      product:
                        type: string
                      version:
                        type: string
                      provenance:
                        type: object

  /billing/checkout:
    post:
      operationId: createCheckoutSession
      summary: Create a Stripe Checkout subscription session
      description: |
        Creates a Stripe Checkout Session for a metered credit subscription
        ($0.05/credit, billed monthly based on consumption). Returns the hosted
        checkout URL inside a standard `{ data, meta }` envelope. No credits charged.
      x-credit-cost: 0
      x-motionworks-status: production
      security: []
      responses:
        '200':
          description: Checkout session created
          content:
            application/json:
              schema:
                type: object
                required: [data, meta]
                properties:
                  data:
                    type: object
                    properties:
                      url:
                        type: string
                        format: uri
                        description: Stripe Checkout hosted page URL
                  meta:
                    type: object
                    properties:
                      request_id:
                        type: string
                      credits_used:
                        type: integer
                      credits_remaining:
                        type: integer
                      product:
                        type: string
                      version:
                        type: string
                      provenance:
                        type: object
        '400':
          description: Stripe API error (passthrough)
        '500':
          description: Internal error during checkout-session creation
        '503':
          description: Stripe is not configured (missing key or price id)

  /billing/webhook:
    post:
      operationId: stripeWebhook
      summary: Stripe webhook receiver (signature-verified)
      description: |
        Receives Stripe webhook events. Verifies the `Stripe-Signature` header
        with constant-time HMAC-SHA256 against the raw body, with a 5-minute
        freshness window. Returns `{ received: true }` on success — the body
        is consumed by Stripe (which only reads the HTTP status), so this
        endpoint is exempt from the standard `{ data, meta }` envelope.
      x-credit-cost: 0
      x-motionworks-status: production
      security: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
      parameters:
        - in: header
          name: stripe-signature
          required: true
          schema:
            type: string
          description: Stripe signature header (`t=<unix-ts>,v1=<hex-hmac>`)
      responses:
        '200':
          description: Event acknowledged
          content:
            application/json:
              schema:
                type: object
                properties:
                  received:
                    type: boolean
                    enum: [true]
        '400':
          description: Invalid signature, malformed signature, or unparseable JSON body
        '503':
          description: Webhook secret is not configured
