{
  "openapi": "3.1.0",
  "info": {
    "title": "Raja Repair Public API",
    "version": "1.0.0",
    "description": "Read-only public data for Raja Repair (rajarepair.id): outlets, device catalogue, repair prices, reviews, repair status lookup and CMS content. Anonymous GET only; every write path requires a session from rajarepair.id. Rate limit 2000 requests/minute per IP (60/minute for /check).",
    "contact": { "name": "Raja Repair", "url": "https://rajarepair.id/kontak" }
  },
  "servers": [{ "url": "https://rajarepair.id/api/v1" }],
  "paths": {
    "/locations": {
      "get": { "summary": "All Raja Repair outlets", "responses": { "200": { "$ref": "#/components/responses/List" } } }
    },
    "/nearest-locations": {
      "get": {
        "summary": "Outlets nearest to a coordinate, with distance in km",
        "parameters": [
          { "name": "lat", "in": "query", "required": true, "schema": { "type": "number" } },
          { "name": "lng", "in": "query", "required": true, "schema": { "type": "number" } }
        ],
        "responses": { "200": { "$ref": "#/components/responses/Object" } }
      }
    },
    "/device-models": {
      "get": {
        "summary": "Device models, optionally filtered by brand",
        "parameters": [{ "name": "brand_id", "in": "query", "schema": { "type": "integer" } }],
        "responses": { "200": { "$ref": "#/components/responses/List" } }
      }
    },
    "/repair-services": {
      "get": {
        "summary": "Repair services and prices for a device model",
        "parameters": [{ "name": "device_model_id", "in": "query", "required": true, "schema": { "type": "integer" } }],
        "responses": { "200": { "$ref": "#/components/responses/List" } }
      }
    },
    "/availability": {
      "get": {
        "summary": "Booked hourly slots for an outlet on a date",
        "parameters": [
          { "name": "location_id", "in": "query", "required": true, "schema": { "type": "integer" } },
          { "name": "date", "in": "query", "required": true, "schema": { "type": "string", "format": "date" } }
        ],
        "responses": { "200": { "$ref": "#/components/responses/Object" } }
      }
    },
    "/reviews": {
      "get": { "summary": "Aggregate rating and recent customer reviews", "responses": { "200": { "$ref": "#/components/responses/Object" } } }
    },
    "/stats": {
      "get": { "summary": "Company stats (repairs, customers, outlets, models)", "responses": { "200": { "$ref": "#/components/responses/Object" } } }
    },
    "/reward-catalog": {
      "get": { "summary": "Active reward vouchers redeemable with points", "responses": { "200": { "$ref": "#/components/responses/List" } } }
    },
    "/check": {
      "get": {
        "summary": "Look up a repair by its job sheet number",
        "parameters": [
          { "name": "search_type", "in": "query", "required": true, "schema": { "type": "string", "enum": ["job_sheet_no"] } },
          { "name": "search_number", "in": "query", "required": true, "schema": { "type": "string" } }
        ],
        "responses": { "200": { "$ref": "#/components/responses/List" }, "429": { "description": "Rate limited (60/minute per IP)" } }
      }
    },
    "/cms/articles": {
      "get": {
        "summary": "Published articles",
        "parameters": [
          { "$ref": "#/components/parameters/Page" },
          { "$ref": "#/components/parameters/PerPage" },
          { "$ref": "#/components/parameters/Locale" }
        ],
        "responses": { "200": { "$ref": "#/components/responses/List" } }
      }
    },
    "/cms/articles/{slug}": {
      "get": {
        "summary": "One article, with sanitised HTML body",
        "parameters": [{ "$ref": "#/components/parameters/Slug" }, { "$ref": "#/components/parameters/Locale" }],
        "responses": { "200": { "$ref": "#/components/responses/Object" }, "404": { "description": "Not found" } }
      }
    },
    "/cms/promos": {
      "get": {
        "summary": "Active promos",
        "parameters": [{ "$ref": "#/components/parameters/PerPage" }, { "$ref": "#/components/parameters/Locale" }],
        "responses": { "200": { "$ref": "#/components/responses/List" } }
      }
    },
    "/cms/promos/{slug}": {
      "get": {
        "summary": "One promo",
        "parameters": [{ "$ref": "#/components/parameters/Slug" }, { "$ref": "#/components/parameters/Locale" }],
        "responses": { "200": { "$ref": "#/components/responses/Object" }, "404": { "description": "Not found" } }
      }
    }
  },
  "components": {
    "parameters": {
      "Page": { "name": "page", "in": "query", "schema": { "type": "integer", "minimum": 1 } },
      "PerPage": { "name": "per_page", "in": "query", "schema": { "type": "integer", "minimum": 1, "maximum": 50 } },
      "Locale": { "name": "locale", "in": "query", "schema": { "type": "string", "enum": ["id", "en"] } },
      "Slug": { "name": "slug", "in": "path", "required": true, "schema": { "type": "string" } }
    },
    "responses": {
      "List": {
        "description": "Envelope with an array payload",
        "content": { "application/json": { "schema": { "type": "object", "properties": { "data": { "type": "array", "items": { "type": "object" } } } } } }
      },
      "Object": {
        "description": "Envelope with an object payload",
        "content": { "application/json": { "schema": { "type": "object", "properties": { "data": { "type": "object" } } } } }
      }
    }
  }
}
