FAQ

  1. Does this make me AML compliant?
  2. When I add a country, can I control how its API response is parsed?
  3. Can I score one country differently from another?
  4. How hard is it to use OpenAI instead of Anthropic?
  5. Does the AI actually use tools, or just one prompt?
  6. Can the AI override a sanctions hit or VAT failure?
  7. Why are there separate packages instead of one?
  8. KRS lookups - why through the White List?
  9. Sanctions without a paid licence?
  10. Can I run it without any framework?
  11. How do I test drivers and the AI offline?
  12. Why do I need to install a separate HTTP client?
  13. Why are $http and $clock injected instead of created internally?
  14. On Laravel/Symfony, do I call discover() myself?
  15. Is the published documentation site public if the repo is private?

Does this make me AML compliant?

No. It is a due-diligence aid. Sanctions/VAT results are deterministic facts; the AI is advisory. Compliance, and acting on requiresHumanReview(), remain your responsibility.

When I add a country, can I control how its API response is parsed?

Yes - completely. RegistryDriver::lookup() is your code: call any API, map fields however you like, and return a LookupResult with whatever data, proofId and sourceUrl you choose. See Adding a country.

Can I score one country differently from another?

Yes. The data you return flows into CheckResult::$raw, and risk rules receive the country, so you write country-guarded rules (or select a whole strategy per country). See Country-specific scoring.

How hard is it to use OpenAI instead of Anthropic?

Trivial - both ship as reference providers; switching is a constructor change, or driven by config/env on the bridges. Any other LLM is one AbstractAiResearchProvider::complete() method. See Swap the LLM provider.

Does the AI actually use tools, or just one prompt?

It uses the model’s native function calling: it calls registry_lookup / verification_report / web_search, the provider executes them and feeds results back, looping until the findings JSON is returned. See Native tool use.

Can the AI override a sanctions hit or VAT failure?

No. Hard facts live in the deterministic report; the AI consumes them as ground truth and can only add advisory context. A sanctions match is always a fail.

Why are there separate packages instead of one?

Independent repos (Spatie-style) with lockstep versioning: install only what you need (core, optionally ai, plus a bridge). The AI package is separate because prompts change often and must not bump the core’s version.

KRS lookups - why through the White List?

The Ministry of Justice API is keyed by KRS number, not NIP, so the driver resolves NIP -> KRS via the public White List, then fetches the extract. Sole proprietors (no KRS) return not-found and are found via CEIDG instead.

Sanctions without a paid licence?

Use the default sanctions.network (free), or self-host yente for OpenSanctions data with no key. The hosted OpenSanctions API requires a commercial licence.

Can I run it without any framework?

Yes - that is the point. counterparty-core depends only on PSR interfaces. The bridges are optional ergonomics. See Getting started.

How do I test drivers and the AI offline?

Drivers: extend RegistryDriverContractTestCase with a mocked PSR-18 client. AI: use FakeAiResearchProvider, InMemoryCache and JSON cassettes - see AI testing.

Why do I need to install a separate HTTP client?

Because the core is HTTP-client agnostic: it depends on the PSR-18 / PSR-17 interfaces, not a concrete client, and declares psr/http-client-implementation + psr/http-factory-implementation as virtual requirements. A library forcing a specific client (say Guzzle) would clash with whatever your app already uses and duplicate dependencies. composer require symfony/http-client nyholm/psr7 is just one valid choice; any PSR-18 client works. Composer will tell you to install one if you forget.

Why are $http and $clock injected instead of created internally?

Dependency injection, for decoupling and testability:

  • $http - the core can’t new a concrete client without depending on it. Injecting a PSR-18 client keeps it client-agnostic and lets tests use a mock (no live network).
  • $clock (PSR-20) - injected so timestamps are deterministic: FrozenClock in tests, SystemClock in production. As of v0.1.1 the clock is optional and defaults to SystemClock, and CounterpartyVerifierFactory::discover()/create() wires everything for you - so the verbose manual example is only for when you want full control. On a framework, the bridge does all the wiring.

On Laravel/Symfony, do I call discover() myself?

No. The factory (CounterpartyVerifierFactory::discover()/create()) is only for framework-less use. On a framework the bridge wires everything:

  • Symfony - zero-config: the bundle registers a Psr18Client and the Verifier service; just inject Verifier.
  • Laravel - zero-config too (v0.1.1+): the service provider auto-discovers an installed PSR-18 client, which on a stock app is the bundled Guzzle. Bind Psr\Http\Client\ClientInterface only if you want a different client.

Is the published documentation site public if the repo is private?

On GitHub Pro you can host Pages from a private repo, but the published site is still publicly accessible by URL. Only Enterprise can restrict the site to org members.


Counterparty Verification - a due-diligence aid, not a compliance product. MIT licensed.

This site uses Just the Docs, a documentation theme for Jekyll.