openapi: 3.1.0
info:
  title: IoneShop Commerce API
  version: 0.1.0
  summary: Enterprise headless commerce API for IoneShop tenants
  description: |
    Partner-facing REST contract for IoneShop (public beta).
    Auth today: API keys (isk_test_… / isk_live_…). OAuth 2.0 is in development.
    Capability matrix: https://developers.ioneshop.eu/docs/availability
    Markdown at https://developers.ioneshop.eu/docs/api-reference is authoritative
    where this OpenAPI draft is incomplete.
    Do not send PAN/CVV.
  contact:
    name: IoneShop Developers
    url: https://developers.ioneshop.eu
    email: developers@ioneshop.eu
  license:
    name: Proprietary
servers:
  - url: https://sandbox-api.ioneshop.cloud/v1
    description: Sandbox
  - url: https://api.ioneshop.cloud/v1
    description: Production
tags:
  - name: Account
  - name: Products
  - name: Orders
  - name: Customers
  - name: Inventory
  - name: Categories
  - name: Webhooks
security:
  - bearerAuth: []
  - apiKeyHeader: []
paths:
  /account:
    get:
      tags: [Account]
      summary: Current API credential context
      operationId: getAccount
      responses:
        "200":
          description: Account envelope
          content:
            application/json:
              schema:
                type: object
                required: [data]
                properties:
                  data:
                    type: object
                    properties:
                      tenant_id:
                        type: string
                      slug:
                        type: string
                      environment:
                        type: string
                        enum: [live, test]
                      scopes:
                        type: array
                        items:
                          type: string
                      api_version:
                        type: string
                      plan:
                        type: [string, "null"]
                      rate_limits:
                        type: object
                        properties:
                          requests_per_minute:
                            type: integer
                          requests_per_day:
                            type: integer
        "401":
          $ref: "#/components/responses/Unauthorized"
  /products:
    get:
      tags: [Products]
      summary: List products
      operationId: listProducts
      parameters:
        - $ref: "#/components/parameters/Limit"
        - $ref: "#/components/parameters/Cursor"
        - $ref: "#/components/parameters/UpdatedSince"
        - name: status
          in: query
          schema:
            type: string
            enum: [draft, published, archived]
      responses:
        "200":
          description: Paginated products
          headers:
            X-Request-Id:
              $ref: "#/components/headers/RequestId"
            X-RateLimit-Limit:
              $ref: "#/components/headers/RateLimitLimit"
            X-RateLimit-Remaining:
              $ref: "#/components/headers/RateLimitRemaining"
            X-RateLimit-Reset:
              $ref: "#/components/headers/RateLimitReset"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ProductList"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "429":
          $ref: "#/components/responses/RateLimited"
    post:
      tags: [Products]
      summary: Create product
      operationId: createProduct
      parameters:
        - $ref: "#/components/parameters/IdempotencyKey"
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/ProductCreate"
      responses:
        "201":
          description: Created
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ProductResource"
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
  /products/{id}:
    parameters:
      - $ref: "#/components/parameters/ResourceId"
    get:
      tags: [Products]
      summary: Get product
      operationId: getProduct
      responses:
        "200":
          description: Product
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ProductResource"
        "404":
          $ref: "#/components/responses/NotFound"
    patch:
      tags: [Products]
      summary: Update product
      operationId: updateProduct
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/ProductUpdate"
      responses:
        "200":
          description: Updated
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Product"
    delete:
      tags: [Products]
      summary: Delete product
      operationId: deleteProduct
      responses:
        "204":
          description: Deleted
  /orders:
    get:
      tags: [Orders]
      summary: List orders
      operationId: listOrders
      parameters:
        - $ref: "#/components/parameters/Limit"
        - $ref: "#/components/parameters/Cursor"
        - $ref: "#/components/parameters/UpdatedSince"
        - name: status
          in: query
          schema:
            type: string
            enum: [pending, paid, fulfilled, cancelled]
      responses:
        "200":
          description: Paginated orders
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/OrderList"
  /orders/{id}:
    parameters:
      - $ref: "#/components/parameters/ResourceId"
    get:
      tags: [Orders]
      summary: Get order
      operationId: getOrder
      responses:
        "200":
          description: Order
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Order"
  /orders/{id}/fulfillments:
    post:
      tags: [Orders]
      summary: Create fulfillment
      operationId: createFulfillment
      parameters:
        - $ref: "#/components/parameters/ResourceId"
        - $ref: "#/components/parameters/IdempotencyKey"
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [items]
              properties:
                tracking_number:
                  type: string
                carrier:
                  type: string
                items:
                  type: array
                  items:
                    type: object
                    required: [line_item_id, quantity]
                    properties:
                      line_item_id:
                        type: string
                      quantity:
                        type: integer
                        minimum: 1
      responses:
        "201":
          description: Fulfillment created
  /customers:
    get:
      tags: [Customers]
      summary: List customers
      operationId: listCustomers
      parameters:
        - $ref: "#/components/parameters/Limit"
        - $ref: "#/components/parameters/Cursor"
        - $ref: "#/components/parameters/UpdatedSince"
      responses:
        "200":
          description: Paginated customers
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/CustomerList"
  /customers/{id}:
    get:
      tags: [Customers]
      summary: Get customer
      operationId: getCustomer
      parameters:
        - $ref: "#/components/parameters/ResourceId"
      responses:
        "200":
          description: Customer envelope
        "404":
          $ref: "#/components/responses/NotFound"
    patch:
      tags: [Customers]
      summary: Update customer
      operationId: updateCustomer
      parameters:
        - $ref: "#/components/parameters/ResourceId"
        - $ref: "#/components/parameters/IdempotencyKey"
      responses:
        "200":
          description: Updated
  /categories:
    get:
      tags: [Categories]
      summary: List categories
      operationId: listCategories
      parameters:
        - $ref: "#/components/parameters/Limit"
        - $ref: "#/components/parameters/Cursor"
      responses:
        "200":
          description: Paginated categories
    post:
      tags: [Categories]
      summary: Create category
      operationId: createCategory
      parameters:
        - $ref: "#/components/parameters/IdempotencyKey"
      responses:
        "201":
          description: Created
  /categories/{id}:
    get:
      tags: [Categories]
      summary: Get category
      operationId: getCategory
      parameters:
        - $ref: "#/components/parameters/ResourceId"
      responses:
        "200":
          description: Category
        "404":
          $ref: "#/components/responses/NotFound"
  /inventory/levels:
    get:
      tags: [Inventory]
      summary: List inventory levels
      operationId: listInventoryLevels
      parameters:
        - name: sku
          in: query
          schema:
            type: string
        - name: location_id
          in: query
          schema:
            type: string
      responses:
        "200":
          description: Levels
    put:
      tags: [Inventory]
      summary: Set absolute inventory level
      operationId: setInventoryLevel
      parameters:
        - $ref: "#/components/parameters/IdempotencyKey"
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [sku, location_id, quantity]
              properties:
                sku:
                  type: string
                location_id:
                  type: string
                quantity:
                  type: integer
                  minimum: 0
                updated_at:
                  type: string
                  format: date-time
      responses:
        "200":
          description: Updated
        "409":
          $ref: "#/components/responses/Conflict"
  /webhooks:
    get:
      tags: [Webhooks]
      summary: List webhooks
      operationId: listWebhooks
      responses:
        "200":
          description: Webhook endpoints
    post:
      tags: [Webhooks]
      summary: Create webhook
      operationId: createWebhook
      parameters:
        - $ref: "#/components/parameters/IdempotencyKey"
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [url, events]
              properties:
                url:
                  type: string
                  format: uri
                events:
                  type: array
                  items:
                    type: string
                description:
                  type: string
      responses:
        "201":
          description: Created (secret shown once)
  /webhooks/{id}:
    get:
      tags: [Webhooks]
      summary: Get webhook
      operationId: getWebhook
      parameters:
        - $ref: "#/components/parameters/ResourceId"
      responses:
        "200":
          description: Webhook (secret never returned)
        "404":
          $ref: "#/components/responses/NotFound"
    delete:
      tags: [Webhooks]
      summary: Delete webhook
      operationId: deleteWebhook
      parameters:
        - $ref: "#/components/parameters/ResourceId"
      responses:
        "204":
          description: Deleted
        "404":
          $ref: "#/components/responses/NotFound"
  /orders/{id}/cancel:
    post:
      tags: [Orders]
      summary: Cancel order
      description: See markdown API reference; schemas may still be incomplete in this draft.
      operationId: cancelOrder
      parameters:
        - $ref: "#/components/parameters/ResourceId"
        - $ref: "#/components/parameters/IdempotencyKey"
      responses:
        "200":
          description: Cancelled
  /orders/{id}/refunds:
    post:
      tags: [Orders]
      summary: Create refund
      description: See markdown API reference; schemas may still be incomplete in this draft.
      operationId: createRefund
      parameters:
        - $ref: "#/components/parameters/ResourceId"
        - $ref: "#/components/parameters/IdempotencyKey"
      responses:
        "200":
          description: Refund created
  /inventory/levels/adjust:
    post:
      tags: [Inventory]
      summary: Adjust inventory level
      operationId: adjustInventoryLevel
      parameters:
        - $ref: "#/components/parameters/IdempotencyKey"
      responses:
        "200":
          description: Adjusted
  /products/batch:
    post:
      tags: [Products]
      summary: Batch product mutations
      description: Contract / in development — confirm via availability + changelog before relying on it.
      operationId: batchProducts
      parameters:
        - $ref: "#/components/parameters/IdempotencyKey"
      responses:
        "200":
          description: Batch result
        "501":
          description: Not implemented yet
components:
  securitySchemes:
    bearerAuth:
      type: http
      scheme: bearer
      bearerFormat: isk_live_… or isk_test_…
    apiKeyHeader:
      type: apiKey
      in: header
      name: X-IoneShop-Api-Key
  parameters:
    Limit:
      name: limit
      in: query
      schema:
        type: integer
        minimum: 1
        maximum: 250
        default: 25
    Cursor:
      name: cursor
      in: query
      schema:
        type: string
    UpdatedSince:
      name: updated_since
      in: query
      schema:
        type: string
        format: date-time
    ResourceId:
      name: id
      in: path
      required: true
      schema:
        type: string
    IdempotencyKey:
      name: Idempotency-Key
      in: header
      required: false
      schema:
        type: string
        maxLength: 255
  headers:
    RequestId:
      schema:
        type: string
      description: Correlate with support
    RateLimitLimit:
      schema:
        type: integer
    RateLimitRemaining:
      schema:
        type: integer
    RateLimitReset:
      schema:
        type: integer
        description: Unix timestamp
  schemas:
    Money:
      type: object
      required: [amount, currency]
      properties:
        amount:
          type: integer
          description: Minor units
        currency:
          type: string
          minLength: 3
          maxLength: 3
    Pagination:
      type: object
      properties:
        limit:
          type: integer
        next_cursor:
          type: [string, "null"]
        has_more:
          type: boolean
    Error:
      type: object
      required: [error]
      properties:
        error:
          type: object
          required: [code, message, request_id]
          properties:
            code:
              type: string
            message:
              type: string
            request_id:
              type: string
            param:
              type: string
            docs_url:
              type: string
              format: uri
    Product:
      type: object
      properties:
        id:
          type: string
        title:
          type: string
        handle:
          type: string
        status:
          type: string
        updated_at:
          type: string
          format: date-time
    ProductCreate:
      type: object
      required: [title]
      properties:
        title:
          type: string
        handle:
          type: string
        status:
          type: string
        description:
          type: string
        variants:
          type: array
          items:
            type: object
    ProductUpdate:
      type: object
      properties:
        title:
          type: string
        status:
          type: string
        description:
          type: string
    ProductResource:
      type: object
      required: [data]
      properties:
        data:
          $ref: "#/components/schemas/Product"
    ProductList:
      type: object
      properties:
        data:
          type: array
          items:
            $ref: "#/components/schemas/Product"
        pagination:
          $ref: "#/components/schemas/Pagination"
    Order:
      type: object
      properties:
        id:
          type: string
        status:
          type: string
        total:
          $ref: "#/components/schemas/Money"
        updated_at:
          type: string
          format: date-time
    OrderList:
      type: object
      properties:
        data:
          type: array
          items:
            $ref: "#/components/schemas/Order"
        pagination:
          $ref: "#/components/schemas/Pagination"
    Customer:
      type: object
      properties:
        id:
          type: string
        email:
          type: string
          format: email
        updated_at:
          type: string
          format: date-time
    CustomerList:
      type: object
      properties:
        data:
          type: array
          items:
            $ref: "#/components/schemas/Customer"
        pagination:
          $ref: "#/components/schemas/Pagination"
  responses:
    BadRequest:
      description: Validation error
      content:
        application/json:
          schema:
            $ref: "#/components/schemas/Error"
    Unauthorized:
      description: Missing or invalid credentials
      content:
        application/json:
          schema:
            $ref: "#/components/schemas/Error"
    NotFound:
      description: Resource not found
      content:
        application/json:
          schema:
            $ref: "#/components/schemas/Error"
    Conflict:
      description: Conflict / stale version
      content:
        application/json:
          schema:
            $ref: "#/components/schemas/Error"
    RateLimited:
      description: Rate limit exceeded
      headers:
        Retry-After:
          schema:
            type: integer
      content:
        application/json:
          schema:
            $ref: "#/components/schemas/Error"
