Metadata-Version: 2.4
Name: taxlane
Version: 0.1.0
Summary: Official Python SDK for TaxLane's free Nigeria tax calculator API (PAYE, VAT, WHT, CIT, CGT & 6 more).
License: MIT
Project-URL: Homepage, https://taxlane.ng/developers
Project-URL: Repository, https://github.com/quineworks/tax-lane
Project-URL: Issues, https://github.com/quineworks/tax-lane/issues
Requires-Python: >=3.9
Description-Content-Type: text/markdown
License-File: LICENSE
Provides-Extra: test
Requires-Dist: pytest>=7; extra == "test"
Dynamic: license-file

# taxlane

Official Python SDK for [TaxLane](https://taxlane.ng)'s free
[Developer API](https://taxlane.ng/developers) — PAYE, VAT, Withholding Tax,
Companies Income Tax, Capital Gains Tax, and 5 more Nigeria tax calculators,
as typed function calls instead of hand-written `urllib`/`curl`.

- **Zero runtime dependencies** — stdlib `urllib.request`, no `requests`.
- **`snake_case` keyword arguments**, hand-mapped from the same field list
  [taxlane.ng/developers](https://taxlane.ng/developers) documents, so a
  call can never drift from the live API's request shape.
- **No API key, no account, no rate-limit tiers to configure** — the API
  itself has none.

Every function returns the parsed JSON response as a plain `dict`, not a
generated type — see [Response shape](#response-shape) below for why.

## Install

```sh
pip install taxlane
```

If this package hasn't been published to the public PyPI registry yet (see
[Distribution](#distribution) below), install it straight from GitHub
instead:

```sh
pip install git+https://github.com/quineworks/tax-lane#subdirectory=sdk-python
```

## Quickstart

```python
from taxlane import calculate_paye

result = calculate_paye(gross_annual_income=6_000_000, annual_rent_paid=1_200_000)
# {'taxableIncome': 5760000, 'payeTax': 826800, 'annualTax': 826800, 'monthlyTax': 68900,
#  'reliefs': {...}, 'bandBreakdown': [...]}
```

Every function raises `TaxLaneApiError` (with `.message` and `.status`) on
a non-200 response, and accepts an optional trailing `base_url` keyword to
point at a local/staging API instead of the production default:

```python
from taxlane import calculate_paye, TaxLaneApiError

try:
    calculate_paye(gross_annual_income='not a number', base_url='http://localhost:3000')
except TaxLaneApiError as err:
    print(err.status, err.message)  # 400 "grossAnnualIncome is required and must be a number"
```

## Endpoints

### `calculate_paye`

```python
from taxlane import calculate_paye

calculate_paye(gross_annual_income=6_000_000, annual_rent_paid=1_200_000)
# {
#   "taxableIncome": 5760000,
#   "payeTax": 826800,
#   "annualTax": 826800,
#   "monthlyTax": 68900,
#   "reliefs": { "rentRelief": 240000, "pensionRelief": 0, "nhfRelief": 0, "nhisRelief": 0, "lifeAssurancePremiumRelief": 0, "minimumWageExempt": false, "taxableTerminationBenefit": 0 },
#   "bandBreakdown": [
#     { "from": 800000, "to": 3000000, "rate": 0.15, "taxInBand": 330000 },
#     { "from": 3000000, "to": 5760000, "rate": 0.18, "taxInBand": 496800 }
#   ]
# }
```

Only `gross_annual_income` is required — every other field defaults to `0`.

### `calculate_payroll_batch`

```python
from taxlane import calculate_payroll_batch

calculate_payroll_batch([
    {"grossAnnualIncome": 6_000_000},
    {"grossAnnualIncome": 900_000, "annualRentPaid": 400_000},
])
# { "results": [ { "taxableIncome": 6000000, ... }, { "taxableIncome": 820000, ... } ] }
```

Calculates PAYE for up to 1,000 employees in one request — `results[i]` is exactly what `calculate_paye(**employees[i])` would return, in the same order. An invalid item raises with an `employees[i].`-prefixed message; there's no partial-success mode.

### `calculate_vat`

```python
from taxlane import calculate_vat

calculate_vat(amount=100_000, mode="exclusive")
# { "net": 100000, "vat": 7500, "gross": 107500 }
```

### `calculate_presumptive`

```python
from taxlane import calculate_presumptive

calculate_presumptive(turnover=20_000_000)
# { "turnover": 20000000, "taxOwed": 200000, "belowFloor": false, "monthlySetAside": 16666.666666666668, "turnoverToFloor": 0 }
```

### `calculate_direct_assessment`

```python
from taxlane import calculate_direct_assessment

calculate_direct_assessment(
    turnover=10_000_000,
    allowable_expenses=3_000_000,
    qualifying_capital_expenditure=1_000_000,
    capital_allowance_asset_class="plantAgriculturalEquipmentFurniture",
)
# { "turnover": 10000000, "allowableExpenses": 3000000, "capitalAllowance": 200000, "taxableProfit": 6800000, "taxOwed": 1014000, "effectiveRate": 0.14911764705882352 }
```

### `calculate_cit`

```python
from taxlane import calculate_cit

calculate_cit(
    turnover=30_000_000,
    fixed_assets=100_000_000,
    is_professional_services=False,
    taxable_profit=5_000_000,
    assessable_profit=8_000_000,
)
# {
#   "eligibility": { "turnover": 30000000, "fixedAssets": 100000000, "isProfessionalServices": false, "turnoverEligible": true, "fixedAssetsEligible": true, "eligible": true, "rate": 0 },
#   "standardRate": { "taxableProfit": 5000000, "annualTax": 1500000 },
#   "developmentLevy": { "assessableProfit": 8000000, "developmentLevy": 320000 }
# }
```

`eligibility` is always returned. `standardRate` is included only when
`taxable_profit` is sent; `developmentLevy` is included only when
`assessable_profit` is sent — the three are independently computed.

### `calculate_wht`

```python
from taxlane import calculate_wht

calculate_wht(transaction_type="professionalFees", amount=1_000_000, mode="gross")
# { "transactionType": "professionalFees", "mode": "gross", "amount": 1000000, "rate": 0.05, "gross": 1000000, "withheld": 50000, "net": 950000 }
```

### `calculate_employer_cost`

```python
from taxlane import calculate_employer_cost

calculate_employer_cost(
    annual_basic_salary=6_000_000,
    annual_housing_allowance=1_000_000,
    annual_transport_allowance=500_000,
)
# { "annualGrossPay": 7500000, "pensionablePay": 7500000, "employerPensionContribution": 750000, ..., "employeePaye": { ... } }
```

### `calculate_cgt`

```python
from taxlane import calculate_cgt

calculate_cgt(
    taxpayer_type="individual",
    asset_type="property",
    disposal_proceeds=50_000_000,
    cost_base=30_000_000,
)
# { "chargeableGain": 20000000, "exempt": false, "cgtLiability": 3630000 }
```

`digital_asset_category` is required when `asset_type` is `"digital-asset"`,
ignored otherwise.

### `calculate_stamp_duty`

```python
from taxlane import calculate_stamp_duty

calculate_stamp_duty(instrument_type="tenancy", amount=12_000_000)
# { "instrumentType": "tenancy", "amount": 12000000, "rate": 0.0078, "duty": 93600, "exempt": false }
```

### `calculate_import_duty`

```python
from taxlane import calculate_import_duty

calculate_import_duty(fob=5_000_000, freight=300_000, insurance=100_000, band="20")
# { "fob": 5000000, "freight": 300000, "insurance": 100000, "cif": 5400000, "band": "20", "dutyRate": 0.2, "duty": 1080000, "surcharge": 75600, "etls": 27000, "fcs": 200000, "vat": 508695, "totalLandedCost": 7291295 }
```

## Response shape

Every function returns the parsed JSON response body as a plain `dict`,
*not* a generated dataclass/`TypedDict`/pydantic model. The JS/TS SDK's
fully-typed responses come free by importing the real TS type across a
same-language package boundary — Python has no equivalent import, and
hand-duplicating every response shape in a second, Python-only schema would
be exactly the kind of extra place to maintain and drift from that SDK
avoided in the first place. A plain `dict` is also the established pattern
Paystack's ([`paystack-python`](https://github.com/PaystackOSS/paystack-python))
and Flutterwave's ([`rave-python`](https://github.com/Flutterwave/rave-python))
own Python SDKs use for response bodies. Ships `py.typed` (PEP 561 marker)
so type checkers (mypy/pyright) still pick up every function's request
signature in consuming projects — only the response is untyped.

## Errors

A missing or non-numeric/non-boolean required field, or an invalid enum
value, raises `TaxLaneApiError` with a `.status` of `400` and a `.message`
describing the offending field. See the full field-by-field reference at
[taxlane.ng/developers](https://taxlane.ng/developers) or the
[OpenAPI spec](https://taxlane.ng/openapi.json).

## Limits

No API key required, no daily cap. The underlying API is limited to 50
requests/second (100 burst) at the shared Gateway level to keep the service
available for everyone — this SDK does no client-side retry/backoff, since
there's no rate limit to work around on a per-caller basis.

## Distribution

Published to the public PyPI registry as `taxlane`, when a fleet PyPI
publisher account exists to publish it under. Until then, install directly
from this repo's `sdk-python/` subdirectory:

```sh
pip install git+https://github.com/quineworks/tax-lane#subdirectory=sdk-python
```

## License

MIT — see the repo root [`LICENSE`](../LICENSE).
