{
  "openapi": "3.1.0",
  "info": {
    "title": "ceylonsalary.lk API",
    "version": "2025.26",
    "description": "Free Sri Lanka salary, APIT, EPF/ETF, freelancer, employer-cost, benchmark, and foreign-income calculations. Indicative only; not financial or tax advice."
  },
  "servers": [{ "url": "https://ceylonsalary.lk" }],
  "paths": {
    "/api/v1/calculate-employee.php": {
      "post": {
        "summary": "Calculate employee take-home pay",
        "description": "Rate limit: 20 requests per hour per IP.",
        "operationId": "calculateEmployee",
        "requestBody": { "required": true, "content": { "application/json": { "schema": { "$ref": "#/components/schemas/EmployeeRequest" } } } },
        "responses": { "200": { "$ref": "#/components/responses/CalculationSuccess" }, "400": { "$ref": "#/components/responses/BadRequest" }, "422": { "$ref": "#/components/responses/ValidationError" }, "429": { "$ref": "#/components/responses/RateLimited" } }
      }
    },
    "/api/v1/calculate-employer.php": {
      "post": {
        "summary": "Calculate employer cost",
        "description": "Rate limit: 20 requests per hour per IP.",
        "operationId": "calculateEmployer",
        "requestBody": { "required": true, "content": { "application/json": { "schema": { "$ref": "#/components/schemas/EmployerRequest" } } } },
        "responses": { "200": { "$ref": "#/components/responses/CalculationSuccess" }, "400": { "$ref": "#/components/responses/BadRequest" }, "422": { "$ref": "#/components/responses/ValidationError" }, "429": { "$ref": "#/components/responses/RateLimited" } }
      }
    },
    "/api/v1/calculate-freelancer.php": {
      "post": {
        "summary": "Calculate a freelancer rate or income",
        "description": "Rate limit: 20 requests per hour per IP.",
        "operationId": "calculateFreelancer",
        "requestBody": { "required": true, "content": { "application/json": { "schema": { "$ref": "#/components/schemas/FreelancerRequest" } } } },
        "responses": { "200": { "$ref": "#/components/responses/CalculationSuccess" }, "400": { "$ref": "#/components/responses/BadRequest" }, "422": { "$ref": "#/components/responses/ValidationError" }, "429": { "$ref": "#/components/responses/RateLimited" } }
      }
    },
    "/api/v1/calculate-foreign-income.php": {
      "post": {
        "summary": "Calculate foreign freelancer or remote-employee tax",
        "description": "Rate limit: 20 requests per hour per IP. The request shape depends on foreignIncomeType.",
        "operationId": "calculateForeignIncome",
        "requestBody": { "required": true, "content": { "application/json": { "schema": { "oneOf": [{ "$ref": "#/components/schemas/ForeignFreelancerRequest" }, { "$ref": "#/components/schemas/ForeignEmployeeRequest" }], "discriminator": { "propertyName": "foreignIncomeType" } } } } },
        "responses": { "200": { "$ref": "#/components/responses/CalculationSuccess" }, "400": { "$ref": "#/components/responses/BadRequest" }, "422": { "$ref": "#/components/responses/ValidationError" }, "429": { "$ref": "#/components/responses/RateLimited" } }
      }
    },
    "/api/v1/benchmarks.php": {
      "get": {
        "summary": "Get sanitized salary benchmark slices",
        "description": "Rate limit: 60 requests per hour per IP.",
        "operationId": "getBenchmarks",
        "parameters": [
          { "name": "categoryKey", "in": "query", "schema": { "type": "string" }, "description": "Optional category filter." },
          { "name": "roleKey", "in": "query", "schema": { "type": "string" }, "description": "Optional role filter." },
          { "name": "seniorityLevel", "in": "query", "schema": { "type": "string", "enum": ["junior", "mid", "senior"] }, "description": "Optional seniority filter." }
        ],
        "responses": { "200": { "$ref": "#/components/responses/DataSuccess" }, "422": { "$ref": "#/components/responses/ValidationError" }, "429": { "$ref": "#/components/responses/RateLimited" } }
      }
    },
    "/api/v1/metadata-tax-year.php": {
      "get": {
        "summary": "Get active tax-year metadata and provenance",
        "description": "Rate limit: 60 requests per hour per IP.",
        "operationId": "getTaxYearMetadata",
        "responses": { "200": { "$ref": "#/components/responses/DataSuccess" }, "429": { "$ref": "#/components/responses/RateLimited" } }
      }
    }
  },
  "components": {
    "schemas": {
      "NonNegativeMoney": { "type": "number", "minimum": 0, "maximum": 100000000 },
      "MarketSelection": {
        "type": "object",
        "properties": {
          "categoryKey": { "type": "string", "default": "technology" },
          "roleKey": { "type": "string", "default": "software_general" },
          "seniorityLevel": { "type": "string", "enum": ["junior", "mid", "senior"], "default": "mid" }
        }
      },
      "Benefit": {
        "type": "object",
        "required": ["id"],
        "properties": { "id": { "type": "string" }, "valueMonthly": { "type": "number", "minimum": 0, "maximum": 10000000 } },
        "additionalProperties": false
      },
      "EmployeeRequest": {
        "allOf": [
          { "$ref": "#/components/schemas/MarketSelection" },
          { "type": "object", "required": ["grossMonthly"], "properties": {
            "grossMonthly": { "$ref": "#/components/schemas/NonNegativeMoney" },
            "allowancesMonthly": { "$ref": "#/components/schemas/NonNegativeMoney", "default": 0 },
            "includeAllowancesInStatutoryBase": { "type": "boolean", "default": true },
            "benefits": { "type": "array", "items": { "$ref": "#/components/schemas/Benefit" } },
            "benefitIds": { "type": "array", "items": { "type": "string", "minLength": 1 }, "description": "Legacy alternative to benefits." }
          } }
        ]
      },
      "EmployerRequest": {
        "allOf": [
          { "$ref": "#/components/schemas/EmployeeRequest" },
          { "type": "object", "properties": {
            "workingHoursPerDay": { "type": "number", "minimum": 1, "maximum": 24, "default": 8 },
            "workingDaysPerMonth": { "type": "number", "minimum": 1, "maximum": 31, "default": 22 },
            "outsourcingBasis": { "type": "string", "enum": ["hours", "deliverables"], "default": "hours" },
            "outsourceHoursPerMonth": { "type": "number", "minimum": 0, "maximum": 10000, "default": 160 },
            "outsourceRatePerHour": { "type": "number", "minimum": 0, "maximum": 1000000, "default": 0 },
            "outsourceDeliverablesPerMonth": { "type": "number", "minimum": 0, "maximum": 100000, "default": 20 },
            "outsourceRatePerDeliverable": { "type": "number", "minimum": 0, "maximum": 10000000, "default": 0 }
          } }
        ]
      },
      "FreelancerRequest": {
        "allOf": [
          { "$ref": "#/components/schemas/MarketSelection" },
          { "type": "object", "properties": {
            "calculationMode": { "type": "string", "enum": ["find_rate", "estimate_income"], "default": "find_rate" },
            "goalPeriod": { "type": "string", "enum": ["monthly", "yearly"], "default": "monthly" },
            "incomeGoal": { "type": "number", "minimum": 0, "maximum": 100000000, "description": "Required when calculationMode=find_rate." },
            "pricingBasis": { "type": "string", "enum": ["hours", "deliverables"], "default": "hours" },
            "ratePerHour": { "type": "number", "minimum": 0, "maximum": 10000000, "description": "Required for estimate_income with hours." },
            "ratePerDeliverable": { "type": "number", "minimum": 0, "maximum": 100000000, "description": "Required for estimate_income with deliverables." },
            "billableHoursPerDay": { "type": "number", "minimum": 1, "maximum": 16, "description": "Required when pricingBasis=hours." },
            "billableDaysPerMonth": { "type": "number", "minimum": 1, "maximum": 31, "description": "Required when pricingBasis=hours." },
            "deliverablesPerMonth": { "type": "number", "minimum": 1, "maximum": 100000, "description": "Required when pricingBasis=deliverables." },
            "monthlyExpenses": { "type": "number", "minimum": 0, "maximum": 10000000, "default": 0 },
            "platformFeePercent": { "type": "number", "minimum": 0, "maximum": 50, "default": 0 },
            "includeFreelancerContributions": { "type": "boolean", "default": false }
          } },
          { "anyOf": [
            { "properties": { "pricingBasis": { "enum": ["hours"] } }, "required": ["billableHoursPerDay", "billableDaysPerMonth"] },
            { "properties": { "pricingBasis": { "const": "deliverables" } }, "required": ["pricingBasis", "deliverablesPerMonth"] }
          ] },
          { "anyOf": [
            { "properties": { "calculationMode": { "enum": ["find_rate"] } }, "required": ["incomeGoal"] },
            { "properties": { "calculationMode": { "const": "estimate_income" }, "pricingBasis": { "enum": ["hours"] } }, "required": ["calculationMode", "ratePerHour"] },
            { "properties": { "calculationMode": { "const": "estimate_income" }, "pricingBasis": { "const": "deliverables" } }, "required": ["calculationMode", "pricingBasis", "ratePerDeliverable"] }
          ] }
        ]
      },
      "ForeignBase": {
        "type": "object",
        "required": ["foreignIncomeType", "fxRate"],
        "properties": {
          "foreignIncomeType": { "type": "string", "enum": ["freelancer", "employee"] },
          "currencyCode": { "type": "string", "pattern": "^[A-Za-z0-9]{2,8}$", "default": "USD", "description": "Normalized to uppercase by the API." },
          "fxRate": { "type": "number", "minimum": 0.000001, "maximum": 10000000 }
        }
      },
      "ForeignFreelancerRequest": {
        "allOf": [
          { "$ref": "#/components/schemas/ForeignBase" },
          { "type": "object", "required": ["foreignAmount"], "properties": {
            "foreignIncomeType": { "const": "freelancer" },
            "incomePeriod": { "type": "string", "enum": ["monthly", "annual"], "default": "monthly" },
            "foreignAmount": { "type": "number", "minimum": 0, "maximum": 1000000000 },
            "remittedThroughBank": { "type": "boolean", "default": true },
            "foreignTaxPaidAnnual": { "type": "number", "minimum": 0, "maximum": 1000000000, "default": 0 }
          } }
        ]
      },
      "ForeignEmployeeRequest": {
        "allOf": [
          { "$ref": "#/components/schemas/ForeignBase" },
          { "type": "object", "properties": {
            "foreignIncomeType": { "const": "employee" },
            "april": { "$ref": "#/components/schemas/ForeignMonthlyAmount" }, "may": { "$ref": "#/components/schemas/ForeignMonthlyAmount" }, "june": { "$ref": "#/components/schemas/ForeignMonthlyAmount" }, "july": { "$ref": "#/components/schemas/ForeignMonthlyAmount" },
            "august": { "$ref": "#/components/schemas/ForeignMonthlyAmount" }, "september": { "$ref": "#/components/schemas/ForeignMonthlyAmount" }, "october": { "$ref": "#/components/schemas/ForeignMonthlyAmount" }, "november": { "$ref": "#/components/schemas/ForeignMonthlyAmount" },
            "december": { "$ref": "#/components/schemas/ForeignMonthlyAmount" }, "january": { "$ref": "#/components/schemas/ForeignMonthlyAmount" }, "february": { "$ref": "#/components/schemas/ForeignMonthlyAmount" }, "march": { "$ref": "#/components/schemas/ForeignMonthlyAmount" }
          } }
        ]
      },
      "ForeignMonthlyAmount": { "type": "number", "minimum": 0, "maximum": 1000000000, "default": 0 },
      "DataEnvelope": { "type": "object", "required": ["data"], "properties": { "data": { "type": "object", "additionalProperties": true }, "provenance": { "type": "object", "additionalProperties": true } } },
      "CalculationEnvelope": { "type": "object", "required": ["data"], "properties": { "data": { "type": "object", "required": ["taxYear", "calculator", "inputs", "outputs", "assumptions", "warnings"], "properties": { "taxYear": { "type": "string", "const": "2025/26" }, "calculator": { "type": "string", "enum": ["employee", "employer", "freelancer", "foreign_income"] }, "inputs": { "type": "object", "additionalProperties": true }, "outputs": { "type": "object", "additionalProperties": true }, "assumptions": { "type": "object", "additionalProperties": true }, "warnings": { "type": "array", "items": {} } }, "additionalProperties": true } } },
      "ErrorEnvelope": { "type": "object", "required": ["error"], "properties": { "error": { "type": "object", "required": ["code", "message"], "properties": { "code": { "type": "string", "enum": ["invalid_json", "validation_error", "rate_limited", "method_not_allowed", "not_found"] }, "message": { "type": "string" }, "field": { "type": "string" } } } } }
    },
    "responses": {
      "CalculationSuccess": { "description": "Successful calculation.", "headers": { "X-Request-Id": { "schema": { "type": "string" } } }, "content": { "application/json": { "schema": { "$ref": "#/components/schemas/CalculationEnvelope" } } } },
      "DataSuccess": { "description": "Successful data response.", "headers": { "X-Request-Id": { "schema": { "type": "string" } } }, "content": { "application/json": { "schema": { "$ref": "#/components/schemas/DataEnvelope" } } } },
      "BadRequest": { "description": "Malformed JSON.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ErrorEnvelope" } } } },
      "ValidationError": { "description": "Input validation failed.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ErrorEnvelope" } } } },
      "RateLimited": { "description": "Rate limit exceeded.", "headers": { "Retry-After": { "schema": { "type": "integer", "minimum": 1 } } }, "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ErrorEnvelope" } } } }
    }
  }
}
