openapi: 3.1.0
info:
  title: Platform
  version: 2.0.0
  description: |
    Shared platform endpoints that every Motionworks product relies on:

    - **API keys** — create, list, rotate and revoke the keys your organization uses to call the data APIs. Managed from a signed-in session, never with a key.
    - **Billing & usage** — your credit balance and wallet, public pricing, feature entitlements, daily usage, subscription and payment details, and the Stripe-hosted checkout and customer portal.
    - **Markets & segments** — reference lists of DMA markets and audience segments (not yet available).
    - **Health** — service and data-freshness status.

    Two kinds of credential appear here. Data endpoints across the Motionworks API take an org-scoped API key (`X-API-Key: mw_…`). Account and billing management takes a signed-in session (`Authorization: Bearer <jwt>`) instead — you manage keys with a session and use keys to call data. Every endpoint in this section is free (0 credits) except the Markets and Segments reference reads (1 credit).
  contact:
    name: Motionworks AI
    url: https://mworks.com
    email: api@mworks.com
servers:
  - url: https://api2.mworks.com/v2
    description: Production
components:
  securitySchemes:
    apiKey:
      type: apiKey
      name: X-API-Key
      in: header
      description: 'An org-scoped Motionworks API key (`X-API-Key: mw_…`). Create one under API keys.'
    bearerAuth:
      type: http
      scheme: bearer
      bearerFormat: JWT
      description: |
        A signed-in user session (`Authorization: Bearer <jwt>`), as used by the Motionworks console. Required for account and billing management.
  schemas:
    Market:
      type: object
      properties:
        market_id:
          type: string
          description: Motionworks market id.
        name:
          type: string
          description: Market name.
        dma_code:
          type: string
          description: Nielsen DMA code.
        population:
          type: integer
          description: Resident population.
        households:
          type: integer
          description: Households.
        spots_count:
          type: integer
          description: Out-of-home display faces measured in the market.
        latitude:
          type: number
          format: double
          description: Latitude of the map center for the market.
        longitude:
          type: number
          format: double
          description: Longitude of the map center for the market.
        zoom:
          type: integer
          description: Suggested map zoom level to frame the market.
      description: A US DMA market.
    Segment:
      type: object
      properties:
        segment_id:
          type: string
          description: Segment id.
        name:
          type: string
          description: Segment name.
        description:
          type: string
          description: What the segment represents.
        category:
          type: string
          enum:
            - Demographic
            - Behavioral
            - Lifestyle
            - Purchase Intent
            - Custom
          description: Segment category.
        universe_size:
          type: integer
          description: Persons in the segment nationally.
      description: An audience segment.
    CreditBalance:
      type: object
      properties:
        org_id:
          type: string
          description: Your organization id.
        tier:
          type: string
          enum:
            - sandbox
            - growth
            - enterprise
          description: Your organization's tier.
        credits_used:
          type: integer
          description: Credits used this period.
        credits_remaining:
          type: integer
          description: Credits remaining.
        credits_total:
          type: integer
          description: Total credits available this period.
        reset_at:
          type: string
          format: date-time
          description: When the period resets (ISO 8601).
      description: Your organization's credit balance for the current period.
    HealthStatus:
      type: object
      properties:
        status:
          type: string
          enum:
            - healthy
            - degraded
            - down
          description: '`healthy`, `degraded` or `down`.'
        version:
          type: string
          description: API version.
        timestamp:
          type: string
          format: date-time
          description: When the status was checked (ISO 8601).
    DataFreshness:
      type: object
      properties:
        latest_measurement_period:
          type: string
          format: date
          description: The latest measurement period published.
        profiles_updated_count:
          type: integer
          description: Profiles updated in the latest refresh.
        next_expected_refresh:
          type: string
          format: date
          description: When the next refresh is expected.
        data_sources:
          type: array
          items:
            type: object
            properties:
              name:
                type: string
              last_updated:
                type: string
                format: date-time
              status:
                type: string
          description: Status of each underlying data source.
    CreateApiKeyRequest:
      type: object
      required:
        - name
      properties:
        name:
          type: string
          minLength: 1
          maxLength: 100
          description: |
            A label for the key, shown on usage and billing reports so you can tell keys apart. Trimmed; 1–100 characters.
        scope:
          type: string
          enum:
            - read
          description: |
            Reserved. Only `read` is accepted today; any other value returns 400 `INVALID_SCOPE`. Narrower scopes will be offered in a future release.
    CreateApiKeyResponse:
      type: object
      required:
        - id
        - key
        - name
        - prefix
        - scope
        - _warning
      properties:
        id:
          type: string
          format: uuid
          description: Key id. Use it to rotate or revoke the key.
        key:
          type: string
          description: The plaintext API key. Returned only in this response — store it now.
        name:
          type: string
          description: The label you gave the key.
        prefix:
          type: string
          description: The first characters of the key, for identifying it in lists.
        scope:
          type: string
          enum:
            - read
          description: Key scope (`read`).
        created_at:
          type: string
          format: date-time
          description: When the key was created (ISO 8601).
        _warning:
          type: string
          description: Reminder that the plaintext key is shown once and cannot be retrieved later.
    ApiKeyListItem:
      type: object
      required:
        - id
        - name
        - prefix
        - scope
        - status
        - created_at
      properties:
        id:
          type: string
          format: uuid
          description: Key id.
        name:
          type: string
          description: The label you gave the key.
        prefix:
          type: string
          description: The first characters of the key, for identifying it.
        scope:
          type: string
          description: Key scope.
        status:
          type: string
          enum:
            - active
            - rotating
            - revoked
            - expired
          description: |
            `active`; `rotating` (rotated within the last 24 hours, the old secret still works); `revoked`; or `expired`.
        last_used_at:
          type: string
          format: date-time
          nullable: true
          description: When the key was last used, or `null` if never.
        created_at:
          type: string
          format: date-time
          description: When the key was created.
        expires_at:
          type: string
          format: date-time
          nullable: true
          description: When the key expires, if an expiry is set.
      description: An API key's metadata. The key material is never included.
    RotateApiKeyResponse:
      type: object
      required:
        - id
        - key
        - prefix
        - _warning
      properties:
        id:
          type: string
          format: uuid
          description: Key id (unchanged by rotation).
        key:
          type: string
          description: The new plaintext API key. Returned only in this response — store it now.
        prefix:
          type: string
          description: The first characters of the new key.
        scope:
          type: string
          enum:
            - read
          description: Key scope (`read`).
        rotation_expires_at:
          type: string
          format: date-time
          nullable: true
          description: |
            When the old key stops working (24 hours after rotation). `null` when `immediate=true` was passed and the old key is already revoked. If `_revoke_failure` is set the old key is still live until this time.
        _warning:
          type: string
          description: Reminder that the plaintext key is shown once and cannot be retrieved later.
        _revoke_failure:
          type: boolean
          enum:
            - true
          description: |
            Present and `true` only when `immediate=true` was passed, the rotation succeeded, but the immediate revoke of the old key failed. Your new key works; the old key stays valid until `rotation_expires_at`. Call `DELETE /v2/account/keys/{id}` to revoke it now.
        _revoke_failure_message:
          type: string
          description: Explanation accompanying `_revoke_failure`.
    PricingOperation:
      type: object
      required:
        - id
        - display_name
        - credits_per_call
        - usd_per_call
        - qualifier
        - endpoints
      properties:
        id:
          type: string
          description: Operation id.
        display_name:
          type: string
          description: Operation name.
        credits_per_call:
          type: number
          minimum: 0
          description: Credits charged per call.
        usd_per_call:
          type: number
          minimum: 0
          description: Equivalent price per call in USD.
        qualifier:
          type: string
          nullable: true
          description: When the cost applies, if it is conditional.
        endpoints:
          type: array
          items:
            type: string
          description: The endpoints (`METHOD /path`) this cost covers.
        status:
          type: string
          enum:
            - production
            - planned
            - roadmap
          description: '`production`, `planned` or `roadmap`.'
        planned_endpoint:
          type: string
          description: The endpoint a planned operation will ship at.
        note:
          type: string
          description: Additional pricing note.
      description: A billable API operation and its credit cost.
    PricingSku:
      type: object
      required:
        - id
        - billing_cadence
      properties:
        id:
          type: string
          description: SKU id.
        display_name:
          type: string
          description: SKU name.
        billing_cadence:
          type: string
          enum:
            - monthly
            - annual
            - one-time
          description: '`monthly`, `annual` or `one-time`.'
        price_usd:
          type: number
          nullable: true
          description: Price in USD per billing period.
        included_calls:
          type: integer
          nullable: true
          description: Calls included per period.
        included_calls_per_year:
          type: integer
          nullable: true
          description: Calls included per year.
        expected_calls:
          type: integer
          nullable: true
          description: Typical call volume the SKU is sized for, per period.
        expected_calls_per_year:
          type: integer
          nullable: true
          description: Typical call volume the SKU is sized for, per year.
        included_operation_id:
          type: string
          description: The operation the included calls apply to.
        status:
          type: string
          enum:
            - production
            - planned
            - roadmap
          description: '`production`, `planned` or `roadmap`.'
        roadmap_issue:
          type: string
          description: Reference for a roadmap SKU.
        included_locations:
          type: integer
          nullable: true
          description: Locations included.
        rate_per_location_yr:
          type: number
          description: Price per location per year in USD.
        annual_minimum_usd:
          type: number
          description: Annual minimum commitment in USD.
        scope:
          type: string
          description: What the SKU covers.
        place_universe:
          type: string
          description: The set of places the SKU applies to.
        price_usd_per_event:
          type: number
          description: Price per event in USD.
        included_select_events_per_year:
          type: integer
          description: Placecast Select events included per year.
        overage_select_event_usd:
          type: number
          description: Price per Placecast Select event beyond the included volume, in USD.
        flexible_composition:
          type: boolean
          description: Whether the included volume can be split across operations.
        note:
          type: string
          description: Additional pricing note.
      description: A purchasable product SKU.
    PricingPlatformSku:
      type: object
      required:
        - id
        - billing_cadence
      properties:
        id:
          type: string
          description: Tier id.
        display_name:
          type: string
          description: Tier name.
        billing_cadence:
          type: string
          enum:
            - monthly
            - annual
            - one-time
          description: '`monthly`, `annual` or `one-time`.'
        price_usd:
          type: number
          nullable: true
          description: Price in USD per billing period.
        weekly_impression_tier:
          type: string
          description: Weekly impression band the tier is sized for.
        included_calls_per_year:
          type: integer
          nullable: true
          description: Calls included per year.
        max_events:
          type: integer
          nullable: true
          description: Maximum events included.
        price_usd_per_event:
          type: number
          description: Price per event in USD.
        pricing_basis:
          type: string
          description: What the price is based on.
        cap:
          type: string
          description: The usage cap for the tier.
        included_campaigns_per_year:
          type: integer
          description: Campaigns included per year.
        status:
          type: string
          enum:
            - production
            - planned
            - roadmap
          description: '`production`, `planned` or `roadmap`.'
        note:
          type: string
          description: Additional pricing note.
      description: A platform subscription tier.
    PricingAddon:
      type: object
      required:
        - id
        - billing_cadence
        - unit
      properties:
        id:
          type: string
          description: Add-on id.
        display_name:
          type: string
          description: Add-on name.
        billing_cadence:
          type: string
          enum:
            - monthly
            - annual
            - one-time
          description: '`monthly`, `annual` or `one-time`.'
        price_usd_per_unit:
          type: number
          nullable: true
          description: Price per unit in USD.
        price_usd_per_unit_pct_of_base:
          type: number
          description: Price per unit as a percentage of the base SKU price.
        unit:
          type: string
          description: What one unit is.
        max_units:
          type: integer
          description: Maximum units that can be attached.
        kind:
          type: string
          enum:
            - registration_and_measurement
            - standing_definition
            - standing_definition_with_usage_budget
          description: |
            How the add-on behaves: `registration_and_measurement`, `standing_definition`, or `standing_definition_with_usage_budget`.
        planned_endpoint:
          type: string
          description: The endpoint a planned add-on will ship at.
        implied_credits_per_unit:
          type: number
          description: Equivalent credits per unit.
        attaches_to:
          type: array
          items:
            type: string
          description: SKU ids the add-on can attach to.
        note:
          type: string
          description: Additional pricing note.
      description: An add-on that attaches to a SKU.
    PricingCrossCuttingOperation:
      type: object
      required:
        - id
        - credits_per_call
        - endpoints
      properties:
        id:
          type: string
          description: Operation id.
        display_name:
          type: string
          description: Operation name.
        credits_per_call:
          type: number
          minimum: 0
          description: Credits charged per call.
        usd_per_call:
          type: number
          minimum: 0
          description: Equivalent price per call in USD.
        qualifier:
          type: string
          nullable: true
          description: When the cost applies, if it is conditional.
        endpoints:
          type: array
          items:
            type: string
          description: The endpoints (`METHOD /path`) this cost covers.
        note:
          type: string
          description: Additional pricing note.
      description: An operation priced independently of any one product.
    FeatureKey:
      type: string
      description: |
        A Motionworks feature key in the form `family.product.feature`. Keys are stable — once shipped they are never renamed — so you can match on them in code.
      enum:
        - markets.reference.geography_tiles
        - oohdisplays.displays.custom_inventory
        - oohdisplays.displays.face_point_tiles
        - oohdisplays.displays.inventory_tiles
        - oohdisplays.displays.library
        - oohdisplays.displays.on_demand_detail
        - oohdisplays.displays.search
        - pathcast.paths.path_line_tiles
        - placecast.places.autocomplete
        - placecast.places.poi_place_tiles
        - placecast.places.poi_retrieval
        - placecast.places.search
        - placecast.profiles.building_dca_tiles
        - placecast.profiles.profile_retrieval
        - popcast.anytime.segment_occ_tiles
        - popcast.at_home.segment_pop_tiles
        - popcast.segments.segment_search
        - viewcast.measurements
        - viewcast.profile_retrieval
    PlatformError:
      type: object
      required:
        - error
      properties:
        error:
          type: object
          required:
            - code
            - message
            - status
            - request_id
          properties:
            code:
              type: string
              description: |
                Stable machine-readable error code (e.g. `UNAUTHORIZED`,
                `PORTAL_NOT_AVAILABLE`, `SUBSCRIPTION_NOT_FOUND`).
            message:
              type: string
              description: A human-readable message safe to show to users.
            status:
              type: integer
              minimum: 400
              maximum: 599
            request_id:
              type: string
            product:
              type: string
              enum:
                - platform
            docs_url:
              type: string
              format: uri
            context:
              type: object
              additionalProperties: true
              description: |
                IETF problem-details extension slot for per-error
                structured fields (e.g. `phase`, `plan`, `reason`,
                `retry_after_seconds`).
          description: |
            The error: a stable `code`, a human-readable `message`, the HTTP `status`, your `request_id`, the `product` that answered, a `docs_url` for the code, and optional `context` with the specifics.
      description: Standard error envelope.
paths:
  /markets:
    get:
      operationId: listMarkets
      summary: List DMA markets
      tags:
        - markets
      description: |
        Not yet available — returns 501 `NOT_IMPLEMENTED` today. It will list the US DMA markets Motionworks measures. Authenticate with an org-scoped API key. Priced at 1 credit per call.
      x-credit-cost: 1
      security:
        - apiKey: []
      parameters:
        - name: include_metrics
          in: query
          schema:
            type: boolean
            default: false
          description: Include summary metrics on each market.
      responses:
        '200':
          description: The list of markets (once the endpoint is live).
  /markets/{id}:
    get:
      operationId: getMarket
      summary: Read one market
      tags:
        - markets
      description: |
        Not yet available — returns 501 `NOT_IMPLEMENTED` today. It will return one DMA market by `market_id`. Authenticate with an org-scoped API key. Priced at 1 credit per call.
      x-credit-cost: 1
      security:
        - apiKey: []
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: string
          description: The market's `market_id`.
      responses:
        '200':
          description: The market (once the endpoint is live).
        '404':
          description: Unknown `market_id`.
  /segments:
    get:
      operationId: listSegments
      summary: List audience segments
      tags:
        - segments
      description: |
        Not yet available — returns 501 `NOT_IMPLEMENTED` today. It will list the audience segments available to your organization. Authenticate with an org-scoped API key. Priced at 1 credit per call.
      x-credit-cost: 1
      security:
        - apiKey: []
      responses:
        '200':
          description: The list of segments (once the endpoint is live).
  /segments/{id}:
    get:
      operationId: getSegment
      summary: Read one segment
      tags:
        - segments
      description: |
        Not yet available — returns 501 `NOT_IMPLEMENTED` today. It will return one audience segment by `segment_id`. Authenticate with an org-scoped API key. Priced at 1 credit per call.
      x-credit-cost: 1
      security:
        - apiKey: []
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: string
          description: The segment's `segment_id`.
      responses:
        '200':
          description: The segment (once the endpoint is live).
        '404':
          description: Unknown `segment_id`.
  /health:
    get:
      operationId: getHealth
      summary: Check service health
      tags:
        - health
      description: Returns the API's current status and version. No authentication, no charge — safe to poll.
      x-credit-cost: 0
      security: []
      responses:
        '200':
          description: Current service status.
  /health/data:
    get:
      operationId: getDataFreshness
      summary: Check data freshness
      tags:
        - health
      description: |
        Data-freshness snapshot: latest published measurement period, next expected refresh, and per-source status. No authentication, no charge.
      x-credit-cost: 0
      security: []
      responses:
        '200':
          description: Data-freshness snapshot.
  /account/keys:
    post:
      operationId: createAccountKey
      summary: Create an API key
      description: |
        Creates a new API key for your organization and returns the plaintext key **once** — store it immediately; it cannot be retrieved again. Requires a signed-in session. Keys are managed with a session and used with `X-API-Key`; a key cannot create keys.

        Any member of your organization can create keys. Each organization has a maximum number of active keys for its plan; when the limit is reached the response is 409 `KEY_LIMIT_REACHED` — revoke a key you no longer use, or contact sales to raise the limit. `scope` is reserved: only `read` is accepted today. Key operations are limited to 10 requests per minute per user, shared across create, list, rotate and revoke; a 429 carries `Retry-After`.
      tags:
        - account
      x-credit-cost: 0
      x-motionworks-status: production
      security:
        - bearerAuth: []
      parameters:
        - name: org_id
          in: query
          required: false
          schema:
            type: string
            format: uuid
          description: Only needed when you belong to more than one organization and want a non-default one. When omitted, your active organization is used (earliest-joined fallback when none is active). Switch your active organization with `POST /v2/workspace/orgs/{org_id}/activate`.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/CreateApiKeyRequest'
      responses:
        '201':
          description: Key created. The plaintext `key` appears in this response only.
          content:
            application/json:
              schema:
                type: object
                required:
                  - data
                  - meta
                properties:
                  data:
                    $ref: '#/components/schemas/CreateApiKeyResponse'
                  meta:
                    type: object
        '400':
          description: |
            Validation error: empty body, `name` missing or over 100 characters, or a `scope` other than `read` (`INVALID_SCOPE`).
        '401':
          description: Missing or invalid session token.
        '403':
          description: You do not have permission to manage keys for this organization.
        '404':
          description: You are not a member of any organization (`NO_ORG`).
        '409':
          description: |
            `KEY_LIMIT_REACHED` — the organization is at its active-key limit. `MULTIPLE_ORGS` — rare residual: you belong to several organizations, passed no `org_id`, and none of your memberships is marked active (set one via `POST /v2/workspace/orgs/{org_id}/activate`). When an active organization exists, it is used automatically and this error does not occur.
        '429':
          description: Rate limited (shared 10-per-minute key-operations bucket). `Retry-After` says how long to wait.
    get:
      operationId: listAccountKeys
      summary: List API keys
      description: |
        Returns the metadata of your organization's API keys — name, prefix, status, last use, expiry. The key material itself is never returned; the plaintext exists only in the create and rotate responses. Requires a signed-in session.

        Owners and admins see every key in the organization. Members and billing users see only the keys they created themselves.
      tags:
        - account
      x-credit-cost: 0
      x-motionworks-status: production
      security:
        - bearerAuth: []
      parameters:
        - name: include_revoked
          in: query
          required: false
          schema:
            type: boolean
            default: false
          description: Include revoked and expired keys in the list.
        - name: org_id
          in: query
          required: false
          schema:
            type: string
            format: uuid
          description: Only needed when you belong to more than one organization and want a non-default one. When omitted, your active organization is used (earliest-joined fallback when none is active).
      responses:
        '200':
          description: The list of keys.
          content:
            application/json:
              schema:
                type: object
                required:
                  - data
                  - meta
                properties:
                  data:
                    type: array
                    items:
                      $ref: '#/components/schemas/ApiKeyListItem'
                  meta:
                    type: object
        '401':
          description: Missing or invalid session token.
        '404':
          description: You are not a member of any organization (`NO_ORG`).
        '409':
          description: 'Rare residual: you belong to several organizations, passed no `org_id`, and none of your memberships is marked active (`MULTIPLE_ORGS`). When an active organization exists, it is used automatically and this error does not occur.'
        '429':
          description: Rate limited (shared 10-per-minute key-operations bucket). `Retry-After` says how long to wait.
  /account/keys/{id}/rotate:
    post:
      operationId: rotateAccountKey
      summary: Rotate an API key
      description: |
        Replaces the key's secret in place and returns the **new** plaintext key once. The old key keeps working for 24 hours so you can roll deployments without downtime; pass `immediate=true` to revoke it at once instead. Requires a signed-in session.

        Owners and admins can rotate any active key in the organization. Members and billing users can rotate only keys they created; rotating someone else's key returns 403.
      tags:
        - account
      x-credit-cost: 0
      x-motionworks-status: production
      security:
        - bearerAuth: []
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: string
            format: uuid
          description: The key's `id` from the create or list response.
        - name: immediate
          in: query
          required: false
          schema:
            type: boolean
            default: false
          description: When `true`, the old key stops working immediately instead of after the 24-hour grace period.
      responses:
        '200':
          description: Key rotated. The new plaintext `key` appears in this response only.
          content:
            application/json:
              schema:
                type: object
                required:
                  - data
                  - meta
                properties:
                  data:
                    $ref: '#/components/schemas/RotateApiKeyResponse'
                  meta:
                    type: object
        '400':
          description: Malformed key id.
        '401':
          description: Missing or invalid session token.
        '403':
          description: You are not an owner or admin and did not create this key.
        '404':
          description: Key not found in your organization.
        '429':
          description: Rate limited (shared 10-per-minute key-operations bucket). `Retry-After` says how long to wait.
  /account/keys/{id}:
    delete:
      operationId: revokeAccountKey
      summary: Revoke an API key
      description: |
        Revokes the key immediately; calls made with it fail from this point on. Requires a signed-in session.

        Owners and admins can revoke any active or rotating key in the organization. Members and billing users can revoke only keys they created; targeting someone else's key returns 403.
      tags:
        - account
      x-credit-cost: 0
      x-motionworks-status: production
      security:
        - bearerAuth: []
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: string
            format: uuid
          description: The key's `id` from the create or list response.
      responses:
        '204':
          description: Key revoked. No body.
        '400':
          description: Malformed key id.
        '401':
          description: Missing or invalid session token.
        '403':
          description: You are not an owner or admin and did not create this key.
        '404':
          description: Key not found in your organization.
        '429':
          description: Rate limited (shared 10-per-minute key-operations bucket). `Retry-After` says how long to wait.
  /billing/credits:
    get:
      operationId: getCreditBalance
      summary: Check your credit balance
      tags:
        - billing
      description: |
        Not yet available — returns 501 `NOT_IMPLEMENTED` today. It will return your organization's credit balance for the current period. Authenticate with an org-scoped API key. Free.
      x-credit-cost: 0
      security:
        - apiKey: []
      responses:
        '200':
          description: Current credit balance (once the endpoint is live).
    post:
      operationId: createCreditsCheckoutSession
      summary: Start a credit purchase (Stripe Checkout)
      description: |
        Creates a Stripe Checkout session for your organization and returns the hosted checkout URL to send the user to. Choose what to buy with `priceKey`: a one-time credit pack, a committed monthly plan, or the pay-as-you-go metered subscription, which bills monthly on what you actually use.

        The Motionworks console drives this flow for you; call it directly only if you are building your own purchase UI. Requires a signed-in session. Free.
      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: |
                    What to buy: a one-time credit pack (`price_pack_10k`, `price_pack_100k`, `price_pack_1M`), a committed monthly plan (`price_sub_10k`, `price_sub_100k`), or the pay-as-you-go metered subscription (`credits_metered`).
                returnUrl:
                  type: string
                  format: uri
                  description: |
                    Optional `https` URL to send the user back to when checkout completes or is cancelled. It must be `https`, carry no `#fragment`, and be on a Motionworks-operated origin (the console or app); anything else falls back to the default return page. Motionworks appends `?checkout=success` or `?checkout=cancel` (your own query string is kept). On success the URL also carries `type=pack`, `type=sub` or `type=payg`.
                  example: https://console.mworks.com/console/
      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: The Stripe Checkout page to send the user to.
                  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: Unknown `priceKey`.
        '401':
          description: Missing or invalid session token.
        '503':
          description: Purchases are temporarily unavailable.
  /billing/credit-schedule:
    get:
      operationId: getCreditWeightSchedule
      summary: '[Deprecated] Get the credit weight schedule'
      deprecated: true
      description: |
        **Deprecated — use `GET /v2/billing/pricing`.** Returns the legacy per-endpoint credit-cost table as a versioned JSON document. No authentication, no charge.

        The response carries `Deprecation`, `Sunset` and `Link: <…/v2/billing/pricing>; rel="successor-version"` headers, and the same notice in `meta.deprecation`. The wire shape will not change while the endpoint remains available.
      tags:
        - billing
      x-credit-cost: 0
      x-motionworks-status: deprecated
      x-deprecated-sunset: '2026-06-08'
      x-successor: /v2/billing/pricing
      security: []
      responses:
        '200':
          description: The legacy credit weight schedule.
          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
                        description: Schedule version.
                      ratePerCredit:
                        type: number
                        example: 0.05
                        description: Price of one credit in USD.
                      updatedAt:
                        type: string
                        format: date
                        example: '2026-05-05'
                        description: When the schedule was last updated (ISO 8601).
                      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
                        description: Credit cost per endpoint, keyed by `METHOD /path`.
                  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 pricing manifest
      description: |
        Returns the Motionworks pricing manifest: the price of a credit, the credit cost of every operation, the SKUs and add-ons of each product, the platform subscription tiers, the overage policy and the per-tier rate limits — as one versioned JSON document. It is the definitive statement of Motionworks pricing; the pricing pages on mworks.com are built from it.

        No authentication, no charge. Cache the manifest on your side and re-fetch when `data.version` changes.
      tags:
        - billing
      x-credit-cost: 0
      x-motionworks-status: production
      security: []
      parameters:
        - in: query
          name: v
          required: false
          schema:
            type: string
          description: |
            Optional. It does not select a version — the current manifest is always returned; read the served version from `data.version`.
      responses:
        '200':
          description: The 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
                      - rate_limits
                    properties:
                      $schema:
                        type: string
                        format: uri
                        example: https://docs.mworks.com/schemas/pricing/v1.json
                        description: JSON Schema the manifest conforms to.
                      version:
                        type: string
                        example: 1.0.0
                        description: Manifest version.
                      updated_at:
                        type: string
                        format: date
                        example: '2026-05-08'
                        description: When the manifest was last updated (ISO 8601).
                      currency:
                        type: string
                        enum:
                          - USD
                        description: Currency of every price in the manifest (`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
                        description: 'The credit: its price and how credits are purchased.'
                      overage_policy:
                        type: object
                        required:
                          - products
                          - platform
                        properties:
                          products:
                            type: string
                            enum:
                              - payg
                              - contact_sales
                          platform:
                            type: string
                            enum:
                              - payg
                              - contact_sales
                        description: What happens when a plan's included volume is used up.
                      products:
                        type: object
                        description: |
                          API-metered products keyed by id (for example `popcast`, `placecast_profiles`). Each 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: |
                          Subscription-tier products keyed by id (for example `displays_profiles`, `viewcast_select`). Usage stops at the tier's quota; overage means talking to 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: Low-cost reference lookups not owned by a single product.
                        items:
                          $ref: '#/components/schemas/PricingCrossCuttingOperation'
                      free_operations:
                        type: array
                        description: Operations that cost 0 credits (health, billing reads, redemption).
                        items:
                          $ref: '#/components/schemas/PricingCrossCuttingOperation'
                      rate_limits:
                        type: object
                        description: |
                          Request-rate limits per tier (`guest`, `free`, `payg`) in requests per minute. Tiles and autocomplete are unrestricted at every tier, and a feature your organization has been explicitly granted is unlimited.
                        required:
                          - unit
                          - by_tier
                          - unrestricted_features
                          - notes
                        properties:
                          unit:
                            type: string
                            enum:
                              - requests_per_minute
                          by_tier:
                            type: object
                            required:
                              - guest
                              - free
                              - payg
                            properties:
                              guest:
                                type: object
                                required:
                                  - per_min
                                properties:
                                  per_min:
                                    type: integer
                                    example: 2
                                  burst:
                                    type: integer
                                    example: 5
                              free:
                                type: object
                                required:
                                  - per_min
                                properties:
                                  per_min:
                                    type: integer
                                    example: 12
                                  burst:
                                    type: integer
                              payg:
                                type: object
                                required:
                                  - per_min
                                properties:
                                  per_min:
                                    type: integer
                                    example: 60
                                  burst:
                                    type: integer
                          unrestricted_features:
                            type: array
                            items:
                              type: string
                            description: Feature keys with no rate ceiling at any tier.
                          notes:
                            type: string
                  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/wallet:
    get:
      operationId: getWallet
      summary: Get your wallet
      description: |
        Returns your organization's credit position as three balances plus the current period and your free-tier eligibility. Requires a signed-in session. Free.

        - `free_tier_balance` — your organization's available credits, including this month's free grant and any credit packs you have bought. The free portion resets each UTC calendar month; purchased credits do not. 0 unless `free_tier_eligibility_status` is `eligible`.
        - `paid_balance` — reserved for purchased credits tracked separately; 0 today.
        - `grant_balance` — reserved for promotional (conference and redemption-code) credits tracked separately; 0 today.

        `free_tier_eligibility_status` is `eligible` (the monthly grant is issued on this call if it has not been yet), `needs_verified_email`, or `needs_card_on_file` (email verified but no payment method on file).
      tags:
        - billing
      x-credit-cost: 0
      x-motionworks-status: production
      security:
        - bearerAuth: []
      responses:
        '200':
          description: Your wallet.
          content:
            application/json:
              schema:
                type: object
                required:
                  - free_tier_balance
                  - paid_balance
                  - grant_balance
                  - period_start_utc
                  - next_reset_utc
                  - free_tier_eligibility_status
                properties:
                  free_tier_balance:
                    type: integer
                    minimum: 0
                    description: |
                      Your organization's available credits: this month's free grant plus any credit packs you have bought. The free portion resets each UTC calendar month; purchased credits do not. 0 unless `free_tier_eligibility_status` is `eligible`.
                  paid_balance:
                    type: integer
                    minimum: 0
                    description: Reserved for purchased credits tracked separately; 0 today.
                  grant_balance:
                    type: integer
                    minimum: 0
                    description: Reserved for promotional (conference and redemption-code) credits tracked separately; 0 today.
                  period_start_utc:
                    type: string
                    format: date-time
                    example: '2026-05-01T00:00:00.000Z'
                    description: Start of the current free-tier month (UTC).
                  next_reset_utc:
                    type: string
                    format: date-time
                    example: '2026-06-01T00:00:00.000Z'
                    description: When the free-tier balance next resets (UTC).
                  free_tier_eligibility_status:
                    type: string
                    enum:
                      - eligible
                      - needs_verified_email
                      - needs_card_on_file
                    description: '`eligible`, `needs_verified_email`, or `needs_card_on_file`.'
        '401':
          description: Missing, malformed or expired session token, or a token for a deleted user.
          content:
            application/json:
              schema:
                type: object
                required:
                  - error
                properties:
                  error:
                    type: object
                    required:
                      - code
                      - message
                      - status
                      - request_id
                      - product
                      - docs_url
                    properties:
                      code:
                        type: string
                        enum:
                          - UNAUTHORIZED
                      message:
                        type: string
                      status:
                        type: integer
                        enum:
                          - 401
                      request_id:
                        type: string
                      product:
                        type: string
                        enum:
                          - platform
                      docs_url:
                        type: string
                        format: uri
        '500':
          description: |
            Internal error. `error.context.phase` names the step that failed so support can triage from the response alone; quote it with your `request_id`.
          content:
            application/json:
              schema:
                type: object
                required:
                  - error
                properties:
                  error:
                    type: object
                    required:
                      - code
                      - message
                      - status
                      - request_id
                      - product
                      - docs_url
                    properties:
                      code:
                        type: string
                        enum:
                          - INTERNAL_ERROR
                      message:
                        type: string
                      status:
                        type: integer
                        enum:
                          - 500
                      request_id:
                        type: string
                      product:
                        type: string
                        enum:
                          - platform
                      docs_url:
                        type: string
                        format: uri
                      context:
                        type: object
                        description: |
                          IETF problem-details extension slot for per-error
                          structured fields.
                        properties:
                          phase:
                            type: string
                            description: |
                              Which handler phase failed. Stable strings safe for
                              clients to switch on; never includes the underlying
                              error message.
                            enum:
                              - config
                              - load_user_context
                              - recompute_grant
                              - read_wallet_projection
  /billing/entitlements:
    get:
      operationId: getEntitlements
      summary: Get your feature entitlements
      description: |
        Returns, for every Motionworks feature key, whether your organization may use it (`access`), why (`source`), and the request-rate limit that applies (`rate`). Use it to build a UI or SDK that knows up front what a call will be allowed to do.

        Three ways to call it, all free: with an org-scoped API key (`user_class: payg`), with a signed-in session (`authenticated`), or with no credentials at all (`anon`, defaults only). A verdict comes either from an explicit grant to your organization (`source: grant`, always `allow`) or from the defaults for your user class (`source: default`). Some features are licensed only by explicit grant and show `deny` by default for everyone — contact sales to enable them.

        This endpoint only reports; it never blocks a call and never returns 403. If the explicit-grant lookup is temporarily unavailable the response is still 200 with default verdicts and `meta.grants_unavailable: true` — defaults are always a subset of what a grant allows, so the answer errs on the safe side.
      tags:
        - billing
      x-credit-cost: 0
      x-motionworks-status: production
      security:
        - apiKey: []
        - bearerAuth: []
        - {}
      responses:
        '200':
          description: Your entitlements.
          content:
            application/json:
              schema:
                type: object
                required:
                  - data
                  - meta
                properties:
                  data:
                    type: object
                    required:
                      - user_class
                      - org_id
                      - features
                    properties:
                      user_class:
                        type: string
                        enum:
                          - anon
                          - authenticated
                          - payg
                        description: |
                          How the request was authenticated: `anon` (no credentials), `authenticated` (signed-in session), or `payg` (API key).
                      org_id:
                        type: string
                        format: uuid
                        nullable: true
                        description: Your organization id, or `null` for anonymous callers.
                      features:
                        type: array
                        description: One entry per feature key in `FeatureKey`.
                        items:
                          type: object
                          required:
                            - key
                            - family
                            - product
                            - feature
                            - label
                            - access
                            - source
                            - rate
                          properties:
                            key:
                              allOf:
                                - $ref: '#/components/schemas/FeatureKey'
                              description: Stable `family.product.feature` key. Never renamed once shipped.
                            family:
                              type: string
                              enum:
                                - placecast
                                - pathcast
                                - popcast
                                - oohdisplays
                                - markets
                              description: Product family the feature belongs to.
                            product:
                              type: string
                              description: Product within the family.
                            feature:
                              type: string
                              description: The feature within the product.
                            label:
                              type: string
                              description: Short human-readable label.
                            access:
                              type: string
                              enum:
                                - allow
                                - allow_limited
                                - deny
                              description: |
                                `allow` and `allow_limited` both license the feature (`allow_limited` carries the tighter anonymous-tier limits); `deny` means it is not licensed for this caller.
                            source:
                              type: string
                              enum:
                                - grant
                                - default
                              description: |
                                `grant` when your organization has been explicitly granted the feature; `default` when the verdict is the default for your user class.
                            rate:
                              description: |
                                Your effective request-rate limit for this feature: the string `unlimited` (tiles, autocomplete, or any explicitly granted feature) or an object with `per_min` and optional `burst`. The same limit the API enforces.
                              oneOf:
                                - type: string
                                  enum:
                                    - unlimited
                                - type: object
                                  required:
                                    - per_min
                                  properties:
                                    per_min:
                                      type: integer
                                    burst:
                                      type: integer
                  meta:
                    type: object
                    required:
                      - request_id
                      - credits_used
                      - credits_remaining
                      - product
                      - provenance
                    properties:
                      request_id:
                        type: string
                      credits_used:
                        type: integer
                        enum:
                          - 0
                      credits_remaining:
                        type: integer
                      product:
                        type: string
                        enum:
                          - platform
                      version:
                        type: string
                      provenance:
                        type: object
                      grants_unavailable:
                        type: boolean
                        description: |
                          `true` when the explicit-grant lookup was unavailable and the verdicts are defaults only. Absent or `false` otherwise.
        '401':
          description: |
            Invalid or expired session token (`UNAUTHORIZED`), or an invalid or revoked API key (`INVALID_API_KEY`).
          content:
            application/json:
              schema:
                type: object
                required:
                  - error
                properties:
                  error:
                    type: object
                    required:
                      - code
                      - message
                      - status
                      - request_id
                      - product
                      - docs_url
                    properties:
                      code:
                        type: string
                        enum:
                          - UNAUTHORIZED
                          - INVALID_API_KEY
                      message:
                        type: string
                      status:
                        type: integer
                        enum:
                          - 401
                      request_id:
                        type: string
                      product:
                        type: string
                        enum:
                          - platform
                      docs_url:
                        type: string
                        format: uri
        '500':
          description: |
            Internal error. `error.context.phase` names the step that failed. A failed grant lookup does not 500 — it returns 200 with `meta.grants_unavailable: true`.
          content:
            application/json:
              schema:
                type: object
                required:
                  - error
                properties:
                  error:
                    type: object
                    required:
                      - code
                      - message
                      - status
                      - request_id
                      - product
                      - docs_url
                    properties:
                      code:
                        type: string
                        enum:
                          - INTERNAL_ERROR
                      message:
                        type: string
                      status:
                        type: integer
                        enum:
                          - 500
                      request_id:
                        type: string
                      product:
                        type: string
                        enum:
                          - platform
                      docs_url:
                        type: string
                        format: uri
                      context:
                        type: object
                        properties:
                          phase:
                            type: string
                            enum:
                              - config
                              - load_org
  /billing/reconciliation-status:
    get:
      operationId: getReconciliationStatus
      summary: Get billing reconciliation status
      description: |
        Shows how far your usage has been reconciled into billing for the current period: the last day fully rolled up (`billed_through_utc`), how many charges are still being reported (`reconciling_count`, normally transient), and how many have not yet been reported (`unbilled_count`). Motionworks reconciles these; you do not need to act. The billing period is your organization's, the same one `GET /v2/organizations/me` reports. Requires a signed-in session. A session for a user that no longer exists is answered with 401. Free.
      tags:
        - billing
      x-credit-cost: 0
      x-motionworks-status: production
      security:
        - bearerAuth: []
      responses:
        '200':
          description: Reconciliation snapshot.
          content:
            application/json:
              schema:
                type: object
                required:
                  - data
                  - meta
                properties:
                  data:
                    type: object
                    required:
                      - billed_through_utc
                      - reconciling_count
                      - unbilled_count
                    properties:
                      billed_through_utc:
                        type: string
                        format: date
                        nullable: true
                        example: '2026-05-28'
                        description: |
                          The most recent day (UTC, `YYYY-MM-DD`) whose usage is fully rolled up into billing. `null` when there is no usage yet.
                      reconciling_count:
                        type: integer
                        minimum: 0
                        description: Charges in the current period still being reported to billing. Normally 0 or briefly non-zero.
                      unbilled_count:
                        type: integer
                        minimum: 0
                        description: |
                          Charges in the current period that have not yet been reported to billing. Motionworks reconciles these; you do not need to act.
                  meta:
                    type: object
        '401':
          description: Missing, malformed or expired session token, or the user no longer exists.
          content:
            application/json:
              schema:
                type: object
                required:
                  - error
                properties:
                  error:
                    type: object
                    required:
                      - code
                      - message
                      - status
                      - request_id
                      - product
                      - docs_url
                    properties:
                      code:
                        type: string
                        enum:
                          - UNAUTHORIZED
                      message:
                        type: string
                      status:
                        type: integer
                        enum:
                          - 401
                      request_id:
                        type: string
                      product:
                        type: string
                        enum:
                          - platform
                      docs_url:
                        type: string
                        format: uri
        '404':
          description: No organization is associated with this user.
          content:
            application/json:
              schema:
                type: object
                required:
                  - error
                properties:
                  error:
                    type: object
                    required:
                      - code
                      - message
                      - status
                      - request_id
                      - product
                      - docs_url
                    properties:
                      code:
                        type: string
                        enum:
                          - ORG_NOT_FOUND
                      message:
                        type: string
                      status:
                        type: integer
                        enum:
                          - 404
                      request_id:
                        type: string
                      product:
                        type: string
                        enum:
                          - platform
                      docs_url:
                        type: string
                        format: uri
        '500':
          description: Internal error. `error.context.phase` names the step that failed.
          content:
            application/json:
              schema:
                type: object
                required:
                  - error
                properties:
                  error:
                    type: object
                    required:
                      - code
                      - message
                      - status
                      - request_id
                      - product
                      - docs_url
                    properties:
                      code:
                        type: string
                        enum:
                          - INTERNAL_ERROR
                      message:
                        type: string
                      status:
                        type: integer
                        enum:
                          - 500
                      request_id:
                        type: string
                      product:
                        type: string
                        enum:
                          - platform
                      docs_url:
                        type: string
                        format: uri
                      context:
                        type: object
                        description: |
                          IETF problem-details extension slot for
                          per-error structured fields.
                        properties:
                          phase:
                            type: string
                            enum:
                              - config
                              - load_org
                              - read_summary
                              - count_outbox
  /usage/daily:
    get:
      operationId: getUsageDaily
      summary: Get daily usage
      description: |
        Returns your organization's usage per UTC day over a window: calls and credits in total, per endpoint, and per API key, plus vector-tile traffic as a separate `tile_credits` / `tile_events` dimension. Requires a signed-in session. Free.

        **Window.** `from` and `to` are inclusive UTC dates. Omit both for the last 30 days. Omit `from` and it defaults to 29 days before `to`; omit `to` and it defaults to today (UTC). The window is capped at 90 days, which is also how long daily usage is retained.

        **Reading the numbers.** `by_key` attributes calls to the API key that made them; calls made from the console with a session count toward the totals but not toward any key. Tile traffic is additive — it is never folded into `total_credits`, `by_endpoint` or `by_key` — and `tile_events` counts tile billing events (one per 1,000 tiles served under current metering), not individual tile fetches. Empty windows return 200 with empty `days` and zero totals, never an error.

        **Spend fields (2026-09-13).** Each day also carries a spend rollup from the org credit ledger: `spend_credits` (full-cost non-tile debits, the wallet truth), `spend_breakdown` (debits by endpoint label), `credits_credited` (gross top-ups, refunds and adjustments), `refund_credits` (the refund subset), `spend_events` (debit count) and `zero_cost_events` (strategic-tier calls included at no cost). These fields are `null` for days before **2026-09-11**, when the ledger began; `null` means unknown, not zero. Days from 2026-09-11 onward report `0` when there was no ledger activity. `total_credits` remains the call-volume figure: for endpoints whose real cost is charged inside the product handler it understates actual spend, while `spend_credits` matches the wallet. Never sum the two. Tile spend is not folded into `spend_credits`; the canonical tile number remains `tile_credits`.
      tags:
        - billing
      x-credit-cost: 0
      x-motionworks-status: production
      security:
        - bearerAuth: []
      parameters:
        - in: query
          name: from
          required: false
          schema:
            type: string
            format: date
            pattern: ^\d{4}-\d{2}-\d{2}$
          description: First day of the window (UTC, `YYYY-MM-DD`, inclusive). Defaults to 29 days before `to`.
          example: '2026-05-01'
        - in: query
          name: to
          required: false
          schema:
            type: string
            format: date
            pattern: ^\d{4}-\d{2}-\d{2}$
          description: Last day of the window (UTC, `YYYY-MM-DD`, inclusive). Defaults to today.
          example: '2026-05-30'
      responses:
        '200':
          description: Daily usage for the window.
          content:
            application/json:
              schema:
                type: object
                required:
                  - data
                  - meta
                properties:
                  data:
                    type: object
                    required:
                      - org_id
                      - from
                      - to
                      - days
                      - totals
                    properties:
                      org_id:
                        type: string
                        format: uuid
                        nullable: true
                        description: Your organization id, or `null` if your account has not been attached to an organization yet.
                      from:
                        type: string
                        format: date
                        example: '2026-05-01'
                        description: First day of the window actually used (UTC).
                      to:
                        type: string
                        format: date
                        example: '2026-05-30'
                        description: Last day of the window actually used (UTC).
                      days:
                        type: array
                        description: One entry per day with usage, ascending by `day`. Empty when the window has no usage.
                        items:
                          type: object
                          required:
                            - day
                            - total_calls
                            - total_credits
                            - by_endpoint
                            - by_key
                            - tile_credits
                            - tile_events
                            - spend_credits
                            - spend_breakdown
                            - credits_credited
                            - refund_credits
                            - spend_events
                            - zero_cost_events
                          properties:
                            day:
                              type: string
                              format: date
                              example: '2026-05-01'
                              description: The UTC day.
                            total_calls:
                              type: integer
                              minimum: 0
                              description: API calls made that day (excluding tile fetches).
                            total_credits:
                              type: integer
                              minimum: 0
                              description: Credits charged that day (excluding tile traffic).
                            by_endpoint:
                              type: object
                              description: |
                                Calls and credits per endpoint, keyed by `METHOD /path` (for example `GET /v2/placecast/profiles/:id`).
                              additionalProperties:
                                type: object
                                required:
                                  - credits
                                  - calls
                                properties:
                                  credits:
                                    type: integer
                                    minimum: 0
                                  calls:
                                    type: integer
                                    minimum: 0
                            by_key:
                              type: array
                              description: |
                                Calls and credits per API key, most active first. Empty when the day's traffic came only from signed-in sessions. A key that has since been deleted is labelled `Deleted key (…<last 4>)` so its usage is never lost.
                              items:
                                type: object
                                required:
                                  - api_key_id
                                  - label
                                  - calls
                                  - credits
                                properties:
                                  api_key_id:
                                    type: string
                                    format: uuid
                                    description: The key's id.
                                  label:
                                    type: string
                                    description: The key's name.
                                  calls:
                                    type: integer
                                    minimum: 0
                                    description: Calls made with this key that day.
                                  credits:
                                    type: integer
                                    minimum: 0
                                    description: Credits charged to this key that day.
                            tile_credits:
                              type: integer
                              minimum: 0
                              description: |
                                Credits billed for vector-tile traffic that day. Additive — not included in `total_credits`, `by_endpoint` or `by_key`.
                            tile_events:
                              type: integer
                              minimum: 0
                              description: |
                                Tile billing events that day (one per 1,000 tiles served under current metering), not a count of individual tile fetches.
                            spend_credits:
                              type: integer
                              minimum: 0
                              nullable: true
                              description: |
                                Full-cost org spend that day (gross debits excluding the tile rail), sourced from the org credit ledger. `null` for days before 2026-09-11 (ledger epoch): unknown, not zero.
                            spend_breakdown:
                              type: object
                              nullable: true
                              description: |
                                The day's debited credits by endpoint label. Keys are concrete request paths or route templates; one customer operation can appear under 2-3 keys. Values sum to the day's gross debited credits INCLUDING the tile rail, so on days with tile traffic they exceed `spend_credits` (which is non-tile only).
                              additionalProperties:
                                type: integer
                                minimum: 0
                            credits_credited:
                              type: integer
                              minimum: 0
                              nullable: true
                              description: |
                                Gross credits added to the wallet that day (top-ups, compensating refunds, admin adjustments). `null` pre-epoch.
                            refund_credits:
                              type: integer
                              minimum: 0
                              nullable: true
                              description: |
                                The refund subset of `credits_credited` (ledger rows whose endpoint label starts `refund:`). `null` pre-epoch.
                            spend_events:
                              type: integer
                              minimum: 0
                              nullable: true
                              description: |
                                Number of debit events that day. `null` pre-epoch.
                            zero_cost_events:
                              type: integer
                              minimum: 0
                              nullable: true
                              description: |
                                Strategic-tier calls observed at zero cost that day (unlimited tier: observable, not billed). `null` pre-epoch.
                      totals:
                        type: object
                        required:
                          - total_calls
                          - total_credits
                          - tile_credits
                          - tile_events
                          - spend_credits
                          - credits_credited
                          - refund_credits
                          - spend_events
                          - zero_cost_events
                        description: Sums over the returned window. Call-volume fields (total_calls, total_credits, tile_credits, tile_events) are 0 when `days` is empty; the spend fields are null unless at least one returned day has a non-null value.
                        properties:
                          total_calls:
                            type: integer
                            minimum: 0
                            description: Total API calls in the window (excluding tile fetches).
                          total_credits:
                            type: integer
                            minimum: 0
                            description: Total credits charged in the window (excluding tile traffic).
                          tile_credits:
                            type: integer
                            minimum: 0
                            description: Total vector-tile credits in the window.
                          tile_events:
                            type: integer
                            minimum: 0
                            description: Total tile billing events in the window.
                          spend_credits:
                            type: integer
                            minimum: 0
                            nullable: true
                            description: Total full-cost non-tile spend in the window. `null` when every returned day is pre-epoch.
                          credits_credited:
                            type: integer
                            minimum: 0
                            nullable: true
                            description: Total credits added to the wallet in the window. `null` when every returned day is pre-epoch.
                          refund_credits:
                            type: integer
                            minimum: 0
                            nullable: true
                            description: Total refunded credits in the window. `null` when every returned day is pre-epoch.
                          spend_events:
                            type: integer
                            minimum: 0
                            nullable: true
                            description: Total debit events in the window. `null` when every returned day is pre-epoch.
                          zero_cost_events:
                            type: integer
                            minimum: 0
                            nullable: true
                            description: Total strategic-tier zero-cost calls in the window. `null` when every returned day is pre-epoch.
                  meta:
                    type: object
        '400':
          description: |
            `INVALID_DATE` — `from` or `to` is not a valid `YYYY-MM-DD` date (`error.context.parameter` says which); `INVALID_RANGE` — `from` is after `to`; `RANGE_TOO_WIDE` — more than 90 days (`error.context.max_days` / `requested_days`).
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/PlatformError'
        '401':
          description: Missing, malformed or expired session token.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/PlatformError'
        '500':
          description: Internal error. `error.context.phase` names the step that failed.
          content:
            application/json:
              schema:
                type: object
                required:
                  - error
                properties:
                  error:
                    type: object
                    required:
                      - code
                      - message
                      - status
                      - request_id
                      - product
                      - docs_url
                    properties:
                      code:
                        type: string
                        enum:
                          - INTERNAL_ERROR
                      message:
                        type: string
                      status:
                        type: integer
                        enum:
                          - 500
                      request_id:
                        type: string
                      product:
                        type: string
                        enum:
                          - platform
                      docs_url:
                        type: string
                        format: uri
                      context:
                        type: object
                        description: |
                          IETF problem-details extension slot for
                          per-error structured fields.
                        properties:
                          phase:
                            type: string
                            enum:
                              - config
                              - load_org
                              - read_summary
  /billing/checkout:
    post:
      operationId: createCheckoutSession
      summary: Start a pay-as-you-go checkout (Stripe Checkout)
      description: |
        Creates a Stripe Checkout session for the pay-as-you-go metered credit subscription — $0.05 per credit, billed monthly on what you use — and returns the hosted checkout URL. The Motionworks console drives this flow; `POST /v2/billing/credits` is the more general purchase endpoint. Free. No authentication is required, but send your signed-in session (`Authorization: Bearer <jwt>`) so the subscription is attached to your organization and existing Stripe customer.
      tags:
        - billing
      x-credit-cost: 0
      x-motionworks-status: production
      security: []
      requestBody:
        required: false
        content:
          application/json:
            schema:
              type: object
              properties:
                returnUrl:
                  type: string
                  format: uri
                  description: |
                    Optional `https` URL to send the user back to when checkout completes or is cancelled. It must be `https`, carry no `#fragment`, and be on a Motionworks-operated origin (the console or app); anything else falls back to the default return page. Motionworks appends `?checkout=success` or `?checkout=cancel` (your own query string is kept).
                  example: https://console.mworks.com/console/
      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: The Stripe Checkout page to send the user to.
                  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 rejected the request; the message carries Stripe's reason.
        '500':
          description: The checkout session could not be created.
        '503':
          description: Purchases are temporarily unavailable.
  /billing/webhook:
    post:
      operationId: stripeWebhook
      summary: Receive Stripe webhook events
      description: |
        Receives billing events from Stripe. **Called by Stripe, not by you** — it is listed here for completeness. Every event is verified against the `Stripe-Signature` header (five-minute freshness window) before it is processed, and the response is `{ received: true }` rather than the usual `{ data, meta }` envelope because Stripe reads only the status code.
      tags:
        - billing
      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's 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 or malformed signature, or an unparseable body.
        '500':
          description: Processing failed; Stripe retries the event.
        '503':
          description: Webhook verification is not configured.
  /organizations/me:
    get:
      operationId: getOrganizationMe
      summary: Get your organization
      description: |
        Returns your organization in one call: name, plan label, sandbox flag, how it is billed (`billing_source`), Stripe customer and subscription ids, credit counters, and the current billing period. Requires a signed-in session. Free.

        `billing_source` is `stripe` when the organization pays through Stripe, `direct` when Motionworks invoices it directly, or `null` when neither is set up yet.
      tags:
        - billing
      x-credit-cost: 0
      x-motionworks-status: production
      security:
        - bearerAuth: []
      responses:
        '200':
          description: Your organization.
          content:
            application/json:
              schema:
                type: object
                required:
                  - data
                  - meta
                properties:
                  data:
                    type: object
                    required:
                      - id
                      - name
                      - plan
                      - sandbox
                      - billing_source
                      - stripe_customer_id
                      - stripe_subscription_id
                      - credits_remaining
                      - credits_allocated
                      - billing_period_start
                      - billing_period_end
                      - created_at
                    properties:
                      id:
                        type: string
                        format: uuid
                        description: Organization id.
                      name:
                        type: string
                        description: Organization name.
                      plan:
                        type: string
                        description: |
                          Plan label as shown in the console — `free`, `paid` or `custom` today. Treat it as an open string; new labels may appear.
                      sandbox:
                        type: boolean
                        description: Whether this is a sandbox organization.
                      billing_source:
                        type: string
                        enum:
                          - stripe
                          - direct
                        nullable: true
                        description: '`stripe`, `direct` (invoiced by Motionworks), or `null`.'
                      stripe_customer_id:
                        type: string
                        nullable: true
                        description: Stripe customer id, when billed through Stripe.
                      stripe_subscription_id:
                        type: string
                        nullable: true
                        description: Stripe subscription id, when a subscription exists.
                      credits_remaining:
                        type: integer
                        minimum: 0
                        description: Credits remaining.
                      credits_allocated:
                        type: integer
                        minimum: 0
                        description: Credits allocated for the period.
                      billing_period_start:
                        type: string
                        format: date-time
                        description: Start of the current billing period (UTC).
                      billing_period_end:
                        type: string
                        format: date-time
                        description: End of the current billing period (UTC).
                      created_at:
                        type: string
                        format: date-time
                        description: When the organization was created.
                  meta:
                    type: object
        '401':
          description: Missing, malformed or expired session token.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/PlatformError'
        '404':
          description: Your account is not attached to an organization.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/PlatformError'
        '500':
          description: Internal error. `error.context.phase` names the step that failed.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/PlatformError'
  /billing/portal:
    post:
      operationId: createBillingPortalSession
      summary: Open the Stripe customer portal
      description: |
        Creates a Stripe Customer Portal session for your organization and returns the URL to send the user to. The portal is where payment methods are updated, invoices are viewed and a subscription is cancelled; Motionworks does not duplicate those screens. Requires a signed-in session. Free. Limited to 5 requests per minute per user.

        Available to organizations on the `paid` plan. Free organizations have no subscription to manage and directly invoiced organizations are handled by Motionworks, so both receive 403 `PORTAL_NOT_AVAILABLE`.
      tags:
        - billing
      x-credit-cost: 0
      x-motionworks-status: production
      security:
        - bearerAuth: []
      requestBody:
        required: false
        content:
          application/json:
            schema:
              type: object
              additionalProperties: false
              properties:
                return_url:
                  type: string
                  format: uri
                  description: |
                    Optional `https` URL the user returns to when leaving the portal. It must be `https`, carry no `#fragment`, and be on a Motionworks-operated origin (the console or app); otherwise the request is rejected with 400 `INVALID_RETURN_URL`. Defaults to your organization page in the console.
                  example: https://app2.mworks.com/console/organization/
      responses:
        '200':
          description: Portal session created.
          content:
            application/json:
              schema:
                type: object
                required:
                  - data
                  - meta
                properties:
                  data:
                    type: object
                    required:
                      - url
                      - expires_at
                    properties:
                      url:
                        type: string
                        format: uri
                        description: The portal session URL to send the user to.
                      expires_at:
                        type: string
                        format: date-time
                        description: When the portal URL stops working (one hour after creation). It can be reused until then.
                  meta:
                    type: object
        '400':
          description: '`return_url` was rejected.'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/PlatformError'
        '401':
          description: Missing, malformed or expired session token.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/PlatformError'
        '403':
          description: |
            `PORTAL_NOT_AVAILABLE` — the portal does not apply to this organization's plan. `error.context.plan` carries the plan; `error.context.reason` may say `missing_stripe_customer`.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/PlatformError'
        '404':
          description: No organization is associated with this user.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/PlatformError'
        '429':
          description: Rate limited (5 requests per minute per user). `Retry-After` says how long to wait.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/PlatformError'
        '502':
          description: Stripe rejected the request. Contact support with your `request_id`.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/PlatformError'
        '503':
          description: Stripe is temporarily unavailable. Retry after the interval in `Retry-After` (30 seconds).
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/PlatformError'
  /billing/subscription:
    get:
      operationId: getBillingSubscription
      summary: Get your subscription
      description: |
        Returns the live state of your organization's Stripe subscription: status, when the current period ends, whether it is set to cancel at period end, and the payment method on file. Requires a signed-in session. Free.

        Returns 404 `SUBSCRIPTION_NOT_FOUND` when there is no subscription to report — free or directly invoiced organizations, or a subscription Stripe no longer knows about.
      tags:
        - billing
      x-credit-cost: 0
      x-motionworks-status: production
      security:
        - bearerAuth: []
      responses:
        '200':
          description: Current subscription state.
          content:
            application/json:
              schema:
                type: object
                required:
                  - data
                  - meta
                properties:
                  data:
                    type: object
                    required:
                      - status
                      - current_period_end
                      - cancel_at_period_end
                      - payment_method
                    properties:
                      status:
                        type: string
                        description: |
                          Stripe subscription status — typically `active`, `past_due`, `canceled`, `incomplete` or `trialing`. Treat it as an open string.
                      current_period_end:
                        type: string
                        format: date-time
                        description: When the current billing period ends (ISO 8601).
                      cancel_at_period_end:
                        type: boolean
                        description: Whether the subscription ends at the close of the current period.
                      payment_method:
                        type: object
                        nullable: true
                        required:
                          - brand
                          - last4
                        properties:
                          brand:
                            type: string
                            description: Card brand, for example `visa`, `mastercard`, `amex`.
                          last4:
                            type: string
                            minLength: 4
                            maxLength: 4
                            description: Last four digits of the card.
                        description: Card on file (brand and last four digits), or `null`.
                  meta:
                    type: object
        '401':
          description: Missing, malformed or expired session token.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/PlatformError'
        '404':
          description: '`SUBSCRIPTION_NOT_FOUND` — no active subscription for this organization.'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/PlatformError'
        '502':
          description: Stripe returned an unexpected response.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/PlatformError'
        '503':
          description: Stripe is temporarily unavailable. Retry after the interval in `Retry-After` (30 seconds).
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/PlatformError'
x-tagGroups:
  - name: Account
    tags:
      - account
  - name: Billing & usage
    tags:
      - billing
  - name: Reference
    tags:
      - markets
      - segments
  - name: Status
    tags:
      - health
tags:
  - name: account
    x-displayName: API keys
    description: Create, list, rotate and revoke the API keys your organization uses to call the data APIs. Managed from a signed-in session. Free.
  - name: billing
    x-displayName: Billing & usage
    description: Your credit balance and wallet, public pricing, feature entitlements, daily usage, subscription and payment details, and the Stripe-hosted checkout and customer portal. Free.
  - name: markets
    x-displayName: Markets
    description: Reference list of US DMA markets. Not yet available (501 today); 1 credit per call when live.
  - name: segments
    x-displayName: Segments
    description: Reference list of audience segments. Not yet available (501 today); 1 credit per call when live.
  - name: health
    x-displayName: Health
    description: Service status and data-freshness probes. No authentication, no charge.
x-customer-voice: true
