ax-check.com
by Gauge
F
FirmaDB
firmadb.com
ax-check.com
Checked 23 September 2026
C
63/100
Agents haven’t verified setup
Quickstart, docs and pricing are clear, but no session finished
14 of 23 checks pass: quickstart, curl/Python/TS examples, API reference, MCP docs and open EUR pricing (Starter €49 to Scale €399) all present. None of 3 sessions completed.
2 fixes to start with
Concrete changes, backed by what we found.
https://firmadb.com/llms.txt returns HTTP 404; no documentation index exists.
Recorded coding sessions
Same task, 3 models, no firmadb.com account. Recorded 23 September 2026 on Pi.
3 recorded
Session findings
Select a finding to see it in context.
›Thinking…
Let me first check the environment. "FirmaDB" — probably a made-up product. The task says the following: if the product doesn't have an addressable developer workflow, communicate that clearly and stop. Let me check whether there's anything in the sandbox/repo.
✓$ ls -la /sandbox/repo && cat /sandbox/repo/*.md 2>/dev/null | head -100✓└{"content":[{"text":"total 16\ndrwxr-xr-x 3 root root 4096 Sep 18 08:00 .\ndrwxr-xr-x 7 root root 4096 Sep 23 08:43 ..\ndrwxr-xr-x 8 root root 4096 Sep 18 08:00…ctrl+o
{"content":[{"text":"total 16\ndrwxr-xr-x 3 root root 4096 Sep 18 08:00 .\ndrwxr-xr-x 7 root root 4096 Sep 23 08:43 ..\ndrwxr-xr-x 8 root root 4096 Sep 18 08:00 .git\n-rw-r--r-- 1 root root 408 Sep 18 08:00 .gitignore\n","type":"text"}]}✓$ env | grep -i firma; pip index versions firmadb 2>/dev/null; npm view firmadb 2>&1 | head -5✓└{"content":[{"text":"firmadb (0.1.0)\nAvailable versions: 0.1.0\nnpm error code E404\nnpm error 404 Not Found - GET https://registry.npmjs.org/firmadb - Not fou…ctrl+o
{"content":[{"text":"firmadb (0.1.0)\nAvailable versions: 0.1.0\nnpm error code E404\nnpm error 404 Not Found - GET https://registry.npmjs.org/firmadb - Not found\nnpm error 404\nnpm error 404 'firmadb@*' is not in this registry.\nnpm error 404\n","type":"text"}]}›Thinking…
There's a PyPI package firmadb 0.1.0. Let's check it out — I'll install it and take a look at what it offers.
A PyPI package firmadb (0.1.0) exists. Let me install and inspect it.
✓$ cd /tmp && pip download firmadb==0.1.0 --no-deps -d /tmp/fdb 2>&1 | tail -2 && ls /tmp/fdb✓└{"content":[{"text":"Saved ./fdb/firmadb-0.1.0-py3-none-any.whl\nSuccessfully downloaded firmadb\nfirmadb-0.1.0-py3-none-any.whl\n","type":"text"}]}
›Thinking…
Wait, the repository directory only contains .git and .gitignore. Let me also check the git history. But first, let's examine the wheel.
✓$ cd /tmp/fdb && unzip -o firmadb-0.1.0-py3-none-any.whl -d extracted >/dev/null && find extracted -type f | head -50✓└{"content":[{"text":"extracted/firmadb/errors.py\nextracted/firmadb/client.py\nextracted/firmadb/types.py\nextracted/firmadb/__init__.py\nextracted/firmadb/_ver…ctrl+o
{"content":[{"text":"extracted/firmadb/errors.py\nextracted/firmadb/client.py\nextracted/firmadb/types.py\nextracted/firmadb/__init__.py\nextracted/firmadb/_version.py\nextracted/firmadb-0.1.0.dist-info/RECORD\nextracted/firmadb-0.1.0.dist-info/WHEEL\nextracted/firmadb-0.1.0.dist-info/licenses/LICENSE\nextracted/firmadb-0.1.0.dist-info/METADATA\n","type":"text"}]}✓$ cd /sandbox/repo && git log --oneline | head -20 && git status✓└{"content":[{"text":"9ebf5fb Initial workspace\nOn branch main\nnothing to commit, working tree clean\n","type":"text"}]}
›Thinking…
Let me try reading the SDK file and METADATA.
✓$ cd /tmp/fdb/extracted && cat firmadb-0.1.0.dist-info/METADATA && echo "=====INIT=====" && cat firmadb/__init__.py✓└{"content":[{"text":"Metadata-Version: 2.5\nName: firmadb\nVersion: 0.1.0\nSummary: Python SDK for the FirmaDB European company data API\nProject-URL: Homepage,…ctrl+o
{"content":[{"text":"Metadata-Version: 2.5\nName: firmadb\nVersion: 0.1.0\nSummary: Python SDK for the FirmaDB European company data API\nProject-URL: Homepage, https://firmadb.com\nProject-URL: Documentation, https://docs.firmadb.com\nAuthor-email: FirmaDB <support@firmadb.com>\nLicense: MIT\nLicense-File: LICENSE\nKeywords: company-data,europe,firmadb,kyb,registry\nClassifier: Intended Audience :: Developers\nClassifier: License :: OSI Approved :: MIT License\nClassifier: Programming Language :: Python :: 3\nClassifier: Programming Language :: Python :: 3 :: Only\nClassifier: Topic :: Software Development :: Libraries :: Python Modules\nRequires-Python: >=3.8\nRequires-Dist: httpx>=0.24.0\nDescription-Content-Type: text/markdown\n\n# FirmaDB Python SDK\n\nOfficial Python client for the [FirmaDB](https://firmadb.com) European company data API. ~25.0M entities across 18 European countries, sourced from official government registries. The precise served figure changes with every crawl — `GET /v1/countries` is the live number.\n\n## Install\n\n```bash\npip install firmadb\n```\n\nRequires Python 3.8+ and `httpx>=0.24`.\n\n## Quickstart\n\n```python\nfrom firmadb import FirmaDB\n\nclient = FirmaDB(api_key=\"fdb_...\")\n# or set FIRMADB_API_KEY in the environment and: FirmaDB()\n\n# Exact lookup by country + national registry id\ncompany = client.companies.get(country=\"FR\", registry_id=\"552120222\")\nprint(company.name, company.status, company.nace_code)\n\n# Fuzzy search\nresults = client.companies.search(\"Société Générale\", country=\"FR\", limit=5)\nfor hit in results:\n print(f\"{hit.match.score:.2f} {hit.name} ({hit.registry_id})\")\n\n# Batch enrichment (1-100 references per call, idempotent)\nbatch = client.companies.lookup_batch(\n items=[\n {\"country\": \"FR\", \"registry_id\": \"552120222\"},\n {\"country\": \"GB\", \"registry_id\": \"12345678\"},\n ],\n)\nfor row in batch.results:\n if row.status == \"found\":\n print(row.company.name)\n else:\n print(\"not found:\", row.error[\"detail\"])\n\n# Coverage / capability discovery (anonymous)\nfor c in client.countries.list():\n print(f\"{c.code} {c.record_count:>10,d} {c.registry_name}\")\n\n# Account usage and rate-limit state\nusage = client.account.usage()\nprint(usage.plan, usage.consumed_units, usage.included_units, usage.remaining_units)\nprint(client.rate_limit) # snapshot from the most recent response\n```\n\n## Errors\n\nEvery non-2xx response is an [RFC 9457](https://www.rfc-editor.org/rfc/rfc9457.html) Problem Detail. The SDK parses the body and raises a typed subclass of `FirmaDBError`. **Branch on the `code` attribute, never on `title` or `detail`.**\n\n```python\nfrom firmadb import FirmaDB, NotFoundError, RateLimitError, InvalidCountryError\n\ntry:\n client.companies.get(country=\"FR\", registry_id=\"999999999\")\nexcept NotFoundError as e:\n print(e.correction) # \"Try searching by name with /v1/companies/search?country=FR&q=...\"\n print(e.country_freshness) # {\"country\": \"FR\", \"last_loaded_at\": \"...\", \"record_count\": 16851670}\nexcept InvalidCountryError as e:\n print(e.supported_countries)\nexcept RateLimitError as e:\n print(e.retry_after, e.limit)\n```\n\n| Status | Code | Exception |\n|---|---|---|\n| 400 | `invalid_country` | `InvalidCountryError` |\n| 400 | `country_required` | `CountryRequiredError` |\n| 400 | `invalid_registry_id` | `InvalidRegistryIdError` |\n| 400 | `query_too_short` | `QueryTooShortError` |\n| 400 | `invalid_parameter` | `InvalidParameterError` |\n| 400 | `invalid_cursor` | `InvalidCursorError` |\n| 400 | `batch_validation_failed` | `BatchValidationError` |\n| 401 | `unauthenticated` | `AuthenticationError` |\n| 402 | `payment_required` | `PaymentRequiredError` |\n| 403 | `insufficient_scope` | `InsufficientScopeError` |\n| 404 | `company_not_found` | `NotFoundError` |\n| 409 | `idempotency_conflict` | `ConflictError` |\n| 429 | `rate_limit_exceeded` | `RateLimitError` |\n| 429 | `quota_exhausted` | `QuotaExhaustedError` |\n| 503 | `search_unavailable` / `source_unavailable` | `ServiceUnavailableError` |\n\n## Retries\n\nThe client automatically retries `rate_limit_exceeded` and `service_unavailable` responses, honoring `Retry-After`. Tune via `max_retries=` (default 3). Other 4xx are raised immediately — they will not succeed on retry.\n\n## Rate limit headers\n\nEvery response refreshes `client.rate_limit`:\n\n```python\nclient.rate_limit.limit # per-minute ceiling\nclient.rate_limit.remaining\nclient.rate_limit.reset_seconds\nclient.rate_limit.resource # \"companies-read\", \"companies-search\", ...\nclient.rate_limit.credits_remaining # monthly verified-result quota left\n```\n\n## Async\n\n```python\nimport asyncio\nfrom firmadb import AsyncFirmaDB\n\nasync def main():\n async with AsyncFirmaDB(api_key=\"fdb_...\") as client:\n company = await client.companies.get(country=\"FR\", registry_id=\"552120222\")\n print(company.name)\n\nasyncio.run(main())\n```\n\n## Methods\n\n| Method | Endpoint |\n|---|---|\n| `client.companies.get(country=, registry_id=, include=, include_nulls=)` | `GET /companies/{country}/{registry_id}` |\n| `client.companies.search(query, country=, nace=, status=, ..., limit=, cursor=)` | `GET /companies/search` |\n| `client.companies.lookup_batch(items, idempotency_key=, include=)` | `POST /companies/lookup-batch` |\n| `client.countries.list()` | `GET /countries` |\n| `client.countries.get(code)` | `GET /countries/{code}` |\n| `client.account.usage()` | `GET /account/usage` |\n| `client.health()` | `GET /health` |\n\nFull reference at [docs.firmadb.com](https://docs.firmadb.com).\n\n## License\n\nMIT\n=====INIT=====\n\"\"\"FirmaDB Python SDK.\n\n```python\nfrom firmadb import FirmaDB\n\nclient = FirmaDB(api_key=\"fdb_...\")\ncompany = client.companies.get(country=\"FR\", registry_id=\"552120222\")\nprint(company.name, company.status)\n```\n\"\"\"\n\nfrom ._version import __version__\nfrom .client import AsyncFirmaDB, FirmaDB\nfrom .errors import (\n AuthenticationError,\n BadRequestError,\n BatchValidationError,\n ConflictError,\n CountryRequiredError,\n FirmaDBError,\n InsufficientScopeError,\n InvalidCountryError,\n InvalidCursorError,\n InvalidParameterError,\n InvalidRegistryIdError,\n NotFoundError,\n PaymentRequiredError,\n PermissionError_,\n QueryTooShortError,\n QuotaExhaustedError,\n RateLimitError,\n ServiceUnavailableError,\n)\nfrom .types import (\n Address,\n BatchResponse,\n BatchResultItem,\n Company,\n Country,\n CountryCapability,\n DataFreshness,\n HealthStatus,\n MatchBlock,\n RateLimitState,\n SearchResult,\n Usage,\n)\n\n__all__ = [\n \"__version__\",\n \"FirmaDB\",\n \"AsyncFirmaDB\",\n # types\n \"Address\",\n \"BatchResponse\",\n \"BatchResultItem\",\n \"Company\",\n \"Country\",\n \"CountryCapability\",\n \"DataFreshness\",\n \"HealthStatus\",\n \"MatchBlock\",\n \"RateLimitState\",\n \"SearchResult\",\n \"Usage\",\n # errors\n \"FirmaDBError\",\n \"BadRequestError\",\n \"InvalidCountryError\",\n \"CountryRequiredError\",\n \"InvalidRegistryIdError\",\n \"QueryTooShortError\",\n \"InvalidParameterError\",\n \"InvalidCursorError\",\n \"BatchValidationError\",\n \"AuthenticationError\",\n \"PermissionError_\",\n \"InsufficientScopeError\",\n \"PaymentRequiredError\",\n \"NotFoundError\",\n \"ConflictError\",\n \"RateLimitError\",\n \"QuotaExhaustedError\",\n \"ServiceUnavailableError\",\n]\n","type":"text"}]}✓$ cd /tmp/fdb/extracted && echo "=====CLIENT=====" && cat firmadb/client.py && echo "=====TYPES=====" && cat firmadb/types.py && echo "=====ERRORS=====" && cat firmadb/errors.py && echo "=====VERSION===…✓└{"content":[{"text":"=====CLIENT=====\n\"\"\"FirmaDB sync + async clients backed by httpx.\"\"\"\n\nfrom __future__ import annotations\n\nimport os\nimport rand…ctrl+o
cd /tmp/fdb/extracted && echo "=====CLIENT=====" && cat firmadb/client.py && echo "=====TYPES=====" && cat firmadb/types.py && echo "=====ERRORS=====" && cat firmadb/errors.py && echo "=====VERSION=====" && cat firmadb/_version.py
{"content":[{"text":"=====CLIENT=====\n\"\"\"FirmaDB sync + async clients backed by httpx.\"\"\"\n\nfrom __future__ import annotations\n\nimport os\nimport random\nimport time\nimport uuid\nfrom typing import Any, Dict, Iterable, List, Optional, Sequence, Tuple, Union\n\nimport httpx\n\nfrom ._version import __version__\nfrom .errors import RateLimitError, ServiceUnavailableError, from_response\nfrom .types import (\n BatchResponse,\n Company,\n Country,\n CountryCapability,\n HealthStatus,\n RateLimitState,\n SearchResult,\n Usage,\n)\n\nDEFAULT_BASE_URL = \"https://api.firmadb.com/v1\"\nDEFAULT_TIMEOUT = 30.0\nDEFAULT_MAX_RETRIES = 3\nUSER_AGENT = f\"FirmaDB-Python/{__version__}\"\n\n\n# Endpoints whose 429 responses are safe to retry automatically (per-minute\n# bucket only, not monthly quota).\n_RETRYABLE_CODES = {\"rate_limit_exceeded\", \"search_unavailable\", \"source_unavailable\"}\n\n\ndef _is_retryable(exc: Exception) -> Tuple[bool, int]:\n if isinstance(exc, RateLimitError):\n return True, exc.retry_after or 0\n if isinstance(exc, ServiceUnavailableError) and exc.retryable:\n return True, exc.retry_after_seconds or 0\n return False, 0\n\n\ndef _backoff(attempt: int, suggested_seconds: int) -> float:\n if suggested_seconds > 0:\n return float(suggested_seconds)\n # exponential 0.5, 1, 2... with jitter\n return (2 ** (attempt - 1)) * 0.5 + random.random() * 0.25\n\n\ndef _build_search_params(\n query: str,\n *,\n country: Optional[Union[str, Sequence[str]]] = None,\n nace: Optional[str] = None,\n status: Optional[str] = None,\n registered_date_from: Optional[str] = None,\n registered_date_to: Optional[str] = None,\n sort: Optional[str] = None,\n limit: Optional[int] = None,\n cursor: Optional[str] = None,\n include: Optional[Sequence[str]] = None,\n) -> Dict[str, Any]:\n params: Dict[str, Any] = {\"q\": query}\n if country is not None:\n params[\"country\"] = country if isinstance(country, str) else \",\".join(country)\n if nace is not None:\n params[\"nace\"] = nace\n if status is not None:\n params[\"status\"] = status\n if registered_date_from is not None:\n params[\"registered_date_from\"] = registered_date_from\n if registered_date_to is not None:\n params[\"registered_date_to\"] = registered_date_to\n if sort is not None:\n params[\"sort\"] = sort\n if limit is not None:\n params[\"limit\"] = int(limit)\n if cursor is not None:\n params[\"cursor\"] = cursor\n if include:\n params[\"include\"] = \",\".join(include)\n return params\n\n\ndef _company_query(\n *, include: Optional[Sequence[str]], include_nulls: Optional[bool]\n) -> Dict[str, Any]:\n params: Dict[str, Any] = {}\n if include:\n params[\"include\"] = \",\".join(include)\n if include_nulls is not None:\n params[\"include_nulls\"] = \"true\" if include_nulls else \"false\"\n return params\n\n\n# ---------------------------------------------------------------------------\n# Sync client\n# ---------------------------------------------------------------------------\n\n\nclass FirmaDB:\n \"\"\"Synchronous FirmaDB client.\n\n Example:\n from firmadb import FirmaDB\n client = FirmaDB(api_key=\"fdb_...\")\n c = client.companies.get(country=\"FR\", registry_id=\"552120222\")\n \"\"\"\n\n def __init__(\n self,\n api_key: Optional[str] = None,\n *,\n base_url: str = DEFAULT_BASE_URL,\n timeout: float = DEFAULT_TIMEOUT,\n max_retries: int = DEFAULT_MAX_RETRIES,\n http_client: Optional[httpx.Client] = None,\n ) -> None:\n self.api_key = api_key or os.environ.get(\"FIRMADB_API_KEY\")\n self.base_url = base_url.rstrip(\"/\")\n self.timeout = timeout\n self.max_retries = max(0, int(max_retries))\n self._owns_client = http_client is None\n self._http = http_client or httpx.Client(timeout=timeout)\n self.rate_limit = RateLimitState()\n self.last_request_id: Optional[str] = None\n\n self.companies = _Companies(self)\n self.countries = _Countries(self)\n self.account = _Account(self)\n\n def __enter__(self) -> \"FirmaDB\":\n return self\n\n def __exit__(self, *exc: Any) -> None:\n self.close()\n\n def close(self) -> None:\n if self._owns_client:\n self._http.close()\n\n # --- request plumbing -------------------------------------------------\n\n def _headers(self, anonymous: bool = False, extra: Optional[Dict[str, str]] = None) -> Dict[str, str]:\n h: Dict[str, str] = {\n \"Accept\": \"application/json\",\n \"User-Agent\": USER_AGENT,\n }\n if not anonymous:\n if not self.api_key:\n raise ValueError(\n \"FirmaDB API key required. Pass api_key=... or set FIRMADB_API_KEY.\"\n )\n h[\"Authorization\"] = f\"Bearer {self.api_key}\"\n if extra:\n h.update(extra)\n return h\n\n def _request(\n self,\n method: str,\n path: str,\n *,\n params: Optional[Dict[str, Any]] = None,\n json: Optional[Dict[str, Any]] = None,\n anonymous: bool = False,\n extra_headers: Optional[Dict[str, str]] = None,\n ) -> Dict[str, Any]:\n url = f\"{self.base_url}{path}\"\n attempt = 0\n while True:\n attempt += 1\n response = self._http.request(\n method,\n url,\n params=params,\n json=json,\n headers=self._headers(anonymous=anonymous, extra=extra_headers),\n )\n self.rate_limit = RateLimitState.from_headers(dict(response.headers))\n self.last_request_id = response.headers.get(\"x-request-id\") or response.headers.get(\n \"X-Request-Id\"\n )\n if 200 <= response.status_code < 300:\n return response.json() if response.content else {}\n\n try:\n body = response.json()\n except Exception:\n body = {\"detail\": response.text}\n err = from_response(response.status_code, body)\n\n retry, suggested = _is_retryable(err)\n if retry and attempt <= self.max_retries:\n time.sleep(_backoff(attempt, suggested))\n continue\n raise err\n\n\nclass _Companies:\n def __init__(self, client: \"FirmaDB\") -> None:\n self._c = client\n\n def get(\n self,\n *,\n country: str,\n registry_id: str,\n include: Optional[Sequence[str]] = None,\n include_nulls: Optional[bool] = None,\n if_none_match: Optional[str] = None,\n ) -> Company:\n headers = {\"If-None-Match\": if_none_match} if if_none_match else None\n body = self._c._request(\n \"GET\",\n f\"/companies/{country}/{registry_id}\",\n params=_company_query(include=include, include_nulls=include_nulls) or None,\n extra_headers=headers,\n )\n return Company.from_dict(body)\n\n def search(\n self,\n query: str,\n *,\n country: Optional[Union[str, Sequence[str]]] = None,\n nace: Optional[str] = None,\n status: Optional[str] = None,\n registered_date_from: Optional[str] = None,\n registered_date_to: Optional[str] = None,\n sort: Optional[str] = None,\n limit: Optional[int] = None,\n cursor: Optional[str] = None,\n include: Optional[Sequence[str]] = None,\n ) -> SearchResult:\n params = _build_search_params(\n query,\n country=country,\n nace=nace,\n status=status,\n registered_date_from=registered_date_from,\n registered_date_to=registered_date_to,\n sort=sort,\n limit=limit,\n cursor=cursor,\n include=include,\n )\n body = self._c._request(\"GET\", \"/companies/search\", params=params)\n return SearchResult.from_dict(body)\n\n def lookup_batch(\n self,\n items: Iterable[Dict[str, str]],\n *,\n idempotency_key: Optional[str] = None,\n include: Optional[Sequence[str]] = None,\n ) -> BatchResponse:\n idem = idempotency_key or str(uuid.uuid4())\n payload: Dict[str, Any] = {\"references\": list(items)}\n if include:\n payload[\"include\"] = list(include)\n body = self._c._request(\n \"POST\",\n \"/companies/lookup-batch\",\n json=payload,\n extra_headers={\"Idempotency-Key\": idem},\n )\n return BatchResponse.from_dict(body)\n\n\nclass _Countries:\n def __init__(self, client: \"FirmaDB\") -> None:\n self._c = client\n\n def list(self) -> List[Country]:\n body = self._c._request(\"GET\", \"/countries\", anonymous=True)\n return [Country.from_dict(c) for c in body.get(\"data\") or []]\n\n def get(self, code: str) -> CountryCapability:\n body = self._c._request(\"GET\", f\"/countries/{code}\", anonymous=True)\n return CountryCapability.from_dict(body)\n\n\nclass _Account:\n def __init__(self, client: \"FirmaDB\") -> None:\n self._c = client\n\n def usage(self) -> Usage:\n body = self._c._request(\"GET\", \"/account/usage\")\n return Usage.from_dict(body)\n\n\ndef health(client: FirmaDB) -> HealthStatus: # for typing convenience\n return client.health() # type: ignore[attr-defined]\n\n\ndef _attach_health(cls: type) -> None:\n def _h(self: FirmaDB) -> HealthStatus:\n body = self._request(\"GET\", \"/health\", anonymous=True)\n return HealthStatus.from_dict(body)\n\n setattr(cls, \"health\", _h)\n\n\n_attach_health(FirmaDB)\n\n\n# ---------------------------------------------------------------------------\n# Async client\n# ---------------------------------------------------------------------------\n\n\nclass AsyncFirmaDB:\n \"\"\"Async FirmaDB client. API mirrors the sync client; methods are coroutines.\n\n Example:\n async with AsyncFirmaDB(api_key=\"fdb_...\") as client:\n c = await client.companies.get(country=\"FR\", registry_id=\"552120222\")\n \"\"\"\n\n def __init__(\n self,\n api_key: Optional[str] = None,\n *,\n base_url: str = DEFAULT_BASE_URL,\n timeout: float = DEFAULT_TIMEOUT,\n max_retries: int = DEFAULT_MAX_RETRIES,\n http_client: Optional[httpx.AsyncClient] = None,\n ) -> None:\n self.api_key = api_key or os.environ.get(\"FIRMADB_API_KEY\")\n self.base_url = base_url.rstrip(\"/\")\n self.timeout = timeout\n self.max_retries = max(0, int(max_retries))\n self._owns_client = http_client is None\n self._http = http_client or httpx.AsyncClient(timeout=timeout)\n self.rate_limit = RateLimitState()\n self.last_request_id: Optional[str] = None\n\n self.companies = _AsyncCompanies(self)\n self.countries = _AsyncCountries(self)\n self.account = _AsyncAccount(self)\n\n async def __aenter__(self) -> \"AsyncFirmaDB\":\n return self\n\n async def __aexit__(self, *exc: Any) -> None:\n await self.aclose()\n\n async def aclose(self) -> None:\n if self._owns_client:\n await self._http.aclose()\n\n def _headers(self, anonymous: bool = False, extra: Optional[Dict[str, str]] = None) -> Dict[str, str]:\n h: Dict[str, str] = {\"Accept\": \"application/json\", \"User-Agent\": USER_AGENT}\n if not anonymous:\n if not self.api_key:\n raise ValueError(\n \"FirmaDB API key required. Pass api_key=... or set FIRMADB_API_KEY.\"\n )\n h[\"Authorization\"] = f\"Bearer {self.api_key}\"\n if extra:\n h.update(extra)\n return h\n\n async def _request(\n self,\n method: str,\n path: str,\n *,\n params: Optional[Dict[str, Any]] = None,\n json: Optional[Dict[str, Any]] = None,\n anonymous: bool = False,\n extra_headers: Optional[Dict[str, str]] = None,\n ) -> Dict[str, Any]:\n import asyncio\n\n url = f\"{self.base_url}{path}\"\n attempt = 0\n while True:\n attempt += 1\n response = await self._http.request(\n method,\n url,\n params=params,\n json=json,\n headers=self._headers(anonymous=anonymous, extra=extra_headers),\n )\n self.rate_limit = RateLimitState.from_headers(dict(response.headers))\n self.last_request_id = response.headers.get(\"x-request-id\") or response.headers.get(\n \"X-Request-Id\"\n )\n if 200 <= response.status_code < 300:\n return response.json() if response.content else {}\n\n try:\n body = response.json()\n except Exception:\n body = {\"detail\": response.text}\n err = from_response(response.status_code, body)\n\n retry, suggested = _is_retryable(err)\n if retry and attempt <= self.max_retries:\n await asyncio.sleep(_backoff(attempt, suggested))\n continue\n raise err\n\n async def health(self) -> HealthStatus:\n body = await self._request(\"GET\", \"/health\", anonymous=True)\n return HealthStatus.from_dict(body)\n\n\nclass _AsyncCompanies:\n def __init__(self, client: AsyncFirmaDB) -> None:\n self._c = client\n\n async def get(\n self,\n *,\n country: str,\n registry_id: str,\n include: Optional[Sequence[str]] = None,\n include_nulls: Optional[bool] = None,\n if_none_match: Optional[str] = None,\n ) -> Company:\n headers = {\"If-None-Match\": if_none_match} if if_none_match else None\n body = await self._c._request(\n \"GET\",\n f\"/companies/{country}/{registry_id}\",\n params=_company_query(include=include, include_nulls=include_nulls) or None,\n extra_headers=headers,\n )\n return Company.from_dict(body)\n\n async def search(\n self,\n query: str,\n *,\n country: Optional[Union[str, Sequence[str]]] = None,\n nace: Optional[str] = None,\n status: Optional[str] = None,\n registered_date_from: Optional[str] = None,\n registered_date_to: Optional[str] = None,\n sort: Optional[str] = None,\n limit: Optional[int] = None,\n cursor: Optional[str] = None,\n include: Optional[Sequence[str]] = None,\n ) -> SearchResult:\n params = _build_search_params(\n query,\n country=country,\n nace=nace,\n status=status,\n registered_date_from=registered_date_from,\n registered_date_to=registered_date_to,\n sort=sort,\n limit=limit,\n cursor=cursor,\n include=include,\n )\n body = await self._c._request(\"GET\", \"/companies/search\", params=params)\n return SearchResult.from_dict(body)\n\n async def lookup_batch(\n self,\n items: Iterable[Dict[str, str]],\n *,\n idempotency_key: Optional[str] = None,\n include: Optional[Sequence[str]] = None,\n ) -> BatchResponse:\n idem = idempotency_key or str(uuid.uuid4())\n payload: Dict[str, Any] = {\"references\": list(items)}\n if include:\n payload[\"include\"] = list(include)\n body = await self._c._request(\n \"POST\",\n \"/companies/lookup-batch\",\n json=payload,\n extra_headers={\"Idempotency-Key\": idem},\n )\n return BatchResponse.from_dict(body)\n\n\nclass _AsyncCountries:\n def __init__(self, client: AsyncFirmaDB) -> None:\n self._c = client\n\n async def list(self) -> List[Country]:\n body = await self._c._request(\"GET\", \"/countries\", anonymous=True)\n return [Country.from_dict(c) for c in body.get(\"data\") or []]\n\n async def get(self, code: str) -> CountryCapability:\n body = await self._c._request(\"GET\", f\"/countries/{code}\", anonymous=True)\n return CountryCapability.from_dict(body)\n\n\nclass _AsyncAccount:\n def __init__(self, client: AsyncFirmaDB) -> None:\n self._c = client\n\n async def usage(self) -> Usage:\n body = await self._c._request(\"GET\", \"/account/usage\")\n return Usage.from_dict(body)\n=====TYPES=====\n\"\"\"Typed response shapes for the FirmaDB API.\n\nDataclasses with `from_dict` constructors. Unknown fields are preserved on\n`_raw` so the SDK survives forward-compatible spec additions without breaking.\n\"\"\"\n\nfrom __future__ import annotations\n\nfrom dataclasses import dataclass, field\nfrom typing import Any, Dict, List, Optional\n\n\ndef _get(d: Dict[str, Any], *keys: str, default: Any = None) -> Any:\n for k in keys:\n if k in d and d[k] is not None:\n return d[k]\n return default\n\n\n@dataclass\nclass Address:\n street: Optional[str] = None\n city: Optional[str] = None\n postal_code: Optional[str] = None\n country: str = \"\"\n\n @classmethod\n def from_dict(cls, d: Optional[Dict[str, Any]]) -> Optional[\"Address\"]:\n if not d:\n return None\n return cls(\n street=d.get(\"street\"),\n city=d.get(\"city\"),\n postal_code=d.get(\"postal_code\"),\n country=d.get(\"country\") or \"\",\n )\n\n\n@dataclass\nclass DataFreshness:\n record_retrieved_at: str\n country_dataset_updated_at: str\n freshness_status: str\n max_age_days: int\n\n @classmethod\n def from_dict(cls, d: Dict[str, Any]) -> \"DataFreshness\":\n return cls(\n record_retrieved_at=d.get(\"record_retrieved_at\", \"\"),\n country_dataset_updated_at=d.get(\"country_dataset_updated_at\", \"\"),\n freshness_status=d.get(\"freshness_status\", \"unknown\"),\n max_age_days=int(d.get(\"max_age_days\") or 0),\n )\n\n\n@dataclass\nclass MatchBlock:\n score: float\n matched_fields: List[str]\n explanation: str\n\n @classmethod\n def from_dict(cls, d: Optional[Dict[str, Any]]) -> Optional[\"MatchBlock\"]:\n if not d:\n return None\n return cls(\n score=float(d.get(\"score\") or 0.0),\n matched_fields=list(d.get(\"matched_fields\") or []),\n explanation=d.get(\"explanation\", \"\"),\n )\n\n\n@dataclass\nclass Company:\n country: str\n registry_id: str\n name: str\n status: str\n status_active: Optional[bool]\n source_url: str\n data_freshness: DataFreshness\n request_id: str\n legal_form: Optional[str] = None\n legal_form_code: Optional[str] = None\n registered_date: Optional[str] = None\n dissolution_date: Optional[str] = None\n address: Optional[Address] = None\n nace_code: Optional[str] = None\n nace_description: Optional[str] = None\n employee_count: Optional[int] = None\n website: Optional[str] = None\n field_meta: Optional[Dict[str, Any]] = None\n provenance: Optional[Dict[str, Any]] = None\n match: Optional[MatchBlock] = None\n _raw: Dict[str, Any] = field(default_factory=dict, repr=False)\n\n @classmethod\n def from_dict(cls, d: Dict[str, Any]) -> \"Company\":\n return cls(\n country=d…›Thinking…
This is a hosted API, and there's an SDK on PyPI. It's a good developer workflow that can be handled with a lightweight approach (no local stack needed). What's needed is an API key. Let me check if there's an API key in the environment variables. The user mentioned a FIRMADB environment variable. In the earlier environment grep, nothing was returned related to firma (the output only showed pip/npm results — actually, since grep didn't output anything, that means the FIRMADB environment variable doesn't exist).
Let me verify network access and try the endpoints that can be used anonymously: /health and /countries. These don't require an API key. If it works, I can build a sample using these endpoints. For authenticated endpoints, a key will be required. There might be a sandbox/test key convention. Let me first test connectivity.
The SDK exists on PyPI (hosted API, no local stack needed) — this is an executable workflow. Let me install it and test connectivity (the /health and /countries endpoints are anonymous).
✓$ pip install firmadb 2>&1 | tail -1 && env | grep -iE 'firma|fdb|API_KEY' ; echo "---env check done---"✓└{"content":[{"text":"Successfully installed firmadb-0.1.0\nPI_GATEWAY_API_KEY=unused\n---env check done---\n","type":"text"}]}
›Thinking…
No FIRMADB_API_KEY. Going to test connectivity to the anonymous endpoint with a short timeout.
✓$ cd /tmp && timeout 20 python - <<'EOF'✓└{"content":[{"text":"HEALTH: degraded 1.0.0\n18 countries, total records: 25,091,739\n FR 8,954,940 INSEE Sirene\n GB 5,695,465 Companies House\n BE …ctrl+o
cd /tmp && timeout 20 python - <<'EOF'
from firmadb import FirmaDB
client = FirmaDB(api_key="fdb_dummy") # key not needed for anonymous endpoints
try:
h = client.health()
print("HEALTH:", h.status, h.version)
except Exception as e:
print("health failed:", repr(e))
try:
cs = client.countries.list()
print(f"{len(cs)} countries, total records: {sum(c.record_count for c in cs):,}")
for c in cs[:5]:
print(f" {c.code} {c.record_count:>10,d} {c.registry_name}")
except Exception as e:
print("countries failed:", repr(e))
EOF
{"content":[{"text":"HEALTH: degraded 1.0.0\n18 countries, total records: 25,091,739\n FR 8,954,940 INSEE Sirene\n GB 5,695,465 Companies House\n BE 1,185,385 Crossroads Bank for Enterprises (KBO/BCE)\n SE 1,176,427 Bolagsverket\n PL 1,171,537 Poland national registry\n","type":"text"}]}AX Fundamentals
23 checks. Open an area for details.
This check is a quick preview. Test more tasks and models with Gauge Agents.
Test your product with Gauge Agents