{
  "openapi": "3.0.3",
  "info": {
    "title": "SIGNIFO Webstore API",
    "description": "REST API for SIGNIFO Webstore — the official online store for SIGNIFO polyester canvas fabric. Endpoints cover product browsing, AI color search, shipping cost lookup, cart management, order status, and transaction creation.\n\nAll responses use a canonical envelope: `{ \"success\": bool, \"message\": string, \"data\": object|array }`.\n\n**CSRF requirement:** all mutating endpoints (POST) require a CSRF token. Agents must first perform a GET on the corresponding page to establish a session and retrieve the token from `<meta name=\"csrf-token\">` or `window.SignifoCSRF.token`. Pass the token in the `X-CSRF-TOKEN` request header.",
    "version": "1.0.0",
    "contact": {
      "name": "SIGNIFO Canvas",
      "url": "https://www.signifocanvas.com"
    },
    "license": {
      "name": "Proprietary",
      "url": "https://shop.signifocanvas.com"
    }
  },
  "servers": [
    {
      "url": "https://shop.signifocanvas.com",
      "description": "Production"
    }
  ],
  "components": {
    "securitySchemes": {
      "CsrfToken": {
        "type": "apiKey",
        "in": "header",
        "name": "X-CSRF-TOKEN",
        "description": "Session CSRF token. Obtain by performing a GET on the target page (e.g. GET /) and reading the `<meta name=\"csrf-token\">` tag."
      }
    },
    "schemas": {
      "ApiEnvelope": {
        "type": "object",
        "required": ["success"],
        "properties": {
          "success": { "type": "boolean" },
          "message": { "type": "string", "example": "" }
        }
      },
      "ApiError": {
        "allOf": [
          { "$ref": "#/components/schemas/ApiEnvelope" },
          {
            "type": "object",
            "properties": {
              "success": { "type": "boolean", "example": false }
            }
          }
        ]
      },
      "ProductSummary": {
        "type": "object",
        "properties": {
          "slug":           { "type": "string",  "example": "26-moss-green" },
          "name":           { "type": "string",  "example": "Kanvas Polyester No.26 Moss Green" },
          "color_name":     { "type": "string",  "example": "Moss Green" },
          "price_per_yard": { "type": "integer", "example": 35000 },
          "currency":       { "type": "string",  "example": "IDR" },
          "hex_color":      { "type": "string",  "example": "#4A7C59" },
          "image_url":      { "type": "string",  "nullable": true, "example": "https://shop.signifocanvas.com/assets/images/products/26-moss-green.jpg" },
          "product_url":    { "type": "string",  "example": "https://shop.signifocanvas.com/product/26-moss-green" }
        }
      },
      "ProductDetail": {
        "allOf": [
          { "$ref": "#/components/schemas/ProductSummary" },
          {
            "type": "object",
            "properties": {
              "description": { "type": "string", "example": "Kain kanvas polyester warna hijau lumut..." }
            }
          }
        ]
      },
      "ShippingRate": {
        "type": "object",
        "properties": {
          "courier": { "type": "string",  "example": "JNE" },
          "service": { "type": "string",  "example": "REG" },
          "cost":    { "type": "integer", "example": 15000 },
          "etd":     { "type": "string",  "example": "2-3", "description": "Estimated transit days" }
        }
      },
      "Order": {
        "type": "object",
        "properties": {
          "order_id":     { "type": "string",  "example": "INV-20260405-A1B2C3D4" },
          "status":       { "type": "string",  "example": "unpaid", "enum": ["paid", "settlement", "capture", "pending", "unpaid", "payment_failed"] },
          "payment_type": { "type": "string",  "example": "static_qris" },
          "gross_amount": { "type": "integer", "example": 185000 },
          "created_at":   { "type": "string",  "nullable": true, "example": "2026-04-05 10:00:00" },
          "updated_at":   { "type": "string",  "nullable": true, "example": "2026-04-05 10:05:00" }
        }
      }
    }
  },
  "paths": {
    "/api/products.php": {
      "get": {
        "operationId": "listProducts",
        "summary": "Product catalog",
        "description": "Returns all active products when called without parameters, or a single product with full description when `?slug=` is provided. No authentication required.",
        "tags": ["Products"],
        "parameters": [
          {
            "name": "slug",
            "in": "query",
            "required": false,
            "description": "Product slug. When supplied, returns the single matching product including its full `description` field.",
            "schema": { "type": "string", "example": "26-moss-green" }
          }
        ],
        "responses": {
          "200": {
            "description": "Success",
            "content": {
              "application/json": {
                "schema": {
                  "oneOf": [
                    {
                      "title": "All products",
                      "description": "Returned when no `slug` parameter is provided.",
                      "allOf": [
                        { "$ref": "#/components/schemas/ApiEnvelope" },
                        {
                          "type": "object",
                          "properties": {
                            "data": {
                              "type": "object",
                              "properties": {
                                "count":    { "type": "integer", "example": 52 },
                                "products": {
                                  "type": "array",
                                  "items": { "$ref": "#/components/schemas/ProductSummary" }
                                }
                              }
                            }
                          }
                        }
                      ]
                    },
                    {
                      "title": "Single product",
                      "description": "Returned when `?slug=` is provided.",
                      "allOf": [
                        { "$ref": "#/components/schemas/ApiEnvelope" },
                        {
                          "type": "object",
                          "properties": {
                            "data": { "$ref": "#/components/schemas/ProductDetail" }
                          }
                        }
                      ]
                    }
                  ]
                }
              }
            }
          },
          "404": {
            "description": "Product not found",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/ApiError" }
              }
            }
          },
          "405": {
            "description": "Method not allowed",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/ApiError" }
              }
            }
          }
        }
      }
    },
    "/api/color_search.php": {
      "post": {
        "operationId": "searchColors",
        "summary": "AI color search",
        "description": "Search the 52+ polyester canvas color catalog using natural language in English or Bahasa Indonesia. Returns 3–5 ranked color slugs suitable for use in cart actions.\n\nRequires a CSRF token from an active session (`GET /` first).",
        "tags": ["Products"],
        "security": [{ "CsrfToken": [] }],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": ["userQuery"],
                "properties": {
                  "userQuery": {
                    "type": "string",
                    "maxLength": 25,
                    "description": "Natural language color description in English or Bahasa Indonesia.",
                    "example": "hijau alami"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Ranked color slugs",
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    { "$ref": "#/components/schemas/ApiEnvelope" },
                    {
                      "type": "object",
                      "properties": {
                        "data": {
                          "type": "array",
                          "items": { "type": "string" },
                          "example": ["26-moss-green", "41-sage-green", "31-forest-green"]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "400": { "description": "Empty or invalid query", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ApiError" } } } },
          "403": { "description": "Invalid CSRF token", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ApiError" } } } },
          "405": { "description": "Method not allowed", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ApiError" } } } },
          "502": { "description": "Upstream AI service error", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ApiError" } } } }
        }
      }
    },
    "/api/shipping.php": {
      "post": {
        "operationId": "getShippingRates",
        "summary": "Shipping cost lookup",
        "description": "Returns available courier services and costs for a given destination city. Cart weight is read from the server-side session — items must already be in the cart.\n\nRequires a CSRF token from an active session (`GET /checkout.php` or `GET /cart.php`).",
        "tags": ["Shipping"],
        "security": [{ "CsrfToken": [] }],
        "requestBody": {
          "required": true,
          "content": {
            "application/x-www-form-urlencoded": {
              "schema": {
                "type": "object",
                "required": ["destination"],
                "properties": {
                  "destination": {
                    "type": "integer",
                    "description": "RajaOngkir city ID for the delivery destination.",
                    "example": 152
                  },
                  "courier": {
                    "type": "string",
                    "description": "Optional. Filter to a specific courier code (e.g. `jne`, `jnt`, `tiki`). Omit to return all available couriers.",
                    "example": "jne"
                  },
                  "csrf_token": {
                    "type": "string",
                    "description": "CSRF token. Alternatively supply via `X-CSRF-TOKEN` header."
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Available shipping rates",
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    { "$ref": "#/components/schemas/ApiEnvelope" },
                    {
                      "type": "object",
                      "properties": {
                        "data": {
                          "type": "array",
                          "items": { "$ref": "#/components/schemas/ShippingRate" }
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "400": { "description": "Missing destination or empty cart weight", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ApiError" } } } },
          "403": { "description": "Invalid CSRF token", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ApiError" } } } },
          "405": { "description": "Method not allowed", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ApiError" } } } },
          "500": { "description": "Upstream shipping API error", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ApiError" } } } }
        }
      }
    },
    "/api/cart_handler.php": {
      "post": {
        "operationId": "addToCart",
        "summary": "Add item to cart",
        "description": "Adds a product to the session cart, or increments its quantity if it is already present.\n\nRequires a CSRF token supplied in the POST body (`csrf_token` field). Establish a session with `GET /` first.",
        "tags": ["Cart"],
        "security": [{ "CsrfToken": [] }],
        "requestBody": {
          "required": true,
          "content": {
            "application/x-www-form-urlencoded": {
              "schema": {
                "type": "object",
                "required": ["product_id", "product_name", "product_price", "quantity", "csrf_token"],
                "properties": {
                  "product_id": {
                    "type": "string",
                    "description": "Product slug, e.g. `26-moss-green`.",
                    "example": "26-moss-green"
                  },
                  "product_name": {
                    "type": "string",
                    "description": "Display name of the product.",
                    "example": "Kanvas Polyester No.26 Moss Green"
                  },
                  "product_price": {
                    "type": "integer",
                    "description": "Unit price per yard in IDR.",
                    "example": 35000
                  },
                  "quantity": {
                    "type": "integer",
                    "minimum": 1,
                    "description": "Number of yards to add.",
                    "example": 3
                  },
                  "csrf_token": {
                    "type": "string",
                    "description": "CSRF token from the active session."
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Item added (or quantity incremented)",
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    { "$ref": "#/components/schemas/ApiEnvelope" },
                    {
                      "type": "object",
                      "properties": {
                        "data": {
                          "type": "object",
                          "properties": {
                            "cart_count":  { "type": "integer", "description": "Number of distinct SKUs in the cart.", "example": 2 },
                            "total_items": { "type": "integer", "description": "Total quantity (sum of all yards) across all SKUs.", "example": 7 }
                          }
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "400": { "description": "Incomplete or invalid request data", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ApiError" } } } },
          "403": { "description": "Invalid CSRF token", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ApiError" } } } },
          "405": { "description": "Method not allowed", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ApiError" } } } }
        }
      }
    },
    "/api/get_order_status.php": {
      "post": {
        "operationId": "getOrderStatus",
        "summary": "Order status check",
        "description": "Returns the current status and payment details for a given order ID.\n\nRequires a CSRF token — establish a session first. The order ID acts as the public access token; no Google OAuth is required.",
        "tags": ["Orders"],
        "security": [{ "CsrfToken": [] }],
        "requestBody": {
          "required": true,
          "content": {
            "application/x-www-form-urlencoded": {
              "schema": {
                "type": "object",
                "required": ["order_id", "csrf_token"],
                "properties": {
                  "order_id": {
                    "type": "string",
                    "description": "Order ID as returned by `POST /api/create_transaction.php`.",
                    "example": "INV-20260405-A1B2C3D4"
                  },
                  "csrf_token": {
                    "type": "string",
                    "description": "CSRF token. Alternatively supply via `X-CSRF-TOKEN` header."
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Order details",
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    { "$ref": "#/components/schemas/ApiEnvelope" },
                    {
                      "type": "object",
                      "properties": {
                        "data": {
                          "type": "object",
                          "properties": {
                            "order": { "$ref": "#/components/schemas/Order" }
                          }
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "400": { "description": "Missing order_id", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ApiError" } } } },
          "403": { "description": "Invalid CSRF token", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ApiError" } } } },
          "404": { "description": "Order not found", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ApiError" } } } },
          "405": { "description": "Method not allowed", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ApiError" } } } }
        }
      }
    },
    "/api/create_transaction.php": {
      "post": {
        "operationId": "createTransaction",
        "summary": "Create transaction",
        "description": "Places an order from the current session cart. Cart contents and selected shipping method are read from the server-side session — no body fields are required.\n\n**Prerequisites (all must be satisfied before calling this endpoint):**\n1. Cart must be non-empty (`POST /api/cart_handler.php`).\n2. Shipping must be selected: load `GET /checkout.php`, then call `POST /api/shipping.php`.\n3. User must be authenticated via Google OAuth (`GET /checkout.php` will redirect to login if not authenticated).\n4. CSRF token from the authenticated session.\n\nOn success, the cart is cleared from the session and an order record is created.",
        "tags": ["Orders"],
        "security": [{ "CsrfToken": [] }],
        "requestBody": {
          "description": "No fields required in the body. The CSRF token must be supplied via the `X-CSRF-TOKEN` header or a `csrf_token` POST field.",
          "required": false,
          "content": {
            "application/x-www-form-urlencoded": {
              "schema": {
                "type": "object",
                "properties": {
                  "csrf_token": {
                    "type": "string",
                    "description": "CSRF token. Alternatively supply via `X-CSRF-TOKEN` header."
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Transaction created. Response shape depends on payment method configured in the session.",
            "content": {
              "application/json": {
                "schema": {
                  "oneOf": [
                    {
                      "title": "QRIS payment",
                      "description": "Returned when the session payment method is `static_qris` or `dynamic_qris`.",
                      "allOf": [
                        { "$ref": "#/components/schemas/ApiEnvelope" },
                        {
                          "type": "object",
                          "properties": {
                            "data": {
                              "type": "object",
                              "properties": {
                                "payment_type": { "type": "string", "example": "static_qris" },
                                "order_id":     { "type": "string", "example": "INV-20260405-A1B2C3D4" },
                                "redirect_url": { "type": "string", "example": "/payment/static_qris_invoice.php?order_id=INV-20260405-A1B2C3D4" }
                              }
                            }
                          }
                        }
                      ]
                    },
                    {
                      "title": "Manual / WhatsApp fallback",
                      "description": "Returned when no automated payment gateway is configured.",
                      "allOf": [
                        { "$ref": "#/components/schemas/ApiEnvelope" },
                        {
                          "type": "object",
                          "properties": {
                            "data": {
                              "type": "object",
                              "properties": {
                                "status":          { "type": "string", "example": "unpaid" },
                                "fallback_manual": { "type": "boolean", "example": true },
                                "order_id":        { "type": "string", "example": "INV-20260405-A1B2C3D4" },
                                "whatsapp_url":    { "type": "string", "description": "Pre-filled WhatsApp URL to confirm the order manually." }
                              }
                            }
                          }
                        }
                      ]
                    }
                  ]
                }
              }
            }
          },
          "400": { "description": "Empty cart or missing shipping selection", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ApiError" } } } },
          "401": { "description": "User not authenticated (Google OAuth required)", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ApiError" } } } },
          "403": { "description": "Invalid CSRF token", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ApiError" } } } },
          "500": { "description": "Transaction creation failed", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ApiError" } } } }
        }
      }
    }
  }
}
