africa-compliance-data
Client library for the Africa Compliance Data API: public holidays, business days, a holiday watch for announced and moon-sighted holidays, VAT (with dated rate history), withholding tax and tax-treaty rates, tax ID formats, filing deadlines and finance-law digests for 54 African countries, with coverage shown per country. Every value cites its source: official texts first; secondary sources, press reports and estimates are labelled, with a confidence level.
No dependencies (standard library only). Python 3.8+.
pip install africa-compliance-data
Quick start
from africa_compliance_data import AfricaComplianceData
acd = AfricaComplianceData() # reads ACD_API_KEY, or pass api_key="..."
holidays = acd.holidays("MA", 2027)
due = acd.add_business_days("KE", "2026-12-23", 5)
watch = acd.holiday_watch(days=30, countries=["KE", "NG", "MA"])
vat = acd.vat("NG") # Pro or Compliance plan
check = acd.validate_tax_id("ZA", "0123456782") # Pro or Compliance plan
Get a key at africacompliancedata.com: the Free plan costs nothing (holidays and business days, non-commercial use); paid plans add commercial use and tax data.
Methods
| Method | Endpoint | Plan |
|---|---|---|
countries() / country(country) |
/v1/countries, /v1/countries/{cc} |
All (tax sections of the profile: Pro for the countries of your pack, Compliance) |
holidays(country, year, include_religious_scopes=None) |
/holidays |
All |
holidays_ics(country, from_year=None, to_year=None, include_religious_scopes=None) |
/holidays.ics (returns iCalendar text) |
All |
holiday_watch(days=None, countries=None) |
/v1/holidays/watch (announced and moon-sighted holidays) |
Developer, Pro, Compliance |
is_business_day(country, date) |
/is-business-day |
All |
business_days(country, from_date, to_date) |
/business-days |
All |
add_business_days(country, date, days) |
/add-business-days |
All |
account() |
/v1/account (plan, monthly quota, country pack, today's usage) |
All |
country_pack(), set_country_pack(countries) |
/v1/account/countries (the countries of tax data in your pack; a change that removes a country: once every 30 days) |
Pro |
vat(country, date=None) |
/vat (with date: the standard rate in force that day) |
Pro (countries of your pack), Compliance |
vat_history(country) |
/vat/history |
Pro (countries of your pack), Compliance |
withholding_tax(country, payment_type=None, recipient_country=None) |
/withholding-tax (with recipient_country: the treaty rates too) |
Pro (countries of your pack), Compliance |
treaties(country, partner=None) |
/treaties |
Pro (countries of your pack), Compliance |
validate_tax_id(country, value, server=False) |
Checked on your machine with the signed format rules (GET /v1/tax-id/rules, verified with the built-in Ed25519 key in pure Python, refreshed daily, valid 35 days): the number is not sent. Same answer as the API plus checked_locally: True. Falls back to POST /tax-id/validate (number in the JSON body, never the URL) if the rules cannot be had; server=True forces it. Options: tax_id_mode="local" never sends a number, "server" always uses the API; tax_id_rules() returns the bundle to save and pass back as tax_id_rules= |
Pro (countries of your pack), Compliance |
deadlines(country, year, month) |
/deadlines |
Pro (countries of your pack), Compliance |
finance_law(country) |
/finance-law |
Compliance |
list_webhooks(), create_webhook(url, countries=None, features=None), delete_webhook(webhook_id), test_webhook(webhook_id) |
/v1/webhooks |
Developer and Pro (holiday changes), Compliance (all changes) |
Optional arguments are keyword-only. A method outside your plan raises ApiError with status 403 and an upgrade message. include_religious_scopes=True adds holidays limited to one group (a faith, women, the public sector); by default only national holidays are returned.
Dates may be YYYY-MM-DD strings or datetime.date objects. Errors raise ApiError with .status and .body. Rate-limit (429) and temporary errors are retried twice, honouring Retry-After. Webhook create and test (POST) are retried only on 429 and 503, never after a timeout or network error (the request may have gone through). In holiday_watch, an empty countries list raises ValueError; omit it for all countries.
Private mode (version 1.2.0)
acd = AfricaComplianceData(private_mode=True, bundle_cache_dir="~/.cache/acd-bundles")
due = acd.add_business_days("KE", "2026-12-23", 5) # computed on your machine
# due["computed_locally"] is True
With private_mode=True, holidays, is_business_day, business_days, add_business_days, vat (with or without date), vat_history and deadlines are computed on your machine. The client downloads a signed bundle per country and year (GET /v1/countries/{cc}/bundle?year=), and the dates, ranges, months and day counts of your queries are not sent. The only details of your queries in those requests are the country and the year (they also carry your API key, like every request). The answers have the same fields and values as the API's, plus computed_locally: True.
- Each bundle is checked before use: its Ed25519 signature (the same built-in key as the tax ID rules, verified in pure Python), the country and year, and its expiry. A bundle expires 1 day after it is issued for the current year and later years, because holiday dates can still be confirmed or moved; for past years it expires after 30 days. An expired bundle is never used: a new one is downloaded.
- Bundles are kept in memory.
bundle_cache_dir(optional) also keeps them in that folder between runs (expanded withos.path.expanduser). Bundles read from disk are verified again. - If a bundle cannot be downloaded or does not verify, the method raises
PrivateModeError. The client never sends the query to the API instead. The API's own refusals pass through unchanged: an unknown country (404), a plan without tax data (403, as the API's tax endpoints say), or the monthly quota (429). - Plans: holiday parts on every plan; tax parts (VAT, VAT history, deadline rules) on Compliance, and on Pro for the countries of your pack. A tax method on Pro adds the country to a pack that has room, as a tax endpoint would. Each bundle download counts as one request against the monthly quota.
private_modealso setstax_id_mode="local"unless you settax_id_modeyourself, so tax ID numbers are not sent either.- Other methods (countries, profiles, holiday watch, iCalendar feeds, withholding tax, treaties, finance law, account, webhooks) call the API as usual.
country_bundle(country, year, parts="all")returns a verified bundle, andverify_bundle(signed, public_key=...)checks one yourself.
Webhooks
from africa_compliance_data import verify_webhook
event = verify_webhook(request.body, request.headers["ACD-Signature"], WEBHOOK_SECRET)
# event["type"] == "dataset.changed"; event["changes"] lists the countries and features that changed.
# A retry carries the same event["id"] (header ACD-Event-Id): use it to ignore duplicates.
Full reference: africacompliancedata.com/docs · OpenAPI: openapi.json
License
MIT · Ingot Supply, LLC. MIT covers this client code only; API data is governed by the Terms of Service. Not legal or tax advice.
Metadata
Release files for africa-compliance-data 1.2.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | Provenance | |
|---|---|---|---|---|
| africa_compliance_data-1.2.0.tar.gz | 40.7 kB | With provenance | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| africa_compliance_data-1.2.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 68.5 kB
Release files / africa_compliance_data-1.2.0.tar.gz
| Download URL | africa_compliance_data-1.2.0.tar.gz |
|---|---|
| Size | 40.7 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
a7cc8aac185120c7bca323112b2cba7fb114c93f60f73b3895d8da77807575cb
|
|
BLAKE2b-256 checksum How to use checksums |
6e55e677de503a62b8acf9628566a5861c5ee42be7d839db128e0b1f73a6c7dd
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Oct 24, 2026.
Transparency logRelease files / africa_compliance_data-1.2.0-py3-none-any.whl
| Download URL | africa_compliance_data-1.2.0-py3-none-any.whl |
|---|---|
| Size | 27.8 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
cc44814f5b5753b7804dfd03561728cd4993ce52cb3ce3cd06a657b51cb67a22
|
|
BLAKE2b-256 checksum How to use checksums |
ffb77766ebe1685d78aaa8193f18a4c3da80ca4c1772bbe2c3ffc9ebe9cf5315
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Oct 24, 2026.
Transparency logSecurity for africa-compliance-data 1.2.0
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
Full provenance
All 2 files in this release have been uploaded with provenance metadata.
Attestation for africa_compliance_data-1.2.0.tar.gz
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Oct 24, 2026.
Transparency logA note on package security
PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.
To learn more about protecting yourself from malicious actors, visit our package security documentation.