afriref
v1.0.0
Published
Official TypeScript client for the afriref fleet: cited government reference data (rates, tax, wages, holidays, FX) for 137 countries, payable per call with x402
Downloads
164
Maintainers
Readme
afriref — TypeScript client
Cited government reference data, typed: central-bank policy rates, VAT, corporate and personal income tax, minimum wages, CPI, statutory interest, official central-bank FX fixes and public holidays across 137 countries.
Every value carries the official source URL it was verified against, the date it took effect, and the date we last re-checked it. Zero runtime dependencies.
npm install afrirefStart free — no key, no wallet, no signup
import { Afriref } from "afriref";
const c = new Afriref(); // afriref.dev (Africa)
const hol = await c.holidays("za"); // free for every country, always
console.log(hol.value.length, hol.source.url);One country per service is completely free — every series, including history and point-in-time. It is the real paid surface, so you can check our citations against the official sources before paying anything:
const c = new Afriref();
c.evaluationCountry; // "gh"
const vat = await c.series("gh", "vat"); // free
console.log(vat.value, vat.unit, vat.confidence, vat.source.url);
// 15 percent primary https://gra.gov.gh/...| Service | Free country | | Service | Free country |
|---|---|---|---|---|
| afriref | 🇬🇭 GH | | usaref | 🇺🇸 US |
| euroref | 🇩🇪 DE | | mearef | 🇦🇪 AE |
| asiaref | 🇯🇵 JP | | ausref | 🇦🇺 AU |
| latamref | 🇧🇷 BR | | | |
Pay per call with x402 — no account
This is the part a REST wrapper can't do. Wrap fetch once and paid calls
settle themselves:
import { Afriref } from "afriref";
import { wrapFetchWithPayment } from "@x402/fetch";
import { createWalletClient, http } from "viem";
import { privateKeyToAccount } from "viem/accounts";
import { base } from "viem/chains";
const account = privateKeyToAccount(process.env.PRIVATE_KEY as `0x${string}`);
const wallet = createWalletClient({ account, chain: base, transport: http() });
const c = new Afriref({ fetch: wrapFetchWithPayment(fetch, wallet) });
const vat = await c.series("ng", "vat"); // 402 → sign → retry, transparently
console.log(vat.value, vat.source.url);Prefer a card? Buy prepaid credit at https://afriref.dev/pricing and pass
apiKey — one key works on all seven services, same balance:
const c = new Afriref({ brand: "euroref", apiKey: process.env.AFRIREF_KEY });Three things this client models that a fetch wrapper gets wrong
1. A refusal is data, not a failure. When the service holds no honest answer it says so, with a reason, and does not charge you:
import { Refused } from "afriref";
try {
await c.workingDays("ng", "2027-01-01", "2027-01-31");
} catch (e) {
if (e instanceof Refused) {
e.reason; // "calendar_not_publishable_in_advance"
e.permanent; // true — do NOT queue a retry
}
}permanent: true means waiting can never help: some public holidays are
confirmed by moon sighting days ahead, or gazetted annually, so the 2027
calendar will not appear later. A client that retries those forever is the
failure mode this flag exists to prevent.
2. HTTP 402 is a price quote. Without payment, you get the exact terms:
import { PaymentRequired } from "afriref";
try {
await c.series("ng", "vat");
} catch (e) {
if (e instanceof PaymentRequired) {
e.priceUsd; // 0.001
e.network; // "eip155:8453" (Base)
e.payTo; // "0x1F04..."
e.amountBaseUnits; // "1000" — settle with this, never the float
}
}3. The citation is the product. Value keeps the evidence typed:
const v = await c.series("gh", "corporate-tax");
v.value; // the number
v.effective_from; // when it took LEGAL effect — not when we published it
v.last_confirmed; // when we last re-read the official source
v.source.url; // the government document. Follow it and check us.
v.confidence; // "primary" = read from the authority's own publication
v.stale; // past its expected update cycle?Point-in-time
const gb = new Afriref({ brand: "euroref" });
(await gb.series("gb", "corporate-tax", { asAt: "2020-06-30" })).value; // 19
(await gb.series("gb", "corporate-tax")).value; // 25Alerts — free to register
Reference values fail silently: a threshold that stood 17 years moves in one budget speech and every hard-coded copy is quietly wrong. We hash every cited source daily, so we know when a value changes.
const sub = await c.createAlert("https://your-app.example/hooks/refdata", [
"za/vat-registration-threshold",
"ke/*",
]);
sub.secret; // store it — payloads are HMAC-SHA256 signed, shown onceRegistering is free and your first deliveries are free; after that each delivery costs one API-key call.
Everything else
await c.catalog({ confidence: "primary" }); // only values with no recorded limitation
await c.history("gh", "vat"); // earlier values with effective ranges
await c.snapshot(); // every series, one call
await c.vat("gh", 1000); // net / tax / gross incl. levies
await c.incomeTax("gh", 650_000); // tax due + per-bracket workings
await c.wageCheck("gh", 5000, "monthly"); // at or above the statutory floor?
await c.settlementDate("za", "2026-03-02"); // T+n across statutory calendars
await c.provenance("gh", "vat"); // sha256 of the exact bytes we read
await c.status(); // live state and rate limitsNotes
history()returning[]means "not established", not "never changed". Where we hold no verified start date for a prior period we publish nothing rather than inventing one.- Each service answers only for its own countries. A
NotFoundon a valid country code usually means right country, wrong brand. - Free discovery routes are rate limited; paid data routes are not.
Independent service, not affiliated with any government. Verify against the cited official source before legal or financial use.
Docs: https://afriref.dev/docs · Accuracy record: https://afriref.dev/accuracy
