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

# Import a taxonomy preset (importer une librairie d'axes)

> Installs every axis of the preset the merchant does not already have, and reports what it did. ADDITIVE and IDEMPOTENT: an existing axis is never overwritten (so a re-import cannot clobber a renamed node — a product binds `Record<axisName, label>` and `skuId()` hashes those pairs, so dropping a label would orphan its products), and importing twice installs nothing the second time. Collisions are matched on the axis NAME, not just the id, because that is what products bind by. The whole install runs in ONE transaction under a lock, so a partial import is impossible and two concurrent imports cannot both create the same axis. Needs `pos.catalog.manage`; 404s an unknown preset id.



## OpenAPI

````yaml /openapi.json post /v1/taxonomy/presets/{presetId}/import
openapi: 3.0.3
info:
  title: Solya POS API
  version: 1.0.0
  description: >-
    The Solya POS backend HTTP surface. Every documented operation is
    agent-ready: it carries an `operationId`, an agent-facing `description`, the
    `pos.*` scopes it enforces (`x-required-permissions`) and an `x-agent-tier`.
    Success responses return the payload as raw JSON; failures return the
    `ErrorResponse` envelope (`{ error: { code, message, statusCode } }`).
servers:
  - url: /
    description: The backend, relative to its deployed origin.
security: []
paths:
  /v1/taxonomy/presets/{presetId}/import:
    post:
      tags:
        - Catalog
      summary: Import a taxonomy preset (importer une librairie d'axes)
      description: >-
        Installs every axis of the preset the merchant does not already have,
        and reports what it did. ADDITIVE and IDEMPOTENT: an existing axis is
        never overwritten (so a re-import cannot clobber a renamed node — a
        product binds `Record<axisName, label>` and `skuId()` hashes those
        pairs, so dropping a label would orphan its products), and importing
        twice installs nothing the second time. Collisions are matched on the
        axis NAME, not just the id, because that is what products bind by. The
        whole install runs in ONE transaction under a lock, so a partial import
        is impossible and two concurrent imports cannot both create the same
        axis. Needs `pos.catalog.manage`; 404s an unknown preset id.
      operationId: importTaxonomyPreset
      parameters:
        - schema:
            type: string
            minLength: 1
          in: path
          name: presetId
          required: true
          description: The preset id (e.g. `solya-fashion`).
      responses:
        '200':
          description: What the import installed and skipped.
          content:
            application/json:
              schema:
                type: object
                properties:
                  presetId:
                    type: string
                  presetName:
                    type: string
                  origin:
                    type: string
                    enum:
                      - solya
                      - internal
                  imported:
                    type: array
                    items:
                      type: object
                      properties:
                        axisId:
                          type: string
                          description: Id the axis is created under.
                        axisName:
                          type: string
                          description: Display name of the axis.
                        nodeCount:
                          type: integer
                          minimum: -9007199254740991
                          maximum: 9007199254740991
                          description: Nodes in the axis tree, at any depth.
                      required:
                        - axisId
                        - axisName
                        - nodeCount
                      additionalProperties: false
                  skipped:
                    type: array
                    items:
                      type: object
                      properties:
                        axisId:
                          type: string
                          description: Id the axis carries in the preset.
                        axisName:
                          type: string
                          description: Display name the axis carries in the preset.
                        reason:
                          type: string
                          enum:
                            - name-taken
                            - id-taken
                            - archived
                            - tombstoned
                          description: >-
                            Why it was not installed: `name-taken` (a live axis
                            already carries this name — the functional key,
                            since products bind by axis name), `id-taken` (a
                            live axis carries this id), `archived` (the
                            colliding axis exists but the merchant DEACTIVATED
                            it, so the preset would reinstate something they
                            retired on purpose), or `tombstoned` (a soft-deleted
                            row still holds this id and installing would
                            resurrect it).
                        conflictingAxisId:
                          description: >-
                            Id of the existing axis holding the name/id, when
                            the collision names one.
                          type: string
                      required:
                        - axisId
                        - axisName
                        - reason
                      additionalProperties: false
                required:
                  - presetId
                  - presetName
                  - origin
                  - imported
                  - skipped
                additionalProperties: false
                description: What the import installed and skipped.
                example:
                  presetId: solya-fashion
                  presetName: Mode & prêt-à-porter
                  origin: solya
                  imported:
                    - axisId: ax-colour
                      axisName: Couleur
                      nodeCount: 51
                  skipped:
                    - axisId: ax-season
                      axisName: Saison
                      reason: archived
                      conflictingAxisId: ax-season
        '400':
          description: >-
            The request failed schema validation; `error.fieldErrors` lists the
            fields.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ValidationErrorResponse'
        '401':
          description: No valid credential was presented — send a bearer token.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        '403':
          description: The actor is authenticated but lacks the required `pos.*` scope.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        '404':
          description: No resource matches the addressed identifier.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        '422':
          description: The request is well-formed but violates a domain rule.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        '500':
          description: An unexpected server error — safe to retry idempotent requests.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
      security:
        - bearerAuth: []
components:
  schemas:
    ValidationErrorResponse:
      type: object
      required:
        - error
      additionalProperties: false
      description: >-
        A `VALIDATION_FAILED` envelope carrying the offending fields in
        `fieldErrors`.
      properties:
        error:
          type: object
          required:
            - code
            - message
            - statusCode
          additionalProperties: false
          properties:
            code:
              type: string
              enum:
                - VALIDATION_FAILED
            message:
              type: string
            statusCode:
              type: integer
            fieldErrors:
              type: array
              description: >-
                One entry per rejected field: the field path and why it was
                rejected.
              items:
                type: object
                required:
                  - field
                  - message
                additionalProperties: false
                properties:
                  field:
                    type: string
                    description: Dot-path of the offending field.
                  message:
                    type: string
                    description: Why the field was rejected.
    ErrorResponse:
      type: object
      required:
        - error
      additionalProperties: false
      description: The uniform failure envelope every non-2xx response returns.
      properties:
        error:
          type: object
          required:
            - code
            - message
            - statusCode
          additionalProperties: false
          properties:
            code:
              type: string
              enum:
                - VALIDATION_FAILED
                - UNAUTHORIZED
                - FORBIDDEN
                - NOT_FOUND
                - CONFLICT
                - BUSINESS_RULE_VIOLATION
                - INTERNAL_ERROR
              description: >-
                Machine-readable kernel `ResultCode` — branch on this, not on
                `message`.
            message:
              type: string
              description: >-
                Human-readable explanation. Safe to surface; never leaks server
                internals.
            statusCode:
              type: integer
              description: >-
                The HTTP status, mirrored into the body so a client need not
                read headers.
  securitySchemes:
    bearerAuth:
      type: http
      scheme: bearer
      bearerFormat: JWT
      description: >-
        `Authorization: Bearer <token>`. Accepts EITHER a Keycloak access token
        (scopes-in-token) OR an opaque POS session token; both resolve to the
        same `pos.*` scope vocabulary the route guards enforce.

````