certvas — Python SDK
Point-in-time, license-clean African & emerging-market data and signals, in three lines.
pip install certvas # add pandas for DataFrames: pip install "certvas[pandas]"
import certvas
cv = certvas.Client(api_key="...") # free sample key: https://certvas.com/signup.html
panel = cv.signals.procurement_demand(country="ZA", as_of="2025-06-30")
panel is a pandas DataFrame (or a list of dicts if pandas isn't installed). Every response carries
provenance and a license class; point-in-time endpoints honour as_of with no look-ahead.
What you can call
# The flagship signal — government-demand share, buyer concentration, 3-month momentum, point-in-time
cv.signals.procurement_demand(country="ZA", as_of="2025-06-30", category="services")
# Underlying datasets
cv.tenders(country="ZA", from_="2024-01-01")
cv.macro(series="FX.ZAR.USD", as_of="2025-06-30") # point-in-time macro
cv.macro(country="NGA", indicator="FP.CPI.TOTL.ZG") # annual macro
# Fundamentals for one issuer, point-in-time (Woolworths Holdings, JSE)
cv.fundamentals("DC4D39BDF1A9471A472810CB32", as_of="2024-02-01", period_type="FY")
cv.fundamentals("DC4D39BDF1A9471A472810CB32", as_reported=True) # originally filed — use this for a backtest
cv.fundamentals("DC4D39BDF1A9471A472810CB32", all_vintages=True) # every restatement, for revision analysis
as_of on fundamentals is a real no-look-ahead read
It filters on two dates, and the second is the one that matters:
period_end <= as_of |
the reporting period had ended |
available_at <= as_of |
the number had actually been published |
FY results for a December year-end are typically not public for another 60–120 days. Filtering on
the period alone hands you numbers nobody had at the time — the classic look-ahead bias. Where an
announcement date could not be evidenced, available_at falls back to the date Certvas captured the
value, which is always later than true publication for a backfilled filing. So the fallback can
only ever exclude a row from your window, never admit one early: weak coverage costs you recall,
not correctness.
Three reads, three different questions. Fundamentals is append-only across restatements, so an issuer × period × line item can hold several rows:
| call | returns | use it for |
|---|---|---|
| (default) | latest vintage available by as_of |
what the tape said on that date |
as_reported=True |
the originally filed value | factor studies and backtests |
all_vintages=True |
every vintage, with restated / vintage_seq / superseding_record_ref |
revision analysis |
Reach for as_reported=True in any historical study. Running one on latest-value history is
restatement bias: those numbers were revised with hindsight and were never on the tape at the
time you are pretending to trade. The same values ship as fundamentals_as_reported in the bulk
AF-FUND package.
Everything else
# Raw escape hatch for any endpoint — returns the full JSON envelope
cv.get("/v1/quality-metrics/af-tender")
Provenance on every value
The DataFrame carries the license notice and query metadata in df.attrs:
panel.attrs["_license"] # the license-clean notice
panel.attrs["as_of"] # the point-in-time date you queried
panel.attrs["count"]
One shape for every dataset: entity + fact + provenance
certvas.shared puts AF-FUND, AF-TENDER and AF-MACRO in one layout: shared_entity (issuers, public
buyers and countries), shared_fact (one row per value, with available_at, the point-in-time column)
and shared_provenance (the source and licence of every fact). It runs locally over the Parquet release
you receive, so nothing extra is hosted or billed:
# pip install "certvas[shared]"
import duckdb
from certvas import shared
con = duckdb.connect()
shared.register_release(con, "path/to/release") # the folder holding gold_*.parquet
shared.build(con)
con.sql("select * from shared_fact where available_at <= date '2025-06-30'").df()
The API serves the same rows from the live tables, with provenance on each row:
cv.shared_facts(entity_id="DC4D39BDF1A9471A472810CB32", as_of="2025-06-30") # an issuer, AF-FUND
cv.shared_facts(entity_id="ZAF") # a country, AF-MACRO
cv.shared_facts(country="KEN", dataset="AF-TENDER", limit=100) # tender values
cv.shared_facts(entity_id="DC9C8E68C8A893E1142B0D9806", dataset="AF-TENDER") # awards an issuer won (Fidson Healthcare)
From a fundamentals response instead: shared.from_api_fundamentals(cv.fundamentals("DC...", as_of="2025-06-30")).
Every column is described in shared.SPEC. Nothing is estimated, a restatement is a new vintage,
available_at is NULL rather than guessed, and a tender buyer carries a Certvas ID only where it was
resolved.
Upgrading in-band
Sample-tier keys are capped. Over-ask and the SDK raises certvas.UpgradeRequired with the checkout
URL, so an agent or script can upgrade without leaving the flow:
try:
cv.signals.procurement_demand(limit=100_000)
except certvas.UpgradeRequired as e:
print("Upgrade:", e.checkout_url)
Errors
certvas.AuthError— missing/invalid key (401)certvas.UpgradeRequired— this request has a price (402); carries.checkout_urland.docs. Raised when a sample key explicitly asks past its per-request row cap. At API v2 the same condition returns429and this exception will be raised for that too, so catchingUpgradeRequiredis the forward-compatible way to handle it — do not branch on the raw status.certvas.CertvasError— base class for everything else
Docs: https://certvas.com/developers.html · Signals: https://certvas.com/signals/
Release files for certvas 0.1.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 | |
|---|---|---|---|
| certvas-0.1.0.tar.gz | 23.8 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| certvas-0.1.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 42.1 kB
Release files / certvas-0.1.0.tar.gz
| Download URL | certvas-0.1.0.tar.gz |
|---|---|
| Size | 23.8 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
517efab37fa250c9799b592558628f83402ec7a4a98735928e9cdde2fbbd2091
|
|
BLAKE2b-256 checksum How to use checksums |
9b0ee67cb79d9c8b6f81c811af0198bab5ed58f005c09f62135511e4d56e4696
|
| 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 Sep 24, 2026.
Transparency logRelease files / certvas-0.1.0-py3-none-any.whl
| Download URL | certvas-0.1.0-py3-none-any.whl |
|---|---|
| Size | 18.3 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
72d1df58bba127e018b9e65e46cff5f90f7855f488ac826fbdea8e40ba968e16
|
|
BLAKE2b-256 checksum How to use checksums |
0d2a1ee81d51375f46c7d64476d024a489cf28150674b83a61ae4a710328b909
|
| 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 Sep 24, 2026.
Transparency log