{
  "openapi": "3.0.3",
  "info": {
    "title": "Rebaar Public API",
    "version": "1.0.0",
    "description": "REST API for Rebaar, the construction ERP for Indian SMBs. Read and write clients, projects, invoices, and vendors in your own workspace. All monetary amounts are integers in paise (rupees × 100); all dates are ISO YYYY-MM-DD strings. Requires the Scale plan. Rate limit: 120 requests/minute/key — responses carry RateLimit-Limit / RateLimit-Remaining / RateLimit-Reset headers, and 429s a Retry-After.",
    "contact": {
      "name": "Rebaar",
      "url": "https://rebaar.in/docs/api",
      "email": "support@rebaar.in"
    }
  },
  "servers": [
    {
      "url": "https://rebaar.in/api/v1"
    }
  ],
  "security": [
    {
      "bearerAuth": []
    }
  ],
  "tags": [
    {
      "name": "clients",
      "description": "The people/companies you bill."
    },
    {
      "name": "projects",
      "description": "Construction/interior projects."
    },
    {
      "name": "invoices",
      "description": "Client invoices. Amounts in paise."
    },
    {
      "name": "vendors",
      "description": "Suppliers and subcontract vendors."
    }
  ],
  "components": {
    "securitySchemes": {
      "bearerAuth": {
        "type": "http",
        "scheme": "bearer",
        "description": "API key from Settings → API keys, sent as `Authorization: Bearer ck_live_…`. Keys carry read and/or write scopes; write covers POST/PATCH/DELETE."
      }
    },
    "schemas": {
      "Error": {
        "type": "object",
        "properties": {
          "error": {
            "type": "string",
            "description": "Machine-readable error code."
          },
          "message": {
            "type": "string",
            "description": "Human-readable explanation."
          }
        },
        "required": [
          "error"
        ]
      },
      "Client": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string"
          },
          "name": {
            "type": "string"
          },
          "contactPerson": {
            "type": "string",
            "nullable": true
          },
          "email": {
            "type": "string",
            "nullable": true
          },
          "phone": {
            "type": "string",
            "nullable": true
          },
          "city": {
            "type": "string",
            "nullable": true
          },
          "pincode": {
            "type": "string",
            "nullable": true
          },
          "district": {
            "type": "string",
            "nullable": true
          },
          "stateCode": {
            "type": "string",
            "nullable": true,
            "description": "2-digit GST state code."
          },
          "gstin": {
            "type": "string",
            "nullable": true,
            "description": "15-character GSTIN."
          },
          "createdAt": {
            "type": "string",
            "format": "date-time"
          }
        }
      },
      "Project": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string"
          },
          "name": {
            "type": "string"
          },
          "type": {
            "type": "string",
            "enum": [
              "Residential",
              "Commercial",
              "Interior",
              "Renovation"
            ]
          },
          "status": {
            "type": "string"
          },
          "value": {
            "type": "integer",
            "description": "Estimated project value in PAISE (rupees × 100). ₹1,500.00 = 150000."
          },
          "progress": {
            "type": "integer",
            "description": "Physical progress percent (0–100)."
          },
          "city": {
            "type": "string",
            "nullable": true
          },
          "pincode": {
            "type": "string",
            "nullable": true
          },
          "district": {
            "type": "string",
            "nullable": true
          },
          "stateCode": {
            "type": "string",
            "nullable": true,
            "description": "2-digit GST state code."
          },
          "client": {
            "type": "object",
            "nullable": true,
            "properties": {
              "id": {
                "type": "string"
              },
              "name": {
                "type": "string"
              }
            }
          }
        }
      },
      "Invoice": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string"
          },
          "number": {
            "type": "string"
          },
          "clientId": {
            "type": "string",
            "nullable": true
          },
          "projectId": {
            "type": "string",
            "nullable": true
          },
          "subtotal": {
            "type": "integer",
            "description": "Pre-tax subtotal in PAISE (rupees × 100). ₹1,500.00 = 150000."
          },
          "taxAmount": {
            "type": "integer",
            "description": "GST amount in PAISE (rupees × 100). ₹1,500.00 = 150000."
          },
          "total": {
            "type": "integer",
            "description": "Grand total in PAISE (rupees × 100). ₹1,500.00 = 150000."
          },
          "amountPaid": {
            "type": "integer",
            "description": "Amount received so far in PAISE (rupees × 100). ₹1,500.00 = 150000."
          },
          "status": {
            "type": "string",
            "enum": [
              "Draft",
              "Sent",
              "Paid",
              "Overdue",
              "Partial",
              "Unpaid",
              "Cancelled"
            ]
          },
          "issueDate": {
            "type": "string",
            "format": "date",
            "description": "Issue date as an ISO YYYY-MM-DD string."
          },
          "dueDate": {
            "type": "string",
            "format": "date",
            "description": "Due date as an ISO YYYY-MM-DD string.",
            "nullable": true
          }
        }
      },
      "Vendor": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string"
          },
          "name": {
            "type": "string"
          },
          "category": {
            "type": "string",
            "nullable": true
          },
          "city": {
            "type": "string",
            "nullable": true
          },
          "pincode": {
            "type": "string",
            "nullable": true
          },
          "district": {
            "type": "string",
            "nullable": true
          },
          "stateCode": {
            "type": "string",
            "nullable": true,
            "description": "2-digit GST state code."
          },
          "gstin": {
            "type": "string",
            "nullable": true
          },
          "phone": {
            "type": "string",
            "nullable": true
          },
          "email": {
            "type": "string",
            "nullable": true
          },
          "leadTimeDays": {
            "type": "integer",
            "nullable": true
          }
        }
      }
    }
  },
  "paths": {
    "/clients": {
      "get": {
        "operationId": "listClients",
        "tags": [
          "clients"
        ],
        "summary": "List clients",
        "parameters": [
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 200,
              "default": 50
            },
            "description": "Page size (1–200)."
          }
        ],
        "responses": {
          "200": {
            "description": "Clients, newest first.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "description": "Client list",
                  "properties": {
                    "object": {
                      "type": "string",
                      "enum": [
                        "list"
                      ]
                    },
                    "data": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/Client"
                      }
                    },
                    "hasMore": {
                      "type": "boolean",
                      "description": "True when the page was full — pass a higher ?limit or filter to narrow."
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "Missing, revoked, or expired API key.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "402": {
            "description": "Workspace has no active Scale-plan subscription.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Key lacks the required scope, or source IP is not on the key's allowlist.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded (120 requests/minute/key). Honour Retry-After.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/clients/create": {
      "post": {
        "operationId": "createClient",
        "tags": [
          "clients"
        ],
        "summary": "Create a client",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "name"
                ],
                "properties": {
                  "name": {
                    "type": "string",
                    "maxLength": 200
                  },
                  "contactPerson": {
                    "type": "string",
                    "maxLength": 120
                  },
                  "email": {
                    "type": "string",
                    "format": "email"
                  },
                  "phone": {
                    "type": "string",
                    "maxLength": 20
                  },
                  "city": {
                    "type": "string",
                    "maxLength": 80
                  },
                  "pincode": {
                    "type": "string",
                    "pattern": "^[1-9][0-9]{5}$",
                    "description": "6-digit PIN code. The server fills stateCode and district from it when they are not sent."
                  },
                  "district": {
                    "type": "string",
                    "maxLength": 80
                  },
                  "stateCode": {
                    "type": "string",
                    "pattern": "^[0-9]{2}$",
                    "description": "2-digit GST state code, e.g. \"29\" (Karnataka). 400 with field \"pincode\" when the PIN is not in that state."
                  },
                  "gstin": {
                    "type": "string",
                    "pattern": "^[0-9A-Z]{15}$"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The created client.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Client"
                }
              }
            }
          },
          "400": {
            "description": "Validation failed.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Missing, revoked, or expired API key.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "402": {
            "description": "Workspace has no active Scale-plan subscription.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Key lacks the required scope, or source IP is not on the key's allowlist.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded (120 requests/minute/key). Honour Retry-After.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/clients/{id}": {
      "get": {
        "operationId": "getClient",
        "tags": [
          "clients"
        ],
        "summary": "Get one client",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "Resource id, as returned by the list endpoint."
          }
        ],
        "responses": {
          "200": {
            "description": "The client.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Client"
                }
              }
            }
          },
          "401": {
            "description": "Missing, revoked, or expired API key.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "402": {
            "description": "Workspace has no active Scale-plan subscription.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Key lacks the required scope, or source IP is not on the key's allowlist.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "No such client in this workspace.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded (120 requests/minute/key). Honour Retry-After.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      },
      "patch": {
        "operationId": "updateClient",
        "tags": [
          "clients"
        ],
        "summary": "Update a client",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "Resource id, as returned by the list endpoint."
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "description": "Any subset of fields; pass null to clear an optional field.",
                "properties": {
                  "name": {
                    "type": "string",
                    "maxLength": 200
                  },
                  "contactPerson": {
                    "type": "string",
                    "nullable": true
                  },
                  "email": {
                    "type": "string",
                    "format": "email",
                    "nullable": true
                  },
                  "phone": {
                    "type": "string",
                    "nullable": true
                  },
                  "city": {
                    "type": "string",
                    "nullable": true
                  },
                  "pincode": {
                    "type": "string",
                    "pattern": "^[1-9][0-9]{5}$",
                    "description": "6-digit PIN code. The server fills stateCode and district from it when they are not sent."
                  },
                  "district": {
                    "type": "string",
                    "maxLength": 80
                  },
                  "stateCode": {
                    "type": "string",
                    "pattern": "^[0-9]{2}$",
                    "description": "2-digit GST state code, e.g. \"29\" (Karnataka). 400 with field \"pincode\" when the PIN is not in that state."
                  },
                  "gstin": {
                    "type": "string",
                    "pattern": "^[0-9A-Z]{15}$",
                    "nullable": true
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The updated client.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Client"
                }
              }
            }
          },
          "400": {
            "description": "Validation failed.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Missing, revoked, or expired API key.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "402": {
            "description": "Workspace has no active Scale-plan subscription.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Key lacks the required scope, or source IP is not on the key's allowlist.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "No such client in this workspace.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded (120 requests/minute/key). Honour Retry-After.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      },
      "delete": {
        "operationId": "deleteClient",
        "tags": [
          "clients"
        ],
        "summary": "Delete a client",
        "description": "Refuses with 409 while the client still has invoices, projects, or quotations.",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "Resource id, as returned by the list endpoint."
          }
        ],
        "responses": {
          "200": {
            "description": "Deleted."
          },
          "401": {
            "description": "Missing, revoked, or expired API key.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "402": {
            "description": "Workspace has no active Scale-plan subscription.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Key lacks the required scope, or source IP is not on the key's allowlist.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "No such client in this workspace.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Client still referenced by invoices/projects/quotations.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded (120 requests/minute/key). Honour Retry-After.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/projects": {
      "get": {
        "operationId": "listProjects",
        "tags": [
          "projects"
        ],
        "summary": "List projects",
        "parameters": [
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 200,
              "default": 50
            },
            "description": "Page size (1–200)."
          }
        ],
        "responses": {
          "200": {
            "description": "Projects with their linked client.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "description": "Project list",
                  "properties": {
                    "object": {
                      "type": "string",
                      "enum": [
                        "list"
                      ]
                    },
                    "data": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/Project"
                      }
                    },
                    "hasMore": {
                      "type": "boolean",
                      "description": "True when the page was full — pass a higher ?limit or filter to narrow."
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "Missing, revoked, or expired API key.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "402": {
            "description": "Workspace has no active Scale-plan subscription.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Key lacks the required scope, or source IP is not on the key's allowlist.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded (120 requests/minute/key). Honour Retry-After.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/projects/create": {
      "post": {
        "operationId": "createProject",
        "tags": [
          "projects"
        ],
        "summary": "Create a project",
        "description": "Subject to the workspace plan project limit — over-limit returns 402.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "name",
                  "type"
                ],
                "properties": {
                  "name": {
                    "type": "string"
                  },
                  "type": {
                    "type": "string",
                    "enum": [
                      "Residential",
                      "Commercial",
                      "Interior",
                      "Renovation"
                    ]
                  },
                  "city": {
                    "type": "string"
                  },
                  "address": {
                    "type": "string"
                  },
                  "pincode": {
                    "type": "string",
                    "pattern": "^[1-9][0-9]{5}$",
                    "description": "6-digit PIN code. The server fills stateCode and district from it when they are not sent."
                  },
                  "district": {
                    "type": "string",
                    "maxLength": 80
                  },
                  "stateCode": {
                    "type": "string",
                    "pattern": "^[0-9]{2}$",
                    "description": "2-digit GST state code, e.g. \"29\" (Karnataka). 400 with field \"pincode\" when the PIN is not in that state."
                  },
                  "value": {
                    "type": "integer",
                    "description": "Estimated project value in PAISE (rupees × 100). ₹1,500.00 = 150000."
                  },
                  "clientId": {
                    "type": "string"
                  },
                  "startDate": {
                    "type": "string",
                    "format": "date",
                    "description": "Start date as an ISO YYYY-MM-DD string."
                  },
                  "endDate": {
                    "type": "string",
                    "format": "date",
                    "description": "End date as an ISO YYYY-MM-DD string."
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The created project.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Project"
                }
              }
            }
          },
          "400": {
            "description": "Validation failed.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Missing, revoked, or expired API key.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "402": {
            "description": "Workspace has no active Scale-plan subscription.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Key lacks the required scope, or source IP is not on the key's allowlist.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded (120 requests/minute/key). Honour Retry-After.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/projects/{id}": {
      "get": {
        "operationId": "getProject",
        "tags": [
          "projects"
        ],
        "summary": "Get one project",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "Resource id, as returned by the list endpoint."
          }
        ],
        "responses": {
          "200": {
            "description": "The project with its linked client.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Project"
                }
              }
            }
          },
          "401": {
            "description": "Missing, revoked, or expired API key.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "402": {
            "description": "Workspace has no active Scale-plan subscription.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Key lacks the required scope, or source IP is not on the key's allowlist.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "No such project in this workspace.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded (120 requests/minute/key). Honour Retry-After.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/invoices": {
      "get": {
        "operationId": "listInvoices",
        "tags": [
          "invoices"
        ],
        "summary": "List invoices",
        "parameters": [
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 200,
              "default": 50
            },
            "description": "Page size (1–200)."
          },
          {
            "name": "status",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "enum": [
                "Draft",
                "Sent",
                "Paid",
                "Overdue",
                "Partial",
                "Unpaid",
                "Cancelled"
              ]
            },
            "description": "Filter by invoice status."
          }
        ],
        "responses": {
          "200": {
            "description": "Invoices, newest first.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "description": "Invoice list",
                  "properties": {
                    "object": {
                      "type": "string",
                      "enum": [
                        "list"
                      ]
                    },
                    "data": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/Invoice"
                      }
                    },
                    "hasMore": {
                      "type": "boolean",
                      "description": "True when the page was full — pass a higher ?limit or filter to narrow."
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "Missing, revoked, or expired API key.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "402": {
            "description": "Workspace has no active Scale-plan subscription.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Key lacks the required scope, or source IP is not on the key's allowlist.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded (120 requests/minute/key). Honour Retry-After.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/invoices/create": {
      "post": {
        "operationId": "createInvoice",
        "tags": [
          "invoices"
        ],
        "summary": "Create an invoice",
        "description": "All amounts are integers in paise (rupees × 100).",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "number",
                  "subtotal",
                  "total"
                ],
                "properties": {
                  "number": {
                    "type": "string",
                    "maxLength": 50
                  },
                  "clientId": {
                    "type": "string"
                  },
                  "projectId": {
                    "type": "string"
                  },
                  "subtotal": {
                    "type": "integer",
                    "description": "Pre-tax subtotal in PAISE (rupees × 100). ₹1,500.00 = 150000."
                  },
                  "taxAmount": {
                    "type": "integer",
                    "description": "GST amount in PAISE (rupees × 100). ₹1,500.00 = 150000.",
                    "default": 0
                  },
                  "total": {
                    "type": "integer",
                    "description": "Grand total in PAISE (rupees × 100). ₹1,500.00 = 150000."
                  },
                  "status": {
                    "type": "string",
                    "enum": [
                      "Draft",
                      "Sent",
                      "Paid",
                      "Overdue",
                      "Partial",
                      "Unpaid",
                      "Cancelled"
                    ],
                    "default": "Draft"
                  },
                  "issueDate": {
                    "type": "string",
                    "format": "date",
                    "description": "Issue date as an ISO YYYY-MM-DD string."
                  },
                  "dueDate": {
                    "type": "string",
                    "format": "date",
                    "description": "Due date as an ISO YYYY-MM-DD string."
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The created invoice.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Invoice"
                }
              }
            }
          },
          "400": {
            "description": "Validation failed.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Missing, revoked, or expired API key.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "402": {
            "description": "Workspace has no active Scale-plan subscription.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Key lacks the required scope, or source IP is not on the key's allowlist.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded (120 requests/minute/key). Honour Retry-After.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/invoices/{id}": {
      "get": {
        "operationId": "getInvoice",
        "tags": [
          "invoices"
        ],
        "summary": "Get one invoice",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "Resource id, as returned by the list endpoint."
          }
        ],
        "responses": {
          "200": {
            "description": "The invoice with linked client and project.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Invoice"
                }
              }
            }
          },
          "401": {
            "description": "Missing, revoked, or expired API key.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "402": {
            "description": "Workspace has no active Scale-plan subscription.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Key lacks the required scope, or source IP is not on the key's allowlist.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "No such invoice in this workspace.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded (120 requests/minute/key). Honour Retry-After.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      },
      "patch": {
        "operationId": "updateInvoice",
        "tags": [
          "invoices"
        ],
        "summary": "Update invoice status / payment fields",
        "description": "Under the same rules as the app. `amountPaid` (and `status: \"Paid\"`, which means \"paid in full\") RECORDS A PAYMENT for the difference — a confirmed receipt dated `paidAt` (default today, IST), visible and voidable in Rebaar, and fires `invoice.paid` when it settles the invoice. The same call twice records one payment. `amountPaid` can only go up: lowering it is 409 `amount_paid_decrease` (void the payment in Rebaar instead). Above the total, or with a `status` that disagrees with the amount, is 422. `Partial` cannot be set on its own — it follows the payments.",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "Resource id, as returned by the list endpoint."
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "status": {
                    "type": "string",
                    "enum": [
                      "Draft",
                      "Sent",
                      "Paid",
                      "Overdue",
                      "Partial",
                      "Unpaid",
                      "Cancelled"
                    ]
                  },
                  "amountPaid": {
                    "type": "integer",
                    "description": "Total amount received so far. Raising it records a payment for the difference; it cannot be lowered. in PAISE (rupees × 100). ₹1,500.00 = 150000."
                  },
                  "paidAt": {
                    "type": "string",
                    "format": "date",
                    "description": "Payment date as an ISO YYYY-MM-DD string.",
                    "nullable": true
                  },
                  "dueDate": {
                    "type": "string",
                    "format": "date",
                    "description": "Due date as an ISO YYYY-MM-DD string.",
                    "nullable": true
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Updated. The body is `{ \"ok\": true }` — GET the invoice for its new state.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "ok": {
                      "type": "boolean"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation failed.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Missing, revoked, or expired API key.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "402": {
            "description": "Workspace has no active Scale-plan subscription.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Key lacks the required scope, or source IP is not on the key's allowlist.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "No such invoice in this workspace.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Refused by an invoice rule — e.g. `amount_paid_decrease`, `paid_follows_payments`, `pending_exists`, `invoice_cancelled`, `invoice_not_issued`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "422": {
            "description": "`amount_exceeds_total` or `status_disagrees_with_amount`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded (120 requests/minute/key). Honour Retry-After.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      },
      "delete": {
        "operationId": "deleteInvoice",
        "tags": [
          "invoices"
        ],
        "summary": "Delete an invoice",
        "description": "Only a Draft with nothing paid, no IRN and no payment awaiting verification can be deleted — anything else returns 409. An issued invoice is cancelled with PATCH, never deleted (issued documents are audit records).",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "Resource id, as returned by the list endpoint."
          }
        ],
        "responses": {
          "200": {
            "description": "Deleted."
          },
          "401": {
            "description": "Missing, revoked, or expired API key.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "402": {
            "description": "Workspace has no active Scale-plan subscription.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Key lacks the required scope, or source IP is not on the key's allowlist.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "No such invoice in this workspace.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Not a deletable Draft (issued, paid, IRN-registered, or a payment awaiting verification).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded (120 requests/minute/key). Honour Retry-After.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/vendors": {
      "get": {
        "operationId": "listVendors",
        "tags": [
          "vendors"
        ],
        "summary": "List vendors",
        "parameters": [
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 200,
              "default": 50
            },
            "description": "Page size (1–200)."
          }
        ],
        "responses": {
          "200": {
            "description": "Vendors, newest first.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "description": "Vendor list",
                  "properties": {
                    "object": {
                      "type": "string",
                      "enum": [
                        "list"
                      ]
                    },
                    "data": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/Vendor"
                      }
                    },
                    "hasMore": {
                      "type": "boolean",
                      "description": "True when the page was full — pass a higher ?limit or filter to narrow."
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "Missing, revoked, or expired API key.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "402": {
            "description": "Workspace has no active Scale-plan subscription.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Key lacks the required scope, or source IP is not on the key's allowlist.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded (120 requests/minute/key). Honour Retry-After.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/vendors/create": {
      "post": {
        "operationId": "createVendor",
        "tags": [
          "vendors"
        ],
        "summary": "Create a vendor",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "name"
                ],
                "properties": {
                  "name": {
                    "type": "string",
                    "maxLength": 200
                  },
                  "category": {
                    "type": "string",
                    "maxLength": 80
                  },
                  "city": {
                    "type": "string",
                    "maxLength": 80
                  },
                  "pincode": {
                    "type": "string",
                    "pattern": "^[1-9][0-9]{5}$",
                    "description": "6-digit PIN code. The server fills stateCode and district from it when they are not sent."
                  },
                  "district": {
                    "type": "string",
                    "maxLength": 80
                  },
                  "stateCode": {
                    "type": "string",
                    "pattern": "^[0-9]{2}$",
                    "description": "2-digit GST state code, e.g. \"29\" (Karnataka). 400 with field \"pincode\" when the PIN is not in that state."
                  },
                  "gstin": {
                    "type": "string",
                    "pattern": "^[0-9A-Z]{15}$"
                  },
                  "phone": {
                    "type": "string",
                    "maxLength": 20
                  },
                  "email": {
                    "type": "string",
                    "format": "email"
                  },
                  "leadTimeDays": {
                    "type": "integer",
                    "minimum": 0
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The created vendor.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Vendor"
                }
              }
            }
          },
          "400": {
            "description": "Validation failed.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Missing, revoked, or expired API key.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "402": {
            "description": "Workspace has no active Scale-plan subscription.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Key lacks the required scope, or source IP is not on the key's allowlist.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded (120 requests/minute/key). Honour Retry-After.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/vendors/{id}": {
      "get": {
        "operationId": "getVendor",
        "tags": [
          "vendors"
        ],
        "summary": "Get one vendor",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "Resource id, as returned by the list endpoint."
          }
        ],
        "responses": {
          "200": {
            "description": "The vendor.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Vendor"
                }
              }
            }
          },
          "401": {
            "description": "Missing, revoked, or expired API key.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "402": {
            "description": "Workspace has no active Scale-plan subscription.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Key lacks the required scope, or source IP is not on the key's allowlist.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "No such vendor in this workspace.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded (120 requests/minute/key). Honour Retry-After.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      },
      "patch": {
        "operationId": "updateVendor",
        "tags": [
          "vendors"
        ],
        "summary": "Update a vendor",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "Resource id, as returned by the list endpoint."
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "description": "Any subset of fields; pass null to clear an optional field.",
                "properties": {
                  "name": {
                    "type": "string",
                    "maxLength": 200
                  },
                  "category": {
                    "type": "string",
                    "nullable": true
                  },
                  "city": {
                    "type": "string",
                    "nullable": true
                  },
                  "pincode": {
                    "type": "string",
                    "pattern": "^[1-9][0-9]{5}$",
                    "description": "6-digit PIN code. The server fills stateCode and district from it when they are not sent."
                  },
                  "district": {
                    "type": "string",
                    "maxLength": 80
                  },
                  "stateCode": {
                    "type": "string",
                    "pattern": "^[0-9]{2}$",
                    "description": "2-digit GST state code, e.g. \"29\" (Karnataka). 400 with field \"pincode\" when the PIN is not in that state."
                  },
                  "gstin": {
                    "type": "string",
                    "pattern": "^[0-9A-Z]{15}$",
                    "nullable": true
                  },
                  "phone": {
                    "type": "string",
                    "nullable": true
                  },
                  "email": {
                    "type": "string",
                    "format": "email",
                    "nullable": true
                  },
                  "leadTimeDays": {
                    "type": "integer",
                    "minimum": 0,
                    "nullable": true
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The updated vendor.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Vendor"
                }
              }
            }
          },
          "400": {
            "description": "Validation failed.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Missing, revoked, or expired API key.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "402": {
            "description": "Workspace has no active Scale-plan subscription.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Key lacks the required scope, or source IP is not on the key's allowlist.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "No such vendor in this workspace.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded (120 requests/minute/key). Honour Retry-After.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      },
      "delete": {
        "operationId": "deleteVendor",
        "tags": [
          "vendors"
        ],
        "summary": "Delete a vendor",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "Resource id, as returned by the list endpoint."
          }
        ],
        "responses": {
          "200": {
            "description": "Deleted."
          },
          "401": {
            "description": "Missing, revoked, or expired API key.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "402": {
            "description": "Workspace has no active Scale-plan subscription.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Key lacks the required scope, or source IP is not on the key's allowlist.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "No such vendor in this workspace.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded (120 requests/minute/key). Honour Retry-After.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    }
  }
}