{
  "openapi": "3.0.3",
  "info": {
    "title": "GrivaOne API",
    "version": "1.0.0",
    "description": "A representative OpenAPI 3 skeleton for the GrivaOne REST API. It documents the real authentication flow and a curated set of endpoints so you can import it into Postman, Insomnia, Swagger UI or an OpenAPI client generator. It is intentionally a skeleton — see https://api.grivaone.com for the exhaustive endpoint catalogue and the full reference at https://developers.grivaone.com.",
    "contact": { "name": "GrivaOne Developers", "url": "https://developers.grivaone.com" }
  },
  "servers": [
    { "url": "https://gulfpay-production.up.railway.app/api/v1", "description": "Production" }
  ],
  "security": [{ "bearerAuth": [] }],
  "tags": [
    { "name": "Authentication" },
    { "name": "Customers" },
    { "name": "Items" },
    { "name": "Sales invoices" }
  ],
  "paths": {
    "/auth/login": {
      "post": {
        "tags": ["Authentication"],
        "summary": "Log in for an access token",
        "description": "Exchange email + password for a JWT access token (or a pre-auth OTP challenge). Public; rate limited to 5 attempts/min/IP.",
        "security": [],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": { "$ref": "#/components/schemas/LoginRequest" },
              "example": { "email": "you@company.com", "password": "your-password" }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Authenticated — tokens and tenant context.",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/LoginEnvelope" }
              }
            }
          },
          "400": { "$ref": "#/components/responses/ValidationError" },
          "401": { "$ref": "#/components/responses/Unauthorized" },
          "429": { "$ref": "#/components/responses/TooManyRequests" }
        }
      }
    },
    "/auth/refresh": {
      "post": {
        "tags": ["Authentication"],
        "summary": "Mint a new access token",
        "description": "Exchange a refresh token for a fresh access token. The refresh token is not rotated.",
        "security": [],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": { "refreshToken": { "type": "string" } },
                "required": ["refreshToken"]
              }
            }
          }
        },
        "responses": {
          "200": { "description": "A new access token and its lifetime." },
          "401": { "$ref": "#/components/responses/Unauthorized" }
        }
      }
    },
    "/auth/me": {
      "get": {
        "tags": ["Authentication"],
        "summary": "Get the current user",
        "responses": {
          "200": { "description": "The authenticated user and tenant context." },
          "401": { "$ref": "#/components/responses/Unauthorized" }
        }
      }
    },
    "/customers": {
      "get": {
        "tags": ["Customers"],
        "summary": "List customers",
        "parameters": [
          { "$ref": "#/components/parameters/CompanyId" },
          { "$ref": "#/components/parameters/Page" },
          { "$ref": "#/components/parameters/Limit" },
          { "$ref": "#/components/parameters/Search" },
          { "$ref": "#/components/parameters/SortDir" }
        ],
        "responses": {
          "200": {
            "description": "A page of customers.",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/CustomerListEnvelope" }
              }
            }
          },
          "401": { "$ref": "#/components/responses/Unauthorized" }
        }
      },
      "post": {
        "tags": ["Customers"],
        "summary": "Create a customer",
        "parameters": [
          { "$ref": "#/components/parameters/CompanyId" },
          { "$ref": "#/components/parameters/IdempotencyKey" }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": { "$ref": "#/components/schemas/CustomerInput" },
              "example": { "name": "Acme Trading LLC", "email": "ap@acme.example" }
            }
          }
        },
        "responses": {
          "201": { "description": "The created customer." },
          "400": { "$ref": "#/components/responses/ValidationError" },
          "401": { "$ref": "#/components/responses/Unauthorized" },
          "409": { "$ref": "#/components/responses/Conflict" }
        }
      }
    },
    "/customers/{id}": {
      "parameters": [
        { "name": "id", "in": "path", "required": true, "schema": { "type": "string" } },
        { "$ref": "#/components/parameters/CompanyId" }
      ],
      "get": {
        "tags": ["Customers"],
        "summary": "Retrieve a customer",
        "responses": {
          "200": { "description": "The customer." },
          "404": { "$ref": "#/components/responses/NotFound" }
        }
      },
      "patch": {
        "tags": ["Customers"],
        "summary": "Update a customer",
        "requestBody": {
          "content": {
            "application/json": { "schema": { "$ref": "#/components/schemas/CustomerInput" } }
          }
        },
        "responses": {
          "200": { "description": "The updated customer." },
          "400": { "$ref": "#/components/responses/ValidationError" },
          "404": { "$ref": "#/components/responses/NotFound" }
        }
      },
      "delete": {
        "tags": ["Customers"],
        "summary": "Delete a customer",
        "responses": {
          "200": { "description": "Deleted." },
          "404": { "$ref": "#/components/responses/NotFound" }
        }
      }
    },
    "/items": {
      "get": {
        "tags": ["Items"],
        "summary": "List items",
        "parameters": [
          { "$ref": "#/components/parameters/CompanyId" },
          { "$ref": "#/components/parameters/Page" },
          { "$ref": "#/components/parameters/Limit" },
          { "$ref": "#/components/parameters/Search" }
        ],
        "responses": {
          "200": { "description": "A page of items." },
          "401": { "$ref": "#/components/responses/Unauthorized" }
        }
      }
    },
    "/sales-invoices": {
      "get": {
        "tags": ["Sales invoices"],
        "summary": "List sales invoices",
        "parameters": [
          { "$ref": "#/components/parameters/CompanyId" },
          { "$ref": "#/components/parameters/Page" },
          { "$ref": "#/components/parameters/Limit" }
        ],
        "responses": {
          "200": { "description": "A page of sales invoices." },
          "401": { "$ref": "#/components/responses/Unauthorized" }
        }
      },
      "post": {
        "tags": ["Sales invoices"],
        "summary": "Create a sales invoice",
        "parameters": [
          { "$ref": "#/components/parameters/CompanyId" },
          { "$ref": "#/components/parameters/IdempotencyKey" }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": { "$ref": "#/components/schemas/SalesInvoiceInput" },
              "example": {
                "customerId": "cus_123",
                "date": "2026-09-07",
                "lines": [{ "itemId": "itm_1", "quantity": 10, "unitPrice": 25.0, "taxRate": 15 }]
              }
            }
          }
        },
        "responses": {
          "201": { "description": "The created invoice." },
          "400": { "$ref": "#/components/responses/ValidationError" },
          "409": { "$ref": "#/components/responses/Conflict" }
        }
      }
    },
    "/sales-invoices/{id}/record-payment": {
      "post": {
        "tags": ["Sales invoices"],
        "summary": "Record a payment against an invoice",
        "parameters": [
          { "name": "id", "in": "path", "required": true, "schema": { "type": "string" } },
          { "$ref": "#/components/parameters/CompanyId" },
          { "$ref": "#/components/parameters/IdempotencyKey" }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "amount": { "type": "number" },
                  "date": { "type": "string", "format": "date" },
                  "method": { "type": "string", "example": "Cash" }
                },
                "required": ["amount"]
              }
            }
          }
        },
        "responses": {
          "200": { "description": "Payment recorded; invoice balance updated." },
          "400": { "$ref": "#/components/responses/ValidationError" },
          "404": { "$ref": "#/components/responses/NotFound" }
        }
      }
    }
  },
  "components": {
    "securitySchemes": {
      "bearerAuth": {
        "type": "http",
        "scheme": "bearer",
        "bearerFormat": "JWT",
        "description": "The accessToken returned by POST /auth/login."
      }
    },
    "parameters": {
      "CompanyId": {
        "name": "X-Company-Id",
        "in": "header",
        "required": false,
        "description": "Selects the active company for multi-company tenants.",
        "schema": { "type": "string" }
      },
      "IdempotencyKey": {
        "name": "Idempotency-Key",
        "in": "header",
        "required": false,
        "description": "A unique value per logical write. A retry with the same key returns 409 idempotentReplay instead of applying the change twice.",
        "schema": { "type": "string", "format": "uuid" }
      },
      "Page": { "name": "page", "in": "query", "schema": { "type": "integer", "default": 1, "minimum": 1 } },
      "Limit": { "name": "limit", "in": "query", "schema": { "type": "integer", "default": 20, "maximum": 1000 } },
      "Search": { "name": "search", "in": "query", "schema": { "type": "string" } },
      "SortDir": { "name": "sortDir", "in": "query", "schema": { "type": "string", "enum": ["asc", "desc"], "default": "desc" } }
    },
    "responses": {
      "Unauthorized": {
        "description": "Missing, expired or invalid access token.",
        "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } }
      },
      "ValidationError": {
        "description": "The request body failed validation.",
        "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } }
      },
      "NotFound": {
        "description": "The resource does not exist or is outside your company scope.",
        "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } }
      },
      "Conflict": {
        "description": "A uniqueness conflict, or an idempotent replay (idempotentReplay: true).",
        "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } }
      },
      "TooManyRequests": {
        "description": "A rate limit was exceeded.",
        "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } }
      }
    },
    "schemas": {
      "Meta": {
        "type": "object",
        "properties": {
          "requestId": { "type": "string" },
          "timestamp": { "type": "string", "format": "date-time" },
          "page": { "type": "integer" },
          "limit": { "type": "integer" },
          "total": { "type": "integer" },
          "totalPages": { "type": "integer" }
        }
      },
      "Error": {
        "type": "object",
        "properties": {
          "statusCode": { "type": "integer" },
          "message": { "oneOf": [{ "type": "string" }, { "type": "array", "items": { "type": "string" } }] },
          "error": { "type": "string" },
          "fields": { "type": "object", "additionalProperties": { "type": "array", "items": { "type": "string" } } },
          "idempotentReplay": { "type": "boolean" },
          "requestId": { "type": "string" },
          "timestamp": { "type": "string", "format": "date-time" },
          "path": { "type": "string" }
        },
        "required": ["statusCode", "message"]
      },
      "LoginRequest": {
        "type": "object",
        "properties": {
          "email": { "type": "string", "format": "email" },
          "password": { "type": "string", "minLength": 8 },
          "rememberMe": { "type": "boolean" }
        },
        "required": ["email", "password"]
      },
      "LoginEnvelope": {
        "type": "object",
        "properties": {
          "data": {
            "type": "object",
            "properties": {
              "requiresOtp": { "type": "boolean" },
              "accessToken": { "type": "string" },
              "expiresIn": { "type": "integer", "example": 900 },
              "refreshToken": { "type": "string" },
              "refreshExpiresAt": { "type": "string", "format": "date-time" },
              "user": { "type": "object" },
              "organization": { "type": "object" },
              "companies": { "type": "array", "items": { "type": "object" } },
              "membership": { "type": "object" }
            }
          },
          "meta": { "$ref": "#/components/schemas/Meta" }
        }
      },
      "Customer": {
        "type": "object",
        "properties": {
          "id": { "type": "string" },
          "name": { "type": "string" },
          "email": { "type": "string" },
          "phone": { "type": "string" }
        }
      },
      "CustomerInput": {
        "type": "object",
        "properties": {
          "name": { "type": "string" },
          "email": { "type": "string" },
          "phone": { "type": "string" }
        },
        "required": ["name"]
      },
      "CustomerListEnvelope": {
        "type": "object",
        "properties": {
          "data": { "type": "array", "items": { "$ref": "#/components/schemas/Customer" } },
          "meta": { "$ref": "#/components/schemas/Meta" }
        }
      },
      "SalesInvoiceInput": {
        "type": "object",
        "properties": {
          "customerId": { "type": "string" },
          "date": { "type": "string", "format": "date" },
          "lines": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "itemId": { "type": "string" },
                "quantity": { "type": "number" },
                "unitPrice": { "type": "number" },
                "taxRate": { "type": "number" }
              },
              "required": ["itemId", "quantity", "unitPrice"]
            }
          }
        },
        "required": ["customerId", "lines"]
      }
    }
  }
}
