{
  "openapi": "3.1.0",
  "info": {
    "title": "Solar 4 RVs Customer API",
    "version": "1.0.0",
    "description": "Server-to-server JSON API for customer stock, ex-GST pricing sync, and customer order creation. This API accepts no query parameters."
  },
  "servers": [
    {
      "url": "https://api.solar4rvs.com.au"
    }
  ],
  "tags": [
    {
      "name": "Customer Sync",
      "description": "Read-only customer catalogue sync endpoints."
    },
    {
      "name": "Customer Orders",
      "description": "Customer order creation endpoints."
    }
  ],
  "paths": {
    "/products": {
      "get": {
        "tags": [
          "Customer Sync"
        ],
        "operationId": "getProducts",
        "summary": "Get ACTIVE Online Store products with SKU, stock, ex-GST B2B prices, and product attributes",
        "description": "Returns product records available to the authenticated customer that are ACTIVE, published to the Online Store, and have a non-empty SKU.",
        "parameters": [],
        "responses": {
          "200": {
            "description": "Product records for the authenticated customer.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ProductsResponse"
                },
                "examples": {
                  "success": {
                    "value": [
                      {
                        "sku": "ABC-123",
                        "productTitle": "Example Product",
                        "barcode": "1234567890123",
                        "brand": "Example Brand",
                        "quantity": 12,
                        "price": "90.91",
                        "breakpoints": [
                          {
                            "min_qty": 10,
                            "price": "55.00"
                          },
                          {
                            "min_qty": 20,
                            "price": "54.00"
                          }
                        ],
                        "weight": 1.25,
                        "shipping": {
                          "length": 0.5,
                          "width": 0.3,
                          "height": 0.2
                        },
                        "updatedAt": "2026-05-07T00:00:00.000Z"
                      }
                    ]
                  }
                }
              }
            }
          },
          "400": {
            "description": "Unsupported query parameters.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Authentication failed.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "The key does not include /products access, or the source IP is not allowed.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "examples": {
                  "insufficientScope": {
                    "summary": "Key lacks /products access",
                    "value": {
                      "error": {
                        "code": "insufficient_scope",
                        "message": "This API key does not include access to /products."
                      }
                    }
                  },
                  "ipNotAllowed": {
                    "summary": "Source IP not allowed",
                    "value": {
                      "error": {
                        "code": "ip_not_allowed",
                        "message": "This API key cannot be used from the current IP address."
                      }
                    }
                  }
                }
              }
            }
          },
          "405": {
            "description": "Method not allowed. Use GET only.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "413": {
            "description": "Request body is too large. Do not send a body to read endpoints.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded.",
            "headers": {
              "Retry-After": {
                "description": "Seconds to wait before retrying.",
                "schema": {
                  "type": "string"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "Temporary upstream or platform failure.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "security": [
          {
            "bearerApiKey": [],
            "customerEmail": []
          }
        ]
      }
    },
    "/order": {
      "post": {
        "tags": [
          "Customer Orders"
        ],
        "operationId": "createOrder",
        "summary": "Create a customer order from SKUs",
        "description": "Creates a Shopify draft order, blocks creation when the account is overdue or already over credit limit, rejects duplicate purchase orders for the authenticated company, rejects locally invalid Australian shipping postcodes before draft creation, then converts the draft order to an order. Send draftOnly true to return the same costing response after draft creation without converting it to an order.",
        "parameters": [],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/OrderRequest"
              },
              "examples": {
                "success": {
                  "value": {
                    "po": "PO-1001",
                    "address": {
                      "firstName": "Alex",
                      "lastName": "Buyer",
                      "company": "Acme RV Services",
                      "address1": "1 Example Street",
                      "address2": "Unit 4",
                      "city": "Melbourne",
                      "state": "VIC",
                      "postcode": "3000",
                      "phone": "+61300000000"
                    },
                    "lines": [
                      {
                        "sku": "ABC-123",
                        "quantity": 2
                      }
                    ],
                    "orderType": "dropship",
                    "courierType": "express",
                    "draftOnly": false
                  }
                }
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Order or draft order created.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/OrderResponse"
                }
              }
            }
          },
          "400": {
            "description": "Invalid request.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Authentication failed.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "402": {
            "description": "Account is overdue or already over credit limit.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "The key does not include /order access, or the source IP is not allowed.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "examples": {
                  "insufficientScope": {
                    "summary": "Key lacks /order access",
                    "value": {
                      "error": {
                        "code": "insufficient_scope",
                        "message": "This API key cannot create orders."
                      }
                    }
                  },
                  "ipNotAllowed": {
                    "summary": "Source IP not allowed",
                    "value": {
                      "error": {
                        "code": "ip_not_allowed",
                        "message": "This API key cannot be used from the current IP address."
                      }
                    }
                  }
                }
              }
            }
          },
          "409": {
            "description": "A completed order already exists for the supplied PO number, or an order is already being created with that PO.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "422": {
            "description": "Order could not be created with the supplied account, SKU, or shipping address data.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded.",
            "headers": {
              "Retry-After": {
                "description": "Seconds to wait before retrying.",
                "schema": {
                  "type": "string"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "Temporary upstream or platform failure.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "security": [
          {
            "bearerApiKey": [],
            "customerEmail": []
          }
        ]
      }
    }
  },
  "components": {
    "securitySchemes": {
      "bearerApiKey": {
        "type": "http",
        "scheme": "bearer"
      },
      "customerEmail": {
        "type": "apiKey",
        "in": "header",
        "name": "X-Customer-Email"
      }
    },
    "schemas": {
      "Error": {
        "type": "object",
        "required": [
          "error"
        ],
        "properties": {
          "error": {
            "type": "object",
            "required": [
              "code",
              "message"
            ],
            "properties": {
              "code": {
                "type": "string"
              },
              "message": {
                "type": "string"
              },
              "details": {
                "type": "object",
                "description": "Optional structured validation details for invalid request payloads and Shopify validation failures, including missing fields, invalid fields, malformed structure, and shipping address validation status.",
                "properties": {
                  "missingFields": {
                    "type": "array",
                    "items": {
                      "type": "string"
                    }
                  },
                  "invalidFields": {
                    "type": "array",
                    "items": {
                      "type": "object",
                      "required": [
                        "field",
                        "reason"
                      ],
                      "properties": {
                        "field": {
                          "type": "string"
                        },
                        "reason": {
                          "type": "string"
                        }
                      }
                    }
                  },
                  "structureIssues": {
                    "type": "array",
                    "items": {
                      "type": "object",
                      "required": [
                        "field",
                        "expected",
                        "received"
                      ],
                      "properties": {
                        "field": {
                          "type": "string"
                        },
                        "expected": {
                          "type": "string"
                        },
                        "received": {
                          "type": "string"
                        }
                      }
                    }
                  }
                },
                "additionalProperties": true
              }
            }
          }
        }
      },
      "ProductItem": {
        "type": "object",
        "required": [
          "sku",
          "productTitle",
          "barcode",
          "brand",
          "quantity",
          "price",
          "breakpoints",
          "weight",
          "shipping",
          "updatedAt"
        ],
        "properties": {
          "sku": {
            "type": "string"
          },
          "productTitle": {
            "type": "string"
          },
          "barcode": {
            "type": [
              "string",
              "null"
            ]
          },
          "brand": {
            "type": [
              "string",
              "null"
            ]
          },
          "quantity": {
            "type": "number"
          },
          "price": {
            "type": "string"
          },
          "breakpoints": {
            "type": "array",
            "items": {
              "type": "object",
              "required": [
                "min_qty",
                "price"
              ],
              "properties": {
                "min_qty": {
                  "type": "number"
                },
                "price": {
                  "type": "string"
                }
              }
            }
          },
          "weight": {
            "type": [
              "number",
              "null"
            ]
          },
          "shipping": {
            "type": "object",
            "required": [
              "length",
              "width",
              "height"
            ],
            "properties": {
              "length": {
                "type": [
                  "number",
                  "null"
                ]
              },
              "width": {
                "type": [
                  "number",
                  "null"
                ]
              },
              "height": {
                "type": [
                  "number",
                  "null"
                ]
              }
            }
          },
          "updatedAt": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          }
        }
      },
      "ProductsResponse": {
        "type": "array",
        "items": {
          "$ref": "#/components/schemas/ProductItem"
        }
      },
      "OrderAddress": {
        "type": "object",
        "required": [
          "address1",
          "city",
          "postcode"
        ],
        "properties": {
          "firstName": {
            "type": "string"
          },
          "lastName": {
            "type": "string"
          },
          "company": {
            "type": "string",
            "description": "Optional shipping address company name."
          },
          "address1": {
            "type": "string"
          },
          "address2": {
            "type": "string",
            "description": "Optional second address line."
          },
          "city": {
            "type": "string"
          },
          "state": {
            "type": "string",
            "description": "Australian state or territory. Both 'VIC' and 'Victoria' are accepted. When supplied, it must match the postcode region."
          },
          "postcode": {
            "type": "string",
            "description": "Four-digit Australian postcode. 0000 is rejected and, when state is supplied, the postcode must match the state region."
          },
          "phone": {
            "type": "string"
          }
        }
      },
      "OrderLine": {
        "type": "object",
        "required": [
          "sku",
          "quantity"
        ],
        "properties": {
          "sku": {
            "type": "string"
          },
          "quantity": {
            "type": "integer",
            "minimum": 1
          }
        }
      },
      "OrderRequest": {
        "type": "object",
        "required": [
          "po",
          "address",
          "lines",
          "orderType",
          "courierType"
        ],
        "properties": {
          "po": {
            "type": "string",
            "description": "Customer purchase order number. This is treated as an idempotency key for the authenticated company account."
          },
          "address": {
            "$ref": "#/components/schemas/OrderAddress"
          },
          "lines": {
            "type": "array",
            "minItems": 1,
            "maxItems": 100,
            "items": {
              "$ref": "#/components/schemas/OrderLine"
            }
          },
          "orderType": {
            "type": "string",
            "enum": [
              "dropship",
              "regular"
            ]
          },
          "courierType": {
            "type": "string",
            "enum": [
              "regular",
              "express"
            ],
            "description": "regular prefers the cheapest Regular Australia Post or standard courier option, then falls back to express or expedited services when needed. express prefers the cheapest Express Australia Post or expedited courier option, then falls back to regular or standard services when needed."
          },
          "authorityToLeave": {
            "type": "boolean",
            "description": "Optional. Defaults to true when omitted.",
            "default": true
          },
          "draftOnly": {
            "type": "boolean",
            "description": "Optional. Defaults to false. When true, creates and returns a Shopify draft order with the full costing response, but does not convert it to an order. Reusing the same PO for draft-only requests is allowed until a completed order exists for that PO.",
            "default": false
          }
        }
      },
      "OrderResponse": {
        "type": "object",
        "required": [
          "po",
          "name",
          "address",
          "skus",
          "orderType",
          "courierType",
          "authorityToLeave",
          "draftOnly",
          "currencyCode",
          "costs",
          "account"
        ],
        "properties": {
          "po": {
            "type": "string"
          },
          "name": {
            "type": "string",
            "description": "The created order name, or the created draft order name when draftOnly is true."
          },
          "address": {
            "$ref": "#/components/schemas/OrderAddress"
          },
          "skus": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/OrderLine"
            }
          },
          "orderType": {
            "type": "string",
            "enum": [
              "dropship",
              "regular"
            ]
          },
          "courierType": {
            "type": "string",
            "enum": [
              "regular",
              "express"
            ]
          },
          "authorityToLeave": {
            "type": "boolean"
          },
          "draftOnly": {
            "type": "boolean"
          },
          "currencyCode": {
            "type": "string"
          },
          "costs": {
            "type": "object",
            "properties": {
              "lineItems": {
                "type": "array",
                "items": {
                  "type": "object",
                  "properties": {
                    "sku": {
                      "type": "string"
                    },
                    "productTitle": {
                      "type": "string"
                    },
                    "quantity": {
                      "type": "integer"
                    },
                    "unitCostExGst": {
                      "type": "string"
                    },
                    "lineItemCostExGst": {
                      "type": "string"
                    },
                    "lineItemCostInclGst": {
                      "type": "string"
                    }
                  }
                }
              },
              "shippingCostExGst": {
                "type": "string"
              },
              "shippingCostInclGst": {
                "type": "string"
              },
              "totalExGst": {
                "type": "string"
              },
              "totalInclGst": {
                "type": "string"
              },
              "gst": {
                "type": "string"
              }
            }
          },
          "account": {
            "type": "object",
            "properties": {
              "companyName": {
                "type": "string"
              },
              "creditLimit": {
                "type": "string"
              },
              "outstandingBeforeOrder": {
                "type": "string"
              },
              "overdueInvoices": {
                "type": "array",
                "items": {
                  "type": "object",
                  "properties": {
                    "orderId": {
                      "type": "string"
                    },
                    "orderName": {
                      "type": "string"
                    },
                    "dueDate": {
                      "type": "string"
                    },
                    "outstandingAmount": {
                      "type": "string"
                    }
                  }
                }
              }
            }
          }
        },
        "example": {
          "po": "PO-1001",
          "name": "#S278743",
          "address": {
            "firstName": "Alex",
            "lastName": "Buyer",
            "company": "Acme RV Services",
            "address1": "1 Example Street",
            "address2": "Unit 4",
            "city": "Melbourne",
            "state": "Victoria",
            "postcode": "3000",
            "phone": "0400000000"
          },
          "skus": [
            {
              "sku": "ABC-123",
              "quantity": 2
            }
          ],
          "orderType": "dropship",
          "courierType": "express",
          "authorityToLeave": true,
          "draftOnly": false,
          "currencyCode": "AUD",
          "costs": {
            "lineItems": [
              {
                "sku": "ABC-123",
                "productTitle": "Example Product",
                "quantity": 2,
                "unitCostExGst": "2.16",
                "lineItemCostExGst": "4.32",
                "lineItemCostInclGst": "4.75"
              }
            ],
            "shippingCostExGst": "12.95",
            "shippingCostInclGst": "14.25",
            "totalExGst": "17.27",
            "totalInclGst": "19.00",
            "gst": "1.73"
          },
          "account": {
            "companyName": "Example Company",
            "creditLimit": "500.00",
            "outstandingBeforeOrder": "0.00",
            "overdueInvoices": []
          }
        }
      }
    }
  }
}