FAQ
- Does this make me AML compliant?
- When I add a country, can I control how its API response is parsed?
- Can I score one country differently from another?
- How hard is it to use OpenAI instead of Anthropic?
- Does the AI actually use tools, or just one prompt?
- Can the AI override a sanctions hit or VAT failure?
- Why are there separate packages instead of one?
- KRS lookups - why through the White List?
- Sanctions without a paid licence?
- Can I run it without any framework?
- How do I test drivers and the AI offline?
- Why do I need to install a separate HTTP client?
- Why are
$httpand$clockinjected instead of created internally? - On Laravel/Symfony, do I call
discover()myself? - 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’tnewa 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:FrozenClockin tests,SystemClockin production. As of v0.1.1 the clock is optional and defaults toSystemClock, andCounterpartyVerifierFactory::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
Psr18Clientand theVerifierservice; just injectVerifier. - 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\ClientInterfaceonly 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.