Skip to content
Gauge

ax-check.com

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.

Docs

Return Markdown when clients request text/markdown.

Homepage returned text/html for a text/markdown request; no Markdown representation offered.

Docs

Add links to your product documentation and quickstart in /llms.txt.

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.

piv0.84.2
modelQwen 3.8 Maxcwd/sandbox/repo
Help me build a simple example using FirmaDB. Tell me how pricing works, and briefly tell me whether this product will be easy for you to manage. Let me know if you get blocked. If this product has no developer workflow you can act on, say so plainly and stop. Stay light: use the hosted product through its SDK or API. Do not start local service stacks or wait for long-running commands; if the quickstart requires either, say so plainly and stop.
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

AX Check by Gauge