openapi: 3.0.3
info:
  title: HOOP Inventory Hub API
  description: |
    Read-only inventory hub API for HOOP (https://hoop.956.jp/).
    Authenticate with a company API key (`Authorization: Bearer hoop_…`).
    Scopes: `catalog:read`, `stock:read`. Write APIs and Webhooks are not shipped yet.
  version: "1.0.0"
  contact:
    name: 956 Inc.
    url: https://www.956.jp/
servers:
  - url: https://hoop.956.jp
    description: Production
paths:
  /api/v1/catalog/skus:
    get:
      operationId: listCatalogSkus
      summary: List SKU catalog
      tags: [Catalog]
      security:
        - bearerAuth: []
      parameters:
        - $ref: "#/components/parameters/publicOnly"
        - $ref: "#/components/parameters/excludeDiscontinued"
        - $ref: "#/components/parameters/updatedSince"
        - $ref: "#/components/parameters/limit"
      responses:
        "200":
          description: SKU list
          content:
            application/json:
              schema:
                type: object
                required: [skus, meta]
                properties:
                  skus:
                    type: array
                    items:
                      $ref: "#/components/schemas/Sku"
                  meta:
                    $ref: "#/components/schemas/ListMeta"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
  /api/v1/skus/{id}:
    get:
      operationId: getSku
      summary: Get one SKU by code (alt_id) or internal id
      tags: [Catalog]
      security:
        - bearerAuth: []
      parameters:
        - $ref: "#/components/parameters/skuId"
      responses:
        "200":
          description: SKU detail
          content:
            application/json:
              schema:
                type: object
                required: [sku]
                properties:
                  sku:
                    $ref: "#/components/schemas/Sku"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
        "404":
          $ref: "#/components/responses/NotFound"
  /api/v1/stock:
    get:
      operationId: listStock
      summary: List stock quantities
      tags: [Stock]
      security:
        - bearerAuth: []
      parameters:
        - name: sku_codes[]
          in: query
          description: Filter by SKU codes (repeatable). Comma-separated values in a single param are also accepted.
          schema:
            type: array
            items:
              type: string
          style: form
          explode: true
        - $ref: "#/components/parameters/limit"
      responses:
        "200":
          description: Stock list
          content:
            application/json:
              schema:
                type: object
                required: [stocks, meta]
                properties:
                  stocks:
                    type: array
                    items:
                      $ref: "#/components/schemas/Stock"
                  meta:
                    $ref: "#/components/schemas/ListMeta"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
  /api/v1/skus/{id}/stock:
    get:
      operationId: getSkuStock
      summary: Get stock for one SKU
      tags: [Stock]
      security:
        - bearerAuth: []
      parameters:
        - $ref: "#/components/parameters/skuId"
      responses:
        "200":
          description: Stock detail
          content:
            application/json:
              schema:
                type: object
                required: [stock]
                properties:
                  stock:
                    $ref: "#/components/schemas/Stock"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
        "404":
          $ref: "#/components/responses/NotFound"
components:
  securitySchemes:
    bearerAuth:
      type: http
      scheme: bearer
      bearerFormat: hoop_…
      description: Company API key from HOOP settings → API keys. Prefix is typically `hoop_`.
  parameters:
    skuId:
      name: id
      in: path
      required: true
      description: SKU code (`alt_id`) preferred; internal numeric id also accepted.
      schema:
        type: string
    publicOnly:
      name: public_only
      in: query
      schema:
        type: string
        enum: ["1", "true", "yes"]
      description: When set, return only public SKUs (`is_public=true`).
    excludeDiscontinued:
      name: exclude_discontinued
      in: query
      schema:
        type: string
        enum: ["1", "true", "yes"]
      description: When set, exclude discontinued SKUs.
    updatedSince:
      name: updated_since
      in: query
      schema:
        type: string
        format: date-time
      description: ISO8601 / parseable timestamp; return SKUs updated at or after this time.
    limit:
      name: limit
      in: query
      schema:
        type: integer
        minimum: 1
        maximum: 500
      description: Max rows (1–500).
  schemas:
    Sku:
      type: object
      properties:
        id:
          type: integer
        code:
          type: string
          description: SKU code (alt_id)
        name:
          type: string
        product_id:
          type: integer
          nullable: true
        product_code:
          type: string
          nullable: true
        product_name:
          type: string
          nullable: true
        category_id:
          type: integer
          nullable: true
        unit:
          type: string
        is_active:
          type: boolean
        is_public:
          type: boolean
        is_discontinued:
          type: boolean
        updated_at:
          type: string
          format: date-time
          nullable: true
    Stock:
      type: object
      properties:
        code:
          type: string
        qty:
          type: number
          description: On-hand quantity
        free_qty:
          type: number
          description: Free (unreserved) quantity
        safe_qty:
          type: number
          description: Safe stock threshold
        unit:
          type: string
        location_code:
          type: string
          description: Currently always `default` (future multi-location)
          example: default
        is_discontinued:
          type: boolean
        updated_at:
          type: string
          format: date-time
          nullable: true
    ListMeta:
      type: object
      properties:
        count:
          type: integer
        location_code:
          type: string
          example: default
    Error:
      type: object
      properties:
        error:
          type: string
        required_scope:
          type: string
  responses:
    Unauthorized:
      description: Missing or invalid API key
      content:
        application/json:
          schema:
            $ref: "#/components/schemas/Error"
          example:
            error: unauthorized
    Forbidden:
      description: API key lacks required scope
      content:
        application/json:
          schema:
            $ref: "#/components/schemas/Error"
          example:
            error: forbidden
            required_scope: catalog:read
    NotFound:
      description: SKU not found in the API key's company
      content:
        application/json:
          schema:
            $ref: "#/components/schemas/Error"
          example:
            error: not_found
