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

# Create a custom org-scoped metric

> Creates a new ORG-scoped metric for the caller's organization. The `body` field must be a valid discriminated-union metric definition (kind: precomputed_column, aggregation, time_windowed_aggregation, composite, or unsupported). The `metricVersion` is set automatically to the current spec version. Requires `intelligenceLayer.configure` permission.



## OpenAPI

````yaml /openapi.json post /api/intelligence-layer/metrics
openapi: 3.0.3
info:
  contact:
    email: dev@solya.io
    name: Solya Team
  description: >-

    # Solya API


    Solya is a fashion retail inventory management platform for buyers and
    merchandisers.

    This API exposes all capabilities needed to manage the inventory lifecycle:
    catalog

    browsing, risk detection, plan creation (Restock / Markdown / Rebalance),
    analytics

    queries, and data-platform operations.


    ## Authentication


    Three authentication schemes are supported:


    | Scheme | Header | Use case |

    |---|---|---|

    | **BearerAuth** | `Authorization: Bearer <nextauth-token>` | Human users
    via the Solya web app (NextAuth session) |

    | **InternalBearerAuth** | `Authorization: Bearer <static-token>` | Internal
    jobs and cron tasks (static token per service) |

    | **ServiceAccountToken** | `Authorization: Bearer solya_sa_*` | LLM agents
    and programmatic clients (opaque token created via Settings) |


    All endpoints except `GET /api/health` require one of the above.

    See [Agent authentication guide](/docs/api/AGENT_AUTH.md) for the Service
    Account token flow.


    ## Response format


    Every endpoint returns an `ActionResponse<T>` envelope:


    **Success:**

    ```json

    { "success": true, "data": { ... } }

    ```


    **Error:**

    ```json

    { "success": false, "errorCode": "PLAN_NOT_FOUND", "error": "Human-readable
    message" }

    ```


    The `errorCode` is a stable machine-readable string (see common error codes
    below).

    The `error` field is for human display only and may change between versions.


    ## Pagination


    List endpoints accept `page` (1-indexed, default 1) and `pageSize` (default
    20, max 100).

    Responses include a `total` field with the total number of matching records.


    ```

    GET /api/shops?page=2&pageSize=50

    → { "success": true, "data": { "data": [...], "total": 120, "page": 2,
    "pageSize": 50 } }

    ```


    ## Common error codes


    | HTTP status | errorCode | Meaning |

    |---|---|---|

    | 401 | `UNAUTHORIZED` | Token missing, expired, or invalid |

    | 403 | `FORBIDDEN` | Token valid but lacks the required permission or scope
    |

    | 404 | `NOT_FOUND` | Requested resource does not exist |

    | 409 | `BUSINESS_RULE_VIOLATION` | Business rule blocked the operation (see
    response details) |

    | 422 | `VALIDATION_ERROR` | Input failed Zod schema validation |

    | 429 | `RATE_LIMIT_EXCEEDED` | Rate limit hit (100 req/min per token) |

    | 500 | `INTERNAL_ERROR` | Unexpected server error |


    ## Rate limiting


    Default limit: **100 requests per minute** per authentication token.

    When the limit is exceeded the API returns HTTP 429 with `errorCode:
    "RATE_LIMIT_EXCEEDED"`.

    Agents should implement exponential back-off and respect the `Retry-After`
    header when present.


    ## Further reading


    See the [Agent guide](/docs/api/AGENT_GUIDE.md) for end-to-end workflows,
    call-chaining

    patterns, and anti-patterns to avoid.
        
  license:
    name: Proprietary
    url: https://solya.app/terms
  title: Solya API
  version: 1.0.0
  x-ai-context: >-
    Solya is a fashion retail inventory management platform for retailers.

    Core concepts:

    - **Organization** (tenant): every endpoint is scoped by organizationId
    extracted from the token.

    - **Shop**: a physical point of sale belonging to the organization.

    - **ProductVariant**: a SKU — a product variant with size and color.

    - **Plan**: a Restock / Markdown / Rebalance grouping PlanItems to
    orchestrate inventory decisions.


    Typical agent workflow:

    1. List the organization's shops — GET /api/shops

    2. List variants at risk (stockout, overstock, slow-mover) — GET
    /api/inventory/risks

    3. Create a plan — POST /api/restock-plans, /api/markdown-plans, or
    /api/rebalance-plans

    4. Add items to the plan — POST /api/restock-plans/{planId}/items (or
    equivalent for other plan types)

    5. Validate / submit the plan via the corresponding action endpoint


    Auth: use a Service Account token (see /docs/api/AGENT_AUTH.md).

    The token is created by an org admin via Settings and has the format
    solya_sa_<43 base64url chars>.


    All responses follow the ActionResponse envelope:

    - Success: { success: true, data: T }

    - Error:   { success: false, errorCode: string, error: string }


    Use the errorCode field to drive retry logic or surface user-facing
    messages.
servers:
  - description: Current environment
    url: https://app.solya.app
security:
  - BearerAuth: []
tags:
  - description: >-
      Health and liveness endpoints. Use GET /api/health to verify the service
      is reachable before starting a workflow. No authentication required.
    name: System
  - description: >-
      Physical points of sale belonging to the organization. Supports listing,
      creation, update, and deactivation. Shops are referenced by all Plan types
      (Restock, Markdown, Rebalance) and by every inventory analytics endpoint.
    name: Shops
  - description: >-
      Product brands configured for the organization. Brands are used to filter
      catalog queries and analytics. Supports CRUD operations.
    name: Brands
  - description: >-
      The product catalog: style-level entities grouping one or more
      ProductVariants. Supports listing with rich filter options (brand, family,
      gender, season) and individual retrieval.
    name: Products
  - description: >-
      SKU-level product entities (a Product with a specific size and color).
      Variants are the atomic unit referenced by PlanItems, inventory risk
      alerts, and analytics queries.
    name: Variants
  - description: >-
      Curated product groupings used for seasonal assortment management. A
      Collection groups Variants and can be referenced when creating or
      filtering Plans.
    name: Collections
  - description: >-
      Current on-hand stock records per Variant per Shop. Used to understand the
      live inventory position before creating a restock or rebalance plan.
    name: Inventory Items
  - description: >-
      AI-detected inventory risk signals: stockout risk, overstock, slow-movers,
      and displaced stock. The primary input for agents building
      recommendation-driven plans. Supports filtering by shop, brand, risk type,
      and severity.
    name: Inventory Risks
  - description: >-
      AI-generated restock quantity recommendations per Variant per Shop.
      Consumed by agents to pre-populate Restock plan items. Based on sales
      velocity, stock coverage, and lead time.
    name: Recommendations - Restock
  - description: >-
      AI-generated markdown discount recommendations for slow-moving or
      overstock Variants. Consumed by agents to pre-populate Markdown plan
      items. Includes recommended discount rate and expected clearance timeline.
    name: Recommendations - Markdown
  - description: >-
      AI-generated stock transfer recommendations between shops to balance
      supply with demand. Consumed by agents to pre-populate Rebalance plan
      items.
    name: Recommendations - Rebalance
  - description: >-
      Historical sales transaction lines at the Variant + Shop + date level.
      Used by analytics and by the AI recommendation engine. Supports date-range
      and multi-dimensional filtering.
    name: Sales Lines
  - description: >-
      Purchase order lines tracking inbound stock from suppliers. Combined with
      stock and sales data to compute forward coverage and restock needs.
    name: Order Lines
  - description: >-
      Inventory movement records (transfers, returns, adjustments). Used to
      reconcile the stock ledger and audit stock changes between shops.
    name: Movement Lines
  - description: >-
      Running stock balance log per Variant per Shop. Provides a point-in-time
      view of stock levels and is the source of truth for coverage computations.
    name: Stock Ledger
  - description: >-
      Rebalance plans orchestrate stock transfers between shops. Supports
      creating plans, adding Variant items with proposed transfer quantities,
      reviewing, and submitting. Business rules are evaluated on item addition.
    name: Plans - Rebalance
  - description: >-
      Restock plans (order plans) orchestrate purchase orders to suppliers.
      Supports creating sessions, adding Variant items with proposed order
      quantities, reviewing totals, and submitting. Integrates with the order
      plan workflow.
    name: Plans - Restock
  - description: >-
      Autocomplete and typeahead search endpoints for catalog dimensions:
      products, brands, shops, sizes, families, genders, and more. Designed for
      fast UI search (low latency, small result sets). Use catalog list
      endpoints for full paginated access.
    name: Search
  - description: >-
      Manage file-based data ingestion: upload CSV/XLSX files, poll ingestion
      status, list historical ingestion runs, and trigger batch reprocessing.
      Used by the data team to import POS data and catalog updates.
    name: Data Platform - File Ingestions
  - description: >-
      Organization-level configuration for the data platform: data source
      connections, POS integration settings, and ingestion schedules. Requires
      elevated permissions.
    name: Data Platform - Settings
  - description: >-
      Configuration of automated inventory alerts: threshold-based rules that
      monitor stock levels, sales velocity, and coverage gaps. Supports CRUD for
      alert definitions; alert evaluation runs are triggered by the data
      platform scheduler.
    name: Data Platform - Alerts
  - description: >-
      Endpoints designed for LLM agents and programmatic clients. These
      endpoints expose agent-optimized response shapes. Authenticate with a
      Service Account token (format: solya_sa_*) created via Settings → API
      Tokens.
    name: Agent
externalDocs:
  description: >-
    Complete guide for LLM agents and programmatic clients: authentication,
    pagination patterns, ActionResponse interpretation, call chaining, business
    rule error handling.
  url: /docs/api/AGENT_GUIDE.md
paths:
  /api/intelligence-layer/metrics:
    post:
      tags:
        - Metrics
      summary: Create a custom org-scoped metric
      description: >-
        Creates a new ORG-scoped metric for the caller's organization. The
        `body` field must be a valid discriminated-union metric definition
        (kind: precomputed_column, aggregation, time_windowed_aggregation,
        composite, or unsupported). The `metricVersion` is set automatically to
        the current spec version. Requires `intelligenceLayer.configure`
        permission.
      operationId: createMetric
      requestBody:
        content:
          application/json:
            examples:
              sample:
                summary: Create an aggregation metric counting sales lines
                value:
                  body:
                    aggregation:
                      column: quantity
                      function: SUM
                    kind: aggregation
                    source:
                      table: gold.fact_sales_lines
                  category: SALES
                  description: Sum of sold quantities across all sales lines.
                  displayName: Total Sold Quantity
                  grain:
                    - shop_id
                    - variant_id
                  isConfigurable: false
                  metricName: total_sold_quantity
                  outputColumn: total_sold_quantity
                  outputType: BIGINT
            schema:
              properties:
                body:
                  oneOf:
                    - additionalProperties: false
                      properties:
                        kind:
                          enum:
                            - precomputed_column
                          type: string
                        source:
                          additionalProperties: false
                          properties:
                            column:
                              minLength: 1
                              type: string
                            table:
                              minLength: 1
                              type: string
                          required:
                            - table
                            - column
                          type: object
                      required:
                        - kind
                        - source
                      type: object
                    - additionalProperties: false
                      properties:
                        aggregation:
                          additionalProperties: false
                          properties:
                            column:
                              minLength: 1
                              type: string
                            function:
                              enum:
                                - SUM
                                - AVG
                                - COUNT
                                - MIN
                                - MAX
                              type: string
                          required:
                            - function
                            - column
                          type: object
                        kind:
                          enum:
                            - aggregation
                          type: string
                        source:
                          additionalProperties: false
                          properties:
                            table:
                              minLength: 1
                              type: string
                          required:
                            - table
                          type: object
                      required:
                        - kind
                        - source
                        - aggregation
                      type: object
                    - additionalProperties: false
                      properties:
                        aggregation:
                          additionalProperties: false
                          properties:
                            column:
                              minLength: 1
                              type: string
                            function:
                              enum:
                                - SUM
                                - AVG
                                - COUNT
                                - MIN
                                - MAX
                              type: string
                          required:
                            - function
                            - column
                          type: object
                        kind:
                          enum:
                            - time_windowed_aggregation
                          type: string
                        source:
                          additionalProperties: false
                          properties:
                            table:
                              minLength: 1
                              type: string
                          required:
                            - table
                          type: object
                        window:
                          additionalProperties: false
                          properties:
                            dateColumn:
                              minLength: 1
                              type: string
                            days:
                              anyOf:
                                - exclusiveMinimum: true
                                  maximum: 9007199254740991
                                  type: integer
                                - pattern: ^\{[A-Za-z_][A-Za-z0-9_]*\}$
                                  type: string
                            direction:
                              default: past
                              enum:
                                - past
                                - future
                              type: string
                            offsetDays:
                              anyOf:
                                - maximum: 9007199254740991
                                  minimum: 0
                                  type: integer
                                - pattern: ^\{[A-Za-z_][A-Za-z0-9_]*\}$
                                  type: string
                              default: 0
                          required:
                            - dateColumn
                            - days
                          type: object
                      required:
                        - kind
                        - source
                        - aggregation
                        - window
                      type: object
                    - additionalProperties: false
                      properties:
                        expression:
                          minLength: 1
                          type: string
                        kind:
                          enum:
                            - composite
                          type: string
                        references:
                          items:
                            additionalProperties: false
                            properties:
                              alias:
                                maxLength: 64
                                minLength: 1
                                pattern: ^[A-Za-z_][A-Za-z0-9_]*$
                                type: string
                              metricId:
                                minLength: 1
                                type: string
                            required:
                              - alias
                              - metricId
                            type: object
                          minItems: 1
                          type: array
                      required:
                        - kind
                        - references
                        - expression
                      type: object
                    - additionalProperties: false
                      properties:
                        kind:
                          enum:
                            - unsupported
                          type: string
                        reason:
                          type: string
                      required:
                        - kind
                        - reason
                      type: object
                category:
                  minLength: 1
                  type: string
                configSchema:
                  items: {}
                  type: array
                defaultConfig:
                  additionalProperties:
                    type: number
                  type: object
                description:
                  type: string
                displayName:
                  maxLength: 255
                  minLength: 1
                  type: string
                grain:
                  items:
                    minLength: 1
                    type: string
                  minItems: 1
                  type: array
                isConfigurable:
                  default: false
                  type: boolean
                metricName:
                  maxLength: 255
                  minLength: 1
                  type: string
                outputColumn:
                  maxLength: 255
                  minLength: 1
                  type: string
                outputType:
                  default: DOUBLE
                  type: string
                subcategory:
                  type: string
              required:
                - metricName
                - displayName
                - category
                - body
                - grain
                - outputColumn
              type: object
        required: true
      responses:
        '200':
          content:
            application/json:
              examples:
                sample:
                  summary: Created metric
                  value:
                    body:
                      aggregation:
                        column: quantity
                        function: SUM
                      kind: aggregation
                      source:
                        table: gold.fact_sales_lines
                    category: SALES
                    configSchema: null
                    createdAt: '2026-06-04T10:00:00.000Z'
                    defaultConfig: null
                    description: Sum of sold quantities across all sales lines.
                    displayName: Total Sold Quantity
                    enabled: true
                    grain:
                      - shop_id
                      - variant_id
                    id: metric-uuid-new-1
                    isConfigurable: false
                    metricName: total_sold_quantity
                    organizationId: org-uuid-123
                    outputColumn: total_sold_quantity
                    outputType: BIGINT
                    parameters: {}
                    scope: ORG
                    subcategory: null
                    updatedAt: null
                    version: 2
              schema:
                properties:
                  data:
                    properties:
                      body:
                        allOf:
                          - properties:
                              kind:
                                description: >-
                                  Discriminator for the metric body type. One
                                  of: "precomputed_column", "aggregation",
                                  "time_windowed_aggregation", "composite",
                                  "unsupported"
                                enum:
                                  - precomputed_column
                                  - aggregation
                                  - time_windowed_aggregation
                                  - composite
                                  - unsupported
                                type: string
                            required:
                              - kind
                            type: object
                          - additionalProperties: {}
                            type: object
                        description: >-
                          Kind-specific body holding the computation definition.
                          Null only for legacy metrics with no body. For
                          `precomputed_column`: `{ kind, source: { table, column
                          } }`. For `aggregation`: `{ kind, source: { table },
                          aggregation: { function, column } }`. For
                          `time_windowed_aggregation`: adds a `window: {
                          dateColumn, days, offsetDays, direction }` field. For
                          `composite`: `{ kind, references: [{ alias, metricId
                          }], expression }`. For `unsupported`: `{ kind, reason
                          }`.
                        nullable: true
                      category:
                        description: >-
                          Category of the metric. Platform GLOBAL metrics use
                          one of: INVENTORY, SALES, TURNOVER, STOCK,
                          GLOBAL_INSIGHT. ORG-scoped custom metrics may carry
                          any arbitrary string — z.string() (not z.enum) is used
                          here so the response schema remains valid for both.
                          Import METRIC_CATEGORY_KNOWN_VALUES for static enum
                          discovery.
                        type: string
                      configSchema:
                        description: >-
                          JSON-Schema array describing each configurable
                          parameter; null for non-configurable metrics
                        items: {}
                        nullable: true
                        type: array
                      createdAt:
                        description: ISO 8601 timestamp when the metric was created
                        format: date-time
                        pattern: >-
                          ^(?:(?:\d\d[2468][048]|\d\d[13579][26]|\d\d0[48]|[02468][048]00|[13579][26]00)-02-29|\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\d|30)|(?:02)-(?:0[1-9]|1\d|2[0-8])))T(?:(?:[01]\d|2[0-3]):[0-5]\d(?::[0-5]\d(?:\.\d+)?)?(?:Z))$
                        type: string
                      defaultConfig:
                        additionalProperties: {}
                        description: >-
                          Default numeric parameter values used when no
                          org-level override is present; null for
                          non-configurable metrics
                        nullable: true
                        type: object
                      description:
                        description: Optional long description of what the metric measures
                        nullable: true
                        type: string
                      displayName:
                        description: >-
                          Human-readable label shown in the UI, or null for
                          unlabelled metrics
                        nullable: true
                        type: string
                      enabled:
                        description: >-
                          Whether this metric is enabled for the calling
                          organization (false = disabled via org config)
                        type: boolean
                      grain:
                        description: >-
                          List of SQL columns that define the grain at which
                          this metric is evaluated (e.g. [shop_id, variant_id])
                        items:
                          type: string
                        type: array
                      id:
                        description: Unique identifier of the metric (UUID)
                        type: string
                      isConfigurable:
                        description: >-
                          Whether org-level numeric parameter overrides (via
                          `parameters`) are allowed for this metric
                        type: boolean
                      metricName:
                        description: >-
                          Internal identifier-style name of the metric (stable,
                          used as a key)
                        type: string
                      organizationId:
                        description: >-
                          Organization that owns this metric when scope is ORG;
                          null for GLOBAL metrics
                        nullable: true
                        type: string
                      outputColumn:
                        description: >-
                          Name of the Spark column that holds the metric value
                          after computation
                        type: string
                      outputType:
                        description: >-
                          Spark SQL output type for the metric value (e.g.
                          "DOUBLE", "BIGINT"); defaults to "DOUBLE"
                        type: string
                      parameters:
                        additionalProperties:
                          type: number
                        description: >-
                          Merged numeric parameters: org-level overrides on top
                          of metric defaults; empty object when not configurable
                        type: object
                      scope:
                        description: >-
                          Visibility scope: "GLOBAL" for platform-wide metrics,
                          "ORG" for org-scoped custom metrics
                        enum:
                          - GLOBAL
                          - ORG
                        type: string
                      subcategory:
                        description: >-
                          Optional sub-category for finer grouping, or null when
                          not applicable
                        nullable: true
                        type: string
                      updatedAt:
                        description: >-
                          ISO 8601 timestamp of the last update, or null if the
                          metric has never been updated
                        format: date-time
                        nullable: true
                        pattern: >-
                          ^(?:(?:\d\d[2468][048]|\d\d[13579][26]|\d\d0[48]|[02468][048]00|[13579][26]00)-02-29|\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\d|30)|(?:02)-(?:0[1-9]|1\d|2[0-8])))T(?:(?:[01]\d|2[0-3]):[0-5]\d(?::[0-5]\d(?:\.\d+)?)?(?:Z))$
                        type: string
                      version:
                        description: >-
                          Metric-spec version (2 for the current
                          discriminated-union body contract)
                        maximum: 9007199254740991
                        minimum: -9007199254740991
                        type: integer
                    required:
                      - id
                      - category
                      - subcategory
                      - metricName
                      - displayName
                      - description
                      - isConfigurable
                      - defaultConfig
                      - configSchema
                      - scope
                      - organizationId
                      - version
                      - grain
                      - outputColumn
                      - outputType
                      - body
                      - enabled
                      - parameters
                      - createdAt
                      - updatedAt
                    type: object
                  success:
                    enum:
                      - true
                    type: boolean
                required:
                  - success
                  - data
                type: object
          description: Successful response
        '400':
          description: Validation error
        '401':
          description: Unauthorized
        '500':
          description: Internal server error
      security:
        - BearerAuth: []
components:
  securitySchemes:
    BearerAuth:
      bearerFormat: JWT
      description: >-
        User session token issued by NextAuth. For human users accessing Solya
        via the web application.
      scheme: bearer
      type: http

````