# Building on D2K Companies House Lookup

D2K Companies House Lookup is built to be depended on. If your Business Central extension - a customer-specific per-tenant extension, a vertical solution or another AppSource app - needs UK company data, declare a dependency on this app and call it rather than writing your own Companies House client. You get the cache, the rate limiting, the retry policy and the background filing monitor for nothing, and your users manage one Companies House API key rather than one per app.

This page covers what you can rely on, how to take the dependency, the behaviour to design around and the compatibility promise. Licensing, the API and a worked example each have their own page.

## In this guide

- [Licensing for apps that depend on D2K Companies House Lookup](https://docs.d2k.uk/companies-house-lookup/developers-licensing): what your
  users need to be licensed for, permissions, and developing and testing.
- [The D2K Companies House Lookup API](https://docs.d2k.uk/companies-house-lookup/developers-api): the interface, events, filed documents,
  filing watches, data and pages.
- [A minimal extension using D2K Companies House Lookup](https://docs.d2k.uk/companies-house-lookup/developers-example): a complete worked
  example.

## What you can rely on

Everything in this table is the supported surface: it is what we test, document and keep compatible under the promise in [Versioning and compatibility](#versioning-and-compatibility).

| Area | Objects |
|---|---|
| Lookup API | `interface "D2KL ICH Resolver"`, implemented by `codeunit "D2KL Default Resolver"` |
| Events | The three integration events in `codeunit "D2KL Events"` |
| Filing watches | `codeunit "D2KL Filing Watch Mgt"`: `RegisterWatch`, `UnregisterWatch`, `SetEnabled` |
| Filed documents | `codeunit "D2KL Filing Document"`: `Download`, `SuggestedFileName` |
| Data (read only) | Tables `D2KL Company Snapshot`, `D2KL Company Snapshot SIC`, `D2KL Officer`, `D2KL Filing Event`, `D2KL Filing Watch` |
| Enums | `D2KL Company Status`, `D2KL Company Type`, `D2KL Filing Type`, `D2KL Officer Role`, `D2KL Filing Status` |
| Pages | `D2KL Company Lookup Dialog` (`LookupAndShow`, `NormaliseCRN`, `SetCRN`, `GetCRN`), `D2KL Company Search` (`SetSearchText`), `D2KL Company Officers` (`LoadFor`), `D2KL Company Filings` (`LoadFor`), `D2KL Company Snapshot List`, `D2KL Company Snapshot Card`, `D2KL Filing Watch List`, `D2KL Filing Event List` |
| Permission sets | `D2KL Read`, `D2KL Edit`, `D2KL Admin` |

Anything not in the table - the other codeunits, the Setup table, the API call log, the rate-limiter state, the internals of the poller - is not supported, even where it compiles today. Objects outside the table may become `Access = Internal` before version 1.0.0, and may change without notice after it. If you need something that is not listed, ask (see [Support](#support)) rather than reaching past the list.

## Taking the dependency

Add the app to `dependencies` in your `app.json`:

```json
{
  "id": "d09a6ce0-d64b-4744-85ee-845cea8af445",
  "name": "D2K Companies House Lookup",
  "publisher": "D2K Limited",
  "version": "1.0.0.0"
}
```

Use the lowest version that has what you need; Business Central accepts any installed version at or above it.

**Symbols.** The app does not ship its source, so take symbols from an environment where it is installed: install it in your development sandbox from AppSource, point your `launch.json` at that sandbox and run *AL: Download symbols* in Visual Studio Code.

**Namespace.** Every object is in the namespace `D2K.CompaniesHouseLookup`. Add `using D2K.CompaniesHouseLookup;` at the top of any file that refers to them, or qualify the names in full.

**Installation for your customers.** If your app is on AppSource, the dependency is enough: Business Central installs D2K Companies House Lookup alongside your app. If your app is a per-tenant extension, the customer installs D2K Companies House Lookup from AppSource first. Either way the app cannot be uninstalled while yours depends on it.

## Behaviour to design around

- **Cache first.** Three TTLs on the Setup page, set by the customer's administrator: Profile (default 7 days), Status (default 24 hours), Filings (default 1 hour). Do not fight them with `ForceRefresh` on every call; if a feature needs fresher data than the tenant's default, use `ResolveByCRNWithMaxAge`.
- **Stale data on outages.** When Companies House is unavailable (a 5xx, a timeout or a rate limit) and a cached snapshot exists, the profile and status methods return the cached snapshot rather than failing. Check `"Last Refreshed"` if the age matters to you. Search, officers and filings have no stale fallback: they error.
- **Rate limit.** Companies House allows 600 requests per five minutes per API key. The app counts every call it makes across all sessions in the tenant and refuses to exceed the limit - the user sees a "throttled locally" error - rather than have the key banned; a 429 from Companies House pauses every session for the `Retry-After` period. The key's budget is shared by every user and every app in the tenant, so do not loop lookups over a customer list. Work through the cache, or register watches and let the poller do it hourly.
- **Retries are built in.** A rate limit is retried once after `Retry-After`; a 5xx up to three times with 1, 2 and 4 second back-off; a transport failure once. Do not add your own retry loop on top.
- **Errors are for the user.** Every failure surfaces as an AL error with a message written for an end user: invalid CRN, company not found (404), API key missing or invalid (401), rate limited (429), service unavailable (5xx), no response. Wrap calls in a `[TryFunction]` when you need to carry on (a batch that skips failures); otherwise let the message reach the user.
- **Transactions.** Interface calls never `Commit`. The cache write happens inside your transaction and rolls back with it. The poller is different: it owns its transactions and commits after each company, which is why it only ever runs from the Job Queue or the Filing Watch List.
- **The API key is the customer's.** One key per Business Central company, entered by an administrator through the Assisted Setup and stored encrypted in Isolated Storage. Your app never sees it and does not need to. When a production environment is copied to a sandbox, the platform's cleanup hook clears the key so the copy never talks to Companies House with the production key; expect `IsConfigured()` to be `false` in a fresh copy.
- **Companies House sandbox.** The Setup page has a *Use Sandbox API* switch that points the app at `api-sandbox.company-information.service.gov.uk`, which serves test data and needs its own key. Useful in a development tenant.

## Versioning and compatibility

From version 1.0.0 the app follows strict semantic versioning, and everything in [What you can rely on](#what-you-can-rely-on) is covered:

- **Patch** (1.0.x): bug fixes only. Nothing public changes.
- **Minor** (1.x.0): additive only - new methods on the interface, new fields on the tables, new enum values, new events. Existing signatures do not change. A dependant compiled against 1.0.0 keeps working on any 1.x.
- **Major** (2.0.0): the only place a breaking change can happen, and we avoid them.

**Obsoletion.** Nothing public is removed or changed without warning. A member that is going away is first marked `ObsoleteState = Pending` with an `ObsoleteReason` naming its replacement and an `ObsoleteTag` naming the version; it keeps working for **at least twelve months** from that release and is removed only in a major version. You see the deprecation as a compiler warning in your own build long before it bites.

<!-- TODO(Dan): confirm the twelve-month obsoletion window before this page goes live. -->

AppSourceCop enforces the additive rules on our build, so the promise is checked by tooling, not only by intent.

## What not to do

- Do not write to any `D2KL` table. Read them; write through the API.
- Do not depend on anything outside the supported surface, including `Access = Internal` objects your symbols may still show you.
- Do not call Companies House yourself with the key this app stores: you cannot read it, and a second client would break the shared rate limit in any case.
- Do not loop `ResolveByCRN` over a list of customers on a timer. Register watches; the poller does this properly.
- Do not raise errors or open a user interface from an event subscriber.
- Do not treat `D2KL Filing Event` or `D2KL Officer` as your archive. Copy what you need, and remember that the data comes from the Companies House public API under Companies House's own terms of use.

## Support

Questions about the API, and requests for something that is not on the supported list, go to **support@d2k.uk**. If you ship an app that depends on this one, tell us its name and app ID: we will warn you before a deprecation lands rather than after.

---
Source: https://docs.d2k.uk/companies-house-lookup/developers
Last updated: 2026-10-04
